diff --git a/model/parsers/laguna.go b/model/parsers/laguna.go
index c97ba34fd..29ce5c398 100644
--- a/model/parsers/laguna.go
+++ b/model/parsers/laguna.go
@@ -39,6 +39,7 @@ type LagunaParser struct {
thinkingEnabled bool
thinkingSuppressed bool
allowLeadingThinkOpen bool
+ atContentStart bool
}
func (p *LagunaParser) HasToolSupport() bool {
@@ -68,10 +69,28 @@ func (p *LagunaParser) Init(tools []api.Tool, lastMessage *api.Message, thinkVal
p.tools = tools
p.callIndex = 0
p.buffer.Reset()
- p.thinkingEnabled = thinkValue == nil || thinkValue.Bool()
- p.thinkingSuppressed = thinkValue != nil && !thinkValue.Bool()
- p.state = lagunaParserStateContent
- p.allowLeadingThinkOpen = false
+ // The prompt primes the reasoning mode: when thinking is enabled
+ // (so the model's output begins with reasoning and no opening tag), or
+ // otherwise. Thinking defaults off, matching the chat template.
+ p.thinkingEnabled = thinkValue != nil && thinkValue.Bool()
+ p.thinkingSuppressed = !p.thinkingEnabled
+ p.atContentStart = true
+
+ // When the request ends with an assistant prefill, the renderer continues
+ // that turn in place instead of emitting a fresh generation prompt: it has
+ // already written the closing , so the model resumes with content
+ // (or a tool call), not reasoning. Start in the content state even when
+ // thinking is enabled, otherwise the continuation would be reported as
+ // thinking until done and clients would receive an empty answer.
+ assistantPrefill := lastMessage != nil && lastMessage.Role == "assistant"
+
+ if p.thinkingEnabled && !assistantPrefill {
+ p.state = lagunaParserStateThinking
+ p.allowLeadingThinkOpen = true
+ } else {
+ p.state = lagunaParserStateContent
+ p.allowLeadingThinkOpen = false
+ }
return tools
}
@@ -118,19 +137,28 @@ func (p *LagunaParser) consumeThinking(done bool) (bool, string) {
if p.allowLeadingThinkOpen {
trimmed := strings.TrimLeftFunc(acc, unicode.IsSpace)
if strings.HasPrefix(trimmed, lagunaThinkingOpenTag) {
+ // the model echoed the primed ; drop it
p.buffer.Reset()
p.buffer.WriteString(strings.TrimLeftFunc(strings.TrimPrefix(trimmed, lagunaThinkingOpenTag), unicode.IsSpace))
p.allowLeadingThinkOpen = false
return true, ""
}
if strings.HasPrefix(lagunaThinkingOpenTag, trimmed) && !done {
+ // possibly a partial opening tag; keep it (minus leading space) and wait
+ p.buffer.Reset()
+ p.buffer.WriteString(trimmed)
return false, ""
}
+ // reasoning begins here: drop the leading whitespace the model emits
+ // after the primed (its trained format is "\n…").
+ p.buffer.Reset()
+ p.buffer.WriteString(trimmed)
p.allowLeadingThinkOpen = false
+ acc = trimmed
}
if idx := strings.Index(acc, lagunaThinkingCloseTag); idx != -1 {
- thinking := acc[:idx]
+ thinking := strings.TrimRightFunc(acc[:idx], unicode.IsSpace)
after := strings.TrimLeftFunc(acc[idx+len(lagunaThinkingCloseTag):], unicode.IsSpace)
p.buffer.Reset()
p.buffer.WriteString(after)
@@ -148,6 +176,7 @@ func (p *LagunaParser) consumeThinking(done bool) (bool, string) {
if done {
p.buffer.Reset()
p.state = lagunaParserStateContent
+ acc = strings.TrimRightFunc(acc, unicode.IsSpace)
return acc != "", acc
}
@@ -164,6 +193,17 @@ func (p *LagunaParser) consumeThinking(done bool) (bool, string) {
}
func (p *LagunaParser) consumeContent(done bool) (bool, string, []api.ToolCall, error) {
+ if p.atContentStart {
+ // Drop the leading whitespace the model emits before content (its trained
+ // format puts a newline after the primed/closed ).
+ trimmed := strings.TrimLeftFunc(p.buffer.String(), unicode.IsSpace)
+ p.buffer.Reset()
+ p.buffer.WriteString(trimmed)
+ if trimmed == "" {
+ return false, "", nil, nil
+ }
+ p.atContentStart = false
+ }
acc := p.buffer.String()
if p.thinkingEnabled || p.thinkingSuppressed {
if idx := strings.Index(acc, lagunaThinkingOpenTag); idx != -1 {
@@ -248,6 +288,7 @@ func (p *LagunaParser) consumeContent(done bool) (bool, string, []api.ToolCall,
}
if done {
p.buffer.Reset()
+ acc = strings.TrimRightFunc(acc, unicode.IsSpace)
return acc != "", acc, nil, nil
}
overlapLen := max(overlap(acc, lagunaToolCallOpenTag), overlap(acc, lagunaUserOpenTag))
diff --git a/model/parsers/laguna_test.go b/model/parsers/laguna_test.go
index 73fb42487..8112b1641 100644
--- a/model/parsers/laguna_test.go
+++ b/model/parsers/laguna_test.go
@@ -375,30 +375,32 @@ func TestLagunaParserUserTaggedNonToolContent(t *testing.T) {
}
}
-func TestLagunaParserThinkingDefaultsOn(t *testing.T) {
+func TestLagunaParserThinkingDefaultsOff(t *testing.T) {
+ // Thinking defaults off (matching the chat template); an emitted
+ // block is suppressed rather than surfaced as reasoning.
parser := ParserForName("laguna")
parser.Init(nil, nil, nil)
content, thinking, calls, err := parser.Add("Need to reason.\nDirect answer.", true)
if err != nil {
t.Fatal(err)
}
- if content != "Direct answer." || thinking != "Need to reason." || len(calls) != 0 {
+ if content != "Direct answer." || thinking != "" || len(calls) != 0 {
t.Fatalf("content=%q thinking=%q calls=%d", content, thinking, len(calls))
}
}
-func TestLagunaParserThinkingDefaultsOnWhenToolsPresent(t *testing.T) {
+func TestLagunaParserThinkingDefaultsOffWhenToolsPresent(t *testing.T) {
parser := ParserForName("laguna")
parser.Init(lagunaTestTools(), nil, nil)
content, thinking, calls, err := parser.Add("Need to reason.\nget_weather\nlocation\nParis\n", true)
if err != nil {
t.Fatal(err)
}
- if thinking != "Need to reason." || len(calls) != 1 {
+ if thinking != "" || len(calls) != 1 {
t.Fatalf("content=%q thinking=%q calls=%d", content, thinking, len(calls))
}
if content != "" {
- t.Fatalf("content=%q, want thinking block suppressed from content when default thinking is enabled", content)
+ t.Fatalf("content=%q, want the suppressed think block and the tool call both removed from content", content)
}
}
@@ -421,7 +423,7 @@ func TestLagunaParserThinkingExplicitlyDisabledDropsLeadingCloseTag(t *testing.T
if err != nil {
t.Fatal(err)
}
- if content != "Tokyo\n" || thinking != "" || len(calls) != 0 {
+ if content != "Tokyo" || thinking != "" || len(calls) != 0 {
t.Fatalf("content=%q thinking=%q calls=%d", content, thinking, len(calls))
}
}
@@ -433,31 +435,122 @@ func TestLagunaParserThinkingEnabledDropsLeadingCloseTag(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- if content != "Tokyo\n" || thinking != "" || len(calls) != 0 {
+ if content != "Tokyo" || thinking != "" || len(calls) != 0 {
t.Fatalf("content=%q thinking=%q calls=%d", content, thinking, len(calls))
}
}
-func TestLagunaParserThinkingDefaultOnDropsLeadingCloseTag(t *testing.T) {
+func TestLagunaParserThinkingDefaultOffDropsLeadingCloseTag(t *testing.T) {
parser := ParserForName("laguna")
parser.Init(nil, nil, nil)
content, thinking, calls, err := parser.Add("\nTokyo\n", true)
if err != nil {
t.Fatal(err)
}
- if content != "Tokyo\n" || thinking != "" || len(calls) != 0 {
+ if content != "Tokyo" || thinking != "" || len(calls) != 0 {
t.Fatalf("content=%q thinking=%q calls=%d", content, thinking, len(calls))
}
}
-func TestLagunaParserThinkingEnabledUntaggedAnswerIsContent(t *testing.T) {
+func TestLagunaParserThinkingEnabledUntaggedAnswerIsThinking(t *testing.T) {
+ // With thinking enabled the prompt primes , so the model's output
+ // begins as reasoning even without an opening tag.
parser := ParserForName("laguna")
parser.Init(nil, nil, &api.ThinkValue{Value: true})
content, thinking, calls, err := parser.Add("Direct answer.", true)
if err != nil {
t.Fatal(err)
}
- if content != "Direct answer." || thinking != "" || len(calls) != 0 {
+ if content != "" || thinking != "Direct answer." || len(calls) != 0 {
+ t.Fatalf("content=%q thinking=%q calls=%d", content, thinking, len(calls))
+ }
+}
+
+func TestLagunaParserAssistantPrefillContinuesAsContent(t *testing.T) {
+ // With thinking enabled but the request ending in an assistant prefill, the
+ // renderer continues the turn in place after a closed , so the
+ // untagged continuation is content, not thinking. (Regression: previously
+ // Init ignored lastMessage and reported it all as thinking, leaving the
+ // client with an empty answer.)
+ parser := ParserForName("laguna")
+ parser.Init(nil, &api.Message{Role: "assistant", Content: "The answer is"}, &api.ThinkValue{Value: true})
+ content, thinking, calls, err := parser.Add(" 42.", true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if content != "42." || thinking != "" || len(calls) != 0 {
+ t.Fatalf("content=%q thinking=%q calls=%d", content, thinking, len(calls))
+ }
+}
+
+func TestLagunaParserAssistantPrefillContinuesAsToolCall(t *testing.T) {
+ // An assistant prefill with thinking enabled resumes past the closed
+ // , so a tool call emitted by the continuation is parsed from the
+ // content state rather than being swallowed as thinking.
+ parser := ParserForName("laguna")
+ parser.Init(lagunaTestTools(), &api.Message{Role: "assistant", Thinking: "earlier reasoning"}, &api.ThinkValue{Value: true})
+ content, thinking, calls, err := parser.Add("get_weather\nlocation\nParis\n", true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if content != "" || thinking != "" || len(calls) != 1 {
+ t.Fatalf("content=%q thinking=%q calls=%d", content, thinking, len(calls))
+ }
+ if calls[0].Function.Name != "get_weather" {
+ t.Fatalf("name=%q, want get_weather", calls[0].Function.Name)
+ }
+}
+
+func TestLagunaParserNonAssistantLastMessageStillPrimesThinking(t *testing.T) {
+ // A trailing user message is not a prefill: the renderer primes , so
+ // the untagged continuation is still reasoning.
+ parser := ParserForName("laguna")
+ parser.Init(nil, &api.Message{Role: "user", Content: "hi"}, &api.ThinkValue{Value: true})
+ content, thinking, calls, err := parser.Add("Direct answer.", true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if content != "" || thinking != "Direct answer." || len(calls) != 0 {
+ t.Fatalf("content=%q thinking=%q calls=%d", content, thinking, len(calls))
+ }
+}
+
+func TestLagunaParserStripsLeadingContentWhitespace(t *testing.T) {
+ // No-think prompts prime , so the model emits a leading newline
+ // before content; the parser drops it.
+ parser := ParserForName("laguna")
+ parser.Init(nil, nil, &api.ThinkValue{Value: false})
+ content, thinking, calls, err := parser.Add("\nHello! How can I help?", true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if content != "Hello! How can I help?" || thinking != "" || len(calls) != 0 {
+ t.Fatalf("content=%q thinking=%q calls=%d", content, thinking, len(calls))
+ }
+}
+
+func TestLagunaParserStripsTrailingContentWhitespace(t *testing.T) {
+ parser := ParserForName("laguna")
+ parser.Init(nil, nil, &api.ThinkValue{Value: false})
+ content, _, _, err := parser.Add("Hello there.\n\n", true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if content != "Hello there." {
+ t.Fatalf("content=%q, want trailing whitespace stripped", content)
+ }
+}
+
+func TestLagunaParserPrimedThinkingThenContent(t *testing.T) {
+ // Thinking enabled: the prompt primes , so output is
+ // "\n{reasoning}\n\n{answer}" with no opening tag.
+ parser := ParserForName("laguna")
+ parser.Init(nil, nil, &api.ThinkValue{Value: true})
+ content, thinking, calls, err := parser.Add("\nReasoning here.\n\nThe answer.", true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if content != "The answer." || thinking != "Reasoning here." || len(calls) != 0 {
t.Fatalf("content=%q thinking=%q calls=%d", content, thinking, len(calls))
}
}
diff --git a/model/renderers/laguna.go b/model/renderers/laguna.go
index 42262c074..4a3b4604f 100644
--- a/model/renderers/laguna.go
+++ b/model/renderers/laguna.go
@@ -2,6 +2,7 @@ package renderers
import (
"strings"
+ "unicode"
"github.com/ollama/ollama/api"
)
@@ -10,6 +11,10 @@ const (
lagunaBOS = "〈|EOS|〉"
lagunaThoughtOpen = ""
lagunaThoughtClose = ""
+
+ // Default system message from the Laguna chat template, used when the
+ // request supplies no system message.
+ lagunaDefaultSystem = "You are a helpful, conversationally-fluent assistant made by Poolside. You are here to be helpful to users through natural language conversations."
)
type LagunaRenderer struct{}
@@ -22,40 +27,50 @@ func (r *LagunaRenderer) Render(messages []api.Message, tools []api.Tool, think
var sb strings.Builder
sb.WriteString(lagunaBOS)
- thinkingEnabled := think == nil || think.Bool()
- systemMessage := ""
+ // The template signals thinking through the generation-prompt token
+ // ( vs ), not through the system message. It defaults off.
+ thinkingEnabled := think != nil && think.Bool()
+
+ // ── header (system message) ──
+ // The template seeds a default system message and lets an explicit leading
+ // system message override it. The header is emitted whenever there is a
+ // system message or tools to advertise.
+ systemMessage := lagunaDefaultSystem
firstMessageIsSystem := len(messages) > 0 && messages[0].Role == "system"
if firstMessageIsSystem {
- systemMessage = strings.TrimRight(messages[0].Content, "\n")
+ systemMessage = messages[0].Content
}
- sb.WriteString("\n")
- if thinkingEnabled {
- sb.WriteString("You should use chain-of-thought reasoning. Put your reasoning inside tags before your response.")
- } else {
- sb.WriteString("You should respond directly without using chain-of-thought reasoning tags.")
- }
- if strings.TrimSpace(systemMessage) != "" {
- sb.WriteByte('\n')
- sb.WriteString(systemMessage)
- }
- if len(tools) > 0 {
- sb.WriteString("\n\n### Tools\n\n")
- sb.WriteString("You may call functions to assist with the user query.\n")
- sb.WriteString("All available function signatures are listed below:\n")
- sb.WriteString("\n")
- for _, tool := range tools {
- if b, err := marshalWithSpaces(tool); err == nil {
- sb.Write(b)
- sb.WriteByte('\n')
- }
+ if strings.TrimSpace(systemMessage) != "" || len(tools) > 0 {
+ sb.WriteString("\n")
+ if strings.TrimSpace(systemMessage) != "" {
+ sb.WriteByte('\n')
+ sb.WriteString(strings.TrimRightFunc(systemMessage, unicode.IsSpace))
}
- sb.WriteString("\n\n")
- sb.WriteString("For each function call, return a json object with function name and arguments within '' and '' tags:\n")
- sb.WriteString("\n{\"name\": , \"arguments\": }\n")
+ if len(tools) > 0 {
+ sb.WriteString("\n\n### Tools\n\n")
+ sb.WriteString("You may call functions to assist with the user query.\n")
+ sb.WriteString("All available function signatures are listed below:\n")
+ sb.WriteString("\n")
+ for _, tool := range tools {
+ if b, err := marshalWithSpaces(tool); err == nil {
+ sb.Write(b)
+ sb.WriteByte('\n')
+ }
+ }
+ sb.WriteString("\n\n")
+ if thinkingEnabled {
+ sb.WriteString("Wrap your thinking in '', '' tags, followed by a function call. For each function call, return an unescaped XML-like object with function name and arguments within '' and '' tags, like here:\n")
+ sb.WriteString(" your thoughts here \n")
+ } else {
+ sb.WriteString("For each function call, return an unescaped XML-like object with function name and arguments within '' and '' tags, like here:\n")
+ }
+ sb.WriteString("function-name\nargument-key\nvalue-of-argument-key\n")
+ }
+ sb.WriteString("\n\n")
}
- sb.WriteString("\n\n")
+ // ── main loop ──
for i, message := range messages {
if i == 0 && firstMessageIsSystem {
continue
@@ -68,18 +83,26 @@ func (r *LagunaRenderer) Render(messages []api.Message, tools []api.Tool, think
sb.WriteString("\n\n")
case "assistant":
lastMessage := i == len(messages)-1
- prefill := lastMessage && (content != "" || message.Thinking != "" || len(message.ToolCalls) > 0)
+ prefill := lastMessage && (strings.TrimSpace(content) != "" || strings.TrimSpace(message.Thinking) != "" || len(message.ToolCalls) > 0)
+
sb.WriteString("\n")
- if thinkingEnabled && message.Thinking != "" {
- sb.WriteString(lagunaThoughtOpen)
- sb.WriteString(message.Thinking)
- sb.WriteString(lagunaThoughtClose)
- sb.WriteByte('\n')
- }
- if strings.Trim(content, "\n") != "" {
- sb.WriteString(strings.Trim(content, "\n"))
+
+ // Every assistant turn opens with the reasoning block: a full
+ // … when there is reasoning, otherwise a bare
+ // marking the turn as direct.
+ if reasoning := strings.TrimSpace(message.Thinking); reasoning != "" {
+ sb.WriteString("\n")
+ sb.WriteString(reasoning)
+ sb.WriteString("\n\n")
+ } else {
+ sb.WriteString("\n")
+ }
+
+ if strings.TrimSpace(content) != "" {
+ sb.WriteString(strings.TrimSpace(content))
sb.WriteByte('\n')
}
+
for _, toolCall := range message.ToolCalls {
sb.WriteString("")
sb.WriteString(toolCall.Function.Name)
@@ -94,6 +117,7 @@ func (r *LagunaRenderer) Render(messages []api.Message, tools []api.Tool, think
}
sb.WriteString("\n")
}
+
if !prefill {
sb.WriteString("\n")
}
@@ -108,8 +132,17 @@ func (r *LagunaRenderer) Render(messages []api.Message, tools []api.Tool, think
}
}
+ // ── generation prompt ──
+ // Continue an assistant prefill in place; otherwise open a fresh assistant
+ // turn and prime the reasoning mode ( when thinking, else ).
if len(messages) == 0 || messages[len(messages)-1].Role != "assistant" {
sb.WriteString("\n")
+ if thinkingEnabled {
+ sb.WriteString(lagunaThoughtOpen)
+ } else {
+ sb.WriteString(lagunaThoughtClose)
+ }
}
+
return sb.String(), nil
}
diff --git a/model/renderers/laguna_test.go b/model/renderers/laguna_test.go
index e5cecfae7..44ad03a83 100644
--- a/model/renderers/laguna_test.go
+++ b/model/renderers/laguna_test.go
@@ -11,13 +11,20 @@ import (
"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."
-)
+// lagunaToolJSON is the get_weather tool as serialized into ,
+// matching lagunaWeatherTool().
+const lagunaToolJSON = `{"type": "function", "function": {"name": "get_weather", "description": "Get weather", "parameters": {"type": "object", "required": ["location"], "properties": {"location": {"type": "string", "description": "City"}}}}}`
+// TestLagunaRendererReferenceFlowCoverage checks the renderer against the Laguna
+// chat template. Each want is byte-for-byte template output (verified by
+// rendering chat_template.jinja), except that history tool-calls use the clean
+// form — the template leaks Jinja indentation there.
func TestLagunaRendererReferenceFlowCoverage(t *testing.T) {
weather := lagunaWeatherTool()
+ think := func(v bool) *api.ThinkValue { return &api.ThinkValue{Value: v} }
+
+ // system header is always emitted; with no system message the default is used
+ defaultHeader := "〈|EOS|〉\n\n" + lagunaDefaultSystem + "\n\n"
tests := []struct {
name string
@@ -27,36 +34,21 @@ func TestLagunaRendererReferenceFlowCoverage(t *testing.T) {
want string
}{
{
- name: "user_only_thinking_default_on",
+ name: "user_only_default",
messages: []api.Message{{Role: "user", Content: "Hello"}},
- want: "" +
- "〈|EOS|〉\n" +
- lagunaThinkDirective +
- "\n\n" +
- "\nHello\n\n" +
- "\n",
+ want: defaultHeader + "\nHello\n\n\n",
},
{
- name: "user_only_thinking_enabled",
+ name: "user_only_think",
messages: []api.Message{{Role: "user", Content: "Hello"}},
- think: &api.ThinkValue{Value: true},
- want: "" +
- "〈|EOS|〉\n" +
- lagunaThinkDirective +
- "\n\n" +
- "\nHello\n\n" +
- "\n",
+ think: think(true),
+ want: defaultHeader + "\nHello\n\n\n",
},
{
- name: "user_only_thinking_disabled",
+ name: "user_only_nothink",
messages: []api.Message{{Role: "user", Content: "Hello"}},
- think: &api.ThinkValue{Value: false},
- want: "" +
- "〈|EOS|〉\n" +
- lagunaDirectDirective +
- "\n\n" +
- "\nHello\n\n" +
- "\n",
+ think: think(false),
+ want: defaultHeader + "\nHello\n\n\n",
},
{
name: "first_system_is_header",
@@ -64,29 +56,20 @@ func TestLagunaRendererReferenceFlowCoverage(t *testing.T) {
{Role: "system", Content: "Stay concise.\n\n"},
{Role: "user", Content: "Hi"},
},
- want: "" +
- "〈|EOS|〉\n" +
- lagunaThinkDirective +
- "\nStay concise." +
- "\n\n" +
- "\nHi\n\n" +
- "\n",
+ want: "〈|EOS|〉\n\nStay concise.\n\n" +
+ "\nHi\n\n\n",
},
{
- name: "additional_system_message_renders_in_loop",
+ name: "additional_system",
messages: []api.Message{
{Role: "system", Content: "Primary."},
{Role: "user", Content: "Hi"},
{Role: "system", Content: "Secondary."},
},
- want: "" +
- "〈|EOS|〉\n" +
- lagunaThinkDirective +
- "\nPrimary." +
- "\n\n" +
+ want: "〈|EOS|〉\n\nPrimary.\n\n" +
"\nHi\n\n" +
"\nSecondary.\n\n" +
- "\n",
+ "\n",
},
{
name: "tools_in_header",
@@ -95,46 +78,32 @@ func TestLagunaRendererReferenceFlowCoverage(t *testing.T) {
{Role: "user", Content: "Weather?"},
},
tools: weather,
- think: &api.ThinkValue{Value: true},
- want: "" +
- "〈|EOS|〉\n" +
- lagunaThinkDirective +
- "\nStay concise." +
- "\n\n### Tools\n\n" +
+ think: think(true),
+ want: "〈|EOS|〉\n\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" + lagunaToolJSON + "\n\n\n" +
+ "Wrap your thinking in '', '' tags, followed by a function call. For each function call, return an unescaped XML-like object with function name and arguments within '' and '' tags, like here:\n" +
+ " your thoughts here \n" +
+ "function-name\nargument-key\nvalue-of-argument-key\n" +
"\n\n" +
- "\nWeather?\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" +
+ name: "tools_default",
+ messages: []api.Message{{Role: "user", Content: "Weather?"}},
+ tools: weather,
+ want: "〈|EOS|〉\n\n" + lagunaDefaultSystem + "\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" + lagunaToolJSON + "\n\n\n" +
+ "For each function call, return an unescaped XML-like object with function name and arguments within '' and '' tags, like here:\n" +
+ "function-name\nargument-key\nvalue-of-argument-key\n" +
"\n\n" +
- "\nWeather?\n\n" +
- "\n",
+ "\nWeather?\n\n\n",
},
{
- name: "assistant_history_with_thinking_content_tool_and_response",
+ name: "assistant_history",
messages: []api.Message{
{Role: "user", Content: "Add these."},
{
@@ -154,14 +123,11 @@ func TestLagunaRendererReferenceFlowCoverage(t *testing.T) {
{Role: "tool", Content: "5"},
{Role: "user", Content: "Thanks"},
},
- think: &api.ThinkValue{Value: true},
- want: "" +
- "〈|EOS|〉\n" +
- lagunaThinkDirective +
- "\n\n" +
+ think: think(true),
+ want: defaultHeader +
"\nAdd these.\n\n" +
"\n" +
- "Need addition.\n" +
+ "\nNeed addition.\n\n" +
"Calling the tool.\n" +
"add\n" +
"a\n2\n" +
@@ -169,21 +135,15 @@ func TestLagunaRendererReferenceFlowCoverage(t *testing.T) {
"\n" +
"\n" +
"\n5\n\n" +
- "\nThanks\n\n" +
- "\n",
+ "\nThanks\n\n\n",
},
{
- name: "final_assistant_prefill_is_continued",
+ name: "final_assistant_prefill",
messages: []api.Message{
{Role: "user", Content: "Complete this"},
{Role: "assistant", Content: "Partial"},
},
- want: "" +
- "〈|EOS|〉\n" +
- lagunaThinkDirective +
- "\n\n" +
- "\nComplete this\n\n" +
- "\nPartial\n",
+ want: defaultHeader + "\nComplete this\n\n\n\nPartial\n",
},
}
@@ -195,7 +155,7 @@ func TestLagunaRendererReferenceFlowCoverage(t *testing.T) {
t.Fatal(err)
}
if diff := cmp.Diff(tt.want, got); diff != "" {
- t.Fatalf("renderer output mismatch (-want +got):\n%s", diff)
+ t.Fatalf("renderer output mismatch vs template (-want +got):\n%s", diff)
}
})
}
@@ -286,9 +246,9 @@ func renderLagunaChatTemplate(t *testing.T, python, modelDir string, messages []
t.Fatalf("failed to marshal messages: %v", err)
}
- enableThinking := "True"
- if think != nil && !think.Bool() {
- enableThinking = "False"
+ enableThinking := "False"
+ if think != nil && think.Bool() {
+ enableThinking = "True"
}
script := `