AI proxy plugin (#420)

This commit is contained in:
VIKI ZHAO
2023-07-07 15:30:04 +08:00
committed by GitHub
parent d4dbaba760
commit 2a97921d2b
5 changed files with 186 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
# 功能说明
`chatgpt-proxy`插件实现了代理请求AI大语言模型服务的功能。
# 配置字段
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| -------- | -------- | -------- | -------- | -------- |
| model | string | 选填 | text-davinci-003 | 配置使用的模型模型名称 |
| apiKey | string | 必填 | - | 配置使用的OpenAI API密钥 |
| promptParam | string | 选填 | prompt | 配置prompt的来源字段名称URL参数 |
| chatgptUri | string | 选填 | api.openai.com/v1/completions | 配置调用AI模型服务的URL路径默认值为OPENAI的API调用路径 |
# 配置示例
## 进行OpenAI curie模型的调用
```yaml
apiKey: "xxxxxxxxxxxxxx",
promptParam: "text",
model: "curie"
```

View File

@@ -0,0 +1,15 @@
module chatgpt-proxy
go 1.19
require (
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230629030002-81e467b6242d
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0
github.com/tidwall/gjson v1.14.4
)
require (
github.com/google/uuid v1.3.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
)

View File

@@ -0,0 +1,22 @@
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230619024848-7d2a05ef1c35 h1:9xBMl8mJ5CGE1ebqtx9s11zrKJHs0559Yf/QWFh0WIQ=
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230619024848-7d2a05ef1c35/go.mod h1:AzSnkuon5c26nIePTiJQIAFsKdhkNdncLcTuahpGtQs=
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230629030002-81e467b6242d h1:PyBnIfssGRMKvBf6wKH11Z1t+X1Z7qegD1pAO60sFrk=
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230629030002-81e467b6242d/go.mod h1:AzSnkuon5c26nIePTiJQIAFsKdhkNdncLcTuahpGtQs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/tetratelabs/proxy-wasm-go-sdk v0.19.1-0.20220822060051-f9d179a57f8c h1:OCUFXVGixHLfNjg6/QYEhv+jHJ5mRGhpEUVFv9eWPJE=
github.com/tetratelabs/proxy-wasm-go-sdk v0.19.1-0.20220822060051-f9d179a57f8c/go.mod h1:5t/pWFNJ9eMyu/K/Z+OeGhDJ9sN9eCo8fc2pyM/Qjg4=
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0 h1:kS7BvMKN+FiptV4pfwiNX8e3q14evxAWkhYbxt8EI1M=
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0/go.mod h1:qkW5MBz2jch2u8bS59wws65WC+Gtx3x0aPUX5JL7CXI=
github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -0,0 +1,128 @@
package main
import (
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
"github.com/tidwall/gjson"
)
func main() {
wrapper.SetCtx(
"chatgpt-proxy",
wrapper.ParseConfigBy(parseConfig),
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
)
}
type MyConfig struct {
Model string
ApiKey string
PromptParam string
ChatgptPath string
HumainId string
AIId string
client wrapper.HttpClient
}
func parseConfig(json gjson.Result, config *MyConfig, log wrapper.Log) error {
chatgptUri := json.Get("chatgptUri").String()
var chatgptHost string
if chatgptUri == "" {
config.ChatgptPath = "/v1/completions"
chatgptHost = "api.openai.com"
} else {
cp, err := url.Parse(chatgptUri)
if err != nil {
return err
}
config.ChatgptPath = cp.Path
chatgptHost = cp.Host
}
if config.ChatgptPath == "" {
return errors.New("not found path in chatgptUri")
}
if chatgptHost == "" {
return errors.New("not found host in chatgptUri")
}
config.client = wrapper.NewClusterClient(wrapper.RouteCluster{
Host: chatgptHost,
})
config.Model = json.Get("model").String()
if config.Model == "" {
config.Model = "text-davinci-003"
}
config.ApiKey = json.Get("apiKey").String()
if config.ApiKey == "" {
return errors.New("no apiKey found in config")
}
config.PromptParam = json.Get("promptParam").String()
if config.PromptParam == "" {
config.PromptParam = "prompt"
}
config.HumainId = json.Get("HumainId").String()
if config.HumainId == "" {
config.HumainId = "Humain:"
}
config.AIId = json.Get("AIId").String()
if config.AIId == "" {
config.AIId = "AI:"
}
return nil
}
const bodyTemplate string = `
{
"model":"%s",
"prompt":"%s",
"temperature":0.9,
"max_tokens": 150,
"top_p": 1,
"frequency_penalty": 0.0,
"presence_penalty": 0.6,
"stop": [" %s", " %s"]
}
`
func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig, log wrapper.Log) types.Action {
pairs := strings.SplitN(ctx.Path(), "?", 2)
if len(pairs) < 2 {
proxywasm.SendHttpResponse(400, nil, []byte("1-need prompt param"), -1)
return types.ActionContinue
}
querys, err := url.ParseQuery(pairs[1])
if err != nil {
proxywasm.SendHttpResponse(400, nil, []byte("2-need prompt param"), -1)
return types.ActionContinue
}
var prompt []string
var ok bool
if prompt, ok = querys[config.PromptParam]; !ok || len(prompt) == 0 {
proxywasm.SendHttpResponse(400, nil, []byte("3-need prompt param"), -1)
return types.ActionContinue
}
body := fmt.Sprintf(bodyTemplate, config.Model, prompt[0], config.HumainId, config.AIId)
err = config.client.Post(config.ChatgptPath, [][2]string{
{"Content-Type", "application/json"},
{"Authorization", "Bearer " + config.ApiKey},
}, []byte(body),
func(statusCode int, responseHeaders http.Header, responseBody []byte) {
var headers [][2]string
for key, value := range responseHeaders {
headers = append(headers, [2]string{key, value[0]})
}
proxywasm.SendHttpResponse(uint32(statusCode), headers, responseBody, -1)
}, 10000)
if err != nil {
proxywasm.SendHttpResponse(500, nil, []byte("Internel Error: "+err.Error()), -1)
return types.ActionContinue
}
return types.ActionPause
}

View File

Binary file not shown.