add new parser, tests, and templates
This commit is contained in:
parent
b5a982ecb0
commit
a44734b030
44
server/testdata/tools/llama3.2.gotmpl
vendored
Normal file
44
server/testdata/tools/llama3.2.gotmpl
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
<|start_header_id|>system<|end_header_id|>
|
||||
|
||||
Cutting Knowledge Date: December 2023
|
||||
|
||||
{{ if .System }}{{ .System }}
|
||||
{{- end }}
|
||||
{{- if .Tools }}When you receive a tool call response, use the output to format an answer to the orginal user question.
|
||||
|
||||
You are a helpful assistant with tool calling capabilities.
|
||||
{{- end }}<|eot_id|>
|
||||
{{- range $i, $_ := .Messages }}
|
||||
{{- $last := eq (len (slice $.Messages $i)) 1 }}
|
||||
{{- if eq .Role "user" }}<|start_header_id|>user<|end_header_id|>
|
||||
{{- if and $.Tools $last }}
|
||||
|
||||
Given the following functions, please respond with a JSON for a function call with its proper arguments that best answers the given prompt.
|
||||
|
||||
Respond in the format {"name": function name, "parameters": dictionary of argument name and its value}. Do not use variables.
|
||||
|
||||
{{ range $.Tools }}
|
||||
{{- . }}
|
||||
{{ end }}
|
||||
{{ .Content }}<|eot_id|>
|
||||
{{- else }}
|
||||
|
||||
{{ .Content }}<|eot_id|>
|
||||
{{- end }}{{ if $last }}<|start_header_id|>assistant<|end_header_id|>
|
||||
|
||||
{{ end }}
|
||||
{{- else if eq .Role "assistant" }}<|start_header_id|>assistant<|end_header_id|>
|
||||
{{- if .ToolCalls }}
|
||||
{{ range .ToolCalls }}
|
||||
{"name": "{{ .Function.Name }}", "parameters": {{ .Function.Arguments }}}{{ end }}
|
||||
{{- else }}
|
||||
|
||||
{{ .Content }}
|
||||
{{- end }}{{ if not $last }}<|eot_id|>{{ end }}
|
||||
{{- else if eq .Role "tool" }}<|start_header_id|>ipython<|end_header_id|>
|
||||
|
||||
{{ .Content }}<|eot_id|>{{ if $last }}<|start_header_id|>assistant<|end_header_id|>
|
||||
|
||||
{{ end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
24
server/testdata/tools/llama3.2.out
vendored
Normal file
24
server/testdata/tools/llama3.2.out
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
<|start_header_id|>system<|end_header_id|>
|
||||
|
||||
Cutting Knowledge Date: December 2023
|
||||
|
||||
You are a knowledgeable assistant. You can answer questions and perform tasks.When you receive a tool call response, use the output to format an answer to the orginal user question.
|
||||
|
||||
You are a helpful assistant with tool calling capabilities.<|eot_id|><|start_header_id|>user<|end_header_id|>
|
||||
|
||||
What's the weather like today in Paris?<|eot_id|><|start_header_id|>assistant<|end_header_id|>
|
||||
|
||||
{"name": "get_current_weather", "parameters": {"format":"celsius","location":"Paris, France"}}<|eot_id|><|start_header_id|>ipython<|end_header_id|>
|
||||
|
||||
22<|eot_id|><|start_header_id|>assistant<|end_header_id|>
|
||||
|
||||
The current temperature in Paris, France is 22 degrees Celsius.<|eot_id|><|start_header_id|>user<|end_header_id|>
|
||||
|
||||
Given the following functions, please respond with a JSON for a function call with its proper arguments that best answers the given prompt.
|
||||
|
||||
Respond in the format {"name": function name, "parameters": dictionary of argument name and its value}. Do not use variables.
|
||||
|
||||
{"type":"function","function":{"name":"get_current_weather","description":"Get the current weather","parameters":{"type":"object","required":["location","format"],"properties":{"format":{"type":"string","description":"The temperature unit to use. Infer this from the user's location.","enum":["celsius","fahrenheit"]},"location":{"type":"string","description":"The city and state, e.g. San Francisco, CA"}}}}}
|
||||
|
||||
What's the weather like today in San Francisco and Toronto?<|eot_id|><|start_header_id|>assistant<|end_header_id|>
|
||||
|
50
server/testdata/tools/qwen3.gotmpl
vendored
Normal file
50
server/testdata/tools/qwen3.gotmpl
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
{{- if .Messages }}
|
||||
{{- if or .System .Tools }}<|im_start|>system
|
||||
{{- if .System }}
|
||||
{{ .System }}
|
||||
{{- end }}
|
||||
{{- if .Tools }}
|
||||
|
||||
# Tools
|
||||
|
||||
You may call one or more functions to assist with the user query.
|
||||
|
||||
You are provided with function signatures within <tools></tools> XML tags:
|
||||
<tools>
|
||||
{{- range .Tools }}
|
||||
{"type": "function", "function": {{ .Function }}}
|
||||
{{- end }}
|
||||
</tools>
|
||||
|
||||
For each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:
|
||||
<tool_call>
|
||||
{"name": <function-name>, "arguments": <args-json-object>}
|
||||
</tool_call>
|
||||
{{- end }}<|im_end|>
|
||||
{{ end }}
|
||||
{{- range $i, $_ := .Messages }}
|
||||
{{- $last := eq (len (slice $.Messages $i)) 1 -}}
|
||||
{{- if eq .Role "user" }}<|im_start|>user
|
||||
{{ .Content }}<|im_end|>
|
||||
{{ else if eq .Role "assistant" }}<|im_start|>assistant
|
||||
{{ if .Content }}{{ .Content }}
|
||||
{{- else if .ToolCalls }}<tool_call>
|
||||
{{ range .ToolCalls }}{"name": "{{ .Function.Name }}", "arguments": {{ .Function.Arguments }}}
|
||||
{{ end }}</tool_call>
|
||||
{{- end }}{{ if not $last }}<|im_end|>
|
||||
{{ end }}
|
||||
{{- else if eq .Role "tool" }}<|im_start|>user
|
||||
<tool_response>
|
||||
{{ .Content }}
|
||||
</tool_response><|im_end|>
|
||||
{{ end }}
|
||||
{{- if and (ne .Role "assistant") $last }}<|im_start|>assistant
|
||||
{{ end }}
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
{{- if .System }}<|im_start|>system
|
||||
{{ .System }}<|im_end|>
|
||||
{{ end }}{{ if .Prompt }}<|im_start|>user
|
||||
{{ .Prompt }}<|im_end|>
|
||||
{{ end }}<|im_start|>assistant
|
||||
{{ end }}{{ .Response }}{{ if .Response }}<|im_end|>{{ end }}
|
31
server/testdata/tools/qwen3.out
vendored
Normal file
31
server/testdata/tools/qwen3.out
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
<|im_start|>system
|
||||
You are a knowledgeable assistant. You can answer questions and perform tasks.
|
||||
|
||||
# Tools
|
||||
|
||||
You may call one or more functions to assist with the user query.
|
||||
|
||||
You are provided with function signatures within <tools></tools> XML tags:
|
||||
<tools>
|
||||
{"type": "function", "function": {"name":"get_current_weather","description":"Get the current weather","parameters":{"type":"object","required":["location","format"],"properties":{"format":{"type":"string","description":"The temperature unit to use. Infer this from the user's location.","enum":["celsius","fahrenheit"]},"location":{"type":"string","description":"The city and state, e.g. San Francisco, CA"}}}}}
|
||||
</tools>
|
||||
|
||||
For each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:
|
||||
<tool_call>
|
||||
{"name": <function-name>, "arguments": <args-json-object>}
|
||||
</tool_call><|im_end|>
|
||||
<|im_start|>user
|
||||
What's the weather like today in Paris?<|im_end|>
|
||||
<|im_start|>assistant
|
||||
<tool_call>
|
||||
{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Paris, France"}}
|
||||
</tool_call><|im_end|>
|
||||
<|im_start|>user
|
||||
<tool_response>
|
||||
22
|
||||
</tool_response><|im_end|>
|
||||
<|im_start|>assistant
|
||||
The current temperature in Paris, France is 22 degrees Celsius.<|im_end|>
|
||||
<|im_start|>user
|
||||
What's the weather like today in San Francisco and Toronto?<|im_end|>
|
||||
<|im_start|>assistant
|
105
server/tools.go
105
server/tools.go
@ -167,7 +167,7 @@ func (p *ToolParser) parseJSONToolCalls(s string) ([]api.ToolCall, bool, bool) {
|
||||
return toolCalls, false, true
|
||||
}
|
||||
|
||||
func (p *ToolParser) updateState(ok bool, partial bool, tcs []api.ToolCall) {
|
||||
func (p *ToolParser) updateOutputState(ok bool, partial bool, tcs []api.ToolCall) {
|
||||
switch {
|
||||
case !ok && !partial && p.state == ForceTools:
|
||||
fmt.Println("Case: !ok && !partial && ForceTools - staying in force tools, resetting buffer")
|
||||
@ -188,7 +188,6 @@ func (p *ToolParser) updateState(ok bool, partial bool, tcs []api.ToolCall) {
|
||||
p.sb.Reset()
|
||||
case !ok && partial:
|
||||
fmt.Println("Case: !ok && partial - accumulating partial content")
|
||||
|
||||
// ! acucumulate
|
||||
|
||||
case len(tcs) > 0:
|
||||
@ -204,68 +203,74 @@ func (p *ToolParser) updateState(ok bool, partial bool, tcs []api.ToolCall) {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ToolParser) updateInputState(s string, hasPrefix bool) (string, bool) {
|
||||
if p.toolPrefix == "" {
|
||||
return s, true
|
||||
}
|
||||
|
||||
if hasPrefix {
|
||||
p.state = ForceTools
|
||||
// partial tool possibly
|
||||
} else if strings.HasPrefix(p.toolPrefix, s) {
|
||||
slog.Debug("tool prefix partially", "prefix", p.toolPrefix, "content", s)
|
||||
// TODO: could possibly err maybe this should be greedy instead?
|
||||
p.state = ForceTools
|
||||
return "", false
|
||||
} else if strings.Contains(s, p.toolPrefix) {
|
||||
idx := strings.Index(s, p.toolPrefix)
|
||||
if idx != -1 {
|
||||
// still keeps the prefix
|
||||
p.state = ContainsPartialPrefix
|
||||
p.sb.Reset()
|
||||
p.sb.WriteString(s[idx:])
|
||||
return s[:idx], false
|
||||
}
|
||||
}
|
||||
// Special token end case
|
||||
if strings.HasSuffix(s, p.toolPrefix[2:]) {
|
||||
// can be with string or just the token
|
||||
if hasPrefix {
|
||||
s = strings.TrimSpace(s[:len(s)-(len(p.toolPrefix)+1)])
|
||||
} else {
|
||||
p.state = ToolSuffix
|
||||
p.sb.Reset()
|
||||
return "", false
|
||||
}
|
||||
slog.Debug("setting to no tool", "content", s)
|
||||
}
|
||||
return s, true
|
||||
}
|
||||
|
||||
// ParseToolCalls extracts tool calls from a string using a tool token prefix or direct JSON parsing.
|
||||
// Returns tool calls, whether parsing is incomplete, and any errors.
|
||||
func (p *ToolParser) ParseToolCalls(s string) ([]api.ToolCall, string, bool) {
|
||||
// append input
|
||||
p.sb.WriteString(s)
|
||||
s = p.sb.String()
|
||||
s = strings.TrimSpace(s)
|
||||
slog.Debug("parse tool calls", "content", s)
|
||||
|
||||
if len(s) == 0 {
|
||||
return nil, "", false
|
||||
}
|
||||
s, hasPrefix := strings.CutPrefix(s, p.toolPrefix)
|
||||
fmt.Println("hasPrefix", hasPrefix)
|
||||
var tcs []api.ToolCall
|
||||
var partial bool
|
||||
var ok bool
|
||||
|
||||
if p.toolPrefix != "" {
|
||||
if hasPrefix {
|
||||
p.state = ForceTools
|
||||
slog.Debug("tool prefix in prefix", "prefix", p.toolPrefix, "content", s)
|
||||
// partial tool possibly
|
||||
} else if strings.HasPrefix(p.toolPrefix, s) {
|
||||
slog.Debug("tool prefix partially", "prefix", p.toolPrefix, "content", s)
|
||||
// TODO: could possibly err maybe this should be greedy instead?
|
||||
p.state = ForceTools
|
||||
return nil, "", false
|
||||
} else if strings.Contains(s, p.toolPrefix) {
|
||||
idx := strings.Index(s, p.toolPrefix)
|
||||
if idx != -1 {
|
||||
// still keeps the prefix
|
||||
p.state = ContainsPartialPrefix
|
||||
p.sb.Reset()
|
||||
p.sb.WriteString(s[idx:])
|
||||
return nil, s[:idx], false
|
||||
}
|
||||
s, hasPrefix := strings.CutPrefix(s, p.toolPrefix)
|
||||
|
||||
s, ok := p.updateInputState(s, hasPrefix)
|
||||
if !ok {
|
||||
if p.state == ContainsPartialPrefix {
|
||||
return nil, s, false
|
||||
}
|
||||
// Special token end case
|
||||
// if s, ok := strings.CutSuffix(s, p.toolPrefix[2:]); ok {
|
||||
if strings.HasSuffix(s, p.toolPrefix[2:]) {
|
||||
// can be with string or just the token
|
||||
if hasPrefix {
|
||||
s = strings.TrimSpace(s[:len(s)-(len(p.toolPrefix)+1)])
|
||||
} else {
|
||||
p.state = ToolSuffix
|
||||
p.sb.Reset()
|
||||
return nil, "", false
|
||||
}
|
||||
slog.Debug("setting to no tool", "content", s)
|
||||
}
|
||||
}
|
||||
fmt.Println("s before parsing", s)
|
||||
if p.state == SendTokens {
|
||||
fmt.Println("returning nil cause of send tokens")
|
||||
return nil, "", false
|
||||
}
|
||||
tcs, partial, ok = p.parseJSONToolCalls(s)
|
||||
slog.Debug("returning tool calls", "tool calls", tcs)
|
||||
fmt.Println("end state", p.state)
|
||||
|
||||
fmt.Println("len tcs", len(tcs))
|
||||
p.updateState(ok, partial, tcs)
|
||||
if p.state == SendTokens {
|
||||
return nil, "", false
|
||||
}
|
||||
|
||||
var tcs []api.ToolCall
|
||||
var partial bool
|
||||
tcs, partial, ok = p.parseJSONToolCalls(s)
|
||||
p.updateOutputState(ok, partial, tcs)
|
||||
if !ok {
|
||||
return nil, "", false
|
||||
}
|
||||
@ -276,7 +281,6 @@ func (p *ToolParser) ParseToolCalls(s string) ([]api.ToolCall, string, bool) {
|
||||
func NewToolParser(model *Model) *ToolParser {
|
||||
templateToolPrefix, _ := ToolPrefix(model.Template.Template)
|
||||
templateToolPrefix = strings.TrimSpace(templateToolPrefix)
|
||||
slog.Debug("tool prefix", "prefix", templateToolPrefix)
|
||||
tmpl, ok := ToolTemplate(model)
|
||||
if !ok {
|
||||
return nil
|
||||
@ -288,6 +292,7 @@ func NewToolParser(model *Model) *ToolParser {
|
||||
} else {
|
||||
state = GreedyToolWithPrefix
|
||||
}
|
||||
fmt.Println("state", state)
|
||||
return &ToolParser{
|
||||
tmpl: tmpl,
|
||||
sb: &strings.Builder{},
|
||||
|
@ -51,7 +51,6 @@ func TestParseToolCalls(t *testing.T) {
|
||||
name string
|
||||
model string
|
||||
output string
|
||||
prefix string
|
||||
expected []api.ToolCall
|
||||
wantErr bool
|
||||
}{
|
||||
@ -59,7 +58,6 @@ func TestParseToolCalls(t *testing.T) {
|
||||
name: "mistral invalid json",
|
||||
model: "mistral",
|
||||
output: `[TOOL_CALLS] [{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}},{"name": "get_curren}]`,
|
||||
prefix: "[TOOL_CALLS]",
|
||||
expected: []api.ToolCall{},
|
||||
wantErr: true,
|
||||
},
|
||||
@ -67,7 +65,6 @@ func TestParseToolCalls(t *testing.T) {
|
||||
name: "mistral multiple tool calls - no prefix",
|
||||
model: "mistral",
|
||||
output: `[{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}},{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}]`,
|
||||
prefix: "[TOOL_CALLS]",
|
||||
expected: []api.ToolCall{t1, t2},
|
||||
wantErr: false,
|
||||
},
|
||||
@ -76,7 +73,6 @@ func TestParseToolCalls(t *testing.T) {
|
||||
model: "mistral",
|
||||
output: `[{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}},{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}]
|
||||
model outputs more tokens here and then [{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}},{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}]`,
|
||||
prefix: "[TOOL_CALLS]",
|
||||
expected: []api.ToolCall{t1, t2},
|
||||
wantErr: false,
|
||||
},
|
||||
@ -84,7 +80,6 @@ func TestParseToolCalls(t *testing.T) {
|
||||
name: "mistral valid json - with prefix",
|
||||
model: "mistral",
|
||||
output: `[TOOL_CALLS] [{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}},{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}]`,
|
||||
prefix: "[TOOL_CALLS]",
|
||||
expected: []api.ToolCall{t1, t2},
|
||||
wantErr: false,
|
||||
},
|
||||
@ -94,7 +89,6 @@ func TestParseToolCalls(t *testing.T) {
|
||||
model: "mistral",
|
||||
output: `[TOOL_CALLS] [{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}},{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}]
|
||||
model outputs more tokens here and then [{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}},{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}]`,
|
||||
prefix: "[TOOL_CALLS]",
|
||||
expected: []api.ToolCall{t1, t2, t1, t2},
|
||||
wantErr: false,
|
||||
},
|
||||
@ -102,7 +96,6 @@ func TestParseToolCalls(t *testing.T) {
|
||||
name: "mistral incomplete json",
|
||||
model: "mistral",
|
||||
output: `[TOOL_CALLS] [{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, `,
|
||||
prefix: "[TOOL_CALLS]",
|
||||
expected: []api.ToolCall{},
|
||||
wantErr: true,
|
||||
},
|
||||
@ -112,7 +105,6 @@ func TestParseToolCalls(t *testing.T) {
|
||||
output: `I'm not aware of that information. However, I can suggest searching for the weather using the "get_current_weather" function:
|
||||
|
||||
[{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}},{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}]`,
|
||||
prefix: "[TOOL_CALLS]",
|
||||
expected: []api.ToolCall{},
|
||||
wantErr: true,
|
||||
},
|
||||
@ -120,7 +112,6 @@ func TestParseToolCalls(t *testing.T) {
|
||||
name: "mistral without tool token - tool first",
|
||||
model: "mistral",
|
||||
output: `[{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}},{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}]`,
|
||||
prefix: "[TOOL_CALLS]",
|
||||
expected: []api.ToolCall{t1, t2},
|
||||
wantErr: false,
|
||||
},
|
||||
@ -145,7 +136,6 @@ func TestParseToolCalls(t *testing.T) {
|
||||
}
|
||||
]
|
||||
` + "```",
|
||||
prefix: "Action: ```json",
|
||||
expected: []api.ToolCall{t1, t2},
|
||||
wantErr: false,
|
||||
},
|
||||
@ -153,7 +143,6 @@ func TestParseToolCalls(t *testing.T) {
|
||||
name: "firefunction with functools",
|
||||
model: "firefunction",
|
||||
output: ` functools[{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}},{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}]`,
|
||||
prefix: "functools",
|
||||
expected: []api.ToolCall{t1, t2},
|
||||
wantErr: false,
|
||||
},
|
||||
@ -163,7 +152,6 @@ func TestParseToolCalls(t *testing.T) {
|
||||
output: `<tool_call>
|
||||
{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}}
|
||||
</tool_call>`,
|
||||
prefix: "<tool_call>",
|
||||
expected: []api.ToolCall{t1},
|
||||
wantErr: false,
|
||||
},
|
||||
@ -171,7 +159,6 @@ func TestParseToolCalls(t *testing.T) {
|
||||
name: "xlam with tool_calls wrapper",
|
||||
model: "xlam",
|
||||
output: `{"tool_calls": [{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}},{"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}]}`,
|
||||
prefix: "",
|
||||
expected: []api.ToolCall{t1, t2},
|
||||
wantErr: false,
|
||||
},
|
||||
@ -179,7 +166,6 @@ func TestParseToolCalls(t *testing.T) {
|
||||
name: "qwen with single tool call",
|
||||
model: "qwen2.5-coder",
|
||||
output: `<tool_call>{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}}</tool_call>`,
|
||||
prefix: "<tool_call>",
|
||||
expected: []api.ToolCall{t1},
|
||||
wantErr: false,
|
||||
},
|
||||
@ -187,7 +173,6 @@ func TestParseToolCalls(t *testing.T) {
|
||||
name: "qwen with invalid tool token",
|
||||
model: "qwen2.5-coder",
|
||||
output: `[{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}}, {"name": "get_current_weather", "arguments": {"format":"celsius","location":"Toronto, Canada"}}]`,
|
||||
prefix: "[TOOL_CALLS]",
|
||||
expected: []api.ToolCall{t1, t2},
|
||||
wantErr: false,
|
||||
},
|
||||
@ -195,7 +180,6 @@ func TestParseToolCalls(t *testing.T) {
|
||||
name: "qwen3 with single tool call and thinking",
|
||||
model: "qwen3",
|
||||
output: `<think>Okay, let me think what tool we should use...</think><tool_call>{"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}}</tool_call>`,
|
||||
prefix: "<tool_call>",
|
||||
expected: []api.ToolCall{t1},
|
||||
wantErr: false,
|
||||
},
|
||||
@ -203,7 +187,6 @@ func TestParseToolCalls(t *testing.T) {
|
||||
name: "qwen3 with single tool call and thinking spaces",
|
||||
model: "qwen3",
|
||||
output: `<think>Okay, let me think what tool we should use...</think> <tool_call> {"name": "get_current_weather", "arguments": {"format":"fahrenheit","location":"San Francisco, CA"}} </tool_call>`,
|
||||
prefix: "<tool_call>",
|
||||
expected: []api.ToolCall{t1},
|
||||
wantErr: false,
|
||||
},
|
||||
@ -211,7 +194,20 @@ func TestParseToolCalls(t *testing.T) {
|
||||
name: "qwen with no tool calls",
|
||||
model: "qwen2.5-coder",
|
||||
output: " The weather in San Francisco, CA is 70°F and in Toronto, Canada is 20°C.",
|
||||
prefix: "",
|
||||
expected: []api.ToolCall{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "llama3.2 with tool call - no prefix",
|
||||
model: "llama3.2",
|
||||
output: `{"name": "get_current_weather", "parameters": {"format":"fahrenheit","location":"San Francisco, CA"}}`,
|
||||
expected: []api.ToolCall{t1},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "llama3.2 with tool call - in middle",
|
||||
model: "llama3.2",
|
||||
output: `some non json text{"name": "get_current_weather", "parameters": {"format":"fahrenheit","location":"San Francisco, CA"}}`,
|
||||
expected: []api.ToolCall{},
|
||||
wantErr: true,
|
||||
},
|
||||
@ -235,11 +231,13 @@ func TestParseToolCalls(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("template", func(t *testing.T) {
|
||||
var actual bytes.Buffer
|
||||
if err := tmpl.Execute(&actual, template.Values{Tools: tools, Messages: messages}); err != nil {
|
||||
actual := &bytes.Buffer{} // Create new buffer for each test
|
||||
t.Log("template", tmpl, "model", tt.model)
|
||||
if err := tmpl.Execute(actual, template.Values{Tools: tools, Messages: messages}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Log("actual", actual.String())
|
||||
if diff := cmp.Diff(actual.String(), readFile(t, p, fmt.Sprintf("%s.out", tt.model)).String()); diff != "" {
|
||||
t.Errorf("mismatch (-got +want):\n%s", diff)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user