Skip to main content

Defining tools

A tool is an operation the model may request, and Golato splits it in two. tool.Definition describes the operation to the model. tool.Tool adds the Go function that performs it. A Generator works with definitions alone, while an Agent registers tools and executes them inside its own loop.

Describing the operation

A definition carries a name, a description, and a self-contained JSON Schema Draft 2020-12 object. Golato compiles it strictly in that dialect and rejects $id, $recursiveRef, $recursiveAnchor, a $schema other than Draft 2020-12, and references outside the document. The schema OpenAI uses for tool calling is a constrained subset of Draft 2020-12, so a schema written for that dialect without the rejected keywords compiles unchanged. A schema that carries a draft-07 $schema or $id is rejected at agent.New.

lookup := tool.Definition{
Name: "lookup_customer",
Description: "Return the current plan for one customer ID.",
InputSchema: json.RawMessage(`{
"type": "object",
"properties": {
"customer_id": {
"type": "string",
"description": "Customer ID such as C-100",
"minLength": 1
}
},
"required": ["customer_id"],
"additionalProperties": false
}`),
}

The description tells the model when to use the operation, and the schema describes the JSON object a call may carry. Golato does not apply JSON Schema defaults or coerce values: what the model sends is what the tool receives.

Sending definitions with a Generator

When the host application runs the calls itself, the request carries the definitions.

stream, err := generator.Stream(ctx, generation.Request{
Model: model,
Messages: []message.Message{message.NewUserMessage("Which plan does C-100 use?")},
Tools: []tool.Definition{lookup},
})

Read calls from answer.ToolCalls() only when Result returned err == nil. Failed or aborted responses may retain partial calls, and a length stop can truncate arguments. Do not execute those calls. A Generator never invokes a Go function.

Attaching the implementation

Add Execute to make the definition runnable by an Agent. The function decodes the raw JSON arguments and returns model-visible content.

type lookupArgs struct {
CustomerID string `json:"customer_id"`
}

lookupTool := tool.Tool{
Name: lookup.Name,
Description: lookup.Description,
InputSchema: lookup.InputSchema,
Execute: func(ctx context.Context, call tool.Call, update tool.Update) (tool.Result, error) {
args, err := tool.DecodeArguments[lookupArgs](call.Arguments)
if err != nil {
return tool.Result{}, err
}
return tool.TextResult("Customer " + args.CustomerID + " is on the Gold plan."), nil
},
}

Register the tool on the Agent.

assistant, err := agent.New(agent.Config{
Generator: generator,
Model: model,
Tools: []tool.Tool{lookupTool},
})

Registration makes a tool available. The model still decides whether to call it. generation.Options.ToolChoice offers auto, none, required, and a named function choice.

Validating arguments and results

Arguments stay json.RawMessage until code decodes them, and the helpers cover each step:

  • tool.ValidateArguments checks only that the JSON value is an object.
  • tool.DecodeArguments[T] unmarshals the object into a Go type.
  • tool.ValidateResult checks the content returned to the model.
  • tool.Validate checks names, duplicates, the execute function, schema shape, and execution mode.
  • agent.New compiles each schema and rejects unsupported schema documents before a run starts.

Schemas may use same-document $ref and $dynamicRef. External references, $id, $recursiveRef, and $recursiveAnchor are rejected.

At execution time the Agent checks the structural keywords, meaning types, required fields, lengths, and additionalProperties. format and the content* keywords are annotations only, so a field declared with "format": "email" accepts a value like "not-an-email" without an error. Defaults are not applied, so a missing optional field stays missing, and values are not coerced, so "123" remains a string.

A Generator only forwards definitions to its adapter. It compiles no schema and never validates calls, because tool execution happens on the Agent path.

Returning text, images, or progress

The result constructors cover model-visible output.

tool.TextResult("done")
tool.ImageResult(pngBytes, "image/png")
tool.TextAndImageResult("rendered chart", pngBytes, "image/png")

For a slow operation, call update with provisional results while it runs. Updates only create events. They never enter conversation state, and calls after Execute returns are ignored. A provisional Terminate flag is ignored too.

Set Terminate on the final tool.Result when the Agent should stop after the current batch ends. Every call in that batch must return a successful terminating result. One error or ordinary sibling keeps the loop running.

Respecting cancellation

The function receives the run context. Check it around expensive work and pass it to downstream operations. Golato cannot stop a function that ignores cancellation, and side effects may already exist when cancellation arrives.

API reference