From 3e64ec275aba1d3f7930030534d76ec27032f51c Mon Sep 17 00:00:00 2001 From: Star Date: Fri, 25 Oct 2024 16:04:43 +0800 Subject: [PATCH] use new gojs --- .gitignore | 6 + aigc.go | 83 ----- chat.go | 133 +++++--- config.go | 30 +- gc.go | 94 ++++++ go.mod | 13 +- zhipu/CHANGELOG.md | 108 ------ zhipu/LICENSE | 21 -- zhipu/README.md | 280 ---------------- zhipu/README.zh.md | 278 ---------------- zhipu/async_result.go | 63 ---- zhipu/async_result_test.go | 1 - zhipu/batch.go | 258 --------------- zhipu/batch_support.go | 63 ---- zhipu/batch_support_test.go | 73 ----- zhipu/batch_test.go | 59 ---- zhipu/chat_completion.go | 577 --------------------------------- zhipu/chat_completion_test.go | 251 -------------- zhipu/client.go | 291 ----------------- zhipu/client_test.go | 17 - zhipu/cog.toml | 25 -- zhipu/embedding.go | 87 ----- zhipu/embedding_test.go | 21 -- zhipu/error.go | 58 ---- zhipu/error_test.go | 38 --- zhipu/file.go | 541 ------------------------------- zhipu/file_test.go | 71 ---- zhipu/fine_tune.go | 456 -------------------------- zhipu/fine_tune_test.go | 3 - zhipu/image_generation.go | 110 ------- zhipu/image_generation_test.go | 21 -- zhipu/knowledge.go | 299 ----------------- zhipu/knowledge_test.go | 50 --- zhipu/string_or.go | 54 --- zhipu/string_or_test.go | 37 --- zhipu/testdata/test-file.jsonl | 3 - zhipu/testdata/test-file.txt | 1 - zhipu/util.go | 22 -- zhipu/util_test.go | 3 - zhipu/video_generation.go | 125 ------- zhipu/video_generation_test.go | 38 --- zhipu/wechat-donation.png | Bin 46799 -> 0 bytes 42 files changed, 215 insertions(+), 4547 deletions(-) create mode 100644 .gitignore delete mode 100644 aigc.go create mode 100644 gc.go delete mode 100644 zhipu/CHANGELOG.md delete mode 100644 zhipu/LICENSE delete mode 100644 zhipu/README.md delete mode 100644 zhipu/README.zh.md delete mode 100644 zhipu/async_result.go delete mode 100644 zhipu/async_result_test.go delete mode 100644 zhipu/batch.go delete mode 100644 zhipu/batch_support.go delete mode 100644 zhipu/batch_support_test.go delete mode 100644 zhipu/batch_test.go delete mode 100644 zhipu/chat_completion.go delete mode 100644 zhipu/chat_completion_test.go delete mode 100644 zhipu/client.go delete mode 100644 zhipu/client_test.go delete mode 100644 zhipu/cog.toml delete mode 100644 zhipu/embedding.go delete mode 100644 zhipu/embedding_test.go delete mode 100644 zhipu/error.go delete mode 100644 zhipu/error_test.go delete mode 100644 zhipu/file.go delete mode 100644 zhipu/file_test.go delete mode 100644 zhipu/fine_tune.go delete mode 100644 zhipu/fine_tune_test.go delete mode 100644 zhipu/image_generation.go delete mode 100644 zhipu/image_generation_test.go delete mode 100644 zhipu/knowledge.go delete mode 100644 zhipu/knowledge_test.go delete mode 100644 zhipu/string_or.go delete mode 100644 zhipu/string_or_test.go delete mode 100644 zhipu/testdata/test-file.jsonl delete mode 100644 zhipu/testdata/test-file.txt delete mode 100644 zhipu/util.go delete mode 100644 zhipu/util_test.go delete mode 100644 zhipu/video_generation.go delete mode 100644 zhipu/video_generation_test.go delete mode 100644 zhipu/wechat-donation.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..09e93e0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.* +!.gitignore +go.sum +env.yml +node_modules +package.json diff --git a/aigc.go b/aigc.go deleted file mode 100644 index f9540db..0000000 --- a/aigc.go +++ /dev/null @@ -1,83 +0,0 @@ -package zhipu - -import ( - "apigo.cc/ai/zhipu/zhipu" - "context" - "errors" - "time" -) - -func (ag *Agent) FastMakeImage(prompt, size, refImage string) ([]string, error) { - return ag.MakeImage(ModelCogView3Plus, prompt, size, refImage) -} - -func (ag *Agent) BestMakeImage(prompt, size, refImage string) ([]string, error) { - return ag.MakeImage(ModelCogView3, prompt, size, refImage) -} - -func (ag *Agent) MakeImage(model, prompt, size, refImage string) ([]string, error) { - c, err := zhipu.NewClient(zhipu.WithAPIKey(ag.config.ApiKey), zhipu.WithBaseURL(ag.config.Endpoint)) - if err != nil { - return nil, err - } - - cc := c.ImageGeneration(model).SetPrompt(prompt) - if size != "" { - cc.SetSize(size) - } - - if r, err := cc.Do(context.Background()); err == nil { - results := make([]string, 0) - for _, item := range r.Data { - results = append(results, item.URL) - } - return results, nil - } else { - return nil, err - } -} - -func (ag *Agent) FastMakeVideo(prompt, size, refImage string) ([]string, []string, error) { - return ag.MakeVideo(ModelCogVideoX, prompt, size, refImage) -} - -func (ag *Agent) BestMakeVideo(prompt, size, refImage string) ([]string, []string, error) { - return ag.MakeVideo(ModelCogVideoX, prompt, size, refImage) -} - -func (ag *Agent) MakeVideo(model, prompt, size, refImage string) ([]string, []string, error) { - c, err := zhipu.NewClient(zhipu.WithAPIKey(ag.config.ApiKey), zhipu.WithBaseURL(ag.config.Endpoint)) - if err != nil { - return nil, nil, err - } - - cc := c.VideoGeneration(model).SetPrompt(prompt) - if refImage != "" { - cc.SetImageURL(refImage) - } - - if resp, err := cc.Do(context.Background()); err == nil { - for i := 0; i < 1200; i++ { - r, err := c.AsyncResult(resp.ID).Do(context.Background()) - if err != nil { - return nil, nil, err - } - if r.TaskStatus == zhipu.VideoGenerationTaskStatusSuccess { - covers := make([]string, 0) - results := make([]string, 0) - for _, item := range r.VideoResult { - results = append(results, item.URL) - covers = append(covers, item.CoverImageURL) - } - return results, covers, nil - } - if r.TaskStatus == zhipu.VideoGenerationTaskStatusFail { - return nil, nil, errors.New("fail on task " + resp.ID) - } - time.Sleep(3 * time.Second) - } - return nil, nil, errors.New("timeout on task " + resp.ID) - } else { - return nil, nil, err - } -} diff --git a/chat.go b/chat.go index 2151f07..e82ca7b 100644 --- a/chat.go +++ b/chat.go @@ -1,70 +1,72 @@ package zhipu import ( - "apigo.cc/ai/agent" - "apigo.cc/ai/zhipu/zhipu" + "apigo.cc/ai/llm/llm" + "bytes" "context" + "encoding/binary" + "fmt" + "github.com/ssgo/u" + "github.com/yankeguo/zhipu" "strings" + "time" ) -func (ag *Agent) FastAsk(messages []agent.ChatMessage, callback func(answer string)) (string, agent.TokenUsage, error) { - return ag.Ask(messages, &agent.ChatModelConfig{ +func (lm *LLM) FastAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { + return lm.Ask(messages, llm.ChatConfig{ Model: ModelGLM4Flash, }, callback) } -func (ag *Agent) LongAsk(messages []agent.ChatMessage, callback func(answer string)) (string, agent.TokenUsage, error) { - return ag.Ask(messages, &agent.ChatModelConfig{ +func (lm *LLM) LongAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { + return lm.Ask(messages, llm.ChatConfig{ Model: ModelGLM4Long, }, callback) } -func (ag *Agent) BatterAsk(messages []agent.ChatMessage, callback func(answer string)) (string, agent.TokenUsage, error) { - return ag.Ask(messages, &agent.ChatModelConfig{ +func (lm *LLM) BatterAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { + return lm.Ask(messages, llm.ChatConfig{ Model: ModelGLM4Plus, }, callback) } -func (ag *Agent) BestAsk(messages []agent.ChatMessage, callback func(answer string)) (string, agent.TokenUsage, error) { - return ag.Ask(messages, &agent.ChatModelConfig{ +func (lm *LLM) BestAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { + return lm.Ask(messages, llm.ChatConfig{ Model: ModelGLM40520, }, callback) } -func (ag *Agent) MultiAsk(messages []agent.ChatMessage, callback func(answer string)) (string, agent.TokenUsage, error) { - return ag.Ask(messages, &agent.ChatModelConfig{ +func (lm *LLM) MultiAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { + return lm.Ask(messages, llm.ChatConfig{ Model: ModelGLM4VPlus, }, callback) } -func (ag *Agent) BestMultiAsk(messages []agent.ChatMessage, callback func(answer string)) (string, agent.TokenUsage, error) { - return ag.Ask(messages, &agent.ChatModelConfig{ +func (lm *LLM) BestMultiAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { + return lm.Ask(messages, llm.ChatConfig{ Model: ModelGLM4V, }, callback) } -func (ag *Agent) CodeInterpreterAsk(messages []agent.ChatMessage, callback func(answer string)) (string, agent.TokenUsage, error) { - return ag.Ask(messages, &agent.ChatModelConfig{ +func (lm *LLM) CodeInterpreterAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { + return lm.Ask(messages, llm.ChatConfig{ Model: ModelGLM4AllTools, - Tools: map[string]any{agent.ToolCodeInterpreter: nil}, + Tools: map[string]any{llm.ToolCodeInterpreter: nil}, }, callback) } -func (ag *Agent) WebSearchAsk(messages []agent.ChatMessage, callback func(answer string)) (string, agent.TokenUsage, error) { - return ag.Ask(messages, &agent.ChatModelConfig{ +func (lm *LLM) WebSearchAsk(messages []llm.ChatMessage, callback func(answer string)) (string, llm.Usage, error) { + return lm.Ask(messages, llm.ChatConfig{ Model: ModelGLM4AllTools, - Tools: map[string]any{agent.ToolWebSearch: nil}, + Tools: map[string]any{llm.ToolWebSearch: nil}, }, callback) } -func (ag *Agent) Ask(messages []agent.ChatMessage, config *agent.ChatModelConfig, callback func(answer string)) (string, agent.TokenUsage, error) { - if config == nil { - config = &agent.ChatModelConfig{} - } - config.SetDefault(&ag.config.DefaultChatModelConfig) - c, err := zhipu.NewClient(zhipu.WithAPIKey(ag.config.ApiKey), zhipu.WithBaseURL(ag.config.Endpoint)) +func (lm *LLM) Ask(messages []llm.ChatMessage, config llm.ChatConfig, callback func(answer string)) (string, llm.Usage, error) { + config.SetDefault(&lm.config.ChatConfig) + c, err := zhipu.NewClient(zhipu.WithAPIKey(lm.config.ApiKey), zhipu.WithBaseURL(lm.config.Endpoint)) if err != nil { - return "", agent.TokenUsage{}, err + return "", llm.Usage{}, err } cc := c.ChatCompletion(config.GetModel()) @@ -76,27 +78,34 @@ func (ag *Agent) Ask(messages []agent.ChatMessage, config *agent.ChatModelConfig part := zhipu.ChatCompletionMultiContent{} part.Type = NameMap[inPart.Type] switch inPart.Type { - case agent.TypeText: + case llm.TypeText: part.Text = inPart.Content - case agent.TypeImage: + case llm.TypeImage: part.ImageURL = &zhipu.URLItem{URL: inPart.Content} - case agent.TypeVideo: - part.VideoURL = &zhipu.URLItem{URL: inPart.Content} + //case llm.TypeVideo: + // part.VideoURL = &zhipu.URLItem{URL: inPart.Content} } contents[j] = part } } - cc.AddMessage(zhipu.ChatCompletionMultiMessage{ - Role: NameMap[msg.Role], - Content: contents, - }) + if len(contents) == 1 && contents[0].Type == llm.TypeText { + cc.AddMessage(zhipu.ChatCompletionMessage{ + Role: NameMap[msg.Role], + Content: contents[0].Text, + }) + } else { + cc.AddMessage(zhipu.ChatCompletionMultiMessage{ + Role: NameMap[msg.Role], + Content: contents, + }) + } } for name := range config.GetTools() { switch name { - case agent.ToolCodeInterpreter: + case llm.ToolCodeInterpreter: cc.AddTool(zhipu.ChatCompletionToolCodeInterpreter{}) - case agent.ToolWebSearch: + case llm.ToolWebSearch: cc.AddTool(zhipu.ChatCompletionToolWebBrowser{}) } } @@ -122,19 +131,65 @@ func (ag *Agent) Ask(messages []agent.ChatMessage, config *agent.ChatModelConfig }) } + if lm.config.Debug { + fmt.Println(cc.BatchMethod(), cc.BatchURL()) + fmt.Println(u.JsonP(cc.BatchBody())) + } + + t1 := time.Now().UnixMilli() if r, err := cc.Do(context.Background()); err == nil { + t2 := time.Now().UnixMilli() - t1 results := make([]string, 0) if r.Choices != nil { for _, ch := range r.Choices { results = append(results, ch.Message.Content) } } - return strings.Join(results, ""), agent.TokenUsage{ + return strings.Join(results, ""), llm.Usage{ AskTokens: r.Usage.PromptTokens, AnswerTokens: r.Usage.CompletionTokens, TotalTokens: r.Usage.TotalTokens, + UsedTime: t2, }, nil } else { - return "", agent.TokenUsage{}, err + return "", llm.Usage{}, err + } +} + +func (lm *LLM) FastEmbedding(text string) ([]byte, llm.Usage, error) { + return lm.Embedding(text, ModelEmbedding3) +} + +func (lm *LLM) BestEmbedding(text string) ([]byte, llm.Usage, error) { + return lm.Embedding(text, ModelEmbedding3) +} + +func (lm *LLM) Embedding(text, model string) ([]byte, llm.Usage, error) { + c, err := zhipu.NewClient(zhipu.WithAPIKey(lm.config.ApiKey), zhipu.WithBaseURL(lm.config.Endpoint)) + if err != nil { + return nil, llm.Usage{}, err + } + + cc := c.Embedding(model) + cc.SetInput(text) + t1 := time.Now().UnixMilli() + if r, err := cc.Do(context.Background()); err == nil { + t2 := time.Now().UnixMilli() - t1 + buf := new(bytes.Buffer) + if r.Data != nil { + for _, ch := range r.Data { + for _, v := range ch.Embedding { + _ = binary.Write(buf, binary.LittleEndian, float32(v)) + } + } + } + return buf.Bytes(), llm.Usage{ + AskTokens: r.Usage.PromptTokens, + AnswerTokens: r.Usage.CompletionTokens, + TotalTokens: r.Usage.TotalTokens, + UsedTime: t2, + }, nil + } else { + return nil, llm.Usage{}, err } } diff --git a/config.go b/config.go index a8e57b0..8f30389 100644 --- a/config.go +++ b/config.go @@ -1,22 +1,22 @@ package zhipu import ( - "apigo.cc/ai/agent" - "apigo.cc/ai/zhipu/zhipu" + "apigo.cc/ai/llm/llm" + "github.com/yankeguo/zhipu" ) -type Agent struct { - config agent.APIConfig +type LLM struct { + config llm.Config } var NameMap = map[string]string{ - agent.TypeText: zhipu.MultiContentTypeText, - agent.TypeImage: zhipu.MultiContentTypeImageURL, - agent.TypeVideo: zhipu.MultiContentTypeVideoURL, - agent.RoleSystem: zhipu.RoleSystem, - agent.RoleUser: zhipu.RoleUser, - agent.RoleAssistant: zhipu.RoleAssistant, - agent.RoleTool: zhipu.RoleTool, + llm.TypeText: zhipu.MultiContentTypeText, + llm.TypeImage: zhipu.MultiContentTypeImageURL, + //llm.TypeVideo: zhipu.MultiContentTypeVideoURL, + llm.RoleSystem: zhipu.RoleSystem, + llm.RoleUser: zhipu.RoleUser, + llm.RoleAssistant: zhipu.RoleAssistant, + llm.RoleTool: zhipu.RoleTool, } const ( @@ -40,8 +40,8 @@ const ( ModelCodeGeeX4 = "CodeGeeX-4" ) -func (ag *Agent) Support() agent.Support { - return agent.Support{ +func (lm *LLM) Support() llm.Support { + return llm.Support{ Ask: true, AskWithImage: true, AskWithVideo: true, @@ -54,7 +54,7 @@ func (ag *Agent) Support() agent.Support { } func init() { - agent.RegisterAgentMaker("zhipu", func(config agent.APIConfig) agent.Agent { - return &Agent{config: config} + llm.Register("zhipu", func(config llm.Config) llm.LLM { + return &LLM{config: config} }) } diff --git a/gc.go b/gc.go new file mode 100644 index 0000000..8a3b920 --- /dev/null +++ b/gc.go @@ -0,0 +1,94 @@ +package zhipu + +import ( + "apigo.cc/ai/llm/llm" + "context" + "errors" + "github.com/yankeguo/zhipu" + "time" +) + +func (lm *LLM) FastMakeImage(prompt string, config llm.GCConfig) ([]string, llm.Usage, error) { + config.Model = ModelCogView3Plus + return lm.MakeImage(prompt, config) +} + +func (lm *LLM) BestMakeImage(prompt string, config llm.GCConfig) ([]string, llm.Usage, error) { + config.Model = ModelCogView3 + return lm.MakeImage(prompt, config) +} + +func (lm *LLM) MakeImage(prompt string, config llm.GCConfig) ([]string, llm.Usage, error) { + c, err := zhipu.NewClient(zhipu.WithAPIKey(lm.config.ApiKey), zhipu.WithBaseURL(lm.config.Endpoint)) + if err != nil { + return nil, llm.Usage{}, err + } + + config.SetDefault(&lm.config.GCConfig) + cc := c.ImageGeneration(config.Model).SetPrompt(prompt) + //cc.SetSize(config.GetSize()) + + t1 := time.Now().UnixMilli() + if r, err := cc.Do(context.Background()); err == nil { + t2 := time.Now().UnixMilli() - t1 + results := make([]string, 0) + for _, item := range r.Data { + results = append(results, item.URL) + } + return results, llm.Usage{ + UsedTime: t2, + }, nil + } else { + return nil, llm.Usage{}, err + } +} + +func (lm *LLM) FastMakeVideo(prompt string, config llm.GCConfig) ([]string, []string, llm.Usage, error) { + config.Model = ModelCogVideoX + return lm.MakeVideo(prompt, config) +} + +func (lm *LLM) BestMakeVideo(prompt string, config llm.GCConfig) ([]string, []string, llm.Usage, error) { + config.Model = ModelCogVideoX + return lm.MakeVideo(prompt, config) +} + +func (lm *LLM) MakeVideo(prompt string, config llm.GCConfig) ([]string, []string, llm.Usage, error) { + c, err := zhipu.NewClient(zhipu.WithAPIKey(lm.config.ApiKey), zhipu.WithBaseURL(lm.config.Endpoint)) + if err != nil { + return nil, nil, llm.Usage{}, err + } + + config.SetDefault(&lm.config.GCConfig) + cc := c.VideoGeneration(config.Model).SetPrompt(prompt) + cc.SetImageURL(config.GetRef()) + + t1 := time.Now().UnixMilli() + if resp, err := cc.Do(context.Background()); err == nil { + t2 := time.Now().UnixMilli() - t1 + for i := 0; i < 1200; i++ { + r, err := c.AsyncResult(resp.ID).Do(context.Background()) + if err != nil { + return nil, nil, llm.Usage{}, err + } + if r.TaskStatus == zhipu.VideoGenerationTaskStatusSuccess { + covers := make([]string, 0) + results := make([]string, 0) + for _, item := range r.VideoResult { + results = append(results, item.URL) + covers = append(covers, item.CoverImageURL) + } + return results, covers, llm.Usage{ + UsedTime: t2, + }, nil + } + if r.TaskStatus == zhipu.VideoGenerationTaskStatusFail { + return nil, nil, llm.Usage{}, errors.New("fail on task " + resp.ID) + } + time.Sleep(3 * time.Second) + } + return nil, nil, llm.Usage{}, errors.New("timeout on task " + resp.ID) + } else { + return nil, nil, llm.Usage{}, err + } +} diff --git a/go.mod b/go.mod index fd5f3e2..d480a5c 100644 --- a/go.mod +++ b/go.mod @@ -3,15 +3,14 @@ module apigo.cc/ai/zhipu go 1.22 require ( - apigo.cc/ai/agent v0.0.1 - github.com/go-resty/resty/v2 v2.14.0 - github.com/golang-jwt/jwt/v5 v5.2.1 - github.com/stretchr/testify v1.9.0 + apigo.cc/ai/llm v0.0.4 + github.com/ssgo/u v1.7.9 + github.com/yankeguo/zhipu v0.1.2 ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/net v0.29.0 // indirect + github.com/go-resty/resty/v2 v2.14.0 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + golang.org/x/net v0.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/zhipu/CHANGELOG.md b/zhipu/CHANGELOG.md deleted file mode 100644 index 8e1033f..0000000 --- a/zhipu/CHANGELOG.md +++ /dev/null @@ -1,108 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines. - -- - - -## v0.1.2 - 2024-08-15 -#### Bug Fixes -- add FinishReasonStopSequence - (01b4201) - GUO YANKE -#### Documentation -- update README.md [skip ci] - (e48a88b) - GUO YANKE -#### Features -- add videos/generations - (7261999) - GUO YANKE -#### Miscellaneous Chores -- relaxing go version to 1.18 - (6acc17c) - GUO YANKE - -- - - - -## v0.1.1 - 2024-07-17 -#### Documentation -- update README.md [skip ci] - (695432a) - GUO YANKE -#### Features -- add support for GLM-4-AllTools - (9627a36) - GUO YANKE - -- - - - -## v0.1.0 - 2024-06-28 -#### Bug Fixes -- rename client function for batch list - (40ac05f) - GUO YANKE -#### Documentation -- update README.md [skip ci] - (6ce5754) - GUO YANKE -#### Features -- add knowledge capacity service - (4ce62b3) - GUO YANKE -#### Refactoring -- update batch service - (b92d438) - GUO YANKE -- update chat completion service - (19dd77f) - GUO YANKE -- update embedding service - (c1bbc2d) - GUO YANKE -- update file services - (7ef4d87) - GUO YANKE -- update fine tune services, using APIError - (15aed88) - GUO YANKE -- update fine tune services - (664523b) - GUO YANKE -- update image generation service - (a18e028) - GUO YANKE -- update knowledge services - (c7bfb73) - GUO YANKE - -- - - - -## v0.0.6 - 2024-06-28 -#### Features -- add batch support for result reader - (c062095) - GUO YANKE -- add fine tune services - (f172f51) - GUO YANKE -- add knowledge service - (09792b5) - GUO YANKE - -- - - - -## v0.0.5 - 2024-06-28 -#### Bug Fixes -- api error parsing - (60a17f4) - GUO YANKE -#### Features -- add batch service - (389aec3) - GUO YANKE -- add batch support for chat completions, image generations and embeddings - (c017ffd) - GUO YANKE -- add file edit/get/delete service - (8a4d309) - GUO YANKE -- add file create serivce - (6d2140b) - GUO YANKE - -- - - - -## v0.0.4 - 2024-06-26 -#### Bug Fixes -- remove Client.R(), hide resty for future removal - (dc2a4ca) - GUO YANKE -#### Features -- add meta support for charglm - (fdd20e7) - GUO YANKE -- add client option to custom http client - (c62d6a9) - GUO YANKE - -- - - - -## v0.0.3 - 2024-06-26 -#### Features -- add image generation service - (9f3f54f) - GUO YANKE -- add support for vision models - (2dcd82a) - GUO YANKE -- add embedding service - (f57806a) - GUO YANKE - -- - - - -## v0.0.2 - 2024-06-26 -#### Bug Fixes -- **(deps)** update golang-jwt/jwt to v5 - (2f76a57) - GUO YANKE -#### Features -- add constants for roles - (3d08a72) - GUO YANKE - -- - - - -## v0.0.1 - 2024-06-26 -#### Bug Fixes -- add json tag "omitempty" to various types - (bf81097) - GUO YANKE -#### Continuous Integration -- add github action workflows for testing - (5a64987) - GUO YANKE -#### Documentation -- update README.md [skip ci] - (d504f57) - GUO YANKE -#### Features -- add chat completion in stream mode - (130fe1d) - GUO YANKE -- add chat completion in non-stream mode - (2326e37) - GUO YANKE -- support debug option while creating client - (0f104d8) - GUO YANKE -- add APIError and APIErrorResponse - (1886d85) - GUO YANKE -- add client struct - (710d8e8) - GUO YANKE -#### Refactoring -- change signature of Client#createJWT since there is no reason to fail - (f0d7887) - GUO YANKE -#### Tests -- add client_test.go - (a3fc217) - GUO YANKE - -- - - - -Changelog generated by [cocogitto](https://github.com/cocogitto/cocogitto). \ No newline at end of file diff --git a/zhipu/LICENSE b/zhipu/LICENSE deleted file mode 100644 index 67dc60b..0000000 --- a/zhipu/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Yanke G. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/zhipu/README.md b/zhipu/README.md deleted file mode 100644 index 555e351..0000000 --- a/zhipu/README.md +++ /dev/null @@ -1,280 +0,0 @@ -# zhipu - -[![Go Reference](https://pkg.go.dev/badge/github.com/yankeguo/zhipu.svg)](https://pkg.go.dev/github.com/yankeguo/zhipu) -[![go](https://github.com/yankeguo/zhipu/actions/workflows/go.yml/badge.svg)](https://github.com/yankeguo/zhipu/actions/workflows/go.yml) - -[中文文档](README.zh.md) - -A 3rd-Party Golang Client Library for Zhipu AI Platform - -## Usage - -### Install the package - -```bash -go get -u github.com/yankeguo/zhipu -``` - -### Create a client - -```go -// this will use environment variables ZHIPUAI_API_KEY -client, err := zhipu.NewClient() -// or you can specify the API key -client, err = zhipu.NewClient(zhipu.WithAPIKey("your api key")) -``` - -### Use the client - -**ChatCompletion** - -```go -service := client.ChatCompletion("glm-4-flash"). - AddMessage(zhipu.ChatCompletionMessage{ - Role: "user", - Content: "你好", - }) - -res, err := service.Do(context.Background()) - -if err != nil { - zhipu.GetAPIErrorCode(err) // get the API error code -} else { - println(res.Choices[0].Message.Content) -} -``` - -**ChatCompletion (Stream)** - -```go -service := client.ChatCompletion("glm-4-flash"). - AddMessage(zhipu.ChatCompletionMessage{ - Role: "user", - Content: "你好", - }).SetStreamHandler(func(chunk zhipu.ChatCompletionResponse) error { - println(chunk.Choices[0].Delta.Content) - return nil - }) - -res, err := service.Do(context.Background()) - -if err != nil { - zhipu.GetAPIErrorCode(err) // get the API error code -} else { - // this package will combine the stream chunks and build a final result mimicking the non-streaming API - println(res.Choices[0].Message.Content) -} -``` - -**ChatCompletion (Stream with GLM-4-AllTools)** - -```go -// CodeInterpreter -s := client.ChatCompletion("GLM-4-AllTools") -s.AddMessage(zhipu.ChatCompletionMultiMessage{ - Role: "user", - Content: []zhipu.ChatCompletionMultiContent{ - { - Type: "text", - Text: "计算[5,10,20,700,99,310,978,100]的平均值和方差。", - }, - }, -}) -s.AddTool(zhipu.ChatCompletionToolCodeInterpreter{ - Sandbox: zhipu.Ptr(CodeInterpreterSandboxAuto), -}) -s.SetStreamHandler(func(chunk zhipu.ChatCompletionResponse) error { - for _, c := range chunk.Choices { - for _, tc := range c.Delta.ToolCalls { - if tc.Type == ToolTypeCodeInterpreter && tc.CodeInterpreter != nil { - if tc.CodeInterpreter.Input != "" { - // DO SOMETHING - } - if len(tc.CodeInterpreter.Outputs) > 0 { - // DO SOMETHING - } - } - } - } - return nil -}) - -// WebBrowser -// CAUTION: NOT 'WebSearch' -s := client.ChatCompletion("GLM-4-AllTools") -s.AddMessage(zhipu.ChatCompletionMultiMessage{ - Role: "user", - Content: []zhipu.ChatCompletionMultiContent{ - { - Type: "text", - Text: "搜索下本周深圳天气如何", - }, - }, -}) -s.AddTool(zhipu.ChatCompletionToolWebBrowser{}) -s.SetStreamHandler(func(chunk zhipu.ChatCompletionResponse) error { - for _, c := range chunk.Choices { - for _, tc := range c.Delta.ToolCalls { - if tc.Type == ToolTypeWebBrowser && tc.WebBrowser != nil { - if tc.WebBrowser.Input != "" { - // DO SOMETHING - } - if len(tc.WebBrowser.Outputs) > 0 { - // DO SOMETHING - } - } - } - } - return nil -}) -s.Do(context.Background()) - -// DrawingTool -s := client.ChatCompletion("GLM-4-AllTools") -s.AddMessage(zhipu.ChatCompletionMultiMessage{ - Role: "user", - Content: []zhipu.ChatCompletionMultiContent{ - { - Type: "text", - Text: "画一个正弦函数图像", - }, - }, -}) -s.AddTool(zhipu.ChatCompletionToolDrawingTool{}) -s.SetStreamHandler(func(chunk zhipu.ChatCompletionResponse) error { - for _, c := range chunk.Choices { - for _, tc := range c.Delta.ToolCalls { - if tc.Type == ToolTypeDrawingTool && tc.DrawingTool != nil { - if tc.DrawingTool.Input != "" { - // DO SOMETHING - } - if len(tc.DrawingTool.Outputs) > 0 { - // DO SOMETHING - } - } - } - } - return nil -}) -s.Do(context.Background()) -``` - -**Embedding** - -```go -service := client.Embedding("embedding-v2").SetInput("你好呀") -service.Do(context.Background()) -``` - -**Image Generation** - -```go -service := client.ImageGeneration("cogview-3").SetPrompt("一只可爱的小猫咪") -service.Do(context.Background()) -``` - -**Video Generation** - -```go -service := client.VideoGeneration("cogvideox").SetPrompt("一只可爱的小猫咪") -resp, err := service.Do(context.Background()) - -for { - result, err := client.AsyncResult(resp.ID).Do(context.Background()) - - if result.TaskStatus == zhipu.VideoGenerationTaskStatusSuccess { - _ = result.VideoResult[0].URL - _ = result.VideoResult[0].CoverImageURL - break - } - - if result.TaskStatus != zhipu.VideoGenerationTaskStatusProcessing { - break - } - - time.Sleep(5 * time.Second) -} -``` - -**Upload File (Retrieval)** - -```go -service := client.FileCreate(zhipu.FilePurposeRetrieval) -service.SetLocalFile(filepath.Join("testdata", "test-file.txt")) -service.SetKnowledgeID("your-knowledge-id") - -service.Do(context.Background()) -``` - -**Upload File (Fine-Tune)** - -```go -service := client.FileCreate(zhipu.FilePurposeFineTune) -service.SetLocalFile(filepath.Join("testdata", "test-file.jsonl")) -service.Do(context.Background()) -``` - -**Batch Create** - -```go -service := client.BatchCreate(). - SetInputFileID("fileid"). - SetCompletionWindow(zhipu.BatchCompletionWindow24h). - SetEndpoint(BatchEndpointV4ChatCompletions) -service.Do(context.Background()) -``` - -**Knowledge Base** - -```go -client.KnowledgeCreate("") -client.KnowledgeEdit("") -``` - -**Fine Tune** - -```go -client.FineTuneCreate("") -``` - -### Batch Support - -**Batch File Writer** - -```go -f, err := os.OpenFile("batch.jsonl", os.O_CREATE|os.O_WRONLY, 0644) - -bw := zhipu.NewBatchFileWriter(f) - -bw.Add("action_1", client.ChatCompletion("glm-4-flash"). - AddMessage(zhipu.ChatCompletionMessage{ - Role: "user", - Content: "你好", - })) -bw.Add("action_2", client.Embedding("embedding-v2").SetInput("你好呀")) -bw.Add("action_3", client.ImageGeneration("cogview-3").SetPrompt("一只可爱的小猫咪")) -``` - -**Batch Result Reader** - -```go -br := zhipu.NewBatchResultReader[zhipu.ChatCompletionResponse](r) - -for { - var res zhipu.BatchResult[zhipu.ChatCompletionResponse] - err := br.Read(&res) - if err != nil { - break - } -} -``` - -## Donation - -Executing unit tests will actually call the ChatGLM API and consume my quota. Please donate and thank you for your support! - - - -## Credits - -GUO YANKE, MIT License diff --git a/zhipu/README.zh.md b/zhipu/README.zh.md deleted file mode 100644 index 2563f05..0000000 --- a/zhipu/README.zh.md +++ /dev/null @@ -1,278 +0,0 @@ -# zhipu - -[![Go Reference](https://pkg.go.dev/badge/github.com/yankeguo/zhipu.svg)](https://pkg.go.dev/github.com/yankeguo/zhipu) -[![go](https://github.com/yankeguo/zhipu/actions/workflows/go.yml/badge.svg)](https://github.com/yankeguo/zhipu/actions/workflows/go.yml) - -Zhipu AI 平台第三方 Golang 客户端库 - -## 用法 - -### 安装库 - -```bash -go get -u github.com/yankeguo/zhipu -``` - -### 创建客户端 - -```go -// 默认使用环境变量 ZHIPUAI_API_KEY -client, err := zhipu.NewClient() -// 或者手动指定密钥 -client, err = zhipu.NewClient(zhipu.WithAPIKey("your api key")) -``` - -### 使用客户端 - -**ChatCompletion(大语言模型)** - -```go -service := client.ChatCompletion("glm-4-flash"). - AddMessage(zhipu.ChatCompletionMessage{ - Role: "user", - Content: "你好", - }) - -res, err := service.Do(context.Background()) - -if err != nil { - zhipu.GetAPIErrorCode(err) // get the API error code -} else { - println(res.Choices[0].Message.Content) -} -``` - -**ChatCompletion(流式调用大语言模型)** - -```go -service := client.ChatCompletion("glm-4-flash"). - AddMessage(zhipu.ChatCompletionMessage{ - Role: "user", - Content: "你好", - }).SetStreamHandler(func(chunk zhipu.ChatCompletionResponse) error { - println(chunk.Choices[0].Delta.Content) - return nil - }) - -res, err := service.Do(context.Background()) - -if err != nil { - zhipu.GetAPIErrorCode(err) // get the API error code -} else { - // this package will combine the stream chunks and build a final result mimicking the non-streaming API - println(res.Choices[0].Message.Content) -} -``` - -**ChatCompletion(流式调用大语言工具模型GLM-4-AllTools)** - -```go -// CodeInterpreter -s := client.ChatCompletion("GLM-4-AllTools") -s.AddMessage(zhipu.ChatCompletionMultiMessage{ - Role: "user", - Content: []zhipu.ChatCompletionMultiContent{ - { - Type: "text", - Text: "计算[5,10,20,700,99,310,978,100]的平均值和方差。", - }, - }, -}) -s.AddTool(zhipu.ChatCompletionToolCodeInterpreter{ - Sandbox: zhipu.Ptr(CodeInterpreterSandboxAuto), -}) -s.SetStreamHandler(func(chunk zhipu.ChatCompletionResponse) error { - for _, c := range chunk.Choices { - for _, tc := range c.Delta.ToolCalls { - if tc.Type == ToolTypeCodeInterpreter && tc.CodeInterpreter != nil { - if tc.CodeInterpreter.Input != "" { - // DO SOMETHING - } - if len(tc.CodeInterpreter.Outputs) > 0 { - // DO SOMETHING - } - } - } - } - return nil -}) - -// WebBrowser -// CAUTION: NOT 'WebSearch' -s := client.ChatCompletion("GLM-4-AllTools") -s.AddMessage(zhipu.ChatCompletionMultiMessage{ - Role: "user", - Content: []zhipu.ChatCompletionMultiContent{ - { - Type: "text", - Text: "搜索下本周深圳天气如何", - }, - }, -}) -s.AddTool(zhipu.ChatCompletionToolWebBrowser{}) -s.SetStreamHandler(func(chunk zhipu.ChatCompletionResponse) error { - for _, c := range chunk.Choices { - for _, tc := range c.Delta.ToolCalls { - if tc.Type == ToolTypeWebBrowser && tc.WebBrowser != nil { - if tc.WebBrowser.Input != "" { - // DO SOMETHING - } - if len(tc.WebBrowser.Outputs) > 0 { - // DO SOMETHING - } - } - } - } - return nil -}) -s.Do(context.Background()) - -// DrawingTool -s := client.ChatCompletion("GLM-4-AllTools") -s.AddMessage(zhipu.ChatCompletionMultiMessage{ - Role: "user", - Content: []zhipu.ChatCompletionMultiContent{ - { - Type: "text", - Text: "画一个正弦函数图像", - }, - }, -}) -s.AddTool(zhipu.ChatCompletionToolDrawingTool{}) -s.SetStreamHandler(func(chunk zhipu.ChatCompletionResponse) error { - for _, c := range chunk.Choices { - for _, tc := range c.Delta.ToolCalls { - if tc.Type == ToolTypeDrawingTool && tc.DrawingTool != nil { - if tc.DrawingTool.Input != "" { - // DO SOMETHING - } - if len(tc.DrawingTool.Outputs) > 0 { - // DO SOMETHING - } - } - } - } - return nil -}) -s.Do(context.Background()) -``` - -**Embedding** - -```go -service := client.Embedding("embedding-v2").SetInput("你好呀") -service.Do(context.Background()) -``` - -**ImageGeneration(图像生成)** - -```go -service := client.ImageGeneration("cogview-3").SetPrompt("一只可爱的小猫咪") -service.Do(context.Background()) -``` - -**VideoGeneration(视频生成)** - -```go -service := client.VideoGeneration("cogvideox").SetPrompt("一只可爱的小猫咪") -resp, err := service.Do(context.Background()) - -for { - result, err := client.AsyncResult(resp.ID).Do(context.Background()) - - if result.TaskStatus == zhipu.VideoGenerationTaskStatusSuccess { - _ = result.VideoResult[0].URL - _ = result.VideoResult[0].CoverImageURL - break - } - - if result.TaskStatus != zhipu.VideoGenerationTaskStatusProcessing { - break - } - - time.Sleep(5 * time.Second) -} -``` - -**UploadFile(上传文件用于取回)** - -```go -service := client.FileCreate(zhipu.FilePurposeRetrieval) -service.SetLocalFile(filepath.Join("testdata", "test-file.txt")) -service.SetKnowledgeID("your-knowledge-id") - -service.Do(context.Background()) -``` - -**UploadFile(上传文件用于微调)** - -```go -service := client.FileCreate(zhipu.FilePurposeFineTune) -service.SetLocalFile(filepath.Join("testdata", "test-file.jsonl")) -service.Do(context.Background()) -``` - -**BatchCreate(创建批量任务)** - -```go -service := client.BatchCreate(). - SetInputFileID("fileid"). - SetCompletionWindow(zhipu.BatchCompletionWindow24h). - SetEndpoint(BatchEndpointV4ChatCompletions) -service.Do(context.Background()) -``` - -**KnowledgeBase(知识库)** - -```go -client.KnowledgeCreate("") -client.KnowledgeEdit("") -``` - -**FineTune(微调)** - -```go -client.FineTuneCreate("") -``` - -### 批量任务辅助工具 - -**批量任务文件创建** - -```go -f, err := os.OpenFile("batch.jsonl", os.O_CREATE|os.O_WRONLY, 0644) - -bw := zhipu.NewBatchFileWriter(f) - -bw.Add("action_1", client.ChatCompletion("glm-4-flash"). - AddMessage(zhipu.ChatCompletionMessage{ - Role: "user", - Content: "你好", - })) -bw.Add("action_2", client.Embedding("embedding-v2").SetInput("你好呀")) -bw.Add("action_3", client.ImageGeneration("cogview-3").SetPrompt("一只可爱的小猫咪")) -``` - -**批量任务结果解析** - -```go -br := zhipu.NewBatchResultReader[zhipu.ChatCompletionResponse](r) - -for { - var res zhipu.BatchResult[zhipu.ChatCompletionResponse] - err := br.Read(&res) - if err != nil { - break - } -} -``` - -## 赞助 - -执行单元测试会真实调用GLM接口,消耗我充值的额度,开发不易,请微信扫码捐赠,感谢您的支持! - - - -## 许可证 - -GUO YANKE, MIT License diff --git a/zhipu/async_result.go b/zhipu/async_result.go deleted file mode 100644 index 5e51db1..0000000 --- a/zhipu/async_result.go +++ /dev/null @@ -1,63 +0,0 @@ -package zhipu - -import ( - "context" - - "github.com/go-resty/resty/v2" -) - -// AsyncResultService creates a new async result get service -type AsyncResultService struct { - client *Client - - id string -} - -// AsyncResultVideo is the video result of the AsyncResultService -type AsyncResultVideo struct { - URL string `json:"url"` - CoverImageURL string `json:"cover_image_url"` -} - -// AsyncResultResponse is the response of the AsyncResultService -type AsyncResultResponse struct { - Model string `json:"model"` - TaskStatus string `json:"task_status"` - RequestID string `json:"request_id"` - ID string `json:"id"` - VideoResult []AsyncResultVideo `json:"video_result"` -} - -// NewAsyncResultService creates a new async result get service -func NewAsyncResultService(client *Client) *AsyncResultService { - return &AsyncResultService{ - client: client, - } -} - -// SetID sets the id parameter -func (s *AsyncResultService) SetID(id string) *AsyncResultService { - s.id = id - return s -} - -func (s *AsyncResultService) Do(ctx context.Context) (res AsyncResultResponse, err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - - if resp, err = s.client.request(ctx). - SetResult(&res). - SetError(&apiError). - Get("async-result/" + s.id); err != nil { - return - } - - if resp.IsError() { - err = apiError - return - } - - return -} diff --git a/zhipu/async_result_test.go b/zhipu/async_result_test.go deleted file mode 100644 index f4a78b5..0000000 --- a/zhipu/async_result_test.go +++ /dev/null @@ -1 +0,0 @@ -package zhipu diff --git a/zhipu/batch.go b/zhipu/batch.go deleted file mode 100644 index d661f56..0000000 --- a/zhipu/batch.go +++ /dev/null @@ -1,258 +0,0 @@ -package zhipu - -import ( - "context" - "encoding/json" - "strconv" - - "github.com/go-resty/resty/v2" -) - -const ( - BatchEndpointV4ChatCompletions = "/v4/chat/completions" - BatchEndpointV4ImagesGenerations = "/v4/images/generations" - BatchEndpointV4Embeddings = "/v4/embeddings" - BatchEndpointV4VideosGenerations = "/v4/videos/generations" - - BatchCompletionWindow24h = "24h" -) - -// BatchRequestCounts represents the counts of the batch requests. -type BatchRequestCounts struct { - Total int64 `json:"total"` - Completed int64 `json:"completed"` - Failed int64 `json:"failed"` -} - -// BatchItem represents a batch item. -type BatchItem struct { - ID string `json:"id"` - Object any `json:"object"` - Endpoint string `json:"endpoint"` - InputFileID string `json:"input_file_id"` - CompletionWindow string `json:"completion_window"` - Status string `json:"status"` - OutputFileID string `json:"output_file_id"` - ErrorFileID string `json:"error_file_id"` - CreatedAt int64 `json:"created_at"` - InProgressAt int64 `json:"in_progress_at"` - ExpiresAt int64 `json:"expires_at"` - FinalizingAt int64 `json:"finalizing_at"` - CompletedAt int64 `json:"completed_at"` - FailedAt int64 `json:"failed_at"` - ExpiredAt int64 `json:"expired_at"` - CancellingAt int64 `json:"cancelling_at"` - CancelledAt int64 `json:"cancelled_at"` - RequestCounts BatchRequestCounts `json:"request_counts"` - Metadata json.RawMessage `json:"metadata"` -} - -// BatchCreateService is a service to create a batch. -type BatchCreateService struct { - client *Client - - inputFileID string - endpoint string - completionWindow string - metadata any -} - -// NewBatchCreateService creates a new BatchCreateService. -func NewBatchCreateService(client *Client) *BatchCreateService { - return &BatchCreateService{client: client} -} - -// SetInputFileID sets the input file id for the batch. -func (s *BatchCreateService) SetInputFileID(inputFileID string) *BatchCreateService { - s.inputFileID = inputFileID - return s -} - -// SetEndpoint sets the endpoint for the batch. -func (s *BatchCreateService) SetEndpoint(endpoint string) *BatchCreateService { - s.endpoint = endpoint - return s -} - -// SetCompletionWindow sets the completion window for the batch. -func (s *BatchCreateService) SetCompletionWindow(window string) *BatchCreateService { - s.completionWindow = window - return s -} - -// SetMetadata sets the metadata for the batch. -func (s *BatchCreateService) SetMetadata(metadata any) *BatchCreateService { - s.metadata = metadata - return s -} - -// Do executes the batch create service. -func (s *BatchCreateService) Do(ctx context.Context) (res BatchItem, err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - - if resp, err = s.client.request(ctx). - SetBody(M{ - "input_file_id": s.inputFileID, - "endpoint": s.endpoint, - "completion_window": s.completionWindow, - "metadata": s.metadata, - }). - SetResult(&res). - SetError(&apiError). - Post("batches"); err != nil { - return - } - - if resp.IsError() { - err = apiError - } - - return -} - -// BatchGetService is a service to get a batch. -type BatchGetService struct { - client *Client - batchID string -} - -// BatchGetResponse represents the response of the batch get service. -type BatchGetResponse = BatchItem - -// NewBatchGetService creates a new BatchGetService. -func NewBatchGetService(client *Client) *BatchGetService { - return &BatchGetService{client: client} -} - -// SetBatchID sets the batch id for the batch get service. -func (s *BatchGetService) SetBatchID(batchID string) *BatchGetService { - s.batchID = batchID - return s -} - -// Do executes the batch get service. -func (s *BatchGetService) Do(ctx context.Context) (res BatchGetResponse, err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - - if resp, err = s.client.request(ctx). - SetPathParam("batch_id", s.batchID). - SetResult(&res). - SetError(&apiError). - Get("batches/{batch_id}"); err != nil { - return - } - - if resp.IsError() { - err = apiError - } - - return -} - -// BatchCancelService is a service to cancel a batch. -type BatchCancelService struct { - client *Client - batchID string -} - -// NewBatchCancelService creates a new BatchCancelService. -func NewBatchCancelService(client *Client) *BatchCancelService { - return &BatchCancelService{client: client} -} - -// SetBatchID sets the batch id for the batch cancel service. -func (s *BatchCancelService) SetBatchID(batchID string) *BatchCancelService { - s.batchID = batchID - return s -} - -// Do executes the batch cancel service. -func (s *BatchCancelService) Do(ctx context.Context) (err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - - if resp, err = s.client.request(ctx). - SetPathParam("batch_id", s.batchID). - SetBody(M{}). - SetError(&apiError). - Post("batches/{batch_id}/cancel"); err != nil { - return - } - - if resp.IsError() { - err = apiError - } - - return -} - -// BatchListService is a service to list batches. -type BatchListService struct { - client *Client - - after *string - limit *int -} - -// BatchListResponse represents the response of the batch list service. -type BatchListResponse struct { - Object string `json:"object"` - Data []BatchItem `json:"data"` - FirstID string `json:"first_id"` - LastID string `json:"last_id"` - HasMore bool `json:"has_more"` -} - -// NewBatchListService creates a new BatchListService. -func NewBatchListService(client *Client) *BatchListService { - return &BatchListService{client: client} -} - -// SetAfter sets the after cursor for the batch list service. -func (s *BatchListService) SetAfter(after string) *BatchListService { - s.after = &after - return s -} - -// SetLimit sets the limit for the batch list service. -func (s *BatchListService) SetLimit(limit int) *BatchListService { - s.limit = &limit - return s -} - -// Do executes the batch list service. -func (s *BatchListService) Do(ctx context.Context) (res BatchListResponse, err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - - req := s.client.request(ctx) - if s.after != nil { - req.SetQueryParam("after", *s.after) - } - if s.limit != nil { - req.SetQueryParam("limit", strconv.Itoa(*s.limit)) - } - - if resp, err = req. - SetResult(&res). - SetError(&apiError). - Get("batches"); err != nil { - return - } - - if resp.IsError() { - err = apiError - } - - return -} diff --git a/zhipu/batch_support.go b/zhipu/batch_support.go deleted file mode 100644 index 9427ef4..0000000 --- a/zhipu/batch_support.go +++ /dev/null @@ -1,63 +0,0 @@ -package zhipu - -import ( - "encoding/json" - "io" -) - -// BatchSupport is the interface for services with batch support. -type BatchSupport interface { - BatchMethod() string - BatchURL() string - BatchBody() any -} - -// BatchFileWriter is a writer for batch files. -type BatchFileWriter struct { - w io.Writer - je *json.Encoder -} - -// NewBatchFileWriter creates a new BatchFileWriter. -func NewBatchFileWriter(w io.Writer) *BatchFileWriter { - return &BatchFileWriter{w: w, je: json.NewEncoder(w)} -} - -// Write writes a batch file. -func (b *BatchFileWriter) Write(customID string, s BatchSupport) error { - return b.je.Encode(M{ - "custom_id": customID, - "method": s.BatchMethod(), - "url": s.BatchURL(), - "body": s.BatchBody(), - }) -} - -// BatchResultResponse is the response of a batch result. -type BatchResultResponse[T any] struct { - StatusCode int `json:"status_code"` - Body T `json:"body"` -} - -// BatchResult is the result of a batch. -type BatchResult[T any] struct { - ID string `json:"id"` - CustomID string `json:"custom_id"` - Response BatchResultResponse[T] `json:"response"` -} - -// BatchResultReader reads batch results. -type BatchResultReader[T any] struct { - r io.Reader - jd *json.Decoder -} - -// NewBatchResultReader creates a new BatchResultReader. -func NewBatchResultReader[T any](r io.Reader) *BatchResultReader[T] { - return &BatchResultReader[T]{r: r, jd: json.NewDecoder(r)} -} - -// Read reads a batch result. -func (r *BatchResultReader[T]) Read(out *BatchResult[T]) error { - return r.jd.Decode(out) -} diff --git a/zhipu/batch_support_test.go b/zhipu/batch_support_test.go deleted file mode 100644 index 59a19c6..0000000 --- a/zhipu/batch_support_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package zhipu - -import ( - "bytes" - "io" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestBatchFileWriter(t *testing.T) { - client, err := NewClient() - require.NoError(t, err) - - buf := &bytes.Buffer{} - - w := NewBatchFileWriter(buf) - err = w.Write("batch-1", client.ChatCompletion("a").AddMessage(ChatCompletionMessage{ - Role: "user", Content: "hello", - })) - require.NoError(t, err) - err = w.Write("batch-2", client.Embedding("c").SetInput("whoa")) - require.NoError(t, err) - err = w.Write("batch-3", client.ImageGeneration("d").SetPrompt("whoa")) - require.NoError(t, err) - - require.Equal(t, `{"body":{"messages":[{"role":"user","content":"hello"}],"model":"a"},"custom_id":"batch-1","method":"POST","url":"/v4/chat/completions"} -{"body":{"input":"whoa","model":"c"},"custom_id":"batch-2","method":"POST","url":"/v4/embeddings"} -{"body":{"model":"d","prompt":"whoa"},"custom_id":"batch-3","method":"POST","url":"/v4/images/generations"} -`, buf.String()) -} - -func TestBatchResultReader(t *testing.T) { - result := ` - {"response":{"status_code":200,"body":{"created":1715959701,"usage":{"completion_tokens":26,"prompt_tokens":89,"total_tokens":115},"model":"glm-4","id":"8668357533850320547","choices":[{"finish_reason":"stop","index":0,"message":{"role":"assistant","content":"'''json\n{\n \"分类标签\": \"负面\",\n \"特定问题标注\": \"订单处理慢\"\n}\n'''"}}],"request_id":"615-request-1"}},"custom_id":"request-1","id":"batch_1791490810192076800"} -{"response":{"status_code":200,"body":{"created":1715959701,"usage":{"completion_tokens":22,"prompt_tokens":94,"total_tokens":116},"model":"glm-4","id":"8668368425887509080","choices":[{"finish_reason":"stop","index":0,"message":{"role":"assistant","content":"'''json\n{\n \"分类标签\": \"负面\",\n \"特定问题标注\": \"产品缺陷\"\n}\n'''"}}],"request_id":"616-request-2"}},"custom_id":"request-2","id":"batch_1791490810192076800"} -{"response":{"status_code":200,"body":{"created":1715959701,"usage":{"completion_tokens":25,"prompt_tokens":86,"total_tokens":111},"model":"glm-4","id":"8668355815863214980","choices":[{"finish_reason":"stop","index":0,"message":{"role":"assistant","content":"'''json\n{\n \"分类标签\": \"正面\",\n \"特定问题标注\": \"性价比\"\n}\n'''"}}],"request_id":"617-request-3"}},"custom_id":"request-3","id":"batch_1791490810192076800"} -{"response":{"status_code":200,"body":{"created":1715959701,"usage":{"completion_tokens":28,"prompt_tokens":89,"total_tokens":117},"model":"glm-4","id":"8668355815863214981","choices":[{"finish_reason":"stop","index":0,"message":{"role":"assistant","content":"'''json\n{\n \"分类标签\": \"负面\",\n \"特定问题标注\": \"说明文档不清晰\"\n}\n'''"}}],"request_id":"618-request-4"}},"custom_id":"request-4","id":"batch_1791490810192076800"} - -{"response":{"status_code":200,"body":{"created":1715959701,"usage":{"completion_tokens":26,"prompt_tokens":88,"total_tokens":114},"model":"glm-4","id":"8668357533850320546","choices":[{"finish_reason":"stop","index":0,"message":{"role":"assistant","content":"'''json\n{\n \"分类标签\": \"中性\",\n \"特定问题标注\": \"价格问题\"\n}\n'''"}}],"request_id":"619-request-5"}},"custom_id":"request-5","id":"batch_1791490810192076800"} - -{"response":{"status_code":200,"body":{"created":1715959701,"usage":{"completion_tokens":26,"prompt_tokens":90,"total_tokens":116},"model":"glm-4","id":"8668356159460662846","choices":[{"finish_reason":"stop","index":0,"message":{"role":"assistant","content":"'''json\n{\n \"分类标签\": \"负面\",\n \"特定问题标注\": \"配送延迟\"\n}\n'''"}}],"request_id":"620-request-6"}},"custom_id":"request-6","id":"batch_1791490810192076800"} - - -{"response":{"status_code":200,"body":{"created":1715959701,"usage":{"completion_tokens":27,"prompt_tokens":88,"total_tokens":115},"model":"glm-4","id":"8668357671289274638","choices":[{"finish_reason":"stop","index":0,"message":{"role":"assistant","content":"'''json\n{\n \"分类标签\": \"负面\",\n \"特定问题标注\": \"产品描述不符\"\n}\n'''"}}],"request_id":"621-request-7"}},"custom_id":"request-7","id":"batch_1791490810192076800"} -{"response":{"status_code":200,"body":{"created":1715959702,"usage":{"completion_tokens":26,"prompt_tokens":87,"total_tokens":113},"model":"glm-4","id":"8668355644064514872","choices":[{"finish_reason":"stop","index":0,"message":{"role":"assistant","content":"'''json\n{\n \"分类标签\": \"正面\",\n \"特定问题标注\": \"客服态度\"\n}\n'''"}}],"request_id":"622-request-8"}},"custom_id":"request-8","id":"batch_1791490810192076800"} - {"response":{"status_code":200,"body":{"created":1715959701,"usage":{"completion_tokens":29,"prompt_tokens":90,"total_tokens":119},"model":"glm-4","id":"8668357671289274639","choices":[{"finish_reason":"stop","index":0,"message":{"role":"assistant","content":"'''json\n{\n \"分类标签\": \"负面\",\n \"特定问题标注\": \"包装问题, 产品损坏\"\n}\n'''"}}],"request_id":"623-request-9"}},"custom_id":"request-9","id":"batch_1791490810192076800"} -{"response":{"status_code":200,"body":{"created":1715959701,"usage":{"completion_tokens":27,"prompt_tokens":87,"total_tokens":114},"model":"glm-4","id":"8668355644064514871","choices":[{"finish_reason":"stop","index":0,"message":{"role":"assistant","content":"'''json\n{\n \"分类标签\": \"正面\",\n \"特定问题标注\": \"产品描述不符\"\n}\n'''"}}],"request_id":"624-request-10"}},"custom_id":"request-10","id":"batch_1791490810192076800"} -` - - brr := NewBatchResultReader[ChatCompletionResponse](bytes.NewReader([]byte(result))) - - var count int - - for { - var res BatchResult[ChatCompletionResponse] - - err := brr.Read(&res) - - if err != nil { - if err == io.EOF { - err = nil - } - require.Equal(t, 10, count) - require.NoError(t, err) - break - } - - require.Equal(t, 200, res.Response.StatusCode) - - count++ - } -} diff --git a/zhipu/batch_test.go b/zhipu/batch_test.go deleted file mode 100644 index 8ce5aa5..0000000 --- a/zhipu/batch_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package zhipu - -import ( - "bytes" - "context" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestBatchServiceAll(t *testing.T) { - client, err := NewClient() - require.NoError(t, err) - - buf := &bytes.Buffer{} - - bfw := NewBatchFileWriter(buf) - err = bfw.Write("batch_1", client.ChatCompletion("glm-4-flash").AddMessage(ChatCompletionMessage{ - Role: RoleUser, Content: "你好呀", - })) - require.NoError(t, err) - err = bfw.Write("batch_2", client.ChatCompletion("glm-4-flash").AddMessage(ChatCompletionMessage{ - Role: RoleUser, Content: "你叫什么名字", - })) - require.NoError(t, err) - - res, err := client.FileCreate(FilePurposeBatch).SetFile(bytes.NewReader(buf.Bytes()), "batch.jsonl").Do(context.Background()) - require.NoError(t, err) - - fileID := res.FileCreateFineTuneResponse.ID - require.NotEmpty(t, fileID) - - res1, err := client.BatchCreate(). - SetInputFileID(fileID). - SetCompletionWindow(BatchCompletionWindow24h). - SetEndpoint(BatchEndpointV4ChatCompletions).Do(context.Background()) - require.NoError(t, err) - require.NotEmpty(t, res1.ID) - - res2, err := client.BatchGet(res1.ID).Do(context.Background()) - require.NoError(t, err) - require.Equal(t, res2.ID, res1.ID) - - res3, err := client.BatchList().Do(context.Background()) - require.NoError(t, err) - require.NotEmpty(t, res3.Data) - - err = client.BatchCancel(res1.ID).Do(context.Background()) - require.NoError(t, err) -} - -func TestBatchListService(t *testing.T) { - client, err := NewClient() - require.NoError(t, err) - - res, err := client.BatchList().Do(context.Background()) - require.NoError(t, err) - t.Log(res) -} diff --git a/zhipu/chat_completion.go b/zhipu/chat_completion.go deleted file mode 100644 index b00db85..0000000 --- a/zhipu/chat_completion.go +++ /dev/null @@ -1,577 +0,0 @@ -package zhipu - -import ( - "bufio" - "bytes" - "context" - "encoding/json" - "errors" - "io" - - "github.com/go-resty/resty/v2" -) - -const ( - RoleSystem = "system" - RoleUser = "user" - RoleAssistant = "assistant" - RoleTool = "tool" - - ToolChoiceAuto = "auto" - - FinishReasonStop = "stop" - FinishReasonStopSequence = "stop_sequence" - FinishReasonToolCalls = "tool_calls" - FinishReasonLength = "length" - FinishReasonSensitive = "sensitive" - FinishReasonNetworkError = "network_error" - - ToolTypeFunction = "function" - ToolTypeWebSearch = "web_search" - ToolTypeRetrieval = "retrieval" - - MultiContentTypeText = "text" - MultiContentTypeImageURL = "image_url" - MultiContentTypeVideoURL = "video_url" - - // New in GLM-4-AllTools - ToolTypeCodeInterpreter = "code_interpreter" - ToolTypeDrawingTool = "drawing_tool" - ToolTypeWebBrowser = "web_browser" - - CodeInterpreterSandboxNone = "none" - CodeInterpreterSandboxAuto = "auto" - - ChatCompletionStatusFailed = "failed" - ChatCompletionStatusCompleted = "completed" - ChatCompletionStatusRequiresAction = "requires_action" -) - -// ChatCompletionTool is the interface for chat completion tool -type ChatCompletionTool interface { - isChatCompletionTool() -} - -// ChatCompletionToolFunction is the function for chat completion -type ChatCompletionToolFunction struct { - Name string `json:"name"` - Description string `json:"description"` - Parameters any `json:"parameters"` -} - -func (ChatCompletionToolFunction) isChatCompletionTool() {} - -// ChatCompletionToolRetrieval is the retrieval for chat completion -type ChatCompletionToolRetrieval struct { - KnowledgeID string `json:"knowledge_id"` - PromptTemplate string `json:"prompt_template,omitempty"` -} - -func (ChatCompletionToolRetrieval) isChatCompletionTool() {} - -// ChatCompletionToolWebSearch is the web search for chat completion -type ChatCompletionToolWebSearch struct { - Enable *bool `json:"enable,omitempty"` - SearchQuery string `json:"search_query,omitempty"` - SearchResult bool `json:"search_result,omitempty"` -} - -func (ChatCompletionToolWebSearch) isChatCompletionTool() {} - -// ChatCompletionToolCodeInterpreter is the code interpreter for chat completion -// only in GLM-4-AllTools -type ChatCompletionToolCodeInterpreter struct { - Sandbox *string `json:"sandbox,omitempty"` -} - -func (ChatCompletionToolCodeInterpreter) isChatCompletionTool() {} - -// ChatCompletionToolDrawingTool is the drawing tool for chat completion -// only in GLM-4-AllTools -type ChatCompletionToolDrawingTool struct { - // no fields -} - -func (ChatCompletionToolDrawingTool) isChatCompletionTool() {} - -// ChatCompletionToolWebBrowser is the web browser for chat completion -type ChatCompletionToolWebBrowser struct { - // no fields -} - -func (ChatCompletionToolWebBrowser) isChatCompletionTool() {} - -// ChatCompletionUsage is the usage for chat completion -type ChatCompletionUsage struct { - PromptTokens int64 `json:"prompt_tokens"` - CompletionTokens int64 `json:"completion_tokens"` - TotalTokens int64 `json:"total_tokens"` -} - -// ChatCompletionWebSearch is the web search result for chat completion -type ChatCompletionWebSearch struct { - Icon string `json:"icon"` - Title string `json:"title"` - Link string `json:"link"` - Media string `json:"media"` - Content string `json:"content"` -} - -// ChatCompletionToolCallFunction is the function for chat completion tool call -type ChatCompletionToolCallFunction struct { - Name string `json:"name"` - Arguments json.RawMessage `json:"arguments"` -} - -// ChatCompletionToolCallCodeInterpreterOutput is the output for chat completion tool call code interpreter -type ChatCompletionToolCallCodeInterpreterOutput struct { - Type string `json:"type"` - Logs string `json:"logs"` - File string `json:"file"` -} - -// ChatCompletionToolCallCodeInterpreter is the code interpreter for chat completion tool call -type ChatCompletionToolCallCodeInterpreter struct { - Input string `json:"input"` - Outputs []ChatCompletionToolCallCodeInterpreterOutput `json:"outputs"` -} - -// ChatCompletionToolCallDrawingToolOutput is the output for chat completion tool call drawing tool -type ChatCompletionToolCallDrawingToolOutput struct { - Image string `json:"image"` -} - -// ChatCompletionToolCallDrawingTool is the drawing tool for chat completion tool call -type ChatCompletionToolCallDrawingTool struct { - Input string `json:"input"` - Outputs []ChatCompletionToolCallDrawingToolOutput `json:"outputs"` -} - -// ChatCompletionToolCallWebBrowserOutput is the output for chat completion tool call web browser -type ChatCompletionToolCallWebBrowserOutput struct { - Title string `json:"title"` - Link string `json:"link"` - Content string `json:"content"` -} - -// ChatCompletionToolCallWebBrowser is the web browser for chat completion tool call -type ChatCompletionToolCallWebBrowser struct { - Input string `json:"input"` - Outputs []ChatCompletionToolCallWebBrowserOutput `json:"outputs"` -} - -// ChatCompletionToolCall is the tool call for chat completion -type ChatCompletionToolCall struct { - ID string `json:"id"` - Type string `json:"type"` - Function *ChatCompletionToolCallFunction `json:"function,omitempty"` - CodeInterpreter *ChatCompletionToolCallCodeInterpreter `json:"code_interpreter,omitempty"` - DrawingTool *ChatCompletionToolCallDrawingTool `json:"drawing_tool,omitempty"` - WebBrowser *ChatCompletionToolCallWebBrowser `json:"web_browser,omitempty"` -} - -type ChatCompletionMessageType interface { - isChatCompletionMessageType() -} - -// ChatCompletionMessage is the message for chat completion -type ChatCompletionMessage struct { - Role string `json:"role"` - Content string `json:"content,omitempty"` - ToolCalls []ChatCompletionToolCall `json:"tool_calls,omitempty"` - ToolCallID string `json:"tool_call_id,omitempty"` -} - -func (ChatCompletionMessage) isChatCompletionMessageType() {} - -type ChatCompletionMultiContent struct { - Type string `json:"type"` - Text string `json:"text"` - ImageURL *URLItem `json:"image_url,omitempty"` - VideoURL *URLItem `json:"video_url,omitempty"` -} - -// ChatCompletionMultiMessage is the multi message for chat completion -type ChatCompletionMultiMessage struct { - Role string `json:"role"` - Content []ChatCompletionMultiContent `json:"content"` -} - -func (ChatCompletionMultiMessage) isChatCompletionMessageType() {} - -// ChatCompletionMeta is the meta for chat completion -type ChatCompletionMeta struct { - UserInfo string `json:"user_info"` - BotInfo string `json:"bot_info"` - UserName string `json:"user_name"` - BotName string `json:"bot_name"` -} - -// ChatCompletionChoice is the choice for chat completion -type ChatCompletionChoice struct { - Index int `json:"index"` - FinishReason string `json:"finish_reason"` - Delta ChatCompletionMessage `json:"delta"` // stream mode - Message ChatCompletionMessage `json:"message"` // non-stream mode -} - -// ChatCompletionResponse is the response for chat completion -type ChatCompletionResponse struct { - ID string `json:"id"` - Created int64 `json:"created"` - Model string `json:"model"` - Choices []ChatCompletionChoice `json:"choices"` - Usage ChatCompletionUsage `json:"usage"` - WebSearch []ChatCompletionWebSearch `json:"web_search"` - // Status is the status of the chat completion, only in GLM-4-AllTools - Status string `json:"status"` -} - -// ChatCompletionStreamHandler is the handler for chat completion stream -type ChatCompletionStreamHandler func(chunk ChatCompletionResponse) error - -var ( - chatCompletionStreamPrefix = []byte("data:") - chatCompletionStreamDone = []byte("[DONE]") -) - -// chatCompletionReduceResponse reduce the chunk to the response -func chatCompletionReduceResponse(out *ChatCompletionResponse, chunk ChatCompletionResponse) { - if len(out.Choices) == 0 { - out.Choices = append(out.Choices, ChatCompletionChoice{}) - } - - // basic - out.ID = chunk.ID - out.Created = chunk.Created - out.Model = chunk.Model - - // choices - if len(chunk.Choices) != 0 { - oc := &out.Choices[0] - cc := chunk.Choices[0] - - oc.Index = cc.Index - if cc.Delta.Role != "" { - oc.Message.Role = cc.Delta.Role - } - oc.Message.Content += cc.Delta.Content - oc.Message.ToolCalls = append(oc.Message.ToolCalls, cc.Delta.ToolCalls...) - if cc.FinishReason != "" { - oc.FinishReason = cc.FinishReason - } - } - - // usage - if chunk.Usage.CompletionTokens != 0 { - out.Usage.CompletionTokens = chunk.Usage.CompletionTokens - } - if chunk.Usage.PromptTokens != 0 { - out.Usage.PromptTokens = chunk.Usage.PromptTokens - } - if chunk.Usage.TotalTokens != 0 { - out.Usage.TotalTokens = chunk.Usage.TotalTokens - } - - // web search - out.WebSearch = append(out.WebSearch, chunk.WebSearch...) -} - -// chatCompletionDecodeStream decode the sse stream of chat completion -func chatCompletionDecodeStream(r io.Reader, fn func(chunk ChatCompletionResponse) error) (err error) { - br := bufio.NewReader(r) - - for { - var line []byte - - if line, err = br.ReadBytes('\n'); err != nil { - if errors.Is(err, io.EOF) { - err = nil - } - break - } - - line = bytes.TrimSpace(line) - - if len(line) == 0 { - continue - } - - if !bytes.HasPrefix(line, chatCompletionStreamPrefix) { - continue - } - - data := bytes.TrimSpace(line[len(chatCompletionStreamPrefix):]) - - if bytes.Equal(data, chatCompletionStreamDone) { - break - } - - if len(data) == 0 { - continue - } - - var chunk ChatCompletionResponse - if err = json.Unmarshal(data, &chunk); err != nil { - return - } - if err = fn(chunk); err != nil { - return - } - } - - return -} - -// ChatCompletionStreamService is the service for chat completion stream -type ChatCompletionService struct { - client *Client - - model string - requestID *string - doSample *bool - temperature *float64 - topP *float64 - maxTokens *int - stop []string - toolChoice *string - userID *string - meta *ChatCompletionMeta - - messages []any - tools []any - - streamHandler ChatCompletionStreamHandler -} - -var ( - _ BatchSupport = &ChatCompletionService{} -) - -// NewChatCompletionService creates a new ChatCompletionService. -func NewChatCompletionService(client *Client) *ChatCompletionService { - return &ChatCompletionService{ - client: client, - } -} - -func (s *ChatCompletionService) BatchMethod() string { - return "POST" -} - -func (s *ChatCompletionService) BatchURL() string { - return BatchEndpointV4ChatCompletions -} - -func (s *ChatCompletionService) BatchBody() any { - return s.buildBody() -} - -// SetModel set the model of the chat completion -func (s *ChatCompletionService) SetModel(model string) *ChatCompletionService { - s.model = model - return s -} - -// SetMeta set the meta of the chat completion, optional -func (s *ChatCompletionService) SetMeta(meta ChatCompletionMeta) *ChatCompletionService { - s.meta = &meta - return s -} - -// SetRequestID set the request id of the chat completion, optional -func (s *ChatCompletionService) SetRequestID(requestID string) *ChatCompletionService { - s.requestID = &requestID - return s -} - -// SetTemperature set the temperature of the chat completion, optional -func (s *ChatCompletionService) SetDoSample(doSample bool) *ChatCompletionService { - s.doSample = &doSample - return s -} - -// SetTemperature set the temperature of the chat completion, optional -func (s *ChatCompletionService) SetTemperature(temperature float64) *ChatCompletionService { - s.temperature = &temperature - return s -} - -// SetTopP set the top p of the chat completion, optional -func (s *ChatCompletionService) SetTopP(topP float64) *ChatCompletionService { - s.topP = &topP - return s -} - -// SetMaxTokens set the max tokens of the chat completion, optional -func (s *ChatCompletionService) SetMaxTokens(maxTokens int) *ChatCompletionService { - s.maxTokens = &maxTokens - return s -} - -// SetStop set the stop of the chat completion, optional -func (s *ChatCompletionService) SetStop(stop ...string) *ChatCompletionService { - s.stop = stop - return s -} - -// SetToolChoice set the tool choice of the chat completion, optional -func (s *ChatCompletionService) SetToolChoice(toolChoice string) *ChatCompletionService { - s.toolChoice = &toolChoice - return s -} - -// SetUserID set the user id of the chat completion, optional -func (s *ChatCompletionService) SetUserID(userID string) *ChatCompletionService { - s.userID = &userID - return s -} - -// SetStreamHandler set the stream handler of the chat completion, optional -// this will enable the stream mode -func (s *ChatCompletionService) SetStreamHandler(handler ChatCompletionStreamHandler) *ChatCompletionService { - s.streamHandler = handler - return s -} - -// AddMessage add the message to the chat completion -func (s *ChatCompletionService) AddMessage(messages ...ChatCompletionMessageType) *ChatCompletionService { - for _, message := range messages { - s.messages = append(s.messages, message) - } - return s -} - -// AddFunction add the function to the chat completion -func (s *ChatCompletionService) AddTool(tools ...ChatCompletionTool) *ChatCompletionService { - for _, tool := range tools { - switch tool := tool.(type) { - case ChatCompletionToolFunction: - s.tools = append(s.tools, map[string]any{ - "type": ToolTypeFunction, - ToolTypeFunction: tool, - }) - case ChatCompletionToolRetrieval: - s.tools = append(s.tools, map[string]any{ - "type": ToolTypeRetrieval, - ToolTypeRetrieval: tool, - }) - case ChatCompletionToolWebSearch: - s.tools = append(s.tools, map[string]any{ - "type": ToolTypeWebSearch, - ToolTypeWebSearch: tool, - }) - case ChatCompletionToolCodeInterpreter: - s.tools = append(s.tools, map[string]any{ - "type": ToolTypeCodeInterpreter, - ToolTypeCodeInterpreter: tool, - }) - case ChatCompletionToolDrawingTool: - s.tools = append(s.tools, map[string]any{ - "type": ToolTypeDrawingTool, - ToolTypeDrawingTool: tool, - }) - case ChatCompletionToolWebBrowser: - s.tools = append(s.tools, map[string]any{ - "type": ToolTypeWebBrowser, - ToolTypeWebBrowser: tool, - }) - } - } - return s -} - -func (s *ChatCompletionService) buildBody() M { - body := map[string]any{ - "model": s.model, - "messages": s.messages, - } - if s.requestID != nil { - body["request_id"] = *s.requestID - } - if s.doSample != nil { - body["do_sample"] = *s.doSample - } - if s.temperature != nil { - body["temperature"] = *s.temperature - } - if s.topP != nil { - body["top_p"] = *s.topP - } - if s.maxTokens != nil { - body["max_tokens"] = *s.maxTokens - } - if len(s.stop) != 0 { - body["stop"] = s.stop - } - if len(s.tools) != 0 { - body["tools"] = s.tools - } - if s.toolChoice != nil { - body["tool_choice"] = *s.toolChoice - } - if s.userID != nil { - body["user_id"] = *s.userID - } - if s.meta != nil { - body["meta"] = s.meta - } - return body -} - -// Do send the request of the chat completion and return the response -func (s *ChatCompletionService) Do(ctx context.Context) (res ChatCompletionResponse, err error) { - body := s.buildBody() - - streamHandler := s.streamHandler - - if streamHandler == nil { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - //fmt.Println(u.BMagenta(u.JsonP(body)), 111) - if resp, err = s.client.request(ctx).SetBody(body).SetResult(&res).SetError(&apiError).Post("chat/completions"); err != nil { - //fmt.Println(u.BRed(err.Error()), 2221) - return - } - if resp.IsError() { - err = apiError - //fmt.Println(u.BRed(err.Error()), 2222) - return - } - //fmt.Println(u.BGreen(u.JsonP(resp.Result())), resp.Status(), resp.Status(), 333) - return - } - - // stream mode - - body["stream"] = true - - var resp *resty.Response - - if resp, err = s.client.request(ctx).SetBody(body).SetDoNotParseResponse(true).Post("chat/completions"); err != nil { - return - } - defer resp.RawBody().Close() - - if resp.IsError() { - err = errors.New(resp.Status()) - return - } - - var choice ChatCompletionChoice - - if err = chatCompletionDecodeStream(resp.RawBody(), func(chunk ChatCompletionResponse) error { - // reduce the chunk to the response - chatCompletionReduceResponse(&res, chunk) - // invoke the stream handler - return streamHandler(chunk) - }); err != nil { - return - } - - res.Choices = append(res.Choices, choice) - - return -} diff --git a/zhipu/chat_completion_test.go b/zhipu/chat_completion_test.go deleted file mode 100644 index 8839850..0000000 --- a/zhipu/chat_completion_test.go +++ /dev/null @@ -1,251 +0,0 @@ -package zhipu - -import ( - "context" - "encoding/json" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestChatCompletionService(t *testing.T) { - client, err := NewClient() - require.NoError(t, err) - - s := client.ChatCompletion("glm-4-flash") - s.AddMessage(ChatCompletionMessage{ - Role: RoleUser, - Content: "你好呀", - }) - res, err := s.Do(context.Background()) - require.NoError(t, err) - require.NotEmpty(t, res.Choices) - choice := res.Choices[0] - require.Equal(t, FinishReasonStop, choice.FinishReason) - require.NotEmpty(t, choice.Message.Content) -} - -func TestChatCompletionServiceCharGLM(t *testing.T) { - client, err := NewClient() - require.NoError(t, err) - - s := client.ChatCompletion("charglm-3") - s.SetMeta( - ChatCompletionMeta{ - UserName: "啵酱", - UserInfo: "啵酱是小少爷", - BotName: "塞巴斯酱", - BotInfo: "塞巴斯酱是一个冷酷的恶魔管家", - }, - ).AddMessage(ChatCompletionMessage{ - Role: RoleUser, - Content: "早上好", - }) - res, err := s.Do(context.Background()) - require.NoError(t, err) - require.NotEmpty(t, res.Choices) - choice := res.Choices[0] - require.Contains(t, []string{FinishReasonStop, FinishReasonStopSequence}, choice.FinishReason) - require.NotEmpty(t, choice.Message.Content) -} - -func TestChatCompletionServiceAllToolsCodeInterpreter(t *testing.T) { - client, err := NewClient() - require.NoError(t, err) - - s := client.ChatCompletion("GLM-4-AllTools") - s.AddMessage(ChatCompletionMultiMessage{ - Role: "user", - Content: []ChatCompletionMultiContent{ - { - Type: "text", - Text: "计算[5,10,20,700,99,310,978,100]的平均值和方差。", - }, - }, - }) - s.AddTool(ChatCompletionToolCodeInterpreter{ - Sandbox: Ptr(CodeInterpreterSandboxAuto), - }) - - foundInterpreterInput := false - foundInterpreterOutput := false - - s.SetStreamHandler(func(chunk ChatCompletionResponse) error { - for _, c := range chunk.Choices { - for _, tc := range c.Delta.ToolCalls { - if tc.Type == ToolTypeCodeInterpreter && tc.CodeInterpreter != nil { - if tc.CodeInterpreter.Input != "" { - foundInterpreterInput = true - } - if len(tc.CodeInterpreter.Outputs) > 0 { - foundInterpreterOutput = true - } - } - } - } - buf, _ := json.MarshalIndent(chunk, "", " ") - t.Log(string(buf)) - return nil - }) - - res, err := s.Do(context.Background()) - require.True(t, foundInterpreterInput) - require.True(t, foundInterpreterOutput) - require.NotNil(t, res) - require.NoError(t, err) -} - -func TestChatCompletionServiceAllToolsDrawingTool(t *testing.T) { - client, err := NewClient() - require.NoError(t, err) - - s := client.ChatCompletion("GLM-4-AllTools") - s.AddMessage(ChatCompletionMultiMessage{ - Role: "user", - Content: []ChatCompletionMultiContent{ - { - Type: "text", - Text: "画一个正弦函数图像", - }, - }, - }) - s.AddTool(ChatCompletionToolDrawingTool{}) - - foundInput := false - foundOutput := false - outputImage := "" - - s.SetStreamHandler(func(chunk ChatCompletionResponse) error { - for _, c := range chunk.Choices { - for _, tc := range c.Delta.ToolCalls { - if tc.Type == ToolTypeDrawingTool && tc.DrawingTool != nil { - if tc.DrawingTool.Input != "" { - foundInput = true - } - if len(tc.DrawingTool.Outputs) > 0 { - foundOutput = true - } - for _, output := range tc.DrawingTool.Outputs { - if output.Image != "" { - outputImage = output.Image - } - } - } - } - } - buf, _ := json.MarshalIndent(chunk, "", " ") - t.Log(string(buf)) - return nil - }) - - res, err := s.Do(context.Background()) - require.True(t, foundInput) - require.True(t, foundOutput) - require.NotEmpty(t, outputImage) - t.Log(outputImage) - require.NotNil(t, res) - require.NoError(t, err) -} - -func TestChatCompletionServiceAllToolsWebBrowser(t *testing.T) { - client, err := NewClient() - require.NoError(t, err) - - s := client.ChatCompletion("GLM-4-AllTools") - s.AddMessage(ChatCompletionMultiMessage{ - Role: "user", - Content: []ChatCompletionMultiContent{ - { - Type: "text", - Text: "搜索下本周深圳天气如何", - }, - }, - }) - s.AddTool(ChatCompletionToolWebBrowser{}) - - foundInput := false - foundOutput := false - outputContent := "" - - s.SetStreamHandler(func(chunk ChatCompletionResponse) error { - for _, c := range chunk.Choices { - for _, tc := range c.Delta.ToolCalls { - if tc.Type == ToolTypeWebBrowser && tc.WebBrowser != nil { - if tc.WebBrowser.Input != "" { - foundInput = true - } - if len(tc.WebBrowser.Outputs) > 0 { - foundOutput = true - } - for _, output := range tc.WebBrowser.Outputs { - if output.Content != "" { - outputContent = output.Content - } - } - } - } - } - buf, _ := json.MarshalIndent(chunk, "", " ") - t.Log(string(buf)) - return nil - }) - - res, err := s.Do(context.Background()) - require.True(t, foundInput) - require.True(t, foundOutput) - require.NotEmpty(t, outputContent) - t.Log(outputContent) - require.NotNil(t, res) - require.NoError(t, err) -} - -func TestChatCompletionServiceStream(t *testing.T) { - client, err := NewClient() - require.NoError(t, err) - - var content string - - s := client.ChatCompletion("glm-4-flash").AddMessage(ChatCompletionMessage{ - Role: RoleUser, - Content: "你好呀", - }).SetStreamHandler(func(chunk ChatCompletionResponse) error { - content += chunk.Choices[0].Delta.Content - return nil - }) - res, err := s.Do(context.Background()) - require.NoError(t, err) - require.NotEmpty(t, res.Choices) - choice := res.Choices[0] - require.Equal(t, FinishReasonStop, choice.FinishReason) - require.NotEmpty(t, choice.Message.Content) - require.Equal(t, content, choice.Message.Content) -} - -func TestChatCompletionServiceVision(t *testing.T) { - client, err := NewClient() - require.NoError(t, err) - - s := client.ChatCompletion("glm-4v") - s.AddMessage(ChatCompletionMultiMessage{ - Role: RoleUser, - Content: []ChatCompletionMultiContent{ - { - Type: MultiContentTypeText, - Text: "图里有什么", - }, - { - Type: MultiContentTypeImageURL, - ImageURL: &URLItem{ - URL: "https://img1.baidu.com/it/u=1369931113,3388870256&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1703696400&t=f3028c7a1dca43a080aeb8239f09cc2f", - }, - }, - }, - }) - res, err := s.Do(context.Background()) - require.NoError(t, err) - require.NotEmpty(t, res.Choices) - require.NotZero(t, res.Usage.CompletionTokens) - choice := res.Choices[0] - require.Equal(t, FinishReasonStop, choice.FinishReason) - require.NotEmpty(t, choice.Message.Content) -} diff --git a/zhipu/client.go b/zhipu/client.go deleted file mode 100644 index 5f2aa9b..0000000 --- a/zhipu/client.go +++ /dev/null @@ -1,291 +0,0 @@ -package zhipu - -import ( - "context" - "errors" - "net/http" - "os" - "strconv" - "strings" - "time" - - "github.com/go-resty/resty/v2" - "github.com/golang-jwt/jwt/v5" -) - -const ( - envAPIKey = "ZHIPUAI_API_KEY" - envBaseURL = "ZHIPUAI_BASE_URL" - envDebug = "ZHIPUAI_DEBUG" - - defaultBaseURL = "https://open.bigmodel.cn/api/paas/v4" -) - -var ( - // ErrAPIKeyMissing is the error when the api key is missing - ErrAPIKeyMissing = errors.New("zhipu: api key is missing") - // ErrAPIKeyMalformed is the error when the api key is malformed - ErrAPIKeyMalformed = errors.New("zhipu: api key is malformed") -) - -type clientOptions struct { - baseURL string - apiKey string - client *http.Client - debug *bool -} - -// ClientOption is a function that configures the client -type ClientOption func(opts *clientOptions) - -// WithAPIKey set the api key of the client -func WithAPIKey(apiKey string) ClientOption { - return func(opts *clientOptions) { - opts.apiKey = apiKey - } -} - -// WithBaseURL set the base url of the client -func WithBaseURL(baseURL string) ClientOption { - return func(opts *clientOptions) { - opts.baseURL = baseURL - } -} - -// WithHTTPClient set the http client of the client -func WithHTTPClient(client *http.Client) ClientOption { - return func(opts *clientOptions) { - opts.client = client - } -} - -// WithDebug set the debug mode of the client -func WithDebug(debug bool) ClientOption { - return func(opts *clientOptions) { - opts.debug = new(bool) - *opts.debug = debug - } -} - -// Client is the client for zhipu ai platform -type Client struct { - client *resty.Client - debug bool - keyID string - keySecret []byte -} - -func (c *Client) createJWT() string { - timestamp := time.Now().UnixMilli() - exp := timestamp + time.Hour.Milliseconds()*24*7 - - t := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - "api_key": c.keyID, - "timestamp": timestamp, - "exp": exp, - }) - t.Header = map[string]interface{}{ - "alg": "HS256", - "sign_type": "SIGN", - } - - token, err := t.SignedString(c.keySecret) - if err != nil { - panic(err) - } - return token -} - -// request creates a new resty request with the jwt token and context -func (c *Client) request(ctx context.Context) *resty.Request { - return c.client.R().SetContext(ctx).SetHeader("Authorization", c.createJWT()) -} - -// NewClient creates a new client -// It will read the api key from the environment variable ZHIPUAI_API_KEY -// It will read the base url from the environment variable ZHIPUAI_BASE_URL -func NewClient(optFns ...ClientOption) (client *Client, err error) { - var opts clientOptions - for _, optFn := range optFns { - optFn(&opts) - } - // base url - if opts.baseURL == "" { - opts.baseURL = strings.TrimSpace(os.Getenv(envBaseURL)) - } - if opts.baseURL == "" { - opts.baseURL = defaultBaseURL - } - // api key - if opts.apiKey == "" { - opts.apiKey = strings.TrimSpace(os.Getenv(envAPIKey)) - } - if opts.apiKey == "" { - err = ErrAPIKeyMissing - return - } - // debug - if opts.debug == nil { - if debugStr := strings.TrimSpace(os.Getenv(envDebug)); debugStr != "" { - if debug, err1 := strconv.ParseBool(debugStr); err1 == nil { - opts.debug = &debug - } - } - } - - keyComponents := strings.SplitN(opts.apiKey, ".", 2) - - if len(keyComponents) != 2 { - err = ErrAPIKeyMalformed - return - } - - client = &Client{ - keyID: keyComponents[0], - keySecret: []byte(keyComponents[1]), - } - - if opts.client == nil { - client.client = resty.New() - } else { - client.client = resty.NewWithClient(opts.client) - } - - client.client = client.client.SetBaseURL(opts.baseURL) - - if opts.debug != nil { - client.client.SetDebug(*opts.debug) - client.debug = *opts.debug - } - return -} - -// BatchCreate creates a new BatchCreateService. -func (c *Client) BatchCreate() *BatchCreateService { - return NewBatchCreateService(c) -} - -// BatchGet creates a new BatchGetService. -func (c *Client) BatchGet(batchID string) *BatchGetService { - return NewBatchGetService(c).SetBatchID(batchID) -} - -// BatchCancel creates a new BatchCancelService. -func (c *Client) BatchCancel(batchID string) *BatchCancelService { - return NewBatchCancelService(c).SetBatchID(batchID) -} - -// BatchList creates a new BatchListService. -func (c *Client) BatchList() *BatchListService { - return NewBatchListService(c) -} - -// ChatCompletion creates a new ChatCompletionService. -func (c *Client) ChatCompletion(model string) *ChatCompletionService { - return NewChatCompletionService(c).SetModel(model) -} - -// Embedding embeds a list of text into a vector space. -func (c *Client) Embedding(model string) *EmbeddingService { - return NewEmbeddingService(c).SetModel(model) -} - -// FileCreate creates a new FileCreateService. -func (c *Client) FileCreate(purpose string) *FileCreateService { - return NewFileCreateService(c).SetPurpose(purpose) -} - -// FileEditService creates a new FileEditService. -func (c *Client) FileEdit(documentID string) *FileEditService { - return NewFileEditService(c).SetDocumentID(documentID) -} - -// FileList creates a new FileListService. -func (c *Client) FileList(purpose string) *FileListService { - return NewFileListService(c).SetPurpose(purpose) -} - -// FileDeleteService creates a new FileDeleteService. -func (c *Client) FileDelete(documentID string) *FileDeleteService { - return NewFileDeleteService(c).SetDocumentID(documentID) -} - -// FileGetService creates a new FileGetService. -func (c *Client) FileGet(documentID string) *FileGetService { - return NewFileGetService(c).SetDocumentID(documentID) -} - -// FileDownload creates a new FileDownloadService. -func (c *Client) FileDownload(fileID string) *FileDownloadService { - return NewFileDownloadService(c).SetFileID(fileID) -} - -// FineTuneCreate creates a new fine tune create service -func (c *Client) FineTuneCreate(model string) *FineTuneCreateService { - return NewFineTuneCreateService(c).SetModel(model) -} - -// FineTuneEventList creates a new fine tune event list service -func (c *Client) FineTuneEventList(jobID string) *FineTuneEventListService { - return NewFineTuneEventListService(c).SetJobID(jobID) -} - -// FineTuneGet creates a new fine tune get service -func (c *Client) FineTuneGet(jobID string) *FineTuneGetService { - return NewFineTuneGetService(c).SetJobID(jobID) -} - -// FineTuneList creates a new fine tune list service -func (c *Client) FineTuneList() *FineTuneListService { - return NewFineTuneListService(c) -} - -// FineTuneDelete creates a new fine tune delete service -func (c *Client) FineTuneDelete(jobID string) *FineTuneDeleteService { - return NewFineTuneDeleteService(c).SetJobID(jobID) -} - -// FineTuneCancel creates a new fine tune cancel service -func (c *Client) FineTuneCancel(jobID string) *FineTuneCancelService { - return NewFineTuneCancelService(c).SetJobID(jobID) -} - -// ImageGeneration creates a new image generation service -func (c *Client) ImageGeneration(model string) *ImageGenerationService { - return NewImageGenerationService(c).SetModel(model) -} - -// KnowledgeCreate creates a new knowledge create service -func (c *Client) KnowledgeCreate() *KnowledgeCreateService { - return NewKnowledgeCreateService(c) -} - -// KnowledgeEdit creates a new knowledge edit service -func (c *Client) KnowledgeEdit(knowledgeID string) *KnowledgeEditService { - return NewKnowledgeEditService(c).SetKnowledgeID(knowledgeID) -} - -// KnowledgeList list all the knowledge -func (c *Client) KnowledgeList() *KnowledgeListService { - return NewKnowledgeListService(c) -} - -// KnowledgeDelete creates a new knowledge delete service -func (c *Client) KnowledgeDelete(knowledgeID string) *KnowledgeDeleteService { - return NewKnowledgeDeleteService(c).SetKnowledgeID(knowledgeID) -} - -// KnowledgeGet creates a new knowledge get service -func (c *Client) KnowledgeCapacity() *KnowledgeCapacityService { - return NewKnowledgeCapacityService(c) -} - -// VideoGeneration creates a new video generation service -func (c *Client) VideoGeneration(model string) *VideoGenerationService { - return NewVideoGenerationService(c).SetModel(model) -} - -// AsyncResult creates a new async result get service -func (c *Client) AsyncResult(id string) *AsyncResultService { - return NewAsyncResultService(c).SetID(id) -} diff --git a/zhipu/client_test.go b/zhipu/client_test.go deleted file mode 100644 index dd4800d..0000000 --- a/zhipu/client_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package zhipu - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestClientR(t *testing.T) { - c, err := NewClient() - require.NoError(t, err) - // the only free api is to list fine-tuning jobs - res, err := c.request(context.Background()).Get("fine_tuning/jobs") - require.NoError(t, err) - require.True(t, res.IsSuccess()) -} diff --git a/zhipu/cog.toml b/zhipu/cog.toml deleted file mode 100644 index ee92ed8..0000000 --- a/zhipu/cog.toml +++ /dev/null @@ -1,25 +0,0 @@ -from_latest_tag = false -ignore_merge_commits = false -disable_changelog = false -disable_bump_commit = false -generate_mono_repository_global_tag = true -branch_whitelist = [] -skip_ci = "[skip ci]" -skip_untracked = false -pre_bump_hooks = [] -post_bump_hooks = [] -pre_package_bump_hooks = [] -post_package_bump_hooks = [] -tag_prefix = "v" - -[git_hooks] - -[commit_types] - -[changelog] -path = "CHANGELOG.md" -authors = [] - -[bump_profiles] - -[packages] diff --git a/zhipu/embedding.go b/zhipu/embedding.go deleted file mode 100644 index 45c672b..0000000 --- a/zhipu/embedding.go +++ /dev/null @@ -1,87 +0,0 @@ -package zhipu - -import ( - "context" - - "github.com/go-resty/resty/v2" -) - -// EmbeddingData is the data for each embedding. -type EmbeddingData struct { - Embedding []float64 `json:"embedding"` - Index int `json:"index"` - Object string `json:"object"` -} - -// EmbeddingResponse is the response from the embedding service. -type EmbeddingResponse struct { - Model string `json:"model"` - Data []EmbeddingData `json:"data"` - Object string `json:"object"` - Usage ChatCompletionUsage `json:"usage"` -} - -// EmbeddingService embeds a list of text into a vector space. -type EmbeddingService struct { - client *Client - - model string - input string -} - -var ( - _ BatchSupport = &EmbeddingService{} -) - -// NewEmbeddingService creates a new EmbeddingService. -func NewEmbeddingService(client *Client) *EmbeddingService { - return &EmbeddingService{client: client} -} - -func (s *EmbeddingService) BatchMethod() string { - return "POST" -} - -func (s *EmbeddingService) BatchURL() string { - return BatchEndpointV4Embeddings -} - -func (s *EmbeddingService) BatchBody() any { - return s.buildBody() -} - -// SetModel sets the model to use for the embedding. -func (s *EmbeddingService) SetModel(model string) *EmbeddingService { - s.model = model - return s -} - -// SetInput sets the input text to embed. -func (s *EmbeddingService) SetInput(input string) *EmbeddingService { - s.input = input - return s -} - -func (s *EmbeddingService) buildBody() M { - return M{"model": s.model, "input": s.input} -} - -func (s *EmbeddingService) Do(ctx context.Context) (res EmbeddingResponse, err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - - if resp, err = s.client.request(ctx). - SetBody(s.buildBody()). - SetResult(&res). - SetError(&apiError). - Post("embeddings"); err != nil { - return - } - if resp.IsError() { - err = apiError - return - } - return -} diff --git a/zhipu/embedding_test.go b/zhipu/embedding_test.go deleted file mode 100644 index 46f4aeb..0000000 --- a/zhipu/embedding_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package zhipu - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestEmbeddingService(t *testing.T) { - client, err := NewClient() - require.NoError(t, err) - - service := client.Embedding("embedding-2") - - resp, err := service.SetInput("你好").Do(context.Background()) - require.NoError(t, err) - require.NotZero(t, resp.Usage.TotalTokens) - require.NotEmpty(t, resp.Data) - require.NotEmpty(t, resp.Data[0].Embedding) -} diff --git a/zhipu/error.go b/zhipu/error.go deleted file mode 100644 index ea07ad7..0000000 --- a/zhipu/error.go +++ /dev/null @@ -1,58 +0,0 @@ -package zhipu - -type APIError struct { - Code string `json:"code"` - Message string `json:"message"` -} - -func (e APIError) Error() string { - return e.Message -} - -type APIErrorResponse struct { - APIError `json:"error"` -} - -func (e APIErrorResponse) Error() string { - return e.APIError.Error() -} - -// GetAPIErrorCode returns the error code of an API error. -func GetAPIErrorCode(err error) string { - if err == nil { - return "" - } - if e, ok := err.(APIError); ok { - return e.Code - } - if e, ok := err.(APIErrorResponse); ok { - return e.Code - } - if e, ok := err.(*APIError); ok && e != nil { - return e.Code - } - if e, ok := err.(*APIErrorResponse); ok && e != nil { - return e.Code - } - return "" -} - -// GetAPIErrorMessage returns the error message of an API error. -func GetAPIErrorMessage(err error) string { - if err == nil { - return "" - } - if e, ok := err.(APIError); ok { - return e.Message - } - if e, ok := err.(APIErrorResponse); ok { - return e.Message - } - if e, ok := err.(*APIError); ok && e != nil { - return e.Message - } - if e, ok := err.(*APIErrorResponse); ok && e != nil { - return e.Message - } - return err.Error() -} diff --git a/zhipu/error_test.go b/zhipu/error_test.go deleted file mode 100644 index 3f2fa08..0000000 --- a/zhipu/error_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package zhipu - -import ( - "encoding/json" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestAPIError(t *testing.T) { - err := APIError{ - Code: "code", - Message: "message", - } - require.Equal(t, "message", err.Error()) - require.Equal(t, "code", GetAPIErrorCode(err)) - require.Equal(t, "message", GetAPIErrorMessage(err)) -} - -func TestAPIErrorResponse(t *testing.T) { - err := APIErrorResponse{ - APIError: APIError{ - Code: "code", - Message: "message", - }, - } - require.Equal(t, "message", err.Error()) - require.Equal(t, "code", GetAPIErrorCode(err)) - require.Equal(t, "message", GetAPIErrorMessage(err)) -} - -func TestAPIErrorResponseFromDoc(t *testing.T) { - var res APIErrorResponse - err := json.Unmarshal([]byte(`{"error":{"code":"1002","message":"Authorization Token非法,请确认Authorization Token正确传递。"}}`), &res) - require.NoError(t, err) - require.Equal(t, "1002", res.Code) - require.Equal(t, "1002", GetAPIErrorCode(res)) -} diff --git a/zhipu/file.go b/zhipu/file.go deleted file mode 100644 index e8b66af..0000000 --- a/zhipu/file.go +++ /dev/null @@ -1,541 +0,0 @@ -package zhipu - -import ( - "context" - "errors" - "io" - "os" - "path/filepath" - "strconv" - - "github.com/go-resty/resty/v2" -) - -const ( - FilePurposeFineTune = "fine-tune" - FilePurposeRetrieval = "retrieval" - FilePurposeBatch = "batch" - - KnowledgeTypeArticle = 1 - KnowledgeTypeQADocument = 2 - KnowledgeTypeQASpreadsheet = 3 - KnowledgeTypeProductDatabaseSpreadsheet = 4 - KnowledgeTypeCustom = 5 -) - -// FileCreateService is a service to create a file. -type FileCreateService struct { - client *Client - - purpose string - - localFile string - file io.Reader - filename string - - customSeparator *string - sentenceSize *int - knowledgeID *string -} - -// FileCreateKnowledgeSuccessInfo is the success info of the FileCreateKnowledgeResponse. -type FileCreateKnowledgeSuccessInfo struct { - Filename string `json:"fileName"` - DocumentID string `json:"documentId"` -} - -// FileCreateKnowledgeFailedInfo is the failed info of the FileCreateKnowledgeResponse. -type FileCreateKnowledgeFailedInfo struct { - Filename string `json:"fileName"` - FailReason string `json:"failReason"` -} - -// FileCreateKnowledgeResponse is the response of the FileCreateService. -type FileCreateKnowledgeResponse struct { - SuccessInfos []FileCreateKnowledgeSuccessInfo `json:"successInfos"` - FailedInfos []FileCreateKnowledgeFailedInfo `json:"failedInfos"` -} - -// FileCreateFineTuneResponse is the response of the FileCreateService. -type FileCreateFineTuneResponse struct { - Bytes int64 `json:"bytes"` - CreatedAt int64 `json:"created_at"` - Filename string `json:"filename"` - Object string `json:"object"` - Purpose string `json:"purpose"` - ID string `json:"id"` -} - -// FileCreateResponse is the response of the FileCreateService. -type FileCreateResponse struct { - FileCreateFineTuneResponse - FileCreateKnowledgeResponse -} - -// NewFileCreateService creates a new FileCreateService. -func NewFileCreateService(client *Client) *FileCreateService { - return &FileCreateService{client: client} -} - -// SetLocalFile sets the local_file parameter of the FileCreateService. -func (s *FileCreateService) SetLocalFile(localFile string) *FileCreateService { - s.localFile = localFile - return s -} - -// SetFile sets the file parameter of the FileCreateService. -func (s *FileCreateService) SetFile(file io.Reader, filename string) *FileCreateService { - s.file = file - s.filename = filename - return s -} - -// SetPurpose sets the purpose parameter of the FileCreateService. -func (s *FileCreateService) SetPurpose(purpose string) *FileCreateService { - s.purpose = purpose - return s -} - -// SetCustomSeparator sets the custom_separator parameter of the FileCreateService. -func (s *FileCreateService) SetCustomSeparator(customSeparator string) *FileCreateService { - s.customSeparator = &customSeparator - return s -} - -// SetSentenceSize sets the sentence_size parameter of the FileCreateService. -func (s *FileCreateService) SetSentenceSize(sentenceSize int) *FileCreateService { - s.sentenceSize = &sentenceSize - return s -} - -// SetKnowledgeID sets the knowledge_id parameter of the FileCreateService. -func (s *FileCreateService) SetKnowledgeID(knowledgeID string) *FileCreateService { - s.knowledgeID = &knowledgeID - return s -} - -// Do makes the request. -func (s *FileCreateService) Do(ctx context.Context) (res FileCreateResponse, err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - - body := map[string]string{"purpose": s.purpose} - - if s.customSeparator != nil { - body["custom_separator"] = *s.customSeparator - } - if s.sentenceSize != nil { - body["sentence_size"] = strconv.Itoa(*s.sentenceSize) - } - if s.knowledgeID != nil { - body["knowledge_id"] = *s.knowledgeID - } - - file, filename := s.file, s.filename - - if file == nil && s.localFile != "" { - var f *os.File - if f, err = os.Open(s.localFile); err != nil { - return - } - defer f.Close() - - file = f - filename = filepath.Base(s.localFile) - } - - if file == nil { - err = errors.New("no file specified") - return - } - - if resp, err = s.client.request(ctx). - SetFileReader("file", filename, file). - SetMultipartFormData(body). - SetResult(&res). - SetError(&apiError). - Post("files"); err != nil { - return - } - - if resp.IsError() { - err = apiError - return - } - - return -} - -// FileEditService is a service to edit a file. -type FileEditService struct { - client *Client - - documentID string - - knowledgeType *int - customSeparator []string - sentenceSize *int -} - -// NewFileEditService creates a new FileEditService. -func NewFileEditService(client *Client) *FileEditService { - return &FileEditService{client: client} -} - -// SetDocumentID sets the document_id parameter of the FileEditService. -func (s *FileEditService) SetDocumentID(documentID string) *FileEditService { - s.documentID = documentID - return s -} - -// SetKnowledgeType sets the knowledge_type parameter of the FileEditService. -func (s *FileEditService) SetKnowledgeType(knowledgeType int) *FileEditService { - s.knowledgeType = &knowledgeType - return s -} - -// SetSentenceSize sets the sentence_size parameter of the FileEditService. -func (s *FileEditService) SetCustomSeparator(customSeparator ...string) *FileEditService { - s.customSeparator = customSeparator - return s -} - -// SetSentenceSize sets the sentence_size parameter of the FileEditService. -func (s *FileEditService) SetSentenceSize(sentenceSize int) *FileEditService { - s.sentenceSize = &sentenceSize - return s -} - -// Do makes the request. -func (s *FileEditService) Do(ctx context.Context) (err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - - body := M{} - - if s.knowledgeType != nil { - body["knowledge_type"] = strconv.Itoa(*s.knowledgeType) - } - if len(s.customSeparator) > 0 { - body["custom_separator"] = s.customSeparator - } - if s.sentenceSize != nil { - body["sentence_size"] = strconv.Itoa(*s.sentenceSize) - } - - if resp, err = s.client.request(ctx). - SetPathParam("document_id", s.documentID). - SetBody(body). - SetError(&apiError). - Put("document/{document_id}"); err != nil { - return - } - - if resp.IsError() { - err = apiError - return - } - - return -} - -// FileListService is a service to list files. -type FileListService struct { - client *Client - - purpose string - - knowledgeID *string - page *int - limit *int - after *string - orderAsc *bool -} - -// FileFailInfo is the failed info of the FileListKnowledgeItem. -type FileFailInfo struct { - EmbeddingCode int `json:"embedding_code"` - EmbeddingMsg string `json:"embedding_msg"` -} - -// FileListKnowledgeItem is the item of the FileListKnowledgeResponse. -type FileListKnowledgeItem struct { - ID string `json:"id"` - Name string `json:"name"` - URL string `json:"url"` - Length int64 `json:"length"` - SentenceSize int64 `json:"sentence_size"` - CustomSeparator []string `json:"custom_separator"` - EmbeddingStat int `json:"embedding_stat"` - FailInfo *FileFailInfo `json:"failInfo"` - WordNum int64 `json:"word_num"` - ParseImage int `json:"parse_image"` -} - -// FileListKnowledgeResponse is the response of the FileListService. -type FileListKnowledgeResponse struct { - Total int `json:"total"` - List []FileListKnowledgeItem `json:"list"` -} - -// FileListFineTuneItem is the item of the FileListFineTuneResponse. -type FileListFineTuneItem struct { - Bytes int64 `json:"bytes"` - CreatedAt int64 `json:"created_at"` - Filename string `json:"filename"` - ID string `json:"id"` - Object string `json:"object"` - Purpose string `json:"purpose"` -} - -// FileListFineTuneResponse is the response of the FileListService. -type FileListFineTuneResponse struct { - Object string `json:"object"` - Data []FileListFineTuneItem `json:"data"` -} - -// FileListResponse is the response of the FileListService. -type FileListResponse struct { - FileListKnowledgeResponse - FileListFineTuneResponse -} - -// NewFileListService creates a new FileListService. -func NewFileListService(client *Client) *FileListService { - return &FileListService{client: client} -} - -// SetPurpose sets the purpose parameter of the FileListService. -func (s *FileListService) SetPurpose(purpose string) *FileListService { - s.purpose = purpose - return s -} - -// SetKnowledgeID sets the knowledge_id parameter of the FileListService. -func (s *FileListService) SetKnowledgeID(knowledgeID string) *FileListService { - s.knowledgeID = &knowledgeID - return s -} - -// SetPage sets the page parameter of the FileListService. -func (s *FileListService) SetPage(page int) *FileListService { - s.page = &page - return s -} - -// SetLimit sets the limit parameter of the FileListService. -func (s *FileListService) SetLimit(limit int) *FileListService { - s.limit = &limit - return s -} - -// SetAfter sets the after parameter of the FileListService. -func (s *FileListService) SetAfter(after string) *FileListService { - s.after = &after - return s -} - -// SetOrder sets the order parameter of the FileListService. -func (s *FileListService) SetOrder(asc bool) *FileListService { - s.orderAsc = &asc - return s -} - -// Do makes the request. -func (s *FileListService) Do(ctx context.Context) (res FileListResponse, err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - - m := map[string]string{ - "purpose": s.purpose, - } - - if s.knowledgeID != nil { - m["knowledge_id"] = *s.knowledgeID - } - if s.page != nil { - m["page"] = strconv.Itoa(*s.page) - } - if s.limit != nil { - m["limit"] = strconv.Itoa(*s.limit) - } - if s.after != nil { - m["after"] = *s.after - } - if s.orderAsc != nil { - if *s.orderAsc { - m["order"] = "asc" - } else { - m["order"] = "desc" - } - } - - if resp, err = s.client.request(ctx). - SetQueryParams(m). - SetResult(&res). - SetError(&apiError). - Get("files"); err != nil { - return - } - - if resp.IsError() { - err = apiError - return - } - - return -} - -// FileDeleteService is a service to delete a file. -type FileDeleteService struct { - client *Client - documentID string -} - -// NewFileDeleteService creates a new FileDeleteService. -func NewFileDeleteService(client *Client) *FileDeleteService { - return &FileDeleteService{client: client} -} - -// SetDocumentID sets the document_id parameter of the FileDeleteService. -func (s *FileDeleteService) SetDocumentID(documentID string) *FileDeleteService { - s.documentID = documentID - return s -} - -// Do makes the request. -func (s *FileDeleteService) Do(ctx context.Context) (err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - - if resp, err = s.client.request(ctx). - SetPathParam("document_id", s.documentID). - SetError(&apiError). - Delete("document/{document_id}"); err != nil { - return - } - - if resp.IsError() { - err = apiError - return - } - - return -} - -// FileGetService is a service to get a file. -type FileGetService struct { - client *Client - documentID string -} - -// FileGetResponse is the response of the FileGetService. -type FileGetResponse = FileListKnowledgeItem - -// NewFileGetService creates a new FileGetService. -func NewFileGetService(client *Client) *FileGetService { - return &FileGetService{client: client} -} - -// SetDocumentID sets the document_id parameter of the FileGetService. -func (s *FileGetService) SetDocumentID(documentID string) *FileGetService { - s.documentID = documentID - return s -} - -// Do makes the request. -func (s *FileGetService) Do(ctx context.Context) (res FileGetResponse, err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - - if resp, err = s.client.request(ctx). - SetPathParam("document_id", s.documentID). - SetResult(&res). - SetError(&apiError). - Get("document/{document_id}"); err != nil { - return - } - - if resp.IsError() { - err = apiError - return - } - - return -} - -// FileDownloadService is a service to download a file. -type FileDownloadService struct { - client *Client - - fileID string - - writer io.Writer - filename string -} - -// NewFileDownloadService creates a new FileDownloadService. -func NewFileDownloadService(client *Client) *FileDownloadService { - return &FileDownloadService{client: client} -} - -// SetFileID sets the file_id parameter of the FileDownloadService. -func (s *FileDownloadService) SetFileID(fileID string) *FileDownloadService { - s.fileID = fileID - return s -} - -// SetOutput sets the output parameter of the FileDownloadService. -func (s *FileDownloadService) SetOutput(w io.Writer) *FileDownloadService { - s.writer = w - return s -} - -// SetOutputFile sets the output_file parameter of the FileDownloadService. -func (s *FileDownloadService) SetOutputFile(filename string) *FileDownloadService { - s.filename = filename - return s -} - -// Do makes the request. -func (s *FileDownloadService) Do(ctx context.Context) (err error) { - var resp *resty.Response - - writer := s.writer - - if writer == nil && s.filename != "" { - var f *os.File - if f, err = os.Create(s.filename); err != nil { - return - } - defer f.Close() - - writer = f - } - - if writer == nil { - return errors.New("no output specified") - } - - if resp, err = s.client.request(ctx). - SetDoNotParseResponse(true). - SetPathParam("file_id", s.fileID). - Get("files/{file_id}/content"); err != nil { - return - } - defer resp.RawBody().Close() - - _, err = io.Copy(writer, resp.RawBody()) - - return -} diff --git a/zhipu/file_test.go b/zhipu/file_test.go deleted file mode 100644 index 3d035ae..0000000 --- a/zhipu/file_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package zhipu - -import ( - "context" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestFileServiceFineTune(t *testing.T) { - client, err := NewClient() - require.NoError(t, err) - - s := client.FileCreate(FilePurposeFineTune) - s.SetLocalFile(filepath.Join("testdata", "test-file.jsonl")) - - res, err := s.Do(context.Background()) - require.NoError(t, err) - require.NotZero(t, res.Bytes) - require.NotZero(t, res.CreatedAt) - require.NotEmpty(t, res.ID) -} - -func TestFileServiceKnowledge(t *testing.T) { - client, err := NewClient() - require.NoError(t, err) - - s := client.FileCreate(FilePurposeRetrieval) - s.SetKnowledgeID(os.Getenv("TEST_KNOWLEDGE_ID")) - s.SetLocalFile(filepath.Join("testdata", "test-file.txt")) - - res, err := s.Do(context.Background()) - require.NoError(t, err) - require.NotEmpty(t, res.SuccessInfos) - require.NotEmpty(t, res.SuccessInfos[0].DocumentID) - require.NotEmpty(t, res.SuccessInfos[0].Filename) - - documentID := res.SuccessInfos[0].DocumentID - - res2, err := client.FileGet(documentID).Do(context.Background()) - require.NoError(t, err) - require.NotEmpty(t, res2.ID) - - err = client.FileEdit(documentID).SetKnowledgeType(KnowledgeTypeCustom).Do(context.Background()) - require.True(t, err == nil || GetAPIErrorCode(err) == "10019") - - err = client.FileDelete(res.SuccessInfos[0].DocumentID).Do(context.Background()) - require.True(t, err == nil || GetAPIErrorCode(err) == "10019") -} - -func TestFileListServiceKnowledge(t *testing.T) { - client, err := NewClient() - require.NoError(t, err) - - s := client.FileList(FilePurposeRetrieval).SetKnowledgeID(os.Getenv("TEST_KNOWLEDGE_ID")) - res, err := s.Do(context.Background()) - require.NoError(t, err) - require.NotEmpty(t, res.List) -} - -func TestFileListServiceFineTune(t *testing.T) { - client, err := NewClient() - require.NoError(t, err) - - s := client.FileList(FilePurposeFineTune) - res, err := s.Do(context.Background()) - require.NoError(t, err) - require.NotEmpty(t, res.Data) -} diff --git a/zhipu/fine_tune.go b/zhipu/fine_tune.go deleted file mode 100644 index f2121ea..0000000 --- a/zhipu/fine_tune.go +++ /dev/null @@ -1,456 +0,0 @@ -package zhipu - -import ( - "context" - "strconv" - - "github.com/go-resty/resty/v2" -) - -const ( - HyperParameterAuto = "auto" - - FineTuneStatusCreate = "create" - FineTuneStatusValidatingFiles = "validating_files" - FineTuneStatusQueued = "queued" - FineTuneStatusRunning = "running" - FineTuneStatusSucceeded = "succeeded" - FineTuneStatusFailed = "failed" - FineTuneStatusCancelled = "cancelled" -) - -// FineTuneItem is the item of the FineTune -type FineTuneItem struct { - ID string `json:"id"` - RequestID string `json:"request_id"` - FineTunedModel string `json:"fine_tuned_model"` - Status string `json:"status"` - Object string `json:"object"` - TrainingFile string `json:"training_file"` - ValidationFile string `json:"validation_file"` - Error APIError `json:"error"` -} - -// FineTuneCreateService creates a new fine tune -type FineTuneCreateService struct { - client *Client - - model string - trainingFile string - validationFile *string - - learningRateMultiplier *StringOr[float64] - batchSize *StringOr[int] - nEpochs *StringOr[int] - - suffix *string - requestID *string -} - -// FineTuneCreateResponse is the response of the FineTuneCreateService -type FineTuneCreateResponse = FineTuneItem - -// NewFineTuneCreateService creates a new FineTuneCreateService -func NewFineTuneCreateService(client *Client) *FineTuneCreateService { - return &FineTuneCreateService{ - client: client, - } -} - -// SetModel sets the model parameter -func (s *FineTuneCreateService) SetModel(model string) *FineTuneCreateService { - s.model = model - return s -} - -// SetTrainingFile sets the trainingFile parameter -func (s *FineTuneCreateService) SetTrainingFile(trainingFile string) *FineTuneCreateService { - s.trainingFile = trainingFile - return s -} - -// SetValidationFile sets the validationFile parameter -func (s *FineTuneCreateService) SetValidationFile(validationFile string) *FineTuneCreateService { - s.validationFile = &validationFile - return s -} - -// SetLearningRateMultiplier sets the learningRateMultiplier parameter -func (s *FineTuneCreateService) SetLearningRateMultiplier(learningRateMultiplier float64) *FineTuneCreateService { - s.learningRateMultiplier = &StringOr[float64]{} - s.learningRateMultiplier.SetValue(learningRateMultiplier) - return s -} - -// SetLearningRateMultiplierAuto sets the learningRateMultiplier parameter to auto -func (s *FineTuneCreateService) SetLearningRateMultiplierAuto() *FineTuneCreateService { - s.learningRateMultiplier = &StringOr[float64]{} - s.learningRateMultiplier.SetString(HyperParameterAuto) - return s -} - -// SetBatchSize sets the batchSize parameter -func (s *FineTuneCreateService) SetBatchSize(batchSize int) *FineTuneCreateService { - s.batchSize = &StringOr[int]{} - s.batchSize.SetValue(batchSize) - return s -} - -// SetBatchSizeAuto sets the batchSize parameter to auto -func (s *FineTuneCreateService) SetBatchSizeAuto() *FineTuneCreateService { - s.batchSize = &StringOr[int]{} - s.batchSize.SetString(HyperParameterAuto) - return s -} - -// SetNEpochs sets the nEpochs parameter -func (s *FineTuneCreateService) SetNEpochs(nEpochs int) *FineTuneCreateService { - s.nEpochs = &StringOr[int]{} - s.nEpochs.SetValue(nEpochs) - return s -} - -// SetNEpochsAuto sets the nEpochs parameter to auto -func (s *FineTuneCreateService) SetNEpochsAuto() *FineTuneCreateService { - s.nEpochs = &StringOr[int]{} - s.nEpochs.SetString(HyperParameterAuto) - return s -} - -// SetSuffix sets the suffix parameter -func (s *FineTuneCreateService) SetSuffix(suffix string) *FineTuneCreateService { - s.suffix = &suffix - return s -} - -// SetRequestID sets the requestID parameter -func (s *FineTuneCreateService) SetRequestID(requestID string) *FineTuneCreateService { - s.requestID = &requestID - return s -} - -// Do makes the request -func (s *FineTuneCreateService) Do(ctx context.Context) (res FineTuneCreateResponse, err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - - body := M{ - "model": s.model, - "training_file": s.trainingFile, - } - - if s.validationFile != nil { - body["validation_file"] = *s.validationFile - } - if s.suffix != nil { - body["suffix"] = *s.suffix - } - if s.requestID != nil { - body["request_id"] = *s.requestID - } - if s.learningRateMultiplier != nil || s.batchSize != nil || s.nEpochs != nil { - hp := M{} - if s.learningRateMultiplier != nil { - hp["learning_rate_multiplier"] = s.learningRateMultiplier - } - if s.batchSize != nil { - hp["batch_size"] = s.batchSize - } - if s.nEpochs != nil { - hp["n_epochs"] = s.nEpochs - } - body["hyperparameters"] = hp - } - - if resp, err = s.client.request(ctx). - SetBody(body). - SetResult(&res). - SetError(&apiError). - Post("fine_tuning/jobs"); err != nil { - return - } - if resp.IsError() { - err = apiError - return - } - return -} - -// FineTuneEventListService creates a new fine tune event list -type FineTuneEventListService struct { - client *Client - - jobID string - - limit *int - after *string -} - -// FineTuneEventData is the data of the FineTuneEventItem -type FineTuneEventData struct { - Acc float64 `json:"acc"` - Loss float64 `json:"loss"` - CurrentSteps int64 `json:"current_steps"` - RemainingTime string `json:"remaining_time"` - ElapsedTime string `json:"elapsed_time"` - TotalSteps int64 `json:"total_steps"` - Epoch int64 `json:"epoch"` - TrainedTokens int64 `json:"trained_tokens"` - LearningRate float64 `json:"learning_rate"` -} - -// FineTuneEventItem is the item of the FineTuneEventListResponse -type FineTuneEventItem struct { - ID string `json:"id"` - Type string `json:"type"` - Level string `json:"level"` - Message string `json:"message"` - Object string `json:"object"` - CreatedAt int64 `json:"created_at"` - Data FineTuneEventData `json:"data"` -} - -// FineTuneEventListResponse is the response of the FineTuneEventListService -type FineTuneEventListResponse struct { - Data []FineTuneEventItem `json:"data"` - HasMore bool `json:"has_more"` - Object string `json:"object"` -} - -// NewFineTuneEventListService creates a new FineTuneEventListService -func NewFineTuneEventListService(client *Client) *FineTuneEventListService { - return &FineTuneEventListService{ - client: client, - } -} - -// SetJobID sets the jobID parameter -func (s *FineTuneEventListService) SetJobID(jobID string) *FineTuneEventListService { - s.jobID = jobID - return s -} - -// SetLimit sets the limit parameter -func (s *FineTuneEventListService) SetLimit(limit int) *FineTuneEventListService { - s.limit = &limit - return s -} - -// SetAfter sets the after parameter -func (s *FineTuneEventListService) SetAfter(after string) *FineTuneEventListService { - s.after = &after - return s -} - -// Do makes the request -func (s *FineTuneEventListService) Do(ctx context.Context) (res FineTuneEventListResponse, err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - - req := s.client.request(ctx) - - if s.limit != nil { - req.SetQueryParam("limit", strconv.Itoa(*s.limit)) - } - if s.after != nil { - req.SetQueryParam("after", *s.after) - } - - if resp, err = req. - SetPathParam("job_id", s.jobID). - SetResult(&res). - SetError(&apiError). - Get("fine_tuning/jobs/{job_id}/events"); err != nil { - return - } - if resp.IsError() { - err = apiError - return - } - return -} - -// FineTuneGetService creates a new fine tune get -type FineTuneGetService struct { - client *Client - jobID string -} - -// NewFineTuneGetService creates a new FineTuneGetService -func NewFineTuneGetService(client *Client) *FineTuneGetService { - return &FineTuneGetService{ - client: client, - } -} - -// SetJobID sets the jobID parameter -func (s *FineTuneGetService) SetJobID(jobID string) *FineTuneGetService { - s.jobID = jobID - return s -} - -// Do makes the request -func (s *FineTuneGetService) Do(ctx context.Context) (res FineTuneItem, err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - - if resp, err = s.client.request(ctx). - SetPathParam("job_id", s.jobID). - SetResult(&res). - SetError(&apiError). - Get("fine_tuning/jobs/{job_id}"); err != nil { - return - } - if resp.IsError() { - err = apiError - return - } - return -} - -// FineTuneListService creates a new fine tune list -type FineTuneListService struct { - client *Client - - limit *int - after *string -} - -// FineTuneListResponse is the response of the FineTuneListService -type FineTuneListResponse struct { - Data []FineTuneItem `json:"data"` - Object string `json:"object"` -} - -// NewFineTuneListService creates a new FineTuneListService -func NewFineTuneListService(client *Client) *FineTuneListService { - return &FineTuneListService{ - client: client, - } -} - -// SetLimit sets the limit parameter -func (s *FineTuneListService) SetLimit(limit int) *FineTuneListService { - s.limit = &limit - return s -} - -// SetAfter sets the after parameter -func (s *FineTuneListService) SetAfter(after string) *FineTuneListService { - s.after = &after - return s -} - -// Do makes the request -func (s *FineTuneListService) Do(ctx context.Context) (res FineTuneListResponse, err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - - req := s.client.request(ctx) - if s.limit != nil { - req.SetQueryParam("limit", strconv.Itoa(*s.limit)) - } - if s.after != nil { - req.SetQueryParam("after", *s.after) - } - - if resp, err = req. - SetResult(&res). - SetError(&apiError). - Get("fine_tuning/jobs"); err != nil { - return - } - if resp.IsError() { - err = apiError - return - } - return -} - -// FineTuneDeleteService creates a new fine tune delete -type FineTuneDeleteService struct { - client *Client - jobID string -} - -// NewFineTuneDeleteService creates a new FineTuneDeleteService -func NewFineTuneDeleteService(client *Client) *FineTuneDeleteService { - return &FineTuneDeleteService{ - client: client, - } -} - -// SetJobID sets the jobID parameter -func (s *FineTuneDeleteService) SetJobID(jobID string) *FineTuneDeleteService { - s.jobID = jobID - return s -} - -// Do makes the request -func (s *FineTuneDeleteService) Do(ctx context.Context) (res FineTuneItem, err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - - if resp, err = s.client.request(ctx). - SetPathParam("job_id", s.jobID). - SetResult(&res). - SetError(&apiError). - Delete("fine_tuning/jobs/{job_id}"); err != nil { - return - } - if resp.IsError() { - err = apiError - return - } - return -} - -// FineTuneCancelService creates a new fine tune cancel -type FineTuneCancelService struct { - client *Client - jobID string -} - -// NewFineTuneCancelService creates a new FineTuneCancelService -func NewFineTuneCancelService(client *Client) *FineTuneCancelService { - return &FineTuneCancelService{ - client: client, - } -} - -// SetJobID sets the jobID parameter -func (s *FineTuneCancelService) SetJobID(jobID string) *FineTuneCancelService { - s.jobID = jobID - return s -} - -// Do makes the request -func (s *FineTuneCancelService) Do(ctx context.Context) (res FineTuneItem, err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - - if resp, err = s.client.request(ctx). - SetPathParam("job_id", s.jobID). - SetResult(&res). - SetError(&apiError). - Post("fine_tuning/jobs/{job_id}/cancel"); err != nil { - return - } - if resp.IsError() { - err = apiError - return - } - return -} diff --git a/zhipu/fine_tune_test.go b/zhipu/fine_tune_test.go deleted file mode 100644 index 2adb1fa..0000000 --- a/zhipu/fine_tune_test.go +++ /dev/null @@ -1,3 +0,0 @@ -package zhipu - -// tests not available since lack of budget to test it diff --git a/zhipu/image_generation.go b/zhipu/image_generation.go deleted file mode 100644 index 32486a9..0000000 --- a/zhipu/image_generation.go +++ /dev/null @@ -1,110 +0,0 @@ -package zhipu - -import ( - "context" - - "github.com/go-resty/resty/v2" -) - -// ImageGenerationService creates a new image generation -type ImageGenerationService struct { - client *Client - - model string - prompt string - size string - userID string -} - -var ( - _ BatchSupport = &ImageGenerationService{} -) - -// ImageGenerationResponse is the response of the ImageGenerationService -type ImageGenerationResponse struct { - Created int64 `json:"created"` - Data []URLItem `json:"data"` -} - -// NewImageGenerationService creates a new ImageGenerationService -func NewImageGenerationService(client *Client) *ImageGenerationService { - return &ImageGenerationService{ - client: client, - } -} - -func (s *ImageGenerationService) BatchMethod() string { - return "POST" -} - -func (s *ImageGenerationService) BatchURL() string { - return BatchEndpointV4ImagesGenerations -} - -func (s *ImageGenerationService) BatchBody() any { - return s.buildBody() -} - -// SetModel sets the model parameter -func (s *ImageGenerationService) SetModel(model string) *ImageGenerationService { - s.model = model - return s -} - -// SetPrompt sets the prompt parameter -func (s *ImageGenerationService) SetPrompt(prompt string) *ImageGenerationService { - s.prompt = prompt - return s -} - -func (s *ImageGenerationService) SetSize(size string) *ImageGenerationService { - s.size = size - return s -} - -// SetUserID sets the userID parameter -func (s *ImageGenerationService) SetUserID(userID string) *ImageGenerationService { - s.userID = userID - return s -} - -func (s *ImageGenerationService) buildBody() M { - body := M{ - "model": s.model, - "prompt": s.prompt, - } - - if s.userID != "" { - body["user_id"] = s.userID - } - - if s.size != "" { - body["size"] = s.size - } - - return body -} - -func (s *ImageGenerationService) Do(ctx context.Context) (res ImageGenerationResponse, err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - - body := s.buildBody() - - if resp, err = s.client.request(ctx). - SetBody(body). - SetResult(&res). - SetError(&apiError). - Post("images/generations"); err != nil { - return - } - - if resp.IsError() { - err = apiError - return - } - - return -} diff --git a/zhipu/image_generation_test.go b/zhipu/image_generation_test.go deleted file mode 100644 index 7ab7807..0000000 --- a/zhipu/image_generation_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package zhipu - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestImageGenerationService(t *testing.T) { - client, err := NewClient() - require.NoError(t, err) - - s := client.ImageGeneration("cogview-3") - s.SetPrompt("一只可爱的小猫") - - res, err := s.Do(context.Background()) - require.NoError(t, err) - require.NotEmpty(t, res.Data) - t.Log(res.Data[0].URL) -} diff --git a/zhipu/knowledge.go b/zhipu/knowledge.go deleted file mode 100644 index 7f43808..0000000 --- a/zhipu/knowledge.go +++ /dev/null @@ -1,299 +0,0 @@ -package zhipu - -import ( - "context" - "strconv" - - "github.com/go-resty/resty/v2" -) - -const ( - KnowledgeEmbeddingIDEmbedding2 = 3 -) - -// KnowledgeCreateService creates a new knowledge -type KnowledgeCreateService struct { - client *Client - - embeddingID int - name string - description *string -} - -// KnowledgeCreateResponse is the response of the KnowledgeCreateService -type KnowledgeCreateResponse = IDItem - -// NewKnowledgeCreateService creates a new KnowledgeCreateService -func NewKnowledgeCreateService(client *Client) *KnowledgeCreateService { - return &KnowledgeCreateService{ - client: client, - } -} - -// SetEmbeddingID sets the embedding id of the knowledge -func (s *KnowledgeCreateService) SetEmbeddingID(embeddingID int) *KnowledgeCreateService { - s.embeddingID = embeddingID - return s -} - -// SetName sets the name of the knowledge -func (s *KnowledgeCreateService) SetName(name string) *KnowledgeCreateService { - s.name = name - return s -} - -// SetDescription sets the description of the knowledge -func (s *KnowledgeCreateService) SetDescription(description string) *KnowledgeCreateService { - s.description = &description - return s -} - -// Do creates the knowledge -func (s *KnowledgeCreateService) Do(ctx context.Context) (res KnowledgeCreateResponse, err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - body := M{ - "name": s.name, - "embedding_id": s.embeddingID, - } - if s.description != nil { - body["description"] = *s.description - } - if resp, err = s.client.request(ctx). - SetBody(body). - SetResult(&res). - SetError(&apiError). - Post("knowledge"); err != nil { - return - } - if resp.IsError() { - err = apiError - return - } - return -} - -// KnowledgeEditService edits a knowledge -type KnowledgeEditService struct { - client *Client - - knowledgeID string - - embeddingID *int - name *string - description *string -} - -// NewKnowledgeEditService creates a new KnowledgeEditService -func NewKnowledgeEditService(client *Client) *KnowledgeEditService { - return &KnowledgeEditService{ - client: client, - } -} - -// SetKnowledgeID sets the knowledge id -func (s *KnowledgeEditService) SetKnowledgeID(knowledgeID string) *KnowledgeEditService { - s.knowledgeID = knowledgeID - return s -} - -// SetName sets the name of the knowledge -func (s *KnowledgeEditService) SetName(name string) *KnowledgeEditService { - s.name = &name - return s -} - -// SetEmbeddingID sets the embedding id of the knowledge -func (s *KnowledgeEditService) SetEmbeddingID(embeddingID int) *KnowledgeEditService { - s.embeddingID = &embeddingID - return s -} - -// SetDescription sets the description of the knowledge -func (s *KnowledgeEditService) SetDescription(description string) *KnowledgeEditService { - s.description = &description - return s -} - -// Do edits the knowledge -func (s *KnowledgeEditService) Do(ctx context.Context) (err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - body := M{} - if s.name != nil { - body["name"] = *s.name - } - if s.description != nil { - body["description"] = *s.description - } - if s.embeddingID != nil { - body["embedding_id"] = *s.embeddingID - } - if resp, err = s.client.request(ctx). - SetPathParam("knowledge_id", s.knowledgeID). - SetBody(body). - SetError(&apiError). - Put("knowledge/{knowledge_id}"); err != nil { - return - } - if resp.IsError() { - err = apiError - return - } - return -} - -// KnowledgeListService lists the knowledge -type KnowledgeListService struct { - client *Client - - page *int - size *int -} - -// KnowledgeItem is an item in the knowledge list -type KnowledgeItem struct { - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Icon string `json:"icon"` - Background string `json:"background"` - EmbeddingID int `json:"embedding_id"` - CustomIdentifier string `json:"custom_identifier"` - WordNum int64 `json:"word_num"` - Length int64 `json:"length"` - DocumentSize int64 `json:"document_size"` -} - -// KnowledgeListResponse is the response of the KnowledgeListService -type KnowledgeListResponse struct { - List []KnowledgeItem `json:"list"` - Total int `json:"total"` -} - -// NewKnowledgeListService creates a new KnowledgeListService -func NewKnowledgeListService(client *Client) *KnowledgeListService { - return &KnowledgeListService{client: client} -} - -// SetPage sets the page of the knowledge list -func (s *KnowledgeListService) SetPage(page int) *KnowledgeListService { - s.page = &page - return s -} - -// SetSize sets the size of the knowledge list -func (s *KnowledgeListService) SetSize(size int) *KnowledgeListService { - s.size = &size - return s -} - -// Do lists the knowledge -func (s *KnowledgeListService) Do(ctx context.Context) (res KnowledgeListResponse, err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - req := s.client.request(ctx) - if s.page != nil { - req.SetQueryParam("page", strconv.Itoa(*s.page)) - } - if s.size != nil { - req.SetQueryParam("size", strconv.Itoa(*s.size)) - } - if resp, err = req. - SetResult(&res). - SetError(&apiError). - Get("knowledge"); err != nil { - return - } - if resp.IsError() { - err = apiError - return - } - return -} - -// KnowledgeDeleteService deletes a knowledge -type KnowledgeDeleteService struct { - client *Client - - knowledgeID string -} - -// NewKnowledgeDeleteService creates a new KnowledgeDeleteService -func NewKnowledgeDeleteService(client *Client) *KnowledgeDeleteService { - return &KnowledgeDeleteService{ - client: client, - } -} - -// SetKnowledgeID sets the knowledge id -func (s *KnowledgeDeleteService) SetKnowledgeID(knowledgeID string) *KnowledgeDeleteService { - s.knowledgeID = knowledgeID - return s -} - -// Do deletes the knowledge -func (s *KnowledgeDeleteService) Do(ctx context.Context) (err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - if resp, err = s.client.request(ctx). - SetPathParam("knowledge_id", s.knowledgeID). - SetError(&apiError). - Delete("knowledge/{knowledge_id}"); err != nil { - return - } - if resp.IsError() { - err = apiError - return - } - return -} - -// KnowledgeCapacityService query the capacity of the knowledge -type KnowledgeCapacityService struct { - client *Client -} - -// KnowledgeCapacityItem is an item in the knowledge capacity -type KnowledgeCapacityItem struct { - WordNum int64 `json:"word_num"` - Length int64 `json:"length"` -} - -// KnowledgeCapacityResponse is the response of the KnowledgeCapacityService -type KnowledgeCapacityResponse struct { - Used KnowledgeCapacityItem `json:"used"` - Total KnowledgeCapacityItem `json:"total"` -} - -// SetKnowledgeID sets the knowledge id -func NewKnowledgeCapacityService(client *Client) *KnowledgeCapacityService { - return &KnowledgeCapacityService{client: client} -} - -// Do query the capacity of the knowledge -func (s *KnowledgeCapacityService) Do(ctx context.Context) (res KnowledgeCapacityResponse, err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - if resp, err = s.client.request(ctx). - SetResult(&res). - SetError(&apiError). - Get("knowledge/capacity"); err != nil { - return - } - if resp.IsError() { - err = apiError - return - } - return -} diff --git a/zhipu/knowledge_test.go b/zhipu/knowledge_test.go deleted file mode 100644 index a330e74..0000000 --- a/zhipu/knowledge_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package zhipu - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestKnowledgeCapacity(t *testing.T) { - client, err := NewClient() - require.NoError(t, err) - - s := client.KnowledgeCapacity() - res, err := s.Do(context.Background()) - require.NoError(t, err) - require.NotEmpty(t, res.Total.Length) - require.NotEmpty(t, res.Total.WordNum) -} - -func TestKnowledgeServiceAll(t *testing.T) { - client, err := NewClient() - require.NoError(t, err) - - s := client.KnowledgeCreate() - s.SetName("test") - s.SetDescription("test description") - s.SetEmbeddingID(KnowledgeEmbeddingIDEmbedding2) - - res, err := s.Do(context.Background()) - require.NoError(t, err) - require.NotEmpty(t, res.ID) - - s2 := client.KnowledgeList() - res2, err := s2.Do(context.Background()) - require.NoError(t, err) - require.NotEmpty(t, res2.List) - require.Equal(t, res.ID, res2.List[0].ID) - - s3 := client.KnowledgeEdit(res.ID) - s3.SetDescription("test description 2") - s3.SetName("test 2") - s3.SetEmbeddingID(KnowledgeEmbeddingIDEmbedding2) - err = s3.Do(context.Background()) - require.NoError(t, err) - - s4 := client.KnowledgeDelete(res.ID) - err = s4.Do(context.Background()) - require.NoError(t, err) -} diff --git a/zhipu/string_or.go b/zhipu/string_or.go deleted file mode 100644 index f0a72e6..0000000 --- a/zhipu/string_or.go +++ /dev/null @@ -1,54 +0,0 @@ -package zhipu - -import ( - "bytes" - "encoding/json" -) - -// StringOr is a struct that can be either a string or a value of type T. -type StringOr[T any] struct { - String *string - Value *T -} - -var ( - _ json.Marshaler = StringOr[float64]{} - _ json.Unmarshaler = &StringOr[float64]{} -) - -// SetString sets the string value of the struct. -func (f *StringOr[T]) SetString(v string) { - f.String = &v - f.Value = nil -} - -// SetValue sets the value of the struct. -func (f *StringOr[T]) SetValue(v T) { - f.String = nil - f.Value = &v -} - -func (f StringOr[T]) MarshalJSON() ([]byte, error) { - if f.Value != nil { - return json.Marshal(f.Value) - } - return json.Marshal(f.String) -} - -func (f *StringOr[T]) UnmarshalJSON(data []byte) error { - if len(data) == 0 { - return nil - } - if bytes.Equal(data, []byte("null")) { - return nil - } - if data[0] == '"' { - f.String = new(string) - f.Value = nil - return json.Unmarshal(data, f.String) - } else { - f.Value = new(T) - f.String = nil - return json.Unmarshal(data, f.Value) - } -} diff --git a/zhipu/string_or_test.go b/zhipu/string_or_test.go deleted file mode 100644 index cdc27da..0000000 --- a/zhipu/string_or_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package zhipu - -import ( - "encoding/json" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestStringOr(t *testing.T) { - data := struct { - Item *StringOr[int] `json:"item,omitempty"` - }{} - data.Item = &StringOr[int]{} - data.Item.SetString("test") - - b, err := json.Marshal(data) - require.NoError(t, err) - require.Equal(t, `{"item":"test"}`, string(b)) - - data.Item.SetValue(1) - b, err = json.Marshal(data) - require.NoError(t, err) - require.Equal(t, `{"item":1}`, string(b)) - - err = json.Unmarshal([]byte(`{"item":"test2"}`), &data) - require.NoError(t, err) - require.NotNil(t, data.Item.String) - require.Nil(t, data.Item.Value) - require.Equal(t, "test2", *data.Item.String) - - err = json.Unmarshal([]byte(`{"item":2}`), &data) - require.NoError(t, err) - require.Nil(t, data.Item.String) - require.NotNil(t, data.Item.Value) - require.Equal(t, 2, *data.Item.Value) -} diff --git a/zhipu/testdata/test-file.jsonl b/zhipu/testdata/test-file.jsonl deleted file mode 100644 index 4737cfa..0000000 --- a/zhipu/testdata/test-file.jsonl +++ /dev/null @@ -1,3 +0,0 @@ -{"messages": [{"role": "system", "content": "你是一位乐于助人,知识渊博的全能AI助手。"},{"role": "user", "content": "请介绍一下AlphaGo的基本原理。"}, {"role": "assistant", "content": "AlphaGo利用深度神经网络与树搜索算法相结合,通过对弈数据进行训练,逐步提升游戏水平,能够对围棋局面进行判断并选择最佳落子位置。"}]} -{"messages": [{"role": "system", "content": "你是一位乐于助人,知识渊博的全能AI助手。"},{"role": "user", "content": "请列举一些健康饮食的重要原则。"}, {"role": "assistant", "content": "健康饮食的重要原则包括:膳食应营养均衡,合理摄入各种营养素;多吃新鲜蔬果,适量饮水;少吃糖、油、盐,忌吃过量快餐;养成规律生活作息习惯,适量运动。"}]} -{"messages": [{"role": "system", "content": "你是一位乐于助人,知识渊博的全能AI助手。"},{"role": "user", "content": "请给出一些改善睡眠质量的建议。"}, {"role": "assistant", "content": "改善睡眠质量的建议包括:保证睡眠时间,避免睡眠不足;睡前放松身心,远离刺激;养成规律作息时间,不要频繁熬夜;适量运动,但不要运动过度;睡前可以喝一杯热牛奶等温和饮料。"}]} \ No newline at end of file diff --git a/zhipu/testdata/test-file.txt b/zhipu/testdata/test-file.txt deleted file mode 100644 index ff3bb63..0000000 --- a/zhipu/testdata/test-file.txt +++ /dev/null @@ -1 +0,0 @@ -The quick brown fox jumps over the lazy dog \ No newline at end of file diff --git a/zhipu/util.go b/zhipu/util.go deleted file mode 100644 index 4912b68..0000000 --- a/zhipu/util.go +++ /dev/null @@ -1,22 +0,0 @@ -package zhipu - -// URLItem is a struct that contains a URL. -type URLItem struct { - URL string `json:"url,omitempty"` -} - -// IDItem is a struct that contains an ID. -type IDItem struct { - ID string `json:"id,omitempty"` -} - -// Ptr returns a pointer to the value passed in. -// Example: -// -// web_search_enable = zhipu.Ptr(false) -func Ptr[T any](v T) *T { - return &v -} - -// M is a shorthand for map[string]any. -type M = map[string]any diff --git a/zhipu/util_test.go b/zhipu/util_test.go deleted file mode 100644 index 52b5052..0000000 --- a/zhipu/util_test.go +++ /dev/null @@ -1,3 +0,0 @@ -package zhipu - -// nothing to test diff --git a/zhipu/video_generation.go b/zhipu/video_generation.go deleted file mode 100644 index 3ae3279..0000000 --- a/zhipu/video_generation.go +++ /dev/null @@ -1,125 +0,0 @@ -package zhipu - -import ( - "context" - - "github.com/go-resty/resty/v2" -) - -const ( - VideoGenerationTaskStatusProcessing = "PROCESSING" - VideoGenerationTaskStatusSuccess = "SUCCESS" - VideoGenerationTaskStatusFail = "FAIL" -) - -// VideoGenerationService creates a new video generation -type VideoGenerationService struct { - client *Client - - model string - prompt string - userID string - imageURL string - requestID string -} - -var ( - _ BatchSupport = &VideoGenerationService{} -) - -// VideoGenerationResponse is the response of the VideoGenerationService -type VideoGenerationResponse struct { - RequestID string `json:"request_id"` - ID string `json:"id"` - Model string `json:"model"` - TaskStatus string `json:"task_status"` -} - -func NewVideoGenerationService(client *Client) *VideoGenerationService { - return &VideoGenerationService{ - client: client, - } -} - -func (s *VideoGenerationService) BatchMethod() string { - return "POST" -} - -func (s *VideoGenerationService) BatchURL() string { - return BatchEndpointV4VideosGenerations -} - -func (s *VideoGenerationService) BatchBody() any { - return s.buildBody() -} - -// SetModel sets the model parameter -func (s *VideoGenerationService) SetModel(model string) *VideoGenerationService { - s.model = model - return s -} - -// SetPrompt sets the prompt parameter -func (s *VideoGenerationService) SetPrompt(prompt string) *VideoGenerationService { - s.prompt = prompt - return s -} - -// SetUserID sets the userID parameter -func (s *VideoGenerationService) SetUserID(userID string) *VideoGenerationService { - s.userID = userID - return s -} - -// SetImageURL sets the imageURL parameter -func (s *VideoGenerationService) SetImageURL(imageURL string) *VideoGenerationService { - s.imageURL = imageURL - return s -} - -// SetRequestID sets the requestID parameter -func (s *VideoGenerationService) SetRequestID(requestID string) *VideoGenerationService { - s.requestID = requestID - return s -} - -func (s *VideoGenerationService) buildBody() M { - body := M{ - "model": s.model, - "prompt": s.prompt, - } - if s.userID != "" { - body["user_id"] = s.userID - } - if s.imageURL != "" { - body["image_url"] = s.imageURL - } - if s.requestID != "" { - body["request_id"] = s.requestID - } - return body -} - -func (s *VideoGenerationService) Do(ctx context.Context) (res VideoGenerationResponse, err error) { - var ( - resp *resty.Response - apiError APIErrorResponse - ) - - body := s.buildBody() - - if resp, err = s.client.request(ctx). - SetBody(body). - SetResult(&res). - SetError(&apiError). - Post("videos/generations"); err != nil { - return - } - - if resp.IsError() { - err = apiError - return - } - - return -} diff --git a/zhipu/video_generation_test.go b/zhipu/video_generation_test.go deleted file mode 100644 index 8dc5ab4..0000000 --- a/zhipu/video_generation_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package zhipu - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func TestVideoGeneration(t *testing.T) { - client, err := NewClient() - require.NoError(t, err) - - s := client.VideoGeneration("cogvideox") - s.SetPrompt("一只可爱的小猫") - - res, err := s.Do(context.Background()) - require.NoError(t, err) - require.NotEmpty(t, res.TaskStatus) - require.NotEmpty(t, res.ID) - t.Log(res.ID) - - for { - res, err := client.AsyncResult(res.ID).Do(context.Background()) - require.NoError(t, err) - require.NotEmpty(t, res.TaskStatus) - if res.TaskStatus == VideoGenerationTaskStatusSuccess { - require.NotEmpty(t, res.VideoResult) - t.Log(res.VideoResult[0].URL) - t.Log(res.VideoResult[0].CoverImageURL) - } - if res.TaskStatus != VideoGenerationTaskStatusProcessing { - break - } - time.Sleep(time.Second * 5) - } -} diff --git a/zhipu/wechat-donation.png b/zhipu/wechat-donation.png deleted file mode 100644 index f6519f67fa337cc63b861b4ef276698cff861e64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46799 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 z 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