diff --git a/plugins/wasm-go/extensions/chatgpt-proxy/README.md b/plugins/wasm-go/extensions/chatgpt-proxy/README.md new file mode 100644 index 000000000..88044c044 --- /dev/null +++ b/plugins/wasm-go/extensions/chatgpt-proxy/README.md @@ -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" +``` + + diff --git a/plugins/wasm-go/extensions/chatgpt-proxy/go.mod b/plugins/wasm-go/extensions/chatgpt-proxy/go.mod new file mode 100644 index 000000000..61e9f298b --- /dev/null +++ b/plugins/wasm-go/extensions/chatgpt-proxy/go.mod @@ -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 +) diff --git a/plugins/wasm-go/extensions/chatgpt-proxy/go.sum b/plugins/wasm-go/extensions/chatgpt-proxy/go.sum new file mode 100644 index 000000000..01899d977 --- /dev/null +++ b/plugins/wasm-go/extensions/chatgpt-proxy/go.sum @@ -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= diff --git a/plugins/wasm-go/extensions/chatgpt-proxy/main.go b/plugins/wasm-go/extensions/chatgpt-proxy/main.go new file mode 100644 index 000000000..15ebb4ba6 --- /dev/null +++ b/plugins/wasm-go/extensions/chatgpt-proxy/main.go @@ -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 +} diff --git a/plugins/wasm-go/extensions/chatgpt-proxy/main.wasm b/plugins/wasm-go/extensions/chatgpt-proxy/main.wasm new file mode 100644 index 000000000..1c74fa1b1 Binary files /dev/null and b/plugins/wasm-go/extensions/chatgpt-proxy/main.wasm differ