Skip to content

Commit 5fda8b9

Browse files
kv-chiuhsluoyz
authored andcommitted
feat: add support of OpenRouter (#619)
1 parent 6d7921a commit 5fda8b9

5 files changed

Lines changed: 161 additions & 0 deletions

File tree

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/casbin/casibase
33
go 1.18
44

55
require (
6+
github.com/Lok-Lu/go-openrouter v0.0.0-20230807015935-ab5cee433ad3
67
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1585
78
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible
89
github.com/anhao/go-ernie v1.0.4

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
3838
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
3939
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
4040
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
41+
github.com/Lok-Lu/go-openrouter v0.0.0-20230807015935-ab5cee433ad3 h1:E15Zr1fu3QEIleqINqXpl4SlYuWkwWPJ4cx6UVaJITE=
42+
github.com/Lok-Lu/go-openrouter v0.0.0-20230807015935-ab5cee433ad3/go.mod h1:nM0kITDAJEkwn9x2DQPdfJynThaLe0vGNpPcqJ8vjAI=
4143
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
4244
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
4345
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=

model/openrouter.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright 2023 The casbin Authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package model
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"io"
21+
"net/http"
22+
"strings"
23+
24+
"github.com/Lok-Lu/go-openrouter"
25+
"github.com/casbin/casibase/proxy"
26+
)
27+
28+
type OpenRouterModelProvider struct {
29+
subType string
30+
secretKey string
31+
siteName string
32+
siteUrl string
33+
}
34+
35+
func NewOpenRouterModelProvider(subType string, secretKey string) (*OpenRouterModelProvider, error) {
36+
p := &OpenRouterModelProvider{
37+
subType: subType,
38+
secretKey: secretKey,
39+
siteName: "Casibase",
40+
siteUrl: "https://casibase.org",
41+
}
42+
return p, nil
43+
}
44+
45+
func (p *OpenRouterModelProvider) getProxyClientFromToken() *openrouter.Client {
46+
config, err := openrouter.DefaultConfig(p.secretKey, p.siteName, p.siteUrl)
47+
if err != nil {
48+
panic(err)
49+
}
50+
51+
config.HTTPClient = proxy.ProxyHttpClient
52+
53+
c := openrouter.NewClientWithConfig(config)
54+
return c
55+
}
56+
57+
func (p *OpenRouterModelProvider) QueryText(question string, writer io.Writer, builder *strings.Builder) error {
58+
client := p.getProxyClientFromToken()
59+
60+
ctx := context.Background()
61+
flusher, ok := writer.(http.Flusher)
62+
if !ok {
63+
return fmt.Errorf("writer does not implement http.Flusher")
64+
}
65+
66+
model := p.subType
67+
if model == "" {
68+
model = openrouter.Gpt35Turbo
69+
}
70+
71+
promptTokens, err := GetTokenSize(model, question)
72+
if err != nil {
73+
return err
74+
}
75+
76+
maxTokens := 4097 - promptTokens
77+
78+
respStream, err := client.CreateChatCompletionStream(
79+
ctx,
80+
&openrouter.ChatCompletionRequest{
81+
Model: p.subType,
82+
Messages: []openrouter.ChatCompletionMessage{
83+
{
84+
Role: openrouter.ChatMessageRoleSystem,
85+
Content: "You are a helpful assistant.",
86+
},
87+
{
88+
Role: openrouter.ChatMessageRoleUser,
89+
Content: question,
90+
},
91+
},
92+
Stream: false,
93+
Temperature: nil,
94+
TopP: nil,
95+
MaxTokens: maxTokens,
96+
},
97+
)
98+
if err != nil {
99+
return err
100+
}
101+
defer respStream.Close()
102+
103+
isLeadingReturn := true
104+
for {
105+
completion, streamErr := respStream.Recv()
106+
if streamErr != nil {
107+
if streamErr == io.EOF {
108+
break
109+
}
110+
return streamErr
111+
}
112+
113+
data := completion.Choices[0].Message.Content
114+
if isLeadingReturn && len(data) != 0 {
115+
if strings.Count(data, "\n") == len(data) {
116+
continue
117+
} else {
118+
isLeadingReturn = false
119+
}
120+
}
121+
122+
if _, err = fmt.Fprintf(writer, "event: message\ndata: %s\n\n", data); err != nil {
123+
return err
124+
}
125+
flusher.Flush()
126+
builder.WriteString(data)
127+
}
128+
129+
return nil
130+
}

model/provider.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ func GetModelProvider(typ string, subType string, clientId string, clientSecret
3030
p, err = NewOpenAiModelProvider(subType, clientSecret)
3131
} else if typ == "Hugging Face" {
3232
p, err = NewHuggingFaceModelProvider(subType, clientSecret)
33+
} else if typ == "OpenRouter" {
34+
p, err = NewOpenRouterModelProvider(subType, clientSecret)
3335
} else if typ == "Ernie" {
3436
p, err = NewErnieModelProvider(subType, clientId, clientSecret)
3537
}

web/src/Setting.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,7 @@ export function getProviderTypeOptions(category) {
659659
[
660660
{id: "OpenAI", name: "OpenAI"},
661661
{id: "Hugging Face", name: "Hugging Face"},
662+
{id: "OpenRouter", name: "OpenRouter"},
662663
{id: "Ernie", name: "Ernie"},
663664
]
664665
);
@@ -739,6 +740,31 @@ export function getProviderSubTypeOptions(category, type) {
739740
{id: "THUDM/chatglm2-6b", name: "THUDM/chatglm2-6b"},
740741
]
741742
);
743+
} else if (type === "OpenRouter") {
744+
return (
745+
[
746+
{id: "google/palm-2-codechat-bison", name: "google/palm-2-codechat-bison"},
747+
{id: "google/palm-2-chat-bison", name: "google/palm-2-chat-bison"},
748+
{id: "openai/gpt-3.5-turbo", name: "openai/gpt-3.5-turbo"},
749+
{id: "openai/gpt-3.5-turbo-16k", name: "openai/gpt-3.5-turbo-16k"},
750+
{id: "openai/gpt-4", name: "openai/gpt-4"},
751+
{id: "openai/gpt-4-32k", name: "openai/gpt-4-32k"},
752+
{id: "anthropic/claude-2", name: "anthropic/claude-2"},
753+
{id: "anthropic/claude-instant-v1", name: "anthropic/claude-instant-v1"},
754+
{id: "meta-llama/llama-2-13b-chat", name: "meta-llama/llama-2-13b-chat"},
755+
{id: "meta-llama/llama-2-70b-chat", name: "meta-llama/llama-2-70b-chat"},
756+
{id: "palm-2-codechat-bison", name: "palm-2-codechat-bison"},
757+
{id: "palm-2-chat-bison", name: "palm-2-chat-bison"},
758+
{id: "gpt-3.5-turbo", name: "gpt-3.5-turbo"},
759+
{id: "gpt-3.5-turbo-16k", name: "gpt-3.5-turbo-16k"},
760+
{id: "gpt-4", name: "gpt-4"},
761+
{id: "gpt-4-32k", name: "gpt-4-32k"},
762+
{id: "claude-2", name: "claude-2"},
763+
{id: "claude-instant-v1", name: "claude-instant-v1"},
764+
{id: "llama-2-13b-chat", name: "llama-2-13b-chat"},
765+
{id: "llama-2-70b-chat", name: "llama-2-70b-chat"},
766+
]
767+
);
742768
} else if (type === "Ernie") {
743769
return (
744770
[

0 commit comments

Comments
 (0)