From db1916a4d0a33b2413d6f9b48a5fb0614c7b2a63 Mon Sep 17 00:00:00 2001 From: eiixy <990656271@qq.com> Date: Wed, 24 Jul 2024 12:16:49 +0800 Subject: [PATCH 1/7] add batch api example --- examples/batch/main.go | 79 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 examples/batch/main.go diff --git a/examples/batch/main.go b/examples/batch/main.go new file mode 100644 index 000000000..612327535 --- /dev/null +++ b/examples/batch/main.go @@ -0,0 +1,79 @@ +package main + +import ( + "context" + "fmt" + "github.com/sashabaranov/go-openai" + "io" + "log" + "os" +) + +func main() { + client := openai.NewClient(os.Getenv("OPENAI_API_KEY")) + ctx := context.Background() + + // create batch + response, err := createBatch(ctx, client) + if err != nil { + log.Fatal(err) + } + fmt.Printf("batchID: %+v\n", response.ID) + + // retrieve Batch + //batchID := "batch_XXXXXXXXXXXXX" + //retrieveBatch(ctx, client, batchID) +} + +func createBatch(ctx context.Context, client *openai.Client) (openai.BatchResponse, error) { + req := openai.CreateBatchWithUploadFileRequest{ + Endpoint: openai.BatchEndpointChatCompletions, + } + comments := []string{ + "it's a good bike but if you have a problem after the sale they either do not respond to you or the parts are not available", + "I ordered 2 Mars 2.0.A blue and an Orange.Blue came first and had shipping damage to the seat post.It came with a flip seat.The Orange came about 10 days later and didnt have a flip seat.I notified customer service about both issues.They shipped a new seat post but it will not fit the blue bike because it is for a non flip seat.I am still waiting for a fix both both of these problems.\nI do not like the fact that the throttle cannot be used without the peddle assist being on.At time I feel the peddle assist is dangerous.You better not try to make a turn with the peddle assist on.", + "This was my first E-bike. Love it so far, it has plenty power and range. I use it for hunting on our land. Works well for me, I am very satisfied.", + "I would definitely recommend this bike. Easy to use. Great battery life, quick delivery!", + "Slight difficulty setting up bike but it’s perfect and love it’s speed and power", + } + prompt := "Please analyze the following product review and extract the mentioned dimensions and reasons.\n\nReview example:\n```\nThese headphones have excellent sound quality, perfect for music lovers. I wear them every day during my commute, and the noise cancellation is great. The customer service is also very good; they patiently solved my issues. The only downside is that wearing them for long periods makes my ears hurt.\n```\n\nExpected JSON output example:\n```json\n{\n \"dimensions\": [\n {\n \"dimension\": \"Usage Scenario\",\n \"value\": \"during commute\",\n \"reason\": \"user wears them every day during commute\"\n },\n {\n \"dimension\": \"Target Audience\",\n \"value\": \"music lovers\",\n \"reason\": \"user is a music lover\"\n },\n {\n \"dimension\": \"Positive Experience\",\n \"value\": \"excellent sound quality\",\n \"reason\": \"user thinks the headphones have excellent sound quality\"\n },\n {\n \"dimension\": \"Positive Experience\",\n \"value\": \"great noise cancellation\",\n \"reason\": \"user thinks the noise cancellation is great\"\n },\n {\n \"dimension\": \"Negative Experience\",\n \"value\": \"ears hurt after long periods\",\n \"reason\": \"user thinks wearing them for long periods makes ears hurt\"\n }\n ]\n}\n```\nPlease analyze accordingly and return the results in JSON format." + + for i, comment := range comments { + req.AddChatCompletion(fmt.Sprintf("req-%d", i), openai.ChatCompletionRequest{ + Model: openai.GPT4oMini20240718, + ResponseFormat: &openai.ChatCompletionResponseFormat{ + Type: openai.ChatCompletionResponseFormatTypeJSONObject, + }, + Messages: []openai.ChatCompletionMessage{ + {Role: openai.ChatMessageRoleSystem, Content: prompt}, + {Role: openai.ChatMessageRoleUser, Content: comment}, + }, + MaxTokens: 2000, + }) + } + return client.CreateBatchWithUploadFile(ctx, req) +} + +func retrieveBatch(ctx context.Context, client *openai.Client, batchID string) { + batch, err := client.RetrieveBatch(ctx, batchID) + if err != nil { + return + } + fmt.Printf("batchStatus: %s\n", batch.Status) + + files := map[string]*string{ + "inputFile": &batch.InputFileID, + "outputFile": batch.OutputFileID, + "errorFile": batch.ErrorFileID, + } + for name, fileID := range files { + if fileID != nil { + content, err := client.GetFileContent(ctx, *fileID) + if err != nil { + return + } + all, _ := io.ReadAll(content) + fmt.Printf("%s: %s\n", name, all) + } + } +} From 06bde09ab39ebd787af5ce65973f2be0630f6163 Mon Sep 17 00:00:00 2001 From: eiixy <990656271@qq.com> Date: Wed, 24 Jul 2024 12:17:40 +0800 Subject: [PATCH 2/7] update batch api example --- examples/batch/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/batch/main.go b/examples/batch/main.go index 612327535..c678a7146 100644 --- a/examples/batch/main.go +++ b/examples/batch/main.go @@ -18,7 +18,7 @@ func main() { if err != nil { log.Fatal(err) } - fmt.Printf("batchID: %+v\n", response.ID) + fmt.Printf("batchID: %s\n", response.ID) // retrieve Batch //batchID := "batch_XXXXXXXXXXXXX" From 4f7228497573600597e651242bdc0ef092f0225e Mon Sep 17 00:00:00 2001 From: eiixy <990656271@qq.com> Date: Mon, 29 Jul 2024 14:56:17 +0800 Subject: [PATCH 3/7] add `CreateBatchWithChatCompletions`, `CreateBatchWithEmbeddings` method --- batch.go | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/batch.go b/batch.go index 3c1a9d0d7..1df06c0d9 100644 --- a/batch.go +++ b/batch.go @@ -209,6 +209,90 @@ func (c *Client) CreateBatchWithUploadFile( }) } +type CreateBatchWithChatCompletionsRequest struct { + CompletionWindow string + Metadata map[string]any + FileName string + ChatCompletions []BatchChatCompletion +} + +type BatchChatCompletion struct { + CustomID string + ChatCompletion ChatCompletionRequest +} + +// CreateBatchWithChatCompletions — API call to Create batch with chat completions. +func (c *Client) CreateBatchWithChatCompletions( + ctx context.Context, + request CreateBatchWithChatCompletionsRequest, +) (response BatchResponse, err error) { + var file File + var lines = make([]BatchLineItem, len(request.ChatCompletions)) + for i, completion := range request.ChatCompletions { + lines[i] = BatchChatCompletionRequest{ + CustomID: completion.CustomID, + Body: completion.ChatCompletion, + Method: "POST", + URL: BatchEndpointChatCompletions, + } + } + file, err = c.UploadBatchFile(ctx, UploadBatchFileRequest{ + FileName: request.FileName, + Lines: lines, + }) + if err != nil { + return + } + return c.CreateBatch(ctx, CreateBatchRequest{ + InputFileID: file.ID, + Endpoint: BatchEndpointChatCompletions, + CompletionWindow: request.CompletionWindow, + Metadata: request.Metadata, + }) +} + +type CreateBatchWithEmbeddingsRequest struct { + CompletionWindow string + Metadata map[string]any + FileName string + Embeddings []BatchEmbedding +} + +type BatchEmbedding struct { + CustomID string `json:"custom_id"` + Embedding EmbeddingRequest `json:"body"` +} + +// CreateBatchWithEmbeddings — API call to Create batch with embeddings. +func (c *Client) CreateBatchWithEmbeddings( + ctx context.Context, + request CreateBatchWithEmbeddingsRequest, +) (response BatchResponse, err error) { + var file File + var lines = make([]BatchLineItem, len(request.Embeddings)) + for i, embedding := range request.Embeddings { + lines[i] = BatchEmbeddingRequest{ + CustomID: embedding.CustomID, + Body: embedding.Embedding, + Method: "POST", + URL: BatchEndpointEmbeddings, + } + } + file, err = c.UploadBatchFile(ctx, UploadBatchFileRequest{ + FileName: request.FileName, + Lines: lines, + }) + if err != nil { + return + } + return c.CreateBatch(ctx, CreateBatchRequest{ + InputFileID: file.ID, + Endpoint: BatchEndpointEmbeddings, + CompletionWindow: request.CompletionWindow, + Metadata: request.Metadata, + }) +} + // RetrieveBatch — API call to Retrieve batch. func (c *Client) RetrieveBatch( ctx context.Context, From 82d93240e002f7f80a7fa9cd270274f9019946d9 Mon Sep 17 00:00:00 2001 From: eiixy <990656271@qq.com> Date: Mon, 29 Jul 2024 14:59:13 +0800 Subject: [PATCH 4/7] Add batch API to integration tests and optimize batch API example --- api_integration_test.go | 68 +++++++++++++++++++++++++++++++++++++++++ examples/batch/main.go | 45 ++++++++++++--------------- 2 files changed, 87 insertions(+), 26 deletions(-) diff --git a/api_integration_test.go b/api_integration_test.go index f34685188..dcae4b4c5 100644 --- a/api_integration_test.go +++ b/api_integration_test.go @@ -5,6 +5,7 @@ package openai_test import ( "context" "errors" + "fmt" "io" "os" "testing" @@ -144,6 +145,73 @@ func TestCompletionStream(t *testing.T) { } } +func TestBatchAPI(t *testing.T) { + ctx := context.Background() + apiToken := os.Getenv("OPENAI_TOKEN") + if apiToken == "" { + t.Skip("Skipping testing against production OpenAI API. Set OPENAI_TOKEN environment variable to enable it.") + } + var err error + c := openai.NewClient(apiToken) + + req := openai.CreateBatchWithUploadFileRequest{ + Endpoint: openai.BatchEndpointChatCompletions, + CompletionWindow: "24h", + } + for i := 0; i < 5; i++ { + req.AddChatCompletion(fmt.Sprintf("req-%d", i), openai.ChatCompletionRequest{ + Model: openai.GPT4oMini, + Messages: []openai.ChatCompletionMessage{ + { + Role: openai.ChatMessageRoleUser, + Content: fmt.Sprintf("What is the square of %d?", i+1), + }, + }, + }) + } + _, err = c.CreateBatchWithUploadFile(ctx, req) + checks.NoError(t, err, "CreateBatchWithUploadFile error") + + var chatCompletions = make([]openai.BatchChatCompletion, 5) + for i := 0; i < 5; i++ { + chatCompletions[i] = openai.BatchChatCompletion{ + CustomID: fmt.Sprintf("req-%d", i), + ChatCompletion: openai.ChatCompletionRequest{ + Model: openai.GPT4oMini, + Messages: []openai.ChatCompletionMessage{ + { + Role: openai.ChatMessageRoleUser, + Content: fmt.Sprintf("What is the square of %d?", i+1), + }, + }, + }, + } + } + _, err = c.CreateBatchWithChatCompletions(ctx, openai.CreateBatchWithChatCompletionsRequest{ + ChatCompletions: chatCompletions, + }) + checks.NoError(t, err, "CreateBatchWithChatCompletions error") + + var embeddings = make([]openai.BatchEmbedding, 3) + for i := 0; i < 3; i++ { + embeddings[i] = openai.BatchEmbedding{ + CustomID: fmt.Sprintf("req-%d", i), + Embedding: openai.EmbeddingRequest{ + Input: "The food was delicious and the waiter...", + Model: openai.AdaEmbeddingV2, + EncodingFormat: openai.EmbeddingEncodingFormatFloat, + }, + } + } + _, err = c.CreateBatchWithEmbeddings(ctx, openai.CreateBatchWithEmbeddingsRequest{ + Embeddings: embeddings, + }) + checks.NoError(t, err, "CreateBatchWithEmbeddings error") + + _, err = c.ListBatch(ctx, nil, nil) + checks.NoError(t, err, "ListBatch error") +} + func TestAPIError(t *testing.T) { apiToken := os.Getenv("OPENAI_TOKEN") if apiToken == "" { diff --git a/examples/batch/main.go b/examples/batch/main.go index c678a7146..286c9b832 100644 --- a/examples/batch/main.go +++ b/examples/batch/main.go @@ -14,7 +14,7 @@ func main() { ctx := context.Background() // create batch - response, err := createBatch(ctx, client) + response, err := createBatchChatCompletion(ctx, client) if err != nil { log.Fatal(err) } @@ -25,33 +25,26 @@ func main() { //retrieveBatch(ctx, client, batchID) } -func createBatch(ctx context.Context, client *openai.Client) (openai.BatchResponse, error) { - req := openai.CreateBatchWithUploadFileRequest{ - Endpoint: openai.BatchEndpointChatCompletions, - } - comments := []string{ - "it's a good bike but if you have a problem after the sale they either do not respond to you or the parts are not available", - "I ordered 2 Mars 2.0.A blue and an Orange.Blue came first and had shipping damage to the seat post.It came with a flip seat.The Orange came about 10 days later and didnt have a flip seat.I notified customer service about both issues.They shipped a new seat post but it will not fit the blue bike because it is for a non flip seat.I am still waiting for a fix both both of these problems.\nI do not like the fact that the throttle cannot be used without the peddle assist being on.At time I feel the peddle assist is dangerous.You better not try to make a turn with the peddle assist on.", - "This was my first E-bike. Love it so far, it has plenty power and range. I use it for hunting on our land. Works well for me, I am very satisfied.", - "I would definitely recommend this bike. Easy to use. Great battery life, quick delivery!", - "Slight difficulty setting up bike but it’s perfect and love it’s speed and power", - } - prompt := "Please analyze the following product review and extract the mentioned dimensions and reasons.\n\nReview example:\n```\nThese headphones have excellent sound quality, perfect for music lovers. I wear them every day during my commute, and the noise cancellation is great. The customer service is also very good; they patiently solved my issues. The only downside is that wearing them for long periods makes my ears hurt.\n```\n\nExpected JSON output example:\n```json\n{\n \"dimensions\": [\n {\n \"dimension\": \"Usage Scenario\",\n \"value\": \"during commute\",\n \"reason\": \"user wears them every day during commute\"\n },\n {\n \"dimension\": \"Target Audience\",\n \"value\": \"music lovers\",\n \"reason\": \"user is a music lover\"\n },\n {\n \"dimension\": \"Positive Experience\",\n \"value\": \"excellent sound quality\",\n \"reason\": \"user thinks the headphones have excellent sound quality\"\n },\n {\n \"dimension\": \"Positive Experience\",\n \"value\": \"great noise cancellation\",\n \"reason\": \"user thinks the noise cancellation is great\"\n },\n {\n \"dimension\": \"Negative Experience\",\n \"value\": \"ears hurt after long periods\",\n \"reason\": \"user thinks wearing them for long periods makes ears hurt\"\n }\n ]\n}\n```\nPlease analyze accordingly and return the results in JSON format." - - for i, comment := range comments { - req.AddChatCompletion(fmt.Sprintf("req-%d", i), openai.ChatCompletionRequest{ - Model: openai.GPT4oMini20240718, - ResponseFormat: &openai.ChatCompletionResponseFormat{ - Type: openai.ChatCompletionResponseFormatTypeJSONObject, +func createBatchChatCompletion(ctx context.Context, client *openai.Client) (openai.BatchResponse, error) { + var chatCompletions = make([]openai.BatchChatCompletion, 5) + for i := 0; i < 5; i++ { + chatCompletions[i] = openai.BatchChatCompletion{ + CustomID: fmt.Sprintf("req-%d", i), + ChatCompletion: openai.ChatCompletionRequest{ + Model: openai.GPT4oMini, + Messages: []openai.ChatCompletionMessage{ + { + Role: openai.ChatMessageRoleUser, + Content: fmt.Sprintf("What is the square of %d?", i+1), + }, + }, }, - Messages: []openai.ChatCompletionMessage{ - {Role: openai.ChatMessageRoleSystem, Content: prompt}, - {Role: openai.ChatMessageRoleUser, Content: comment}, - }, - MaxTokens: 2000, - }) + } } - return client.CreateBatchWithUploadFile(ctx, req) + + return client.CreateBatchWithChatCompletions(ctx, openai.CreateBatchWithChatCompletionsRequest{ + ChatCompletions: chatCompletions, + }) } func retrieveBatch(ctx context.Context, client *openai.Client, batchID string) { From 19d683381e8628ca50e5afe064c405195aa87c4e Mon Sep 17 00:00:00 2001 From: eiixy <990656271@qq.com> Date: Mon, 29 Jul 2024 15:04:37 +0800 Subject: [PATCH 5/7] Add test cases for `CreateBatchWithChatCompletions` and `CreateBatchWithEmbeddings` methods --- batch_test.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/batch_test.go b/batch_test.go index 4b2261e0e..62be85506 100644 --- a/batch_test.go +++ b/batch_test.go @@ -66,6 +66,62 @@ func TestCreateBatchWithUploadFile(t *testing.T) { checks.NoError(t, err, "CreateBatchWithUploadFile error") } +func TestCreateBatchWithChatCompletions(t *testing.T) { + client, server, teardown := setupOpenAITestServer() + defer teardown() + server.RegisterHandler("/v1/files", handleCreateFile) + server.RegisterHandler("/v1/batches", handleBatchEndpoint) + + var chatCompletions = make([]openai.BatchChatCompletion, 5) + for i := 0; i < 5; i++ { + chatCompletions[i] = openai.BatchChatCompletion{ + CustomID: fmt.Sprintf("req-%d", i), + ChatCompletion: openai.ChatCompletionRequest{ + Model: openai.GPT4oMini, + Messages: []openai.ChatCompletionMessage{ + { + Role: openai.ChatMessageRoleUser, + Content: fmt.Sprintf("What is the square of %d?", i+1), + }, + }, + }, + } + } + _, err := client.CreateBatchWithChatCompletions( + context.Background(), + openai.CreateBatchWithChatCompletionsRequest{ + ChatCompletions: chatCompletions, + }, + ) + checks.NoError(t, err, "CreateBatchWithChatCompletions error") +} + +func TestCreateBatchWithEmbeddings(t *testing.T) { + client, server, teardown := setupOpenAITestServer() + defer teardown() + server.RegisterHandler("/v1/files", handleCreateFile) + server.RegisterHandler("/v1/batches", handleBatchEndpoint) + + var embeddings = make([]openai.BatchEmbedding, 3) + for i := 0; i < 3; i++ { + embeddings[i] = openai.BatchEmbedding{ + CustomID: fmt.Sprintf("req-%d", i), + Embedding: openai.EmbeddingRequest{ + Input: "The food was delicious and the waiter...", + Model: openai.AdaEmbeddingV2, + EncodingFormat: openai.EmbeddingEncodingFormatFloat, + }, + } + } + _, err := client.CreateBatchWithEmbeddings( + context.Background(), + openai.CreateBatchWithEmbeddingsRequest{ + Embeddings: embeddings, + }, + ) + checks.NoError(t, err, "CreateBatchWithEmbeddings error") +} + func TestRetrieveBatch(t *testing.T) { client, server, teardown := setupOpenAITestServer() defer teardown() From 593c282e9fde5d6e935ff757e7b00d3ec56456bf Mon Sep 17 00:00:00 2001 From: eiixy <990656271@qq.com> Date: Mon, 29 Jul 2024 15:34:24 +0800 Subject: [PATCH 6/7] Add test cases for `CreateBatchWithChatCompletions` and `CreateBatchWithEmbeddings` methods --- client_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client_test.go b/client_test.go index e49da9b3d..71a171fb0 100644 --- a/client_test.go +++ b/client_test.go @@ -402,6 +402,12 @@ func TestClientReturnsRequestBuilderErrors(t *testing.T) { {"CreateBatchWithUploadFile", func() (any, error) { return client.CreateBatchWithUploadFile(ctx, CreateBatchWithUploadFileRequest{}) }}, + {"CreateBatchWithChatCompletions", func() (any, error) { + return client.CreateBatchWithChatCompletions(ctx, CreateBatchWithChatCompletionsRequest{}) + }}, + {"CreateBatchWithEmbeddings", func() (any, error) { + return client.CreateBatchWithEmbeddings(ctx, CreateBatchWithEmbeddingsRequest{}) + }}, {"RetrieveBatch", func() (any, error) { return client.RetrieveBatch(ctx, "") }}, From 5f14c63b23e5a787def4c7008ab4f1483a1be973 Mon Sep 17 00:00:00 2001 From: eiixy <990656271@qq.com> Date: Mon, 29 Jul 2024 15:58:18 +0800 Subject: [PATCH 7/7] Add batch API example code to README.md --- README.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/README.md b/README.md index 799dc602b..fbb0fdfeb 100644 --- a/README.md +++ b/README.md @@ -741,6 +741,49 @@ func main() { // // fmt.Println(resp.Choices[0].Text) } +``` + + +
+BatchAPI + +```go +package main + +import ( + "context" + "fmt" +) + +func main() { + client := openai.NewClient("your token") + ctx := context.Background() + var chatCompletions = make([]openai.BatchChatCompletion, 5) + for i := 0; i < 5; i++ { + chatCompletions[i] = openai.BatchChatCompletion{ + CustomID: fmt.Sprintf("req-%d", i), + ChatCompletion: openai.ChatCompletionRequest{ + Model: openai.GPT4oMini, + Messages: []openai.ChatCompletionMessage{ + { + Role: openai.ChatMessageRoleUser, + Content: fmt.Sprintf("What is the square of %d?", i+1), + }, + }, + }, + } + } + + resp, err := client.CreateBatchWithChatCompletions(ctx, openai.CreateBatchWithChatCompletionsRequest{ + ChatCompletions: chatCompletions, + }) + if err != nil { + fmt.Printf("CreateBatchWithChatCompletions error: %v\n", err) + return + } + fmt.Println("batchID:", resp.ID) +} + ```
See the `examples/` folder for more.