diff --git a/runtime/llm/sse.go b/runtime/llm/sse.go index 8ba0c4f..6edf9d2 100644 --- a/runtime/llm/sse.go +++ b/runtime/llm/sse.go @@ -195,7 +195,7 @@ func emitParsed(ctx context.Context, r io.Reader, out chan<- Event) { // A finish reason alone does not prove the tool arguments arrived intact. // Preserve returned usage when rejecting a partial terminal, so the host // can charge this call and stop rather than treating it as a free retry. - if finished == "tool_calls" && len(order) == 0 { + if (finished == "tool_calls" || finished == "function_call") && len(order) == 0 { sendTerminalFailure("tool call finish reason without a tool call") return } diff --git a/runtime/llm/sse_test.go b/runtime/llm/sse_test.go index 48d28e4..3f79d8c 100644 --- a/runtime/llm/sse_test.go +++ b/runtime/llm/sse_test.go @@ -94,20 +94,24 @@ func TestParseSSEToolCallIncrements(t *testing.T) { } func TestParseSSEToolFinishWithoutCallFails(t *testing.T) { - body := strings.Join([]string{ - `data: {"choices":[{"delta":{},"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":12,"completion_tokens":5,"total_tokens":17}}`, - `data: [DONE]`, - "", - }, "\n") - var end Event - for ev := range ParseSSE(context.Background(), strings.NewReader(body)) { - if ev.Kind == KindMessageEnd { - end = ev - } - } - if end.Err == nil || end.Message.StopReason != StopError || end.Usage.TotalTokens != 17 || - !end.StreamComplete || len(end.Message.ToolCalls) != 0 { - t.Fatalf("empty tool finish must fail and retain Usage: %#v", end) + for _, reason := range []string{"tool_calls", "function_call"} { + t.Run(reason, func(t *testing.T) { + body := strings.Join([]string{ + `data: {"choices":[{"delta":{},"finish_reason":"` + reason + `"}],"usage":{"prompt_tokens":12,"completion_tokens":5,"total_tokens":17}}`, + `data: [DONE]`, + "", + }, "\n") + var end Event + for ev := range ParseSSE(context.Background(), strings.NewReader(body)) { + if ev.Kind == KindMessageEnd { + end = ev + } + } + if end.Err == nil || end.Message.StopReason != StopError || end.Usage.TotalTokens != 17 || + !end.StreamComplete || len(end.Message.ToolCalls) != 0 { + t.Fatalf("empty tool finish must fail and retain Usage: %#v", end) + } + }) } }