package renderers import ( "encoding/json" "os" "os/exec" "strings" "testing" "github.com/google/go-cmp/cmp" "github.com/ollama/ollama/api" ) const ( lagunaDirectDirective = "You should respond directly without using chain-of-thought reasoning tags." lagunaThinkDirective = "You should use chain-of-thought reasoning. Put your reasoning inside tags before your response." ) func TestLagunaRendererReferenceFlowCoverage(t *testing.T) { weather := lagunaWeatherTool() tests := []struct { name string messages []api.Message tools []api.Tool think *api.ThinkValue want string }{ { name: "user_only_thinking_default_on", messages: []api.Message{{Role: "user", Content: "Hello"}}, want: "" + "〈|EOS|〉\n" + lagunaThinkDirective + "\n\n" + "\nHello\n\n" + "\n", }, { name: "user_only_thinking_enabled", messages: []api.Message{{Role: "user", Content: "Hello"}}, think: &api.ThinkValue{Value: true}, want: "" + "〈|EOS|〉\n" + lagunaThinkDirective + "\n\n" + "\nHello\n\n" + "\n", }, { name: "user_only_thinking_disabled", messages: []api.Message{{Role: "user", Content: "Hello"}}, think: &api.ThinkValue{Value: false}, want: "" + "〈|EOS|〉\n" + lagunaDirectDirective + "\n\n" + "\nHello\n\n" + "\n", }, { name: "first_system_is_header", messages: []api.Message{ {Role: "system", Content: "Stay concise.\n\n"}, {Role: "user", Content: "Hi"}, }, want: "" + "〈|EOS|〉\n" + lagunaThinkDirective + "\nStay concise." + "\n\n" + "\nHi\n\n" + "\n", }, { name: "additional_system_message_renders_in_loop", messages: []api.Message{ {Role: "system", Content: "Primary."}, {Role: "user", Content: "Hi"}, {Role: "system", Content: "Secondary."}, }, want: "" + "〈|EOS|〉\n" + lagunaThinkDirective + "\nPrimary." + "\n\n" + "\nHi\n\n" + "\nSecondary.\n\n" + "\n", }, { name: "tools_in_header", messages: []api.Message{ {Role: "system", Content: "Stay concise."}, {Role: "user", Content: "Weather?"}, }, tools: weather, think: &api.ThinkValue{Value: true}, want: "" + "〈|EOS|〉\n" + lagunaThinkDirective + "\nStay concise." + "\n\n### Tools\n\n" + "You may call functions to assist with the user query.\n" + "All available function signatures are listed below:\n" + "\n" + `{"type": "function", "function": {"name": "get_weather", "description": "Get weather", "parameters": {"type": "object", "required": ["location"], "properties": {"location": {"type": "string", "description": "City"}}}}}` + "\n" + "\n\n" + "For each function call, return a json object with function name and arguments within '' and '' tags:\n" + "\n{\"name\": , \"arguments\": }\n" + "\n\n" + "\nWeather?\n\n" + "\n", }, { name: "tools_default_thinking_on_when_unspecified", messages: []api.Message{ {Role: "user", Content: "Weather?"}, }, tools: weather, want: "" + "〈|EOS|〉\n" + lagunaThinkDirective + "\n\n### Tools\n\n" + "You may call functions to assist with the user query.\n" + "All available function signatures are listed below:\n" + "\n" + `{"type": "function", "function": {"name": "get_weather", "description": "Get weather", "parameters": {"type": "object", "required": ["location"], "properties": {"location": {"type": "string", "description": "City"}}}}}` + "\n" + "\n\n" + "For each function call, return a json object with function name and arguments within '' and '' tags:\n" + "\n{\"name\": , \"arguments\": }\n" + "\n\n" + "\nWeather?\n\n" + "\n", }, { name: "assistant_history_with_thinking_content_tool_and_response", messages: []api.Message{ {Role: "user", Content: "Add these."}, { Role: "assistant", Content: "\nCalling the tool.\n", Thinking: "Need addition.", ToolCalls: []api.ToolCall{{ Function: api.ToolCallFunction{ Name: "add", Arguments: testArgsOrdered([]orderedArg{ {Key: "a", Value: 2}, {Key: "b", Value: 3}, }), }, }}, }, {Role: "tool", Content: "5"}, {Role: "user", Content: "Thanks"}, }, think: &api.ThinkValue{Value: true}, want: "" + "〈|EOS|〉\n" + lagunaThinkDirective + "\n\n" + "\nAdd these.\n\n" + "\n" + "Need addition.\n" + "Calling the tool.\n" + "add\n" + "a\n2\n" + "b\n3\n" + "\n" + "\n" + "\n5\n\n" + "\nThanks\n\n" + "\n", }, { name: "final_assistant_prefill_is_continued", messages: []api.Message{ {Role: "user", Content: "Complete this"}, {Role: "assistant", Content: "Partial"}, }, want: "" + "〈|EOS|〉\n" + lagunaThinkDirective + "\n\n" + "\nComplete this\n\n" + "\nPartial\n", }, } renderer := &LagunaRenderer{} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := renderer.Render(tt.messages, tt.tools, tt.think) if err != nil { t.Fatal(err) } if diff := cmp.Diff(tt.want, got); diff != "" { t.Fatalf("renderer output mismatch (-want +got):\n%s", diff) } }) } } func TestLagunaRendererMatchesLocalJinjaControlFlow(t *testing.T) { if os.Getenv("VERIFY_LAGUNA_JINJA2") == "" { t.Skip("set VERIFY_LAGUNA_JINJA2=1 to compare against the local Laguna chat_template.jinja") } python := "/Users/daniel/.codex/worktrees/7038/ollama/.venv/bin/python3" if _, err := os.Stat(python); err != nil { t.Fatalf("VERIFY_LAGUNA_JINJA2 requires %s with jinja2 installed", python) } tests := []struct { name string messages []api.Message think *api.ThinkValue }{ { name: "user_only", messages: []api.Message{{Role: "user", Content: "Hello"}}, }, { name: "system_user", messages: []api.Message{ {Role: "system", Content: "Stay concise.\n"}, {Role: "user", Content: "Hello"}, }, }, { name: "additional_system_and_tool_response", messages: []api.Message{ {Role: "system", Content: "Primary."}, {Role: "user", Content: "Weather?"}, {Role: "assistant", Content: "Calling."}, {Role: "tool", Content: "Sunny"}, {Role: "system", Content: "Secondary."}, }, }, { name: "thinking_enabled", messages: []api.Message{{Role: "user", Content: "Think briefly."}}, think: &api.ThinkValue{Value: true}, }, { name: "thinking_disabled", messages: []api.Message{{Role: "user", Content: "Answer directly."}}, think: &api.ThinkValue{Value: false}, }, } renderer := &LagunaRenderer{} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := renderer.Render(tt.messages, nil, tt.think) if err != nil { t.Fatal(err) } for _, modelDir := range []string{ "/Users/daniel/Models/poolside/laguna-xs-23-04-2026", } { want := renderLagunaChatTemplate(t, python, modelDir, tt.messages, tt.think) if diff := cmp.Diff(want, got); diff != "" { t.Fatalf("%s mismatch (-chat_template +renderer):\n%s", modelDir, diff) } } }) } } func renderLagunaChatTemplate(t *testing.T, python, modelDir string, messages []api.Message, think *api.ThinkValue) string { t.Helper() type templateMessage struct { Role string `json:"role"` Content string `json:"content"` } templateMessages := make([]templateMessage, 0, len(messages)) for _, msg := range messages { templateMessages = append(templateMessages, templateMessage{ Role: msg.Role, Content: msg.Content, }) } messagesJSON, err := json.Marshal(templateMessages) if err != nil { t.Fatalf("failed to marshal messages: %v", err) } enableThinking := "True" if think != nil && !think.Bool() { enableThinking = "False" } script := ` import json import sys from transformers import AutoTokenizer model_dir = sys.argv[1] messages = json.loads(sys.argv[2]) enable_thinking = sys.argv[3] == "True" tok = AutoTokenizer.from_pretrained(model_dir, trust_remote_code=True) print(tok.apply_chat_template( messages, tokenize=False, add_generation_prompt=True, enable_thinking=enable_thinking, ), end="") ` cmd := exec.Command(python, "-c", script, modelDir, string(messagesJSON), enableThinking) var stdout, stderr strings.Builder cmd.Stdout = &stdout cmd.Stderr = &stderr if err := cmd.Run(); err != nil { t.Fatalf("chat_template render failed: %v\nstderr: %s", err, stderr.String()) } return stdout.String() } func lagunaWeatherTool() []api.Tool { return []api.Tool{{ Type: "function", Function: api.ToolFunction{ Name: "get_weather", Description: "Get weather", Parameters: api.ToolFunctionParameters{ Type: "object", Required: []string{"location"}, Properties: testPropsOrdered([]orderedProp{{ Key: "location", Value: api.ToolProperty{ Type: api.PropertyType{"string"}, Description: "City", }, }}), }, }, }} }