Skip to content
skyl

ToolCall

The model's request to invoke a tool.

A ToolCall arrives on the assistant's turn when the model wants you to run something. You execute it and send back a ToolResult.

Reference#

type ToolCall struct { ID string Name string Arguments json.RawMessage }

Parameters

  • ID — correlates this call with its result. Providers generate it.
  • Name — the tool the model wants to run.
  • Arguments — the JSON object the model produced. Raw, because skyl cannot know your tool's schema.

Caveats

  • Validate requires both ID and Name on a call you construct.
  • On Gemini, ID is set to the function name, because Gemini issues no call IDs. Two parallel calls to the same tool are indistinguishable by ID — pair by position.
  • On Anthropic, Arguments is validated as JSON and a malformed value is rejected; the other adapters treat it as opaque.
  • Streaming emits EventToolCall once, when the arguments are whole — you never see a fragment.
  • On the OpenAI family, a streamed call whose name never arrived is silently discarded.

Usage#

Running a call

goCompiles
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)))
}

Replaying an assistant turn that contained calls

goCompiles
parts := []skyl.Part{skyl.Text{Text: "Let me check."}}
for _, c := range calls {
	parts = append(parts, c)
}
turn := skyl.Message{Role: skyl.RoleAssistant, Parts: parts}
parts := []skyl.Part{skyl.Text{Text: "Let me check."}}
for _, c := range calls {
	parts = append(parts, c)
}
turn := skyl.Message{Role: skyl.RoleAssistant, Parts: parts}

In practice you rarely build this — Response.Message already is it.

Troubleshooting#

Two calls to the same tool collapsed into one

You keyed a map by call.ID on Gemini, where the ID is the function name. Index by position instead.

Anthropic rejected my replayed tool call

Anthropic validates Arguments as JSON. If you constructed the call yourself with a non-JSON value, it is rejected there and accepted elsewhere.

A streamed call never arrived

On OpenAI and openaicompat, a call accumulated across frames with no name is discarded — it cannot be dispatched. Inspect StreamEvent.Raw on the preceding frames.

Edit this page on GitHub