From 5a5e4fffe44cfe7a7432258d9a16d99a6948d127 Mon Sep 17 00:00:00 2001 From: Augustinas Malinauskas Date: Fri, 6 Jun 2025 11:42:46 -0700 Subject: [PATCH 1/2] fix: stream accumulator fix --- streamaccumulator.go | 6 +- streamaccumulator_test.go | 263 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 253 insertions(+), 16 deletions(-) diff --git a/streamaccumulator.go b/streamaccumulator.go index 53b7069f..2fcda3c6 100644 --- a/streamaccumulator.go +++ b/streamaccumulator.go @@ -163,11 +163,11 @@ func (prev *chatCompletionResponseState) update(chunk ChatCompletionChunk) (just delta := chunk.Choices[0].Delta new := chatCompletionResponseState{} switch { - case delta.JSON.Content.Valid(): + case delta.Content != "": new.state = contentResponseState - case delta.JSON.Refusal.Valid(): + case delta.Refusal != "": new.state = refusalResponseState - case delta.JSON.ToolCalls.Valid(): + case len(delta.ToolCalls) > 0: new.state = toolResponseState new.index = int(delta.ToolCalls[0].Index) default: diff --git a/streamaccumulator_test.go b/streamaccumulator_test.go index 6c602842..0229733c 100644 --- a/streamaccumulator_test.go +++ b/streamaccumulator_test.go @@ -5,12 +5,10 @@ import ( "errors" "io" "net/http" - "os" "strings" "testing" "github.com/openai/openai-go" - "github.com/openai/openai-go/internal/testutil" "github.com/openai/openai-go/option" "github.com/openai/openai-go/shared" ) @@ -34,20 +32,16 @@ Santorini is a gem in the Aegean Sea, known for its stunning sunsets, white-wash Now, let's check the weather in Santorini.` func TestStreamingAccumulatorWithToolCalls(t *testing.T) { - baseURL := "http://localhost:4010" - if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { - baseURL = envURL - } - if !testutil.CheckTestServer(t, baseURL) { - return - } - client := openai.NewClient( - option.WithBaseURL(baseURL), + option.WithBaseURL("http://localhost:4010"), // URL doesn't matter since we're mocking option.WithAPIKey("My API Key"), option.WithMiddleware(func(req *http.Request, next option.MiddlewareNext) (*http.Response, error) { - res, _ := next(req) - res.Body = io.NopCloser(strings.NewReader(mockResponseBody)) + // Don't call next() - just return our mocked response directly + res := &http.Response{ + StatusCode: 200, + Header: make(http.Header), + Body: io.NopCloser(strings.NewReader(mockResponseBody)), + } return res, nil }), ) @@ -140,6 +134,113 @@ func TestStreamingAccumulatorWithToolCalls(t *testing.T) { } } +// Expected values for Ollama test +var expectedOllamaToolCall openai.ChatCompletionMessageToolCallFunction = openai.ChatCompletionMessageToolCallFunction{ + Arguments: `{}`, + Name: "ping", +} + +var expectedOllamaContents string = ` +Okay, the user sent "Ping?". I need to respond with "pong" as per the function. Let me check the tools available. There's a ping function that returns "pong". So I should call that function. No parameters needed. Just return the function call in the specified format. + + +` + +func TestStreamingAccumulatorWithOllamaToolCalls(t *testing.T) { + client := openai.NewClient( + option.WithBaseURL("http://localhost:4010"), // URL doesn't matter since we're mocking + option.WithAPIKey("My API Key"), + option.WithMiddleware(func(req *http.Request, next option.MiddlewareNext) (*http.Response, error) { + // Don't call next() - just return our mocked response directly + res := &http.Response{ + StatusCode: 200, + Header: make(http.Header), + Body: io.NopCloser(strings.NewReader(mockOllamaResponseBody)), + } + return res, nil + }), + ) + stream := client.Chat.Completions.NewStreaming(context.TODO(), openai.ChatCompletionNewParams{ + Messages: []openai.ChatCompletionMessageParamUnion{ + {OfUser: &openai.ChatCompletionUserMessageParam{ + Content: openai.ChatCompletionUserMessageParamContentUnion{ + OfString: openai.String("Ping?"), + }, + }}, + }, + Model: openai.ChatModelGPT4o, + Tools: []openai.ChatCompletionToolParam{{ + Function: shared.FunctionDefinitionParam{ + Name: "ping", + Description: openai.String("responds with pong"), + Parameters: openai.FunctionParameters{ + "type": "object", + "properties": map[string]interface{}{}, + }, + }, + }}, + }) + + acc := openai.ChatCompletionAccumulator{} + + var err error + anythingFinished := false + + for stream.Next() { + chunk := stream.Current() + if !acc.AddChunk(chunk) { + err = errors.New("Chunk was not incorporated correctly") + break + } + + if _, ok := acc.JustFinishedContent(); ok { + anythingFinished = true + } + if _, ok := acc.JustFinishedToolCall(); ok { + anythingFinished = true + } + if _, ok := acc.JustFinishedRefusal(); ok { + anythingFinished = true + } + } + + if err == nil { + err = stream.Err() + } + + if err != nil { + var apierr *openai.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } + + if acc.Choices == nil || len(acc.Choices) == 0 { + t.Fatal("No choices in accumulation") + } + + if acc.Choices[0].Message.Content != expectedOllamaContents { + t.Logf("Expected: %q", expectedOllamaContents) + t.Logf("Actual: %q", acc.Choices[0].Message.Content) + t.Fatalf("Found unexpected content") + } + + if len(acc.Choices[0].Message.ToolCalls) == 0 { + t.Fatalf("No tool calls found in accumulation") + } + + if expectedOllamaToolCall.Arguments != acc.Choices[0].Message.ToolCalls[0].Function.Arguments || expectedOllamaToolCall.Name != acc.Choices[0].Message.ToolCalls[0].Function.Name { + t.Fatalf("Found unexpected tool call. Expected: %v %v, Got: %v %v", + expectedOllamaToolCall.Name, expectedOllamaToolCall.Arguments, + acc.Choices[0].Message.ToolCalls[0].Function.Name, acc.Choices[0].Message.ToolCalls[0].Function.Arguments) + } + + if !anythingFinished { + t.Fatalf("No finish events sent in accumulation") + } +} + // manually created on 11/3/2024 var mockResponseBody = `data: {"id":"chatcmpl-A3Tguz3LSXTHBTY2NAPBCSyfBltxF","object":"chat.completion.chunk","created":1725392480,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_157b3831f5","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":{"content":[],"refusal":null},"finish_reason":null}],"usage":null} @@ -536,3 +637,139 @@ data: {"id":"chatcmpl-A3Tguz3LSXTHBTY2NAPBCSyfBltxF","object":"chat.completion.c data: [DONE] ` + +// Mock Ollama response for tool calls +var mockOllamaResponseBody = `data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"\u003cthink\u003e"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"\n"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"Okay"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":","},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" the"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" user"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" sent"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" \""},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"Ping"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"?"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"\"."},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" I"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" need"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" to"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" respond"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" with"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" \""},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"pong"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"\""},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" as"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" per"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" the"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" function"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"."},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" Let"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" me"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" check"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" the"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" tools"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" available"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"."},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" There"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"'s"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" a"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" ping"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" function"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" that"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" returns"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" \""},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"pong"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"\"."},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" So"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" I"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" should"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" call"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" that"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" function"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"."},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" No"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" parameters"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" needed"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"."},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" Just"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" return"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" the"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" function"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" call"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" in"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" the"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" specified"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" format"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":".\n"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"\u003c/think\u003e"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"\n\n"},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234144,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":[{"id":"call_qulqfb3l","index":0,"type":"function","function":{"name":"ping","arguments":"{}"}}]},"finish_reason":null}]} + +data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234144,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"tool_calls"}]} + +data: [DONE] +` From b1bec520f3aaa5fac5bc8d1ce19edc0c91684809 Mon Sep 17 00:00:00 2001 From: Augustinas Malinauskas Date: Sun, 8 Jun 2025 09:57:32 -0700 Subject: [PATCH 2/2] fix: restore test --- streamaccumulator_test.go | 265 ++------------------------------------ 1 file changed, 14 insertions(+), 251 deletions(-) diff --git a/streamaccumulator_test.go b/streamaccumulator_test.go index 0229733c..db871f11 100644 --- a/streamaccumulator_test.go +++ b/streamaccumulator_test.go @@ -5,10 +5,12 @@ import ( "errors" "io" "net/http" + "os" "strings" "testing" "github.com/openai/openai-go" + "github.com/openai/openai-go/internal/testutil" "github.com/openai/openai-go/option" "github.com/openai/openai-go/shared" ) @@ -32,16 +34,20 @@ Santorini is a gem in the Aegean Sea, known for its stunning sunsets, white-wash Now, let's check the weather in Santorini.` func TestStreamingAccumulatorWithToolCalls(t *testing.T) { + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := openai.NewClient( - option.WithBaseURL("http://localhost:4010"), // URL doesn't matter since we're mocking + option.WithBaseURL(baseURL), option.WithAPIKey("My API Key"), option.WithMiddleware(func(req *http.Request, next option.MiddlewareNext) (*http.Response, error) { - // Don't call next() - just return our mocked response directly - res := &http.Response{ - StatusCode: 200, - Header: make(http.Header), - Body: io.NopCloser(strings.NewReader(mockResponseBody)), - } + res, _ := next(req) + res.Body = io.NopCloser(strings.NewReader(mockResponseBody)) return res, nil }), ) @@ -134,113 +140,6 @@ func TestStreamingAccumulatorWithToolCalls(t *testing.T) { } } -// Expected values for Ollama test -var expectedOllamaToolCall openai.ChatCompletionMessageToolCallFunction = openai.ChatCompletionMessageToolCallFunction{ - Arguments: `{}`, - Name: "ping", -} - -var expectedOllamaContents string = ` -Okay, the user sent "Ping?". I need to respond with "pong" as per the function. Let me check the tools available. There's a ping function that returns "pong". So I should call that function. No parameters needed. Just return the function call in the specified format. - - -` - -func TestStreamingAccumulatorWithOllamaToolCalls(t *testing.T) { - client := openai.NewClient( - option.WithBaseURL("http://localhost:4010"), // URL doesn't matter since we're mocking - option.WithAPIKey("My API Key"), - option.WithMiddleware(func(req *http.Request, next option.MiddlewareNext) (*http.Response, error) { - // Don't call next() - just return our mocked response directly - res := &http.Response{ - StatusCode: 200, - Header: make(http.Header), - Body: io.NopCloser(strings.NewReader(mockOllamaResponseBody)), - } - return res, nil - }), - ) - stream := client.Chat.Completions.NewStreaming(context.TODO(), openai.ChatCompletionNewParams{ - Messages: []openai.ChatCompletionMessageParamUnion{ - {OfUser: &openai.ChatCompletionUserMessageParam{ - Content: openai.ChatCompletionUserMessageParamContentUnion{ - OfString: openai.String("Ping?"), - }, - }}, - }, - Model: openai.ChatModelGPT4o, - Tools: []openai.ChatCompletionToolParam{{ - Function: shared.FunctionDefinitionParam{ - Name: "ping", - Description: openai.String("responds with pong"), - Parameters: openai.FunctionParameters{ - "type": "object", - "properties": map[string]interface{}{}, - }, - }, - }}, - }) - - acc := openai.ChatCompletionAccumulator{} - - var err error - anythingFinished := false - - for stream.Next() { - chunk := stream.Current() - if !acc.AddChunk(chunk) { - err = errors.New("Chunk was not incorporated correctly") - break - } - - if _, ok := acc.JustFinishedContent(); ok { - anythingFinished = true - } - if _, ok := acc.JustFinishedToolCall(); ok { - anythingFinished = true - } - if _, ok := acc.JustFinishedRefusal(); ok { - anythingFinished = true - } - } - - if err == nil { - err = stream.Err() - } - - if err != nil { - var apierr *openai.Error - if errors.As(err, &apierr) { - t.Log(string(apierr.DumpRequest(true))) - } - t.Fatalf("err should be nil: %s", err.Error()) - } - - if acc.Choices == nil || len(acc.Choices) == 0 { - t.Fatal("No choices in accumulation") - } - - if acc.Choices[0].Message.Content != expectedOllamaContents { - t.Logf("Expected: %q", expectedOllamaContents) - t.Logf("Actual: %q", acc.Choices[0].Message.Content) - t.Fatalf("Found unexpected content") - } - - if len(acc.Choices[0].Message.ToolCalls) == 0 { - t.Fatalf("No tool calls found in accumulation") - } - - if expectedOllamaToolCall.Arguments != acc.Choices[0].Message.ToolCalls[0].Function.Arguments || expectedOllamaToolCall.Name != acc.Choices[0].Message.ToolCalls[0].Function.Name { - t.Fatalf("Found unexpected tool call. Expected: %v %v, Got: %v %v", - expectedOllamaToolCall.Name, expectedOllamaToolCall.Arguments, - acc.Choices[0].Message.ToolCalls[0].Function.Name, acc.Choices[0].Message.ToolCalls[0].Function.Arguments) - } - - if !anythingFinished { - t.Fatalf("No finish events sent in accumulation") - } -} - // manually created on 11/3/2024 var mockResponseBody = `data: {"id":"chatcmpl-A3Tguz3LSXTHBTY2NAPBCSyfBltxF","object":"chat.completion.chunk","created":1725392480,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_157b3831f5","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":{"content":[],"refusal":null},"finish_reason":null}],"usage":null} @@ -636,140 +535,4 @@ data: {"id":"chatcmpl-A3Tguz3LSXTHBTY2NAPBCSyfBltxF","object":"chat.completion.c data: [DONE] -` - -// Mock Ollama response for tool calls -var mockOllamaResponseBody = `data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"\u003cthink\u003e"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"\n"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"Okay"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":","},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" the"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" user"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" sent"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" \""},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"Ping"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"?"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"\"."},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" I"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" need"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" to"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" respond"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" with"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" \""},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"pong"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"\""},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" as"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" per"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" the"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" function"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"."},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" Let"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" me"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" check"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" the"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" tools"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" available"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"."},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" There"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"'s"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" a"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" ping"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" function"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" that"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" returns"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" \""},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"pong"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"\"."},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" So"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" I"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" should"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" call"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" that"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" function"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"."},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" No"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" parameters"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" needed"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"."},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" Just"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" return"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" the"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" function"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" call"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" in"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" the"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" specified"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":" format"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":".\n"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"\u003c/think\u003e"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234143,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"\n\n"},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234144,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":[{"id":"call_qulqfb3l","index":0,"type":"function","function":{"name":"ping","arguments":"{}"}}]},"finish_reason":null}]} - -data: {"id":"chatcmpl-197","object":"chat.completion.chunk","created":1749234144,"model":"qwen3:8b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"tool_calls"}]} - -data: [DONE] -` +` \ No newline at end of file