diff --git a/openai/openai.go b/openai/openai.go index 22ec932a..46272e6f 100644 --- a/openai/openai.go +++ b/openai/openai.go @@ -1,6 +1,15 @@ package openai -import "github.com/pkoukk/tiktoken-go" +import ( + "encoding/base64" + "fmt" + + "github.com/pkoukk/tiktoken-go" + "github.com/yaoapp/gou/connector" + "github.com/yaoapp/gou/http" + "github.com/yaoapp/kun/exception" + "github.com/yaoapp/kun/utils" +) // Tiktoken get number of tokens func Tiktoken(model string, input string) (int, error) { @@ -11,3 +20,190 @@ func Tiktoken(model string, input string) (int, error) { token := tkm.Encode(input, nil, nil) return len(token), nil } + +// OpenAI struct +type OpenAI struct { + key string + model string + host string +} + +// New create a new OpenAI instance by connector id +func New(id string) (*OpenAI, error) { + c, err := connector.Select(id) + if err != nil { + return nil, err + } + + if !c.Is(connector.OPENAI) { + return nil, fmt.Errorf("The connector %s is not a OpenAI connector", id) + } + + setting := c.Setting() + return &OpenAI{ + key: setting["key"].(string), + model: setting["model"].(string), + host: setting["host"].(string), + }, nil +} + +// Completions Creates a completion for the provided prompt and parameters. +// https://platform.openai.com/docs/api-reference/completions/create +func (openai OpenAI) Completions(prompt interface{}, option map[string]interface{}, cb func(data []byte) int) (interface{}, *exception.Exception) { + if option == nil { + option = map[string]interface{}{} + } + option["prompt"] = prompt + + if cb != nil { + option["stream"] = true + return nil, openai.stream("/v1/completions", option, cb) + } + + option["stream"] = false + return openai.post("/v1/completions", option) +} + +// ChatCompletions Creates a model response for the given chat conversation. +// https://platform.openai.com/docs/api-reference/chat/create +func (openai OpenAI) ChatCompletions(messages []map[string]interface{}, option map[string]interface{}, cb func(data []byte) int) (interface{}, *exception.Exception) { + if option == nil { + option = map[string]interface{}{} + } + option["messages"] = messages + + if cb != nil { + option["stream"] = true + return nil, openai.stream("/v1/chat/completions", option, cb) + } + + option["stream"] = false + return openai.post("/v1/chat/completions", option) +} + +// Edits Creates a new edit for the provided input, instruction, and parameters. +// https://platform.openai.com/docs/api-reference/edits/create +func (openai OpenAI) Edits(instruction string, option map[string]interface{}) (interface{}, *exception.Exception) { + if option == nil { + option = map[string]interface{}{} + } + option["instruction"] = instruction + return openai.post("/v1/edits", option) +} + +// Embeddings Creates an embedding vector representing the input text. +// https://platform.openai.com/docs/api-reference/embeddings/create +func (openai OpenAI) Embeddings(input interface{}, user string) (interface{}, *exception.Exception) { + payload := map[string]interface{}{"input": input} + if user != "" { + payload["user"] = user + } + return openai.post("/v1/embeddings", payload) +} + +// AudioTranscriptions Transcribes audio into the input language. +func (openai OpenAI) AudioTranscriptions(dataBase64 string, option map[string]interface{}) (interface{}, *exception.Exception) { + data, err := base64.StdEncoding.DecodeString(dataBase64) + if err != nil { + return nil, exception.New("Base64 error :%s", 400, err.Error()) + } + + if option == nil { + option = map[string]interface{}{} + } + return openai.postFile("/v1/audio/transcriptions", map[string][]byte{"file": data}, option) +} + +// Tiktoken get number of tokens +func (openai OpenAI) Tiktoken(input string) (int, error) { + tkm, err := tiktoken.EncodingForModel(openai.model) + if err != nil { + return 0, err + } + token := tkm.Encode(input, nil, nil) + return len(token), nil +} + +// post post request +func (openai OpenAI) post(path string, payload map[string]interface{}) (interface{}, *exception.Exception) { + + url := fmt.Sprintf("%s%s", openai.host, path) + key := fmt.Sprintf("Bearer %s", openai.key) + payload["model"] = openai.model + + req := http.New(url). + WithHeader(map[string][]string{"Authorization": {key}}) + + res := req.Post(payload) + if err := openai.isError(res); err != nil { + return nil, err + } + return res.Data, nil +} + +// post post request +func (openai OpenAI) postFile(path string, files map[string][]byte, option map[string]interface{}) (interface{}, *exception.Exception) { + + url := fmt.Sprintf("%s%s", openai.host, path) + key := fmt.Sprintf("Bearer %s", openai.key) + option["model"] = openai.model + + req := http.New(url). + WithHeader(map[string][]string{ + "Authorization": {key}, + "Content-Type": {"multipart/form-data"}, + }) + + for name, data := range files { + req.AddFileBytes(name, fmt.Sprintf("%s.mp3", name), data) + } + + res := req.Send("POST", option) + if err := openai.isError(res); err != nil { + return nil, err + } + return res.Data, nil +} + +// stream post request +func (openai OpenAI) stream(path string, payload map[string]interface{}, cb func(data []byte) int) *exception.Exception { + url := fmt.Sprintf("%s%s", openai.host, path) + key := fmt.Sprintf("Bearer %s", openai.key) + payload["model"] = openai.model + req := http.New(url) + err := req. + WithHeader(map[string][]string{ + "Content-Type": {"application/json; charset=utf-8"}, + "Authorization": {key}, + }). + Stream("POST", payload, cb) + + if err != nil { + return exception.New(err.Error(), 500) + } + return nil +} + +func (openai OpenAI) isError(res *http.Response) *exception.Exception { + + if res.Status != 200 { + utils.Dump(res) + + message := "OpenAI Error" + if data, ok := res.Data.(map[string]interface{}); ok { + if err, has := data["error"]; has { + if err, ok := err.(map[string]interface{}); ok { + if msg, has := err["message"].(string); has { + message = msg + } + if code, has := err["code"].(string); has { + message = fmt.Sprintf("OpenAI %s %s", code, message) + } + } + } + } + return exception.New(message, res.Status) + } + + return nil +} diff --git a/openai/openai_test.go b/openai/openai_test.go new file mode 100644 index 00000000..e40ab25b --- /dev/null +++ b/openai/openai_test.go @@ -0,0 +1,177 @@ +package openai + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/connector" + "github.com/yaoapp/yao/test" +) + +func TestCompletions(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + openai := prepare(t, "text-davinci-003") + data, err := openai.Completions("Hello", nil, nil) + if err != nil { + t.Fatal(err.Message) + } + assert.NotNil(t, data.(map[string]interface{})["id"]) + + data, err = openai.Completions("Hello", map[string]interface{}{"max_tokens": 2}, nil) + if err != nil { + t.Fatal(err.Message) + } + + usage := data.(map[string]interface{})["usage"].(map[string]interface{}) + assert.Equal(t, 2, int(usage["completion_tokens"].(float64))) + + res := []byte{} + _, err = openai.Completions("Hello", nil, func(data []byte) int { + res = append(res, data...) + if len(data) == 0 { + res = append(res, []byte("\n")...) + } + + if string(data) == "data: [DONE]" { + return 0 + } + + return 1 + }) + + if err != nil { + t.Fatal(err) + } + + assert.NotEmpty(t, res) +} + +func TestChatCompletions(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + openai := prepare(t, "gpt-3_5-turbo") + data, err := openai.ChatCompletions([]map[string]interface{}{{"role": "user", "content": "hello"}}, nil, nil) + if err != nil { + t.Fatal(err.Message) + } + assert.NotNil(t, data.(map[string]interface{})["id"]) + + data, err = openai.ChatCompletions([]map[string]interface{}{{"role": "user", "content": "hello"}}, map[string]interface{}{"max_tokens": 2}, nil) + if err != nil { + t.Fatal(err.Message) + } + + usage := data.(map[string]interface{})["usage"].(map[string]interface{}) + assert.Equal(t, 2, int(usage["completion_tokens"].(float64))) + + res := []byte{} + _, err = openai.ChatCompletions([]map[string]interface{}{{"role": "user", "content": "hello"}}, nil, func(data []byte) int { + res = append(res, data...) + if len(data) == 0 { + res = append(res, []byte("\n")...) + } + + if string(data) == "data: [DONE]" { + return 0 + } + + return 1 + }) + + if err != nil { + t.Fatal(err) + } + + assert.NotEmpty(t, res) +} + +func TestEdits(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + openai := prepare(t, "text-davinci-edit-001") + data, err := openai.Edits("Fix the spelling mistakes", nil) + if err != nil { + t.Fatal(err.Message) + } + assert.NotNil(t, data.(map[string]interface{})["created"]) + + data, err = openai.Edits("Fix the spelling mistakes 2nd", map[string]interface{}{"input": "What day of the wek is it?"}) + if err != nil { + t.Fatal(err.Message) + } + assert.NotNil(t, data.(map[string]interface{})["created"]) + +} + +func TestEmbeddings(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + openai := prepare(t, "text-embedding-ada-002") + data, err := openai.Embeddings("The food was delicious and the waiter", "") + if err != nil { + t.Fatal(err.Message) + } + + assert.NotNil(t, data.(map[string]interface{})["data"]) + + data, err = openai.Embeddings([]string{"The food was delicious and the waiter", "hello"}, "user-01") + if err != nil { + t.Fatal(err.Message) + } + assert.NotNil(t, data.(map[string]interface{})["data"]) +} + +func TestAudioTranscriptions(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + openai := prepare(t, "whisper-1") + data, err := openai.AudioTranscriptions(audio(), nil) + if err != nil { + t.Fatal(err.Message) + } + assert.Equal(t, "今晚打老虎", data.(map[string]interface{})["text"]) +} + +// ProcessTiktoken get number of tokens +func TestTiktoken(t *testing.T) { + test.Prepare(t, config.Conf) + defer test.Clean() + + openai := prepare(t, "gpt-3_5-turbo") + res, err := openai.Tiktoken("hello world") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 2, res) + + res, err = openai.Tiktoken("你好世界!") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 6, res) +} + +func prepare(t *testing.T, id string) *OpenAI { + err := connector.Load(config.Conf) + if err != nil { + t.Fatal(err) + } + + openai, err := New(id) + if err != nil { + t.Fatal(err) + } + + return openai +} + +func audio() string { + return `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qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NIxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NIxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NIxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NIxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NIxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NIxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NIxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NIxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NIxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NIxHwAAANIAAAAAKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq` +} diff --git a/openai/process_test.go b/openai/process_test.go index d653f0fe..9ab42e07 100644 --- a/openai/process_test.go +++ b/openai/process_test.go @@ -7,7 +7,7 @@ import ( "github.com/yaoapp/gou/process" ) -func TestTiktoken(t *testing.T) { +func TestProcessTiktoken(t *testing.T) { // Hash args := []interface{}{"gpt-3.5-turbo", "hello world"} res := process.New("yao.openai.Tiktoken", args...).Run()