ToolCalls extracts the ToolCall parts from the
assistant's message. It is shorthand for Response.Message.ToolCalls().
Reference#
func (r *Response) ToolCalls() []ToolCall
Returns
Every tool call in the order the model produced them. Returns nil when there
are none, so a plain range is safe without a length check.
Caveats
- Nil-safe on a nil
*Response. - On Gemini,
ToolCall.IDis the function name, because Gemini issues no call IDs. Two parallel calls to the same tool are therefore indistinguishable by ID — pair results by position instead. Argumentsisjson.RawMessage; skyl cannot know your tool's schema.- It flattens ordering against text. Use
Message.Partswhen the interleaving matters.
Usage#
Running the calls
for _, call := range resp.ToolCalls() {
var args struct {
City string `json:"city"`
}
if err := json.Unmarshal(call.Arguments, &args); err != nil {
req.Messages = append(req.Messages,
skyl.ToolErrorMessage(call.ID, "could not parse arguments: "+err.Error()))
continue
}
req.Messages = append(req.Messages,
skyl.ToolResultMessage(call.ID, weather(args.City)))
}for _, call := range resp.ToolCalls() {
var args struct {
City string `json:"city"`
}
if err := json.Unmarshal(call.Arguments, &args); err != nil {
req.Messages = append(req.Messages,
skyl.ToolErrorMessage(call.ID, "could not parse arguments: "+err.Error()))
continue
}
req.Messages = append(req.Messages,
skyl.ToolResultMessage(call.ID, weather(args.City)))
}Detecting the end of a tool loop
// More reliable than StopReason: on Gemini, any function call forces
// StopToolUse regardless of the actual finishReason.
if len(resp.ToolCalls()) == 0 {
return resp, nil
}// More reliable than StopReason: on Gemini, any function call forces
// StopToolUse regardless of the actual finishReason.
if len(resp.ToolCalls()) == 0 {
return resp, nil
}Pairing results safely on every provider
calls := resp.ToolCalls()
results := make([]string, len(calls))
for i, call := range calls {
results[i] = run(call) // indexed, so identical Gemini IDs cannot collide
}
req.Messages = append(req.Messages, resp.Message)
for i, call := range calls {
req.Messages = append(req.Messages, skyl.ToolResultMessage(call.ID, results[i]))
}calls := resp.ToolCalls()
results := make([]string, len(calls))
for i, call := range calls {
results[i] = run(call) // indexed, so identical Gemini IDs cannot collide
}
req.Messages = append(req.Messages, resp.Message)
for i, call := range calls {
req.Messages = append(req.Messages, skyl.ToolResultMessage(call.ID, results[i]))
}Troubleshooting#
Two calls to the same tool collapsed into one
You keyed a map by call.ID and are on Gemini, where the ID is the function
name. Index by position instead.
A call I expected did not appear, while streaming
On OpenAI and openaicompat, a streamed tool call whose name never arrived is
silently discarded — it cannot be dispatched. Inspect StreamEvent.Raw on
the preceding frames.
The provider rejected my next request with a 400
Almost always ordering: resp.Message must be appended before any tool
result. Every provider rejects a result that does not follow its call.