Tools let agents take actions. The Go SDK currently supports three kinds of tools:
Hosted OpenAI tools (web search, file search, code interpreter, computer use) are not supported yet — see Differences from Python.
NewFunctionTool[A, R] turns a Go function into a tool. The argument type A (a struct) is reflected into a strict JSON schema; the result R is returned to the model (serialized to JSON unless it is already a string).
type queryArgs struct {
SQL string `json:"sql" jsonschema:"the SQL query to run"`
Limit int `json:"limit" jsonschema:"max rows to return"`
}
runQuery := agents.NewFunctionTool("run_query", "Run a read-only SQL query.",
func(ctx context.Context, tc *agents.ToolContext, args queryArgs) ([]map[string]any, error) {
return db.Query(ctx, args.SQL, args.Limit)
})
agent.Tools = []agents.Tool{runQuery}
jsonschema:"..." struct tag is the parameter description shown to the model.ctx is the run’s context (cancellation propagates into tools).tc *ToolContext carries the run context plus call metadata (ToolName, ToolCallID, ToolArguments).This replaces Python’s @function_tool decorator: compile-time generics instead of signature inspection, struct tags instead of docstrings.
Strict schema mode is on by default and the reflected schema is rewritten to the strict subset OpenAI requires (additionalProperties:false, all properties required, …). Disable per tool when you need schema features strict mode forbids:
t := agents.NewFunctionTool("lookup", "…", fn)
t.Strict = false
By default a tool error is fed back to the model as the tool output so it can recover (DefaultToolErrorFunction), matching the Python SDK. Customize the message, or make errors fatal:
t.FailureErrorFunction = func(ctx context.Context, tc *agents.ToolContext, err error) string {
return "lookup failed, try a different spelling"
}
t.FailureErrorFunction = nil // a tool error now aborts the whole run
Timeout bounds one invocation; on expiry the tool’s context is canceled and the call fails with *agents.ToolTimeoutError (fed back to the model via FailureErrorFunction when set, fatal otherwise):
t.Timeout = 30 * time.Second
IsEnabled decides per run whether the tool is offered to the model:
t.IsEnabled = func(ctx context.Context, rc *agents.RunContext, agent *agents.Agent) (bool, error) {
return rc.Context.(*MyAppContext).IsAdmin, nil
}
NeedsApproval (or per-call NeedsApprovalFunc) pauses the run before the tool executes, surfacing an interruption you approve or reject — see Human-in-the-loop.
Tools can carry their own input/output guardrails — see Guardrails.
By default a tool’s return value goes back to the model as text (JSON for non-string values). To hand the model native image or file input instead, return a ToolOutputContent — or a []ToolOutputContent for several parts — which becomes a function_call_output content list:
type chartArgs struct {
Metric string `json:"metric" jsonschema:"which metric to chart"`
}
renderChart := agents.NewFunctionTool("render_chart", "Render a chart as an image.",
func(ctx context.Context, tc *agents.ToolContext, args chartArgs) ([]agents.ToolOutputContent, error) {
png := plot(args.Metric) // []byte
return []agents.ToolOutputContent{
agents.ToolOutputText{Text: "chart for " + args.Metric},
agents.ToolOutputImageFromBytes("image/png", png),
}, nil
})
The three content parts mirror the Responses API:
ToolOutputText{Text} — a text part (same as returning the string directly, but combinable with images/files).ToolOutputImage{ImageURL, FileID, Detail} — native image input; set ImageURL (a URL or a base64 data: URL — ToolOutputImageFromBytes(mime, bytes) builds one) or FileID (an uploaded file).ToolOutputFile{FileData, FileURL, FileID, Filename} — native file input (e.g. a PDF).A runnable example lives in examples/toolimage. This is the Go counterpart of Python’s ToolOutputText / ToolOutputImage / ToolOutputFileContent; it is also what lets MCP image results reach the model as real images (MCP).
FunctionTool is an exported struct, so advanced callers can build one directly with a custom ParamsJSONSchema and raw-JSON OnInvoke:
t := &agents.FunctionTool{
Name: "echo",
Description: "Echo the arguments back.",
ParamsJSONSchema: map[string]any{"type": "object", "properties": map[string]any{"text": map[string]any{"type": "string"}}, "required": []any{"text"}, "additionalProperties": false},
Strict: true,
FailureErrorFunction: agents.DefaultToolErrorFunction,
OnInvoke: func(ctx context.Context, tc *agents.ToolContext, argsJSON string) (any, error) {
return argsJSON, nil
},
}
sandbox.CodeTool wraps an isolated execution backend (local, Docker) as a “run this code” tool — see Sandbox agents.
tools/bravesearch is a ready-made function tool that searches the web via the Brave Search API. It is a plain, provider-agnostic function tool — the SDK calls Brave’s REST API from Go and returns formatted results — so it works with any model backend (the SDK does not use provider-hosted search tools).
import "github.com/zzir/agents-go/tools/bravesearch"
search, err := bravesearch.New(bravesearch.Options{
// APIKey defaults to the BRAVE_API_KEY environment variable.
Count: 5, // results to request (1-20)
})
if err != nil {
log.Fatal(err)
}
agent := &agents.Agent{
Name: "research-bot",
Model: "gpt-4o",
Tools: []agents.Tool{search},
}
The model controls only the query; Count, Country, SearchLang, SafeSearch and Freshness are fixed by Options. A runnable example lives in examples/bravesearch.
tools/editor gives an agent file-editing tools following the str_replace editor pattern — provider-agnostic function tools, no hosted apply_patch and no diff parser (the SDK does not model OpenAI’s hosted apply_patch/local_shell tools):
import "github.com/zzir/agents-go/tools/editor"
agent := &agents.Agent{
Name: "coder",
Model: "gpt-4o",
Tools: editor.NewTools("./workspace"), // view_file, create_file, str_replace, insert_text
}
view_file — print a file with line numbers, or list a directory.create_file — create a new file (fails if it exists, so edits never clobber).str_replace — replace a snippet that occurs exactly once (the model includes surrounding context to make it unique); 0 or >1 matches are rejected so edits stay precise.insert_text — insert a line after a given line number.Every read and write is confined to the directory passed to NewTools via Go’s os.Root, so ../ traversal and symlink escapes are rejected, and reads are capped at 1 MiB.