Unverified Commit 65f10c28 authored by Parth Sareen's avatar Parth Sareen Committed by GitHub
Browse files

tools: resiliency upgrade to name and arg extraction from template (#10917)

parent aaa78180
...@@ -166,31 +166,26 @@ func extractToolArgs(tmpl *gotmpl.Template) (name, arguments string, err error) ...@@ -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()
return "", "", err 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 objs []map[string]any var obj map[string]any
switch v := obj.(type) { if err := json.Unmarshal([]byte(jsonStr), &obj); err != nil {
case map[string]any: return "", "", err
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 // Find name and arguments fields
for k, v := range objs[0] { for k, v := range obj {
switch v.(type) { if str, ok := v.(string); ok && str == "@@name@@" {
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) { ...@@ -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",
name: "tool call with no text after", wantErr: false,
template: `{{if .ToolCalls}}{{.Something}}{{end}}`,
want: "",
ok: true,
}, },
{ {
name: "nested tool call", name: "tool call with extra content",
template: `{{if .Something}}{{if .ToolCalls}}[TOOL_CALL]{{end}}{{end}}`, template: `Before {{range .ToolCalls}}
want: "[TOOL_CALL]", {"name": "{{.Function.Name}}", "arguments": {{.Function.Arguments}}}{{end}} After`,
ok: true, wantName: "name",
wantArgs: "arguments",
wantErr: false,
}, },
{ {
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) { ...@@ -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 err != nil {
return
}
if gotName != tt.wantName {
t.Errorf("extractToolArgs() gotName = %q, want %q", gotName, tt.wantName)
} }
if ok != tt.ok { if gotArgs != tt.wantArgs {
t.Errorf("TextAfterToolCalls() ok = %v, want %v", ok, tt.ok) t.Errorf("extractToolArgs() gotArgs = %q, want %q", gotArgs, tt.wantArgs)
} }
}) })
} }
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment