mirror of
https://github.com/dogkeeper886/ollama37.git
synced 2025-12-10 07:46:59 +00:00
tools: resiliency upgrade to name and arg extraction from template (#10917)
This commit is contained in:
@@ -166,31 +166,26 @@ func extractToolArgs(tmpl *gotmpl.Template) (name, arguments string, err error)
|
|||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
var obj any
|
// Extract JSON object between curly braces
|
||||||
err = json.Unmarshal(b.Bytes(), &obj)
|
// JSON arrays are also valid as they will not be repeated in the template
|
||||||
if err != nil {
|
output := b.String()
|
||||||
|
start := strings.Index(output, "{")
|
||||||
|
end := strings.LastIndex(output, "}")
|
||||||
|
if start == -1 || end == -1 || start > end {
|
||||||
|
return "", "", errors.New("no valid JSON object found in template output")
|
||||||
|
}
|
||||||
|
jsonStr := output[start : end+1]
|
||||||
|
|
||||||
|
var obj map[string]any
|
||||||
|
if err := json.Unmarshal([]byte(jsonStr), &obj); err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
var objs []map[string]any
|
// Find name and arguments fields
|
||||||
switch v := obj.(type) {
|
for k, v := range obj {
|
||||||
case map[string]any:
|
if str, ok := v.(string); ok && str == "@@name@@" {
|
||||||
objs = []map[string]any{v}
|
|
||||||
case []map[string]any:
|
|
||||||
objs = v
|
|
||||||
case []any:
|
|
||||||
objs = collect(v)
|
|
||||||
}
|
|
||||||
if len(objs) == 0 {
|
|
||||||
return "", "", errors.New("no template objects found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// find the keys that correspond to the name and arguments fields
|
|
||||||
for k, v := range objs[0] {
|
|
||||||
switch v.(type) {
|
|
||||||
case string:
|
|
||||||
name = k
|
name = k
|
||||||
case map[string]any:
|
} else if _, ok := v.(map[string]any); ok {
|
||||||
arguments = k
|
arguments = k
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -271,74 +271,99 @@ func TestExtractToolArgs(t *testing.T) {
|
|||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
template string
|
template string
|
||||||
want string
|
wantName string
|
||||||
ok bool
|
wantArgs string
|
||||||
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "basic tool call with text after",
|
name: "basic tool call",
|
||||||
template: `{{if .ToolCalls}}tool response{{end}}`,
|
template: `{{ range .ToolCalls }}
|
||||||
want: "tool response",
|
{"name": "{{ .Function.Name }}", "parameters": {{ .Function.Arguments }}}{{ end }}`,
|
||||||
ok: true,
|
wantName: "name",
|
||||||
|
wantArgs: "parameters",
|
||||||
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "tool call with mixed content after",
|
name: "tool call with whitespace",
|
||||||
template: `{{if .ToolCalls}}<tool_call>{{.Something}}{{end}}`,
|
template: `{{range .ToolCalls}}
|
||||||
want: "<tool_call>",
|
{"name": "{{.Function.Name}}", "parameters": {{.Function.Arguments}}}
|
||||||
ok: true,
|
{{end}}`,
|
||||||
|
wantName: "name",
|
||||||
|
wantArgs: "parameters",
|
||||||
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "tool call with no text after",
|
name: "tool call with extra content",
|
||||||
template: `{{if .ToolCalls}}{{.Something}}{{end}}`,
|
template: `Before {{range .ToolCalls}}
|
||||||
want: "",
|
{"name": "{{.Function.Name}}", "arguments": {{.Function.Arguments}}}{{end}} After`,
|
||||||
ok: true,
|
wantName: "name",
|
||||||
},
|
wantArgs: "arguments",
|
||||||
{
|
wantErr: false,
|
||||||
name: "nested tool call",
|
|
||||||
template: `{{if .Something}}{{if .ToolCalls}}[TOOL_CALL]{{end}}{{end}}`,
|
|
||||||
want: "[TOOL_CALL]",
|
|
||||||
ok: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no tool calls",
|
name: "no tool calls",
|
||||||
template: `{{if .Something}}no tools here{{end}}`,
|
template: `{{if .Something}}no tools here{{end}}`,
|
||||||
want: "",
|
wantName: "",
|
||||||
ok: false,
|
wantArgs: "",
|
||||||
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "empty template",
|
name: "empty template",
|
||||||
template: ``,
|
template: ``,
|
||||||
want: "",
|
wantName: "",
|
||||||
ok: false,
|
wantArgs: "",
|
||||||
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple tool calls sections",
|
name: "prefix within tool call",
|
||||||
template: `{{if .ToolCalls}}first{{end}}{{if .ToolCalls}}second{{end}}`,
|
template: `{{- if .ToolCalls }}
|
||||||
want: "first",
|
{{ range .ToolCalls }}
|
||||||
ok: true,
|
<tool_call>
|
||||||
|
{"name": "{{ .Function.Name }}", "arguments": {{ .Function.Arguments }}}
|
||||||
|
</tool_call>{{ end }}{{- end }}`,
|
||||||
|
wantName: "name",
|
||||||
|
wantArgs: "arguments",
|
||||||
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "range over tool calls",
|
name: "JSON array",
|
||||||
template: `{{if .ToolCalls}}{{range .ToolCalls}}tool{{end}}{{end}}`,
|
template: `{{ range .ToolCalls }}
|
||||||
want: "",
|
[{"name": "{{ .Function.Name }}", "arguments": {{ .Function.Arguments }}}]{{ end }}`,
|
||||||
ok: true,
|
wantName: "name",
|
||||||
|
wantArgs: "arguments",
|
||||||
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "tool calls with pipe delimiters",
|
name: "invalid JSON",
|
||||||
template: `{{if .ToolCalls}}<|tool|>{{end}}`,
|
template: `{{ range .ToolCalls }}
|
||||||
want: "<|tool|>",
|
{"name": "{{ .Function.Name }}", "arguments": {{ .Function.Arguments }}, invalid}{{ end }}`,
|
||||||
ok: true,
|
wantName: "",
|
||||||
|
wantArgs: "",
|
||||||
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "tool calls with nested template",
|
name: "missing name field",
|
||||||
template: `{{if .ToolCalls}}{{template "tool" .}}{{end}}`,
|
template: `{{ range .ToolCalls }}
|
||||||
want: "",
|
{"parameters": {{ .Function.Arguments }}}{{ end }}`,
|
||||||
ok: true,
|
wantName: "",
|
||||||
|
wantArgs: "",
|
||||||
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "tool calls with whitespace variations",
|
name: "missing arguments field",
|
||||||
template: `{{if .ToolCalls}} tool {{end}}`,
|
template: `{{ range .ToolCalls }}
|
||||||
want: " tool ",
|
{"name": "{{ .Function.Name }}"}{{ end }}`,
|
||||||
ok: true,
|
wantName: "",
|
||||||
|
wantArgs: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "malformed JSON",
|
||||||
|
template: `{{ range .ToolCalls }}
|
||||||
|
{"name": {{ .Function.Name }}, "arguments": {{ .Function.Arguments }}{{ end }}`,
|
||||||
|
wantName: "",
|
||||||
|
wantArgs: "",
|
||||||
|
wantErr: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,12 +374,20 @@ func TestExtractToolArgs(t *testing.T) {
|
|||||||
t.Fatalf("failed to parse template: %v", err)
|
t.Fatalf("failed to parse template: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
got, ok := extractToolCallsFormat(tmpl)
|
gotName, gotArgs, err := extractToolArgs(tmpl)
|
||||||
if got != tt.want {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("TextAfterToolCalls() got = %q, want %q", got, tt.want)
|
t.Errorf("extractToolArgs() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if ok != tt.ok {
|
if err != nil {
|
||||||
t.Errorf("TextAfterToolCalls() ok = %v, want %v", ok, tt.ok)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotName != tt.wantName {
|
||||||
|
t.Errorf("extractToolArgs() gotName = %q, want %q", gotName, tt.wantName)
|
||||||
|
}
|
||||||
|
if gotArgs != tt.wantArgs {
|
||||||
|
t.Errorf("extractToolArgs() gotArgs = %q, want %q", gotArgs, tt.wantArgs)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user