on main help sign in
fix: openai provider tool calls
0c8e111Kujtim Hoxha
internal/llm/provider/openai.go+15−15
@@ -7,7 +7,6 @@ import (
7 "fmt"
7 "fmt"
8 "io"
8 "io"
9 "log/slog"
9 "log/slog"
10− "slices"
11 "strings"
10 "strings"
12 "time"
11 "time"
13
12
@@ -342,18 +341,15 @@ func (o *openaiClient) stream(ctx context.Context, messages []message.Message, t
342 acc := openai.ChatCompletionAccumulator{}
341 acc := openai.ChatCompletionAccumulator{}
343 currentContent := ""
342 currentContent := ""
344 toolCalls := make([]message.ToolCall, 0)
343 toolCalls := make([]message.ToolCall, 0)
345− var msgToolCalls []openai.ChatCompletionMessageToolCall
344+ msgToolCalls := make(map[int64]openai.ChatCompletionMessageToolCall)
346 for openaiStream.Next() {
345 for openaiStream.Next() {
347 chunk := openaiStream.Current()
346 chunk := openaiStream.Current()
348− if len(chunk.Choices) == 0 {
349− continue
350− }
351 // Kujtim: this is an issue with openrouter qwen, its sending -1 for the tool index
347 // Kujtim: this is an issue with openrouter qwen, its sending -1 for the tool index
352− if len(chunk.Choices[0].Delta.ToolCalls) > 0 && chunk.Choices[0].Delta.ToolCalls[0].Index == -1 {
348+ if len(chunk.Choices) != 0 && len(chunk.Choices[0].Delta.ToolCalls) > 0 && chunk.Choices[0].Delta.ToolCalls[0].Index == -1 {
353 chunk.Choices[0].Delta.ToolCalls[0].Index = 0
349 chunk.Choices[0].Delta.ToolCalls[0].Index = 0
354 }
350 }
355 acc.AddChunk(chunk)
351 acc.AddChunk(chunk)
356− for i, choice := range chunk.Choices {
352+ for _, choice := range chunk.Choices {
357 reasoning, ok := choice.Delta.JSON.ExtraFields["reasoning"]
353 reasoning, ok := choice.Delta.JSON.ExtraFields["reasoning"]
358 if ok && reasoning.Raw() != "" {
354 if ok && reasoning.Raw() != "" {
359 reasoningStr := ""
355 reasoningStr := ""
@@ -374,14 +370,14 @@ func (o *openaiClient) stream(ctx context.Context, messages []message.Message, t
374 } else if len(choice.Delta.ToolCalls) > 0 {
370 } else if len(choice.Delta.ToolCalls) > 0 {
375 toolCall := choice.Delta.ToolCalls[0]
371 toolCall := choice.Delta.ToolCalls[0]
376 newToolCall := false
372 newToolCall := false
377− if len(msgToolCalls)-1 >= int(toolCall.Index) { // tool call exists
373+ if existingToolCall, ok := msgToolCalls[toolCall.Index]; ok { // tool call exists
378− existingToolCall := msgToolCalls[toolCall.Index]
379 if toolCall.ID != "" && toolCall.ID != existingToolCall.ID {
374 if toolCall.ID != "" && toolCall.ID != existingToolCall.ID {
380 found := false
375 found := false
381 // try to find the tool based on the ID
376 // try to find the tool based on the ID
382− for i, tool := range msgToolCalls {
377+ for _, tool := range msgToolCalls {
383 if tool.ID == toolCall.ID {
378 if tool.ID == toolCall.ID {
384− msgToolCalls[i].Function.Arguments += toolCall.Function.Arguments
379+ existingToolCall.Function.Arguments += toolCall.Function.Arguments
380+ msgToolCalls[toolCall.Index] = existingToolCall
385 found = true
381 found = true
386 }
382 }
387 }
383 }
@@ -389,7 +385,8 @@ func (o *openaiClient) stream(ctx context.Context, messages []message.Message, t
389 newToolCall = true
385 newToolCall = true
390 }
386 }
391 } else {
387 } else {
388+ existingToolCall.Function.Arguments += toolCall.Function.Arguments
392− msgToolCalls[toolCall.Index].Function.Arguments += toolCall.Function.Arguments
389+ msgToolCalls[toolCall.Index] = existingToolCall
393 }
390 }
394 } else {
391 } else {
395 newToolCall = true
392 newToolCall = true
@@ -406,17 +403,16 @@ func (o *openaiClient) stream(ctx context.Context, messages []message.Message, t
406 Finished: false,
403 Finished: false,
407 },
404 },
408 }
405 }
409− msgToolCalls = append(msgToolCalls, openai.ChatCompletionMessageToolCall{
406+ msgToolCalls[toolCall.Index] = openai.ChatCompletionMessageToolCall{
410 ID: toolCall.ID,
407 ID: toolCall.ID,
411 Type: "function",
408 Type: "function",
412 Function: openai.ChatCompletionMessageToolCallFunction{
409 Function: openai.ChatCompletionMessageToolCallFunction{
413 Name: toolCall.Function.Name,
410 Name: toolCall.Function.Name,
414 Arguments: toolCall.Function.Arguments,
411 Arguments: toolCall.Function.Arguments,
415 },
412 },
416− })
413+ }
417 }
414 }
418 }
415 }
419− acc.Choices[i].Message.ToolCalls = slices.Clone(msgToolCalls)
420 }
416 }
421 }
417 }
422
418
@@ -541,6 +537,10 @@ func (o *openaiClient) toolCalls(completion openai.ChatCompletion) []message.Too
541
537
542 if len(completion.Choices) > 0 && len(completion.Choices[0].Message.ToolCalls) > 0 {
538 if len(completion.Choices) > 0 && len(completion.Choices[0].Message.ToolCalls) > 0 {
543 for _, call := range completion.Choices[0].Message.ToolCalls {
539 for _, call := range completion.Choices[0].Message.ToolCalls {
540+ // accumulator for some reason does this.
541+ if call.Function.Name == "" {
542+ continue
543+ }
544 toolCall := message.ToolCall{
544 toolCall := message.ToolCall{
545 ID: call.ID,
545 ID: call.ID,
546 Name: call.Function.Name,
546 Name: call.Function.Name,