package main import ( "net/http/httptest" "strings" "testing" ) func TestFormatPromptToolResolutionAndGrouping(t *testing.T) { req := ChatCompletionRequest{ Model: "qwen/qwen3.6-27b", Tools: []Tool{ { Type: "function", Function: map[string]interface{}{ "name": "calculator", "description": "Calculate math expression", }, }, }, Messages: []ChatMessage{ { Role: "user", Content: "Calculate 25 * 4 and then tell me what the square root of that is.", }, { Role: "assistant", ToolCalls: []ToolCall{ { ID: "call_abc123", Type: "function", Function: ToolCallFunction{ Name: "calculator", Arguments: `{"expr":"25*4"}`, }, }, }, }, { Role: "tool", ToolCallID: "call_abc123", Content: "100", }, }, } prompt := FormatPrompt(req) // Verify tool name was resolved from assistant tool call if !strings.Contains(prompt, `"name": "calculator"`) { t.Errorf("Expected prompt to contain resolved tool name calculator, got:\n%s", prompt) } // Verify tool_call_id is preserved if !strings.Contains(prompt, `"tool_call_id": "call_abc123"`) { t.Errorf("Expected prompt to contain tool_call_id, got:\n%s", prompt) } // Verify Tool Execution Results block exists if !strings.Contains(prompt, "[Tool Execution Results]") { t.Errorf("Expected prompt to have [Tool Execution Results], got:\n%s", prompt) } // Verify Next Steps Directive exists if !strings.Contains(prompt, "[Next Steps Directive]") { t.Errorf("Expected prompt to have [Next Steps Directive], got:\n%s", prompt) } // Verify tool result was not placed under [User] if strings.Contains(prompt, "[User]\n") { t.Errorf("Tool result should not be placed under [User], got:\n%s", prompt) } } func TestFormatPromptParallelToolResults(t *testing.T) { req := ChatCompletionRequest{ Model: "qwen/qwen3.6-27b", Messages: []ChatMessage{ { Role: "user", Content: "What is the weather in Tokyo and Paris?", }, { Role: "assistant", ToolCalls: []ToolCall{ { ID: "call_tokyo", Type: "function", Function: ToolCallFunction{ Name: "get_weather", Arguments: `{"city":"Tokyo"}`, }, }, { ID: "call_paris", Type: "function", Function: ToolCallFunction{ Name: "get_weather", Arguments: `{"city":"Paris"}`, }, }, }, }, { Role: "tool", ToolCallID: "call_tokyo", Content: `{"temp": 18, "condition": "sunny"}`, }, { Role: "tool", ToolCallID: "call_paris", Content: `{"temp": 15, "condition": "rainy"}`, }, }, } prompt := FormatPrompt(req) // Both tool results must be grouped under [Tool Execution Results] execIdx := strings.Index(prompt, "[Tool Execution Results]") if execIdx == -1 { t.Fatalf("Expected [Tool Execution Results], got:\n%s", prompt) } execBlock := prompt[execIdx:] if !strings.Contains(execBlock, "call_tokyo") { t.Errorf("Expected call_tokyo in Tool Execution Results block, got:\n%s", execBlock) } if !strings.Contains(execBlock, "call_paris") { t.Errorf("Expected call_paris in Tool Execution Results block, got:\n%s", execBlock) } // Neither should be in [Conversation History] as an orphan turn historyIdx := strings.Index(prompt, "[Conversation History]") if historyIdx != -1 { historyBlock := prompt[historyIdx:execIdx] if strings.Contains(historyBlock, "call_tokyo") || strings.Contains(historyBlock, "call_paris") { t.Errorf("Trailing tool results should not be in history block, got:\n%s", historyBlock) } } } func TestDetectToolCallsFormats(t *testing.T) { allowedTools := map[string]bool{ "search": true, "weather": true, "tool_a": true, "tool_b": true, "direct_tool": true, } // Test 1: XML single tool call xmlSingle := ` {"name": "search", "arguments": {"query": "golang"}} ` calls, rem, ok := DetectToolCalls(xmlSingle, allowedTools) if !ok || len(calls) != 1 || calls[0].Function.Name != "search" { t.Errorf("Failed to detect XML single tool call: ok=%v, calls=%v, rem=%q", ok, calls, rem) } if calls[0].Index == nil || *calls[0].Index != 0 { t.Errorf("Expected index 0, got %v", calls[0].Index) } // Test 2: XML multiple tool calls xmlMulti := ` {"name": "search", "arguments": {"query": "golang"}} {"name": "weather", "arguments": {"city": "Tokyo"}} ` calls2, _, ok2 := DetectToolCalls(xmlMulti, allowedTools) if !ok2 || len(calls2) != 2 { t.Fatalf("Failed to detect XML multiple tool calls: ok=%v, calls=%v", ok2, calls2) } if calls2[0].Function.Name != "search" || calls2[1].Function.Name != "weather" { t.Errorf("Tool call names mismatch: %v", calls2) } if calls2[0].Index == nil || *calls2[0].Index != 0 || calls2[1].Index == nil || *calls2[1].Index != 1 { t.Errorf("Indices mismatch: %v, %v", calls2[0].Index, calls2[1].Index) } // Test 3: JSON array inside XML xmlArray := ` [ {"name": "tool_a", "arguments": {"a": 1}}, {"name": "tool_b", "arguments": {"b": 2}} ] ` calls3, _, ok3 := DetectToolCalls(xmlArray, allowedTools) if !ok3 || len(calls3) != 2 { t.Fatalf("Failed to detect JSON array inside XML tool call: ok=%v, calls=%v", ok3, calls3) } if calls3[0].Function.Name != "tool_a" || calls3[1].Function.Name != "tool_b" { t.Errorf("Tool names mismatch: %v", calls3) } // Test 4: Direct JSON array without tags jsonArray := `[{"name": "direct_tool", "arguments": {"key": "val"}}]` calls4, _, ok4 := DetectToolCalls(jsonArray, allowedTools) if !ok4 || len(calls4) != 1 || calls4[0].Function.Name != "direct_tool" { t.Errorf("Failed to detect direct JSON array: ok=%v, calls=%v", ok4, calls4) } } func TestWriteCompletionResponseToolCallsNilContent(t *testing.T) { rec := httptest.NewRecorder() idx := 0 calls := []ToolCall{ { Index: &idx, ID: "call_test", Type: "function", Function: ToolCallFunction{ Name: "test_func", Arguments: `{}`, }, }, } WriteCompletionResponse(rec, "cmpl-1", 123456, "qwen/qwen3.6-27b", "Some intermediate thought", "", calls, "tool_calls") body := rec.Body.String() if !strings.Contains(body, `"content":null`) { t.Errorf("Expected content:null when tool_calls present, got:\n%s", body) } if !strings.Contains(body, `"reasoning_content":"Some intermediate thought"`) { t.Errorf("Expected preamble text to be preserved in reasoning_content, got:\n%s", body) } if !strings.Contains(body, `"finish_reason":"tool_calls"`) { t.Errorf("Expected finish_reason tool_calls, got:\n%s", body) } } func TestIsStaticUIMarkdown(t *testing.T) { cases := []struct { text string prompt string expected bool }{ {"Model: qwen/qwen3.6-27b", "hello", true}, {"Model: openai/gpt-oss-20b", "hello", true}, {"Model : deepseek-r1-distill-llama-70b", "hello", true}, {"- **Model ID:** `qwen/qwen3.6-27b`", "hello", true}, {"[Powered by Groq](https://groq.com)", "hello", true}, {"Partial response: generation did not finish.", "hello", true}, {"[Notice] **Important:** Do not rely on AI without checking", "hello", true}, {"Important: Do not rely on AI without checking", "hello", true}, {"3 older messages were left out of this request.", "hello", true}, {"Here is the solution to your question.", "hello", false}, {"thinkingResult: 42", "hello", false}, {"{\"name\":\"calc\"}", "hello", false}, } for _, c := range cases { got := isStaticUIMarkdown(c.text, c.prompt) if got != c.expected { t.Errorf("isStaticUIMarkdown(%q) = %v; expected %v", c.text, got, c.expected) } } } func TestFormatPromptCorruptedCaptionFiltering(t *testing.T) { req := ChatCompletionRequest{ Model: "qwen/qwen3.6-27b", Messages: []ChatMessage{ {Role: "user", Content: "Hello"}, {Role: "assistant", Content: "Model: qwen/qwen3.6-27b"}, {Role: "user", Content: "What is 2+2?"}, }, } prompt := FormatPrompt(req) if strings.Contains(prompt, "Model: qwen/qwen3.6-27b") { t.Errorf("Expected corrupted Model: caption to be filtered out of history, got:\n%s", prompt) } if !strings.Contains(prompt, "What is 2+2?") { t.Errorf("Expected user question to remain, got:\n%s", prompt) } } func TestFormatPromptHistoryPruning(t *testing.T) { var messages []ChatMessage messages = append(messages, ChatMessage{Role: "user", Content: "Initial user query to remember"}) // Add 30 turns of long conversation for i := 1; i <= 30; i++ { messages = append(messages, ChatMessage{Role: "assistant", Content: strings.Repeat("Long assistant response ", 30)}) messages = append(messages, ChatMessage{Role: "user", Content: strings.Repeat("Long user question ", 30)}) } messages = append(messages, ChatMessage{Role: "user", Content: "Final question"}) req := ChatCompletionRequest{ Model: "qwen/qwen3.6-27b", Messages: messages, } prompt := FormatPrompt(req) // Verify initial user query is preserved if !strings.Contains(prompt, "Initial user query to remember") { t.Errorf("Expected prompt to preserve initial user query") } // Verify truncation marker is present if !strings.Contains(prompt, "[... earlier conversation turns omitted for brevity ...]") { t.Errorf("Expected prompt to contain omission notice") } // Verify prompt size is kept bounded if len(prompt) > 20000 { t.Errorf("Prompt size too large after pruning: %d chars", len(prompt)) } } func TestParseRetryAfterAndRateLimit(t *testing.T) { alert1 := "Rate limit reached. Please retry in a few seconds. Retry after 10 seconds." if !isRateLimit(alert1) { t.Errorf("Expected alert1 to be identified as rate limit") } if sec := parseRetryAfter(alert1); sec != 10 { t.Errorf("Expected retry seconds 10, got %d", sec) } alert2 := "429 Too Many Requests: retry in 5" if !isRateLimit(alert2) { t.Errorf("Expected alert2 to be identified as rate limit") } if sec := parseRetryAfter(alert2); sec != 5 { t.Errorf("Expected retry seconds 5, got %d", sec) } alert3 := "Your message or image is too large for this model." if isRateLimit(alert3) { t.Errorf("Expected alert3 NOT to be rate limit") } } func TestWriteAPIError(t *testing.T) { rec := httptest.NewRecorder() WriteAPIError(rec, 429, "Rate limit reached. Retry after 10 seconds.", "rate_limit_error", 429) if rec.Code != 429 { t.Errorf("Expected status 429, got %d", rec.Code) } body := rec.Body.String() if !strings.Contains(body, `"rate_limit_error"`) || !strings.Contains(body, `"code":429`) { t.Errorf("Unexpected error response body: %s", body) } } func TestDetectToolCallsWithUnregisteredTool(t *testing.T) { allowed := map[string]bool{"calculator": true} content := `Here is an example: {"name": "non_existing_tool", "arguments": {"foo": "bar"}} Do not execute it.` calls, rem, ok := DetectToolCalls(content, allowed) if ok || len(calls) > 0 { t.Errorf("Expected unregistered tool to not be detected as tool call, got ok=%v, calls=%v", ok, calls) } if !strings.Contains(rem, "non_existing_tool") { t.Errorf("Expected remaining text to keep unregistered tool text intact, got:\n%s", rem) } } func TestDetectToolCallsWithEmptyAllowedTools(t *testing.T) { content := ` {"name": "calculator", "arguments": {"expr": "1+1"}} ` calls, rem, ok := DetectToolCalls(content, nil) if ok || len(calls) > 0 { t.Errorf("Expected tool calls to be disabled when allowedTools is nil, got ok=%v, calls=%v", ok, calls) } if rem != content { t.Errorf("Expected content to remain unchanged when allowedTools is nil") } } func TestDetectToolCallsWithUnclosedTag(t *testing.T) { allowed := map[string]bool{"calculator": true} content := "Please explain what means in your prompt. My name is Alice." calls, rem, ok := DetectToolCalls(content, allowed) if ok || len(calls) > 0 { t.Errorf("Expected unclosed tag to not be detected as tool call, got ok=%v, calls=%v", ok, calls) } if rem != content { t.Errorf("Expected content to remain completely unchanged, got:\n%s", rem) } } func TestDetectToolCallsPartiallyValid(t *testing.T) { allowed := map[string]bool{"calculator": true} content := ` {"name": "calculator", "arguments": {"expr": "2+2"}} And here is a fake tool: {"name": "fake_tool", "arguments": {}} ` calls, rem, ok := DetectToolCalls(content, allowed) if !ok || len(calls) != 1 { t.Fatalf("Expected 1 valid tool call, got ok=%v, calls=%v", ok, calls) } if calls[0].Function.Name != "calculator" { t.Errorf("Expected tool name calculator, got %s", calls[0].Function.Name) } if !strings.Contains(rem, "fake_tool") { t.Errorf("Expected remaining content to keep fake_tool block, got:\n%s", rem) } if strings.Contains(rem, "calculator") { t.Errorf("Expected valid calculator block to be removed from remaining content, got:\n%s", rem) } } func TestStreamToolCallFilterNonExistingTool(t *testing.T) { allowed := map[string]bool{"calculator": true} filter := NewStreamToolCallFilter(allowed) var contentParts []string var reasoningParts []string var toolCalls []ToolCall onContent := func(s string) { contentParts = append(contentParts, s) } onReasoning := func(s string) { reasoningParts = append(reasoningParts, s) } onToolCall := func(tc ToolCall) { toolCalls = append(toolCalls, tc) } chunks := []string{ "Here is ", "an example:\n", "\n", "{\"name\": \"unknown_tool\", ", "\"arguments\": {}}\n", "\n", "Done.", } for _, chunk := range chunks { filter.Feed(chunk, onContent, onReasoning, onToolCall) } filter.Flush(onContent, onReasoning, onToolCall) if len(toolCalls) > 0 { t.Errorf("Expected zero tool calls emitted for unknown tool, got %d", len(toolCalls)) } if filter.emittedCall { t.Errorf("Expected emittedCall to be false") } fullContent := strings.Join(contentParts, "") if !strings.Contains(fullContent, "unknown_tool") { t.Errorf("Expected fullContent to contain unknown_tool block, got:\n%s", fullContent) } if !strings.Contains(fullContent, "Done.") { t.Errorf("Expected fullContent to contain Done., got:\n%s", fullContent) } } func TestStreamToolCallFilterEmptyAllowedTools(t *testing.T) { filter := NewStreamToolCallFilter(nil) var contentParts []string var toolCalls []ToolCall onContent := func(s string) { contentParts = append(contentParts, s) } onReasoning := func(s string) {} onToolCall := func(tc ToolCall) { toolCalls = append(toolCalls, tc) } chunks := []string{"", `{"name":"calc"}`, ""} for _, ch := range chunks { filter.Feed(ch, onContent, onReasoning, onToolCall) } filter.Flush(onContent, onReasoning, onToolCall) if len(toolCalls) > 0 { t.Errorf("Expected zero tool calls when allowedTools is nil") } fullContent := strings.Join(contentParts, "") if fullContent != "{\"name\":\"calc\"}" { t.Errorf("Expected direct pass-through, got:\n%s", fullContent) } } func TestStreamToolCallFilterValidTool(t *testing.T) { allowed := map[string]bool{"calculator": true} filter := NewStreamToolCallFilter(allowed) var contentParts []string var reasoningParts []string var toolCalls []ToolCall onContent := func(s string) { contentParts = append(contentParts, s) } onReasoning := func(s string) { reasoningParts = append(reasoningParts, s) } onToolCall := func(tc ToolCall) { toolCalls = append(toolCalls, tc) } filter.Feed("Let me calculate that for you.\n", onContent, onReasoning, onToolCall) filter.Feed("\n{\"name\": \"calculator\", \"arguments\": {\"expr\": \"5*5\"}}\n", onContent, onReasoning, onToolCall) filter.Flush(onContent, onReasoning, onToolCall) if len(toolCalls) != 1 { t.Fatalf("Expected 1 tool call, got %d", len(toolCalls)) } if toolCalls[0].Function.Name != "calculator" { t.Errorf("Expected tool name calculator, got %s", toolCalls[0].Function.Name) } if !filter.emittedCall { t.Errorf("Expected emittedCall to be true") } fullReasoning := strings.Join(reasoningParts, "") if !strings.Contains(fullReasoning, "Let me calculate that") { t.Errorf("Expected preamble text in reasoning, got:\n%s", fullReasoning) } } func TestGetAllowedToolNames(t *testing.T) { tools := []Tool{ { Type: "function", Function: map[string]interface{}{ "name": "search", }, }, { Type: "function", Function: ToolCallFunction{ Name: "calculator", }, }, } // Auto choice m := GetAllowedToolNames(tools, "auto") if !m["search"] || !m["calculator"] || len(m) != 2 { t.Errorf("Expected both tools allowed for auto, got %v", m) } // None choice mNone := GetAllowedToolNames(tools, "none") if mNone != nil { t.Errorf("Expected nil for tool_choice none, got %v", mNone) } // Specific choice mSpecific := GetAllowedToolNames(tools, map[string]interface{}{ "type": "function", "function": map[string]interface{}{ "name": "calculator", }, }) if !mSpecific["calculator"] || mSpecific["search"] || len(mSpecific) != 1 { t.Errorf("Expected only calculator allowed, got %v", mSpecific) } // Empty tools if GetAllowedToolNames(nil, "auto") != nil { t.Errorf("Expected nil for nil tools") } } func TestFormatPromptAssistantDeduplication(t *testing.T) { req := ChatCompletionRequest{ Model: "qwen/qwen3.6-27b", Tools: []Tool{ {Type: "function", Function: map[string]interface{}{"name": "calc"}}, }, Messages: []ChatMessage{ {Role: "user", Content: "Calculate 2+2"}, { Role: "assistant", Content: `{"name": "calc", "arguments": {"expr": "2+2"}}`, ToolCalls: []ToolCall{ { ID: "call_1", Type: "function", Function: ToolCallFunction{ Name: "calc", Arguments: `{"expr": "2+2"}`, }, }, }, }, {Role: "tool", ToolCallID: "call_1", Content: "4"}, }, } prompt := FormatPrompt(req) // Verify there is only one block in the Assistant turn assistIdx := strings.Index(prompt, "Assistant:") if assistIdx == -1 { t.Fatalf("Expected Assistant turn in prompt, got:\n%s", prompt) } toolExecIdx := strings.Index(prompt, "[Tool Execution Results]") assistSection := prompt[assistIdx:toolExecIdx] count := strings.Count(assistSection, "") if count != 1 { t.Errorf("Expected exactly 1 block in Assistant turn, got %d:\n%s", count, assistSection) } } func TestFormatPromptUserMessageWithToolCall(t *testing.T) { req := ChatCompletionRequest{ Model: "qwen/qwen3.6-27b", Messages: []ChatMessage{ {Role: "user", Content: "How do I format a tag in my script?"}, }, } prompt := FormatPrompt(req) if !strings.Contains(prompt, "How do I format a tag in my script?") { t.Errorf("Expected user prompt to preserve text, got:\n%s", prompt) } }