diff --git a/openai/language_model.go b/openai/language_model.go index 303c55deb9ba7f413e050a6bcd880e8d46a376d6..188d81ee82320480167c8bf5de3e3c35140d636b 100644 --- a/openai/language_model.go +++ b/openai/language_model.go @@ -21,6 +21,8 @@ type languageModel struct { provider string modelID string client openai.Client + uniqueToolCallIds bool + generateIDFunc LanguageModelGenerateIDFunc prepareCallFunc LanguageModelPrepareCallFunc mapFinishReasonFunc LanguageModelMapFinishReasonFunc extraContentFunc LanguageModelExtraContentFunc @@ -68,11 +70,24 @@ func WithLanguageModelStreamUsageFunc(fn LanguageModelStreamUsageFunc) LanguageM } } +func WithLanguageUniqueToolCallIds() LanguageModelOption { + return func(l *languageModel) { + l.uniqueToolCallIds = true + } +} + +func WithLanguageModelGenerateIDFunc(fn LanguageModelGenerateIDFunc) LanguageModelOption { + return func(l *languageModel) { + l.generateIDFunc = fn + } +} + func newLanguageModel(modelID string, provider string, client openai.Client, opts ...LanguageModelOption) languageModel { model := languageModel{ modelID: modelID, provider: provider, client: client, + generateIDFunc: defaultGenerateID, prepareCallFunc: defaultPrepareLanguageModelCall, mapFinishReasonFunc: defaultMapFinishReason, usageFunc: defaultUsage, @@ -261,8 +276,8 @@ func (o languageModel) Generate(ctx context.Context, call ai.Call) (*ai.Response } for _, tc := range choice.Message.ToolCalls { toolCallID := tc.ID - if toolCallID == "" { - toolCallID = uuid.NewString() + if toolCallID == "" || o.uniqueToolCallIds { + toolCallID = o.generateIDFunc() } content = append(content, ai.ToolCallContent{ ProviderExecuted: false, // TODO: update when handling other tools @@ -419,6 +434,15 @@ func (o languageModel) Stream(ctx context.Context, call ai.Call) (ai.StreamRespo return } + // some providers do not send this as a unique id + // for some usecases in crush we need this ID to be unique. + // it won't change the behavior on the provider side because the + // provider only cares about the tool call id matching the result + // and in our case that will still be the case + if o.uniqueToolCallIds { + toolCallDelta.ID = o.generateIDFunc() + } + if !yield(ai.StreamPart{ Type: ai.StreamPartTypeToolInputStart, ID: toolCallDelta.ID, diff --git a/openai/language_model_hooks.go b/openai/language_model_hooks.go index 6b44d4caa29a35520c1486b73e7cd6c3252d3e02..ceb2a57c71afb9bcecbf93acb66b87aa38fcdb63 100644 --- a/openai/language_model_hooks.go +++ b/openai/language_model_hooks.go @@ -4,12 +4,14 @@ import ( "fmt" "github.com/charmbracelet/fantasy/ai" + "github.com/google/uuid" "github.com/openai/openai-go/v2" "github.com/openai/openai-go/v2/packages/param" "github.com/openai/openai-go/v2/shared" ) type ( + LanguageModelGenerateIDFunc = func() string LanguageModelPrepareCallFunc = func(model ai.LanguageModel, params *openai.ChatCompletionNewParams, call ai.Call) ([]ai.CallWarning, error) LanguageModelMapFinishReasonFunc = func(choice openai.ChatCompletionChoice) ai.FinishReason LanguageModelUsageFunc = func(choice openai.ChatCompletion) (ai.Usage, ai.ProviderOptionsData) @@ -19,6 +21,10 @@ type ( LanguageModelStreamProviderMetadataFunc = func(choice openai.ChatCompletionChoice, metadata ai.ProviderMetadata) ai.ProviderMetadata ) +func defaultGenerateID() string { + return uuid.NewString() +} + func defaultPrepareLanguageModelCall(model ai.LanguageModel, params *openai.ChatCompletionNewParams, call ai.Call) ([]ai.CallWarning, error) { if call.ProviderOptions == nil { return nil, nil diff --git a/openrouter/openrouter.go b/openrouter/openrouter.go index 40c140439cf454a98569d161c76de7b461b19725..8b7a1b7dbb911a780ef42207246b687109f0a77c 100644 --- a/openrouter/openrouter.go +++ b/openrouter/openrouter.go @@ -9,7 +9,8 @@ import ( ) type options struct { - openaiOptions []openai.Option + openaiOptions []openai.Option + languageModelOptions []openai.LanguageModelOption } const ( @@ -24,19 +25,21 @@ func New(opts ...Option) ai.Provider { openaiOptions: []openai.Option{ openai.WithName(Name), openai.WithBaseURL(DefaultURL), - openai.WithLanguageModelOptions( - openai.WithLanguageModelPrepareCallFunc(languagePrepareModelCall), - openai.WithLanguageModelUsageFunc(languageModelUsage), - openai.WithLanguageModelStreamUsageFunc(languageModelStreamUsage), - openai.WithLanguageModelStreamExtraFunc(languageModelStreamExtra), - openai.WithLanguageModelExtraContentFunc(languageModelExtraContent), - openai.WithLanguageModelMapFinishReasonFunc(languageModelMapFinishReason), - ), + }, + languageModelOptions: []openai.LanguageModelOption{ + openai.WithLanguageModelPrepareCallFunc(languagePrepareModelCall), + openai.WithLanguageModelUsageFunc(languageModelUsage), + openai.WithLanguageModelStreamUsageFunc(languageModelStreamUsage), + openai.WithLanguageModelStreamExtraFunc(languageModelStreamExtra), + openai.WithLanguageModelExtraContentFunc(languageModelExtraContent), + openai.WithLanguageModelMapFinishReasonFunc(languageModelMapFinishReason), }, } for _, o := range opts { o(&providerOptions) } + + providerOptions.openaiOptions = append(providerOptions.openaiOptions, openai.WithLanguageModelOptions(providerOptions.languageModelOptions...)) return openai.New(providerOptions.openaiOptions...) } @@ -64,6 +67,18 @@ func WithHTTPClient(client option.HTTPClient) Option { } } +func WithLanguageUniqueToolCallIds() Option { + return func(l *options) { + l.languageModelOptions = append(l.languageModelOptions, openai.WithLanguageUniqueToolCallIds()) + } +} + +func WithLanguageModelGenerateIDFunc(fn openai.LanguageModelGenerateIDFunc) Option { + return func(l *options) { + l.languageModelOptions = append(l.languageModelOptions, openai.WithLanguageModelGenerateIDFunc(fn)) + } +} + func structToMapJSON(s any) (map[string]any, error) { var result map[string]any jsonBytes, err := json.Marshal(s) diff --git a/providertests/common_test.go b/providertests/common_test.go index f75ae84a1ec98588701a447297098d652c1725b8..124000fe9947e1e6bf463088a8946f8286ff0bb3 100644 --- a/providertests/common_test.go +++ b/providertests/common_test.go @@ -147,10 +147,6 @@ func testTool(t *testing.T, pair builderPair) { } func testMultiTool(t *testing.T, pair builderPair) { - type WeatherInput struct { - Location string `json:"location" description:"the city"` - } - type CalculatorInput struct { A int `json:"a" description:"first number"` B int `json:"b" description:"second number"` diff --git a/providertests/openrouter_test.go b/providertests/openrouter_test.go index 31ac9c6f6bf0e80f87eb9c8da1b27476dbfc202a..f033f3c49d0786e7c40ceee8d0ecb547f18fea68 100644 --- a/providertests/openrouter_test.go +++ b/providertests/openrouter_test.go @@ -1,12 +1,16 @@ package providertests import ( + "context" "net/http" "os" + "strconv" + "strings" "testing" "github.com/charmbracelet/fantasy/ai" "github.com/charmbracelet/fantasy/openrouter" + "github.com/google/uuid" "github.com/stretchr/testify/require" "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" ) @@ -51,6 +55,104 @@ func testOpenrouterThinking(t *testing.T, result *ai.AgentResult) { require.Greater(t, reasoningContentCount, 0) } +func TestWithUniqueToolCallIDs(t *testing.T) { + type CalculatorInput struct { + A int `json:"a" description:"first number"` + B int `json:"b" description:"second number"` + } + + addTool := ai.NewAgentTool( + "add", + "Add two numbers", + func(ctx context.Context, input CalculatorInput, _ ai.ToolCall) (ai.ToolResponse, error) { + result := input.A + input.B + return ai.NewTextResponse(strings.TrimSpace(strconv.Itoa(result))), nil + }, + ) + multiplyTool := ai.NewAgentTool( + "multiply", + "Multiply two numbers", + func(ctx context.Context, input CalculatorInput, _ ai.ToolCall) (ai.ToolResponse, error) { + result := input.A * input.B + return ai.NewTextResponse(strings.TrimSpace(strconv.Itoa(result))), nil + }, + ) + checkResult := func(t *testing.T, result *ai.AgentResult) { + require.Len(t, result.Steps, 2) + + var toolCalls []ai.ToolCallContent + for _, content := range result.Steps[0].Content { + if content.GetType() == ai.ContentTypeToolCall { + toolCalls = append(toolCalls, content.(ai.ToolCallContent)) + } + } + for _, tc := range toolCalls { + require.False(t, tc.Invalid) + require.Contains(t, tc.ToolCallID, "test-") + } + require.Len(t, toolCalls, 2) + + finalText := result.Response.Content.Text() + require.Contains(t, finalText, "5", "expected response to contain '5', got: %q", finalText) + require.Contains(t, finalText, "6", "expected response to contain '6', got: %q", finalText) + } + + generateIDFunc := func() string { + return "test-" + uuid.NewString() + } + + t.Run("unique tool call ids", func(t *testing.T) { + r := newRecorder(t) + + provider := openrouter.New( + openrouter.WithAPIKey(os.Getenv("OPENROUTER_API_KEY")), + openrouter.WithHTTPClient(&http.Client{Transport: r}), + openrouter.WithLanguageUniqueToolCallIds(), + openrouter.WithLanguageModelGenerateIDFunc(generateIDFunc), + ) + languageModel, err := provider.LanguageModel("moonshotai/kimi-k2-0905") + require.NoError(t, err, "failed to build language model") + + agent := ai.NewAgent( + languageModel, + ai.WithSystemPrompt("You are a helpful assistant. CRITICAL: Always use both add and multiply at the same time ALWAYS."), + ai.WithTools(addTool), + ai.WithTools(multiplyTool), + ) + result, err := agent.Generate(t.Context(), ai.AgentCall{ + Prompt: "Add and multiply the number 2 and 3", + MaxOutputTokens: ai.IntOption(4000), + }) + require.NoError(t, err, "failed to generate") + checkResult(t, result) + }) + t.Run("stream unique tool call ids", func(t *testing.T) { + r := newRecorder(t) + + provider := openrouter.New( + openrouter.WithAPIKey(os.Getenv("OPENROUTER_API_KEY")), + openrouter.WithHTTPClient(&http.Client{Transport: r}), + openrouter.WithLanguageUniqueToolCallIds(), + openrouter.WithLanguageModelGenerateIDFunc(generateIDFunc), + ) + languageModel, err := provider.LanguageModel("moonshotai/kimi-k2-0905") + require.NoError(t, err, "failed to build language model") + + agent := ai.NewAgent( + languageModel, + ai.WithSystemPrompt("You are a helpful assistant. Always use both add and multiply at the same time."), + ai.WithTools(addTool), + ai.WithTools(multiplyTool), + ) + result, err := agent.Stream(t.Context(), ai.AgentStreamCall{ + Prompt: "Add and multiply the number 2 and 3", + MaxOutputTokens: ai.IntOption(4000), + }) + require.NoError(t, err, "failed to generate") + checkResult(t, result) + }) +} + func builderOpenRouterKimiK2(r *recorder.Recorder) (ai.LanguageModel, error) { provider := openrouter.New( openrouter.WithAPIKey(os.Getenv("OPENROUTER_API_KEY")), diff --git a/providertests/testdata/TestWithUniqueToolCallIDs/stream_unique_tool_call_ids.yaml b/providertests/testdata/TestWithUniqueToolCallIDs/stream_unique_tool_call_ids.yaml new file mode 100644 index 0000000000000000000000000000000000000000..83618145960c8b161ee478109894bd3cf033486a --- /dev/null +++ b/providertests/testdata/TestWithUniqueToolCallIDs/stream_unique_tool_call_ids.yaml @@ -0,0 +1,207 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 890 + host: "" + body: '{"messages":[{"content":"You are a helpful assistant. Always use both add and multiply at the same time.","role":"system"},{"content":"Add and multiply the number 2 and 3","role":"user"}],"model":"moonshotai/kimi-k2-0905","max_tokens":4000,"stream_options":{"include_usage":true},"tool_choice":"auto","tools":[{"function":{"name":"add","strict":false,"description":"Add two numbers","parameters":{"properties":{"a":{"description":"first number","type":"integer"},"b":{"description":"second number","type":"integer"}},"required":["a","b"],"type":"object"}},"type":"function"},{"function":{"name":"multiply","strict":false,"description":"Multiply two numbers","parameters":{"properties":{"a":{"description":"first number","type":"integer"},"b":{"description":"second number","type":"integer"}},"required":["a","b"],"type":"object"}},"type":"function"}],"usage":{"include":true},"stream":true}' + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - OpenAI/Go 2.3.0 + url: https://openrouter.ai/api/v1/chat/completions + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + body: |+ + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":"I'll"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":" add"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":" and"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":" multiply"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":" the"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":" numbers"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":" "},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":"2"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":" and"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":" "},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":"3"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":" for"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":" you"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":"."},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":"functions.add:0","index":0,"type":"function","function":{"name":"add","arguments":""}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":null,"index":0,"type":"function","function":{"name":null,"arguments":"{\"a"}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":null,"index":0,"type":"function","function":{"name":null,"arguments":"\":"}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":null,"index":0,"type":"function","function":{"name":null,"arguments":" "}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":null,"index":0,"type":"function","function":{"name":null,"arguments":"2"}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":null,"index":0,"type":"function","function":{"name":null,"arguments":","}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":null,"index":0,"type":"function","function":{"name":null,"arguments":" \""}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":null,"index":0,"type":"function","function":{"name":null,"arguments":"b"}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":null,"index":0,"type":"function","function":{"name":null,"arguments":"\":"}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":null,"index":0,"type":"function","function":{"name":null,"arguments":" "}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":null,"index":0,"type":"function","function":{"name":null,"arguments":"3"}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":null,"index":0,"type":"function","function":{"name":null,"arguments":"}"}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":"functions.multiply:1","index":1,"type":"function","function":{"name":"multiply","arguments":""}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":null,"index":1,"type":"function","function":{"name":null,"arguments":"{\"a"}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":null,"index":1,"type":"function","function":{"name":null,"arguments":"\":"}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":null,"index":1,"type":"function","function":{"name":null,"arguments":" "}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":null,"index":1,"type":"function","function":{"name":null,"arguments":"2"}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":null,"index":1,"type":"function","function":{"name":null,"arguments":","}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":null,"index":1,"type":"function","function":{"name":null,"arguments":" \""}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":null,"index":1,"type":"function","function":{"name":null,"arguments":"b"}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":null,"index":1,"type":"function","function":{"name":null,"arguments":"\":"}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":null,"index":1,"type":"function","function":{"name":null,"arguments":" "}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":null,"index":1,"type":"function","function":{"name":null,"arguments":"3"}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"id":null,"index":1,"type":"function","function":{"name":null,"arguments":"}"}}]},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"tool_calls","native_finish_reason":"tool_calls","logprobs":null}]} + + data: {"id":"gen-1758792034-4DFG24Xt5SzxfAVIPAsj","provider":"Chutes","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792034,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":197,"completion_tokens":55,"total_tokens":252,"cost":0.00015846,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.00007486,"upstream_inference_completions_cost":0.0000836},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}} + + data: [DONE] + + headers: + Content-Type: + - text/event-stream + status: 200 OK + code: 200 + duration: 1.0912445s + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 1432 + host: "" + body: '{"messages":[{"content":"You are a helpful assistant. Always use both add and multiply at the same time.","role":"system"},{"content":"Add and multiply the number 2 and 3","role":"user"},{"content":"I''ll add and multiply the numbers 2 and 3 for you.","tool_calls":[{"id":"test-5127e4bb-57fe-4257-ba4e-c9bf89cf3fa1","function":{"arguments":"{\"a\": 2, \"b\": 3}","name":"add"},"type":"function"},{"id":"test-970b5c26-bef5-4285-9a05-4c9b83741a5d","function":{"arguments":"{\"a\": 2, \"b\": 3}","name":"multiply"},"type":"function"}],"role":"assistant"},{"content":"5","tool_call_id":"test-5127e4bb-57fe-4257-ba4e-c9bf89cf3fa1","role":"tool"},{"content":"6","tool_call_id":"test-970b5c26-bef5-4285-9a05-4c9b83741a5d","role":"tool"}],"model":"moonshotai/kimi-k2-0905","max_tokens":4000,"stream_options":{"include_usage":true},"tool_choice":"auto","tools":[{"function":{"name":"add","strict":false,"description":"Add two numbers","parameters":{"properties":{"a":{"description":"first number","type":"integer"},"b":{"description":"second number","type":"integer"}},"required":["a","b"],"type":"object"}},"type":"function"},{"function":{"name":"multiply","strict":false,"description":"Multiply two numbers","parameters":{"properties":{"a":{"description":"first number","type":"integer"},"b":{"description":"second number","type":"integer"}},"required":["a","b"],"type":"object"}},"type":"function"}],"usage":{"include":true},"stream":true}' + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - OpenAI/Go 2.3.0 + url: https://openrouter.ai/api/v1/chat/completions + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + body: |+ + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":"The"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":" results"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":" are"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":":\n"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":"-"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":" Addition"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":":"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":" "},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":"2"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":" +"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":" "},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":"3"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":" ="},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":" "},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":"5"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":"\n"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":"-"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":" Multi"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":"plication"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":":"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":" "},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":"2"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":" ×"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":" "},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":"3"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":" ="},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":" "},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":"6"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}],"system_fingerprint":null} + + data: {"id":"gen-1758792037-X8yvMJtJyFuHSepPJpsI","provider":"BaseTen","model":"moonshotai/kimi-k2-0905","object":"chat.completion.chunk","created":1758792037,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":390,"completion_tokens":29,"total_tokens":419,"cost":0.0003065,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.000234,"upstream_inference_completions_cost":0.0000725},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}} + + data: [DONE] + + headers: + Content-Type: + - text/event-stream + status: 200 OK + code: 200 + duration: 662.265667ms diff --git a/providertests/testdata/TestWithUniqueToolCallIDs/unique_tool_call_ids.yaml b/providertests/testdata/TestWithUniqueToolCallIDs/unique_tool_call_ids.yaml new file mode 100644 index 0000000000000000000000000000000000000000..308aed9ce5cd98eb7e3d730f6a5f7d659c41eb2b --- /dev/null +++ b/providertests/testdata/TestWithUniqueToolCallIDs/unique_tool_call_ids.yaml @@ -0,0 +1,63 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 853 + host: "" + body: '{"messages":[{"content":"You are a helpful assistant. CRITICAL: Always use both add and multiply at the same time ALWAYS.","role":"system"},{"content":"Add and multiply the number 2 and 3","role":"user"}],"model":"moonshotai/kimi-k2-0905","max_tokens":4000,"tool_choice":"auto","tools":[{"function":{"name":"add","strict":false,"description":"Add two numbers","parameters":{"properties":{"a":{"description":"first number","type":"integer"},"b":{"description":"second number","type":"integer"}},"required":["a","b"],"type":"object"}},"type":"function"},{"function":{"name":"multiply","strict":false,"description":"Multiply two numbers","parameters":{"properties":{"a":{"description":"first number","type":"integer"},"b":{"description":"second number","type":"integer"}},"required":["a","b"],"type":"object"}},"type":"function"}],"usage":{"include":true}}' + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - OpenAI/Go 2.3.0 + url: https://openrouter.ai/api/v1/chat/completions + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + uncompressed: true + body: "\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n{\"id\":\"gen-1758792026-4PDXsLSjIK2zFVEr7CYE\",\"provider\":\"SiliconFlow\",\"model\":\"moonshotai/kimi-k2-0905\",\"object\":\"chat.completion\",\"created\":1758792026,\"choices\":[{\"logprobs\":null,\"finish_reason\":\"tool_calls\",\"native_finish_reason\":\"tool_calls\",\"index\":0,\"message\":{\"role\":\"assistant\",\"content\":\"I'll use both the add and multiply functions with the numbers 2 and 3 for you.\",\"refusal\":null,\"reasoning\":null,\"tool_calls\":[{\"index\":0,\"id\":\"functions.add:0\",\"type\":\"function\",\"function\":{\"name\":\"add\",\"arguments\":\"{\\\"a\\\": 2, \\\"b\\\": 3}\"}},{\"index\":1,\"id\":\"functions.multiply:1\",\"type\":\"function\",\"function\":{\"name\":\"multiply\",\"arguments\":\"{\\\"a\\\": 2, \\\"b\\\": 3}\"}}]}}],\"system_fingerprint\":\"\",\"usage\":{\"prompt_tokens\":220,\"completion_tokens\":59,\"total_tokens\":279,\"cost\":0.00026271,\"is_byok\":false,\"prompt_tokens_details\":{\"cached_tokens\":0,\"audio_tokens\":0},\"cost_details\":{\"upstream_inference_cost\":null,\"upstream_inference_prompt_cost\":0.0001276,\"upstream_inference_completions_cost\":0.00013511},\"completion_tokens_details\":{\"reasoning_tokens\":0,\"image_tokens\":0}}}" + headers: + Content-Type: + - application/json + status: 200 OK + code: 200 + duration: 1.986534959s + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 1423 + host: "" + body: '{"messages":[{"content":"You are a helpful assistant. CRITICAL: Always use both add and multiply at the same time ALWAYS.","role":"system"},{"content":"Add and multiply the number 2 and 3","role":"user"},{"content":"I''ll use both the add and multiply functions with the numbers 2 and 3 for you.","tool_calls":[{"id":"test-d346f60a-252a-4ffa-8228-8cae83ee6b1f","function":{"arguments":"{\"a\": 2, \"b\": 3}","name":"add"},"type":"function"},{"id":"test-164932d8-c1cb-44d7-94b9-05616b31bd74","function":{"arguments":"{\"a\": 2, \"b\": 3}","name":"multiply"},"type":"function"}],"role":"assistant"},{"content":"5","tool_call_id":"test-d346f60a-252a-4ffa-8228-8cae83ee6b1f","role":"tool"},{"content":"6","tool_call_id":"test-164932d8-c1cb-44d7-94b9-05616b31bd74","role":"tool"}],"model":"moonshotai/kimi-k2-0905","max_tokens":4000,"tool_choice":"auto","tools":[{"function":{"name":"add","strict":false,"description":"Add two numbers","parameters":{"properties":{"a":{"description":"first number","type":"integer"},"b":{"description":"second number","type":"integer"}},"required":["a","b"],"type":"object"}},"type":"function"},{"function":{"name":"multiply","strict":false,"description":"Multiply two numbers","parameters":{"properties":{"a":{"description":"first number","type":"integer"},"b":{"description":"second number","type":"integer"}},"required":["a","b"],"type":"object"}},"type":"function"}],"usage":{"include":true}}' + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - OpenAI/Go 2.3.0 + url: https://openrouter.ai/api/v1/chat/completions + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + uncompressed: true + body: "\n \n\n \n\n \n\n \n{\"id\":\"gen-1758792032-yD4OKp7VjJKd81wvmOej\",\"provider\":\"Chutes\",\"model\":\"moonshotai/kimi-k2-0905\",\"object\":\"chat.completion\",\"created\":1758792033,\"choices\":[{\"logprobs\":null,\"finish_reason\":\"stop\",\"native_finish_reason\":\"stop\",\"index\":0,\"message\":{\"role\":\"assistant\",\"content\":\"The results are:\\n- Addition: 2 + 3 = 5\\n- Multiplication: 2 × 3 = 6\",\"refusal\":null,\"reasoning\":null}}],\"usage\":{\"prompt_tokens\":376,\"completion_tokens\":29,\"total_tokens\":405,\"cost\":0.00018696,\"is_byok\":false,\"prompt_tokens_details\":{\"cached_tokens\":0,\"audio_tokens\":0},\"cost_details\":{\"upstream_inference_cost\":null,\"upstream_inference_prompt_cost\":0.00014288,\"upstream_inference_completions_cost\":0.00004408},\"completion_tokens_details\":{\"reasoning_tokens\":0,\"image_tokens\":0}}}" + headers: + Content-Type: + - application/json + status: 200 OK + code: 200 + duration: 1.327371916s