Skip to content

Commit c2e798d

Browse files
committed
Refactor tool choice handling and enhance response processing - Updated chatToolChoice to accept tool aliases and improved parsing logic for tool choices. - Introduced toolChoiceSpec and parseToolChoice to standardize tool choice representation across different protocols. - Enhanced requestedParallelToolCalls to better manage parallel tool execution settings. - Modified response rendering functions to incorporate tool choice and parallel execution handling. - Improved cleanNativeResponsesInput to handle function arguments more effectively and added utility functions for argument stringification and output flattening. - Updated backend stream handling to support tool calls and their associated states, ensuring proper terminal reasons are returned. - Added tests to verify the correct restoration of tool aliases and the handling of fragmented tool start events.
1 parent b045e60 commit c2e798d

10 files changed

Lines changed: 894 additions & 106 deletions

File tree

‎internal/grok/client_test.go‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -689,14 +689,16 @@ func TestUnauthorizedRetryRerendersForDifferentAccountDescriptor(t *testing.T) {
689689
t.Fatalf("second attempt = %#v", second)
690690
}
691691
secondOutput, _ := second.body["output_config"].(map[string]any)
692-
if secondOutput["effort"] != "low" || second.body["reasoning"] != nil || second.body["tools"] != nil {
692+
secondTools, _ := second.body["tools"].([]any)
693+
if secondOutput["effort"] != "low" || second.body["reasoning"] != nil || len(secondTools) != 1 || secondTools[0].(map[string]any)["name"] != "ns__lookup" || second.body["tool_choice"] != nil {
693694
t.Fatalf("second attempt was not rerendered and silently cleaned: %#v", second.body)
694695
}
695696
if first.requestID != second.requestID || first.sessionID != second.sessionID || first.turn != second.turn ||
696697
first.requestID != "logical-request" || first.sessionID != "logical-session" || first.turn != "0" {
697698
t.Fatalf("logical identity changed across retry: first=%#v second=%#v", first, second)
698699
}
699-
if result.Attempt.Backend != modelcatalog.BackendMessages || result.Attempt.ReasoningEffort != "low" || len(result.Attempt.Adapter.ToolAliases) != 0 {
700+
alias := result.Attempt.Adapter.ToolAliases["ns__lookup"]
701+
if result.Attempt.Backend != modelcatalog.BackendMessages || result.Attempt.ReasoningEffort != "low" || alias.Name != "lookup" || alias.Namespace != "ns__" {
700702
t.Fatalf("result attempt = %#v", result.Attempt)
701703
}
702704
}

‎internal/inference/canonical.go‎

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ type canonicalTool struct {
4646
type canonicalRequest struct {
4747
messages []canonicalMessage
4848
tools []canonicalTool
49+
toolAliases map[string]ToolAlias
4950
maxTokens any
5051
temperature any
5152
topP any
@@ -194,6 +195,7 @@ func canonicalFromResponses(body map[string]any) canonicalRequest {
194195
maxTokens: first(body, "max_output_tokens", "max_completion_tokens", "max_tokens"),
195196
temperature: body["temperature"], topP: body["top_p"],
196197
}
198+
request.tools, request.toolAliases = responsesTools(responseToolSources(body))
197199
request.user = firstString(body, "safety_identifier", "user")
198200
if text, ok := body["text"].(map[string]any); ok {
199201
request.format = text["format"]
@@ -208,6 +210,7 @@ func canonicalFromResponses(body map[string]any) canonicalRequest {
208210
if !exists {
209211
rawInput = body["messages"]
210212
}
213+
request.toolAliases = ensureResponseInputToolAliases(rawInput, request.toolAliases)
211214
if text, ok := rawInput.(string); ok {
212215
request.messages = append(request.messages, canonicalMessage{role: "user", blocks: []canonicalBlock{{kind: blockText, text: text}}})
213216
} else if input, ok := rawInput.([]any); ok {
@@ -241,11 +244,21 @@ func canonicalFromResponses(body map[string]any) canonicalRequest {
241244
if id == "" || name == "" {
242245
continue
243246
}
244-
args := stringValue(item["arguments"])
247+
wireName, identity := responseToolWireIdentity(kind, name, trimmedString(item["namespace"]), request.toolAliases)
248+
if wireName != "" {
249+
name = wireName
250+
}
251+
args := stringifyFunctionArguments(item["arguments"])
252+
input := cloneValue(item["input"])
245253
if kind == "custom_tool_call" {
246-
args = jsonString(item["input"])
254+
input = map[string]any{"input": input}
255+
args = jsonString(input)
256+
} else if identity.Kind == "custom" {
257+
// A cached/replayed custom call may already be represented as a
258+
// function call. Keep its portable wrapper intact.
259+
input = decodeObject(args)
247260
}
248-
request.messages = append(request.messages, canonicalMessage{role: "assistant", blocks: []canonicalBlock{{kind: blockToolCall, id: id, name: name, arguments: args, input: cloneValue(item["input"])}}})
261+
request.messages = append(request.messages, canonicalMessage{role: "assistant", blocks: []canonicalBlock{{kind: blockToolCall, id: id, name: name, arguments: args, input: input}}})
249262
case "function_call_output", "custom_tool_call_output":
250263
id := trimmedString(item["call_id"])
251264
if id == "" {
@@ -254,10 +267,13 @@ func canonicalFromResponses(body map[string]any) canonicalRequest {
254267
if output, ok := portableOutput(item["output"]); ok {
255268
request.messages = append(request.messages, canonicalMessage{role: "tool", blocks: []canonicalBlock{{kind: blockToolResult, id: id, output: output}}})
256269
}
270+
case "tool_search_output":
271+
if clientToolExecution(item) {
272+
request.messages = append(request.messages, canonicalMessage{role: "system", blocks: []canonicalBlock{{kind: blockText, text: toolSearchCompletedText}}})
273+
}
257274
}
258275
}
259276
}
260-
request.tools = responsesTools(body["tools"])
261277
return request
262278
}
263279

@@ -289,32 +305,44 @@ func responsesContent(raw any, role string) []canonicalBlock {
289305
return out
290306
}
291307

292-
func responsesTools(raw any) []canonicalTool {
293-
tools, _ := raw.([]any)
308+
func responsesTools(raw any) ([]canonicalTool, map[string]ToolAlias) {
309+
items, _ := raw.([]any)
310+
tools, aliases := cleanNativeResponsesTools(items, false)
294311
var out []canonicalTool
295312
seen := map[string]bool{}
296313
for _, rawTool := range tools {
297314
tool, ok := rawTool.(map[string]any)
298315
if !ok {
299316
continue
300317
}
301-
kind := strings.ToLower(trimmedString(tool["type"]))
302-
if kind != "function" && kind != "custom" {
318+
if strings.ToLower(trimmedString(tool["type"])) != "function" {
303319
continue
304320
}
305321
name := trimmedString(tool["name"])
306322
parameters, ok := tool["parameters"].(map[string]any)
307-
if kind == "custom" && !ok {
308-
parameters = map[string]any{"type": "object"}
309-
ok = true
310-
}
311323
if name == "" || !ok || seen[name] {
312324
continue
313325
}
314326
seen[name] = true
315327
out = append(out, canonicalTool{name: name, description: stringValue(tool["description"]), parameters: cloneMap(parameters)})
316328
}
317-
return out
329+
return out, aliases
330+
}
331+
332+
func responseToolWireIdentity(kind, name, namespace string, aliases map[string]ToolAlias) (string, ToolAlias) {
333+
wantKind := "function"
334+
if kind == "custom_tool_call" {
335+
wantKind = "custom"
336+
}
337+
for wire, alias := range aliases {
338+
if alias.Name == name && alias.Namespace == namespace && alias.Kind == wantKind {
339+
return wire, alias
340+
}
341+
}
342+
if alias, ok := aliases[name]; ok {
343+
return name, alias
344+
}
345+
return "", ToolAlias{}
318346
}
319347

320348
func canonicalFromMessages(body map[string]any) canonicalRequest {

‎internal/inference/plan.go‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,9 @@ func (p *RequestPlan) Render(descriptor modelcatalog.ModelDescriptor) (*Rendered
366366
if err != nil {
367367
return nil, err
368368
}
369+
if len(toolAliases) == 0 && p.protocol == ProtocolResponses {
370+
toolAliases = p.canonical().toolAliases
371+
}
369372

370373
effort := ""
371374
if p.effortSupplied {

0 commit comments

Comments
 (0)