Skip to content

How to

Build an Agent with Tools

Define a typed tool, hand it to an agent, and read the run result

Read this when
  • Giving a model the ability to act
  • Wiring your first agent loop

Build the tool first, then pass it to the agent.

Define a tool

type inventoryQuery struct {
SKU string `json:"sku"`
}
type inventoryStatus struct {
SKU string `json:"sku"`
InStock bool `json:"in_stock"`
Count int `json:"count"`
}
lookupInventory := ai.DefineTool(
"lookup_inventory",
"Look up current stock for one SKU.",
func(ctx context.Context, in inventoryQuery) (inventoryStatus, error) {
return inventoryStatus{SKU: in.SKU, InStock: true, Count: 7}, nil
},
)

ai.DefineTool infers the input and output types from the function. It also builds the tool schemas. The JSON tags name the fields the model sees.

Create the agent

a, err := pi.Agent(
"openai-completions/gpt-5-mini",
pi.WithSystemPrompt("Answer with current inventory data."),
pi.WithTools(lookupInventory),
pi.WithMaxTurns(4),
)
defer a.Close()

pi.Agent wraps the model in the default agent loop. WithMaxTurns caps the tool loop.

Run it and read the answer

answer, err := agent.Prompt(ctx, a, "Do we have SKU mug-12?")
fmt.Println(answer.JoinText(""))

Use agent.Prompt for the blocking path. Use Run and Stream.Events when a UI needs events.

Add the built-in tools

fsys := sandbox.New(".", sandbox.Strict())
a, err := pi.Agent(
"openai-completions/gpt-5-mini",
pi.WithSystemPrompt("Work only inside the configured filesystem."),
pi.WithTools(
ai.DefineTool(read.ToolName, read.Description, read.New(read.FS{FS: fsys})),
ai.DefineParallelTool(grep.ToolName, grep.Description, grep.New(grep.FS{FS: fsys})),
ai.DefineTool(edit.ToolName, edit.Description, edit.New(edit.FS{FS: fsys})),
// ... bash, write, find, todowrite
),
pi.WithMaxTurns(8),
)

sandbox.Strict confines the file tools to the root directory. Add more tool packages only when the agent needs them.

Next: Stream a Run into a UI.