agent

package
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 16, 2025 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package agent provides autonomous tool-calling agent functionality for the gains library.

An agent orchestrates a conversation loop where the model can request tool calls, which are automatically executed and the results fed back to the model until the model produces a final response without tool calls.

Basic Usage

Create a registry, register tools with their handlers, then create an agent:

// Create and populate registry
registry := agent.NewRegistry()
registry.MustRegister(
    gains.Tool{
        Name:        "get_weather",
        Description: "Get current weather for a location",
        Parameters:  json.RawMessage(`{"type":"object","properties":{"location":{"type":"string"}},"required":["location"]}`),
    },
    func(ctx context.Context, call gains.ToolCall) (string, error) {
        var args struct{ Location string }
        json.Unmarshal([]byte(call.Arguments), &args)
        return fmt.Sprintf(`{"temp": 72, "location": %q}`, args.Location), nil
    },
)

// Create agent
a := agent.New(client, registry)

// Run and get final result (blocking)
result, err := a.Run(ctx, messages, agent.WithMaxSteps(5))

Streaming Events

Use RunStream() to receive events as the agent executes:

events := a.RunStream(ctx, messages, agent.WithMaxSteps(5))
for event := range events {
    switch event.Type {
    case agent.EventStreamDelta:
        fmt.Print(event.Delta)
    case agent.EventToolCallRequested:
        fmt.Printf("[Tool: %s]\n", event.ToolCall.Name)
    case agent.EventAgentComplete:
        fmt.Println("Done!")
    }
}

Human-in-the-Loop Approval

Use WithApprover to require approval before tool execution:

events := a.Run(ctx, messages,
    agent.WithApprover(func(ctx context.Context, call gains.ToolCall) (bool, string) {
        fmt.Printf("Approve %s? (y/n): ", call.Name)
        var input string
        fmt.Scanln(&input)
        return input == "y", "User rejected"
    }),
)

Configuration Options

The agent supports various configuration options:

  • WithMaxSteps(n): Limit iterations to prevent infinite loops (default: 10)
  • WithTimeout(d): Set overall execution timeout
  • WithHandlerTimeout(d): Set per-handler timeout (default: 30s)
  • WithParallelToolCalls(bool): Enable/disable parallel tool execution (default: true)
  • WithApprover(fn): Enable human-in-the-loop approval
  • WithApprovalRequired(tools...): Require approval only for specific tools
  • WithStopPredicate(fn): Custom termination condition
  • WithChatOptions(opts...): Pass options to underlying ChatProvider

Termination Conditions

The agent stops when any of these conditions are met:

  • The model responds without tool calls (TerminationComplete)
  • MaxSteps is reached (TerminationMaxSteps)
  • Timeout is exceeded (TerminationTimeout)
  • Context is cancelled (TerminationCancelled)
  • StopPredicate returns true (TerminationCustom)
  • All tool calls are rejected (TerminationRejected)
  • An error occurs (TerminationError)

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrMaxStepsReached indicates the agent hit the step limit.
	ErrMaxStepsReached = errors.New("agent: maximum steps reached")

	// ErrAgentTimeout indicates the overall timeout was exceeded.
	ErrAgentTimeout = errors.New("agent: timeout exceeded")
)

Sentinel errors for agent termination conditions.

Functions

func MustRegisterFunc

func MustRegisterFunc[T any](r *Registry, name, description string, params json.RawMessage, fn TypedHandler[T])

MustRegisterFunc is like RegisterFunc but panics on error.

func RegisterFunc

func RegisterFunc[T any](r *Registry, name, description string, params json.RawMessage, fn TypedHandler[T]) error

RegisterFunc registers a tool with a typed handler that automatically unmarshals the arguments JSON into the specified type T.

Example:

type SearchArgs struct {
    Query string `json:"query"`
}
params := schema.Object().
    Field("query", schema.String().Desc("Search query").Required()).
    MustBuild()
RegisterFunc(registry, "search", "Search the web", params,
    func(ctx context.Context, args SearchArgs) (string, error) {
        return doSearch(args.Query), nil
    },
)

Types

type Agent

type Agent struct {
	// contains filtered or unexported fields
}

Agent orchestrates autonomous tool-calling conversations.

func New

func New(c ChatClient, registry *Registry) *Agent

New creates a new Agent with the given chat client and tool registry.

func (*Agent) Run

func (a *Agent) Run(ctx context.Context, messages []ai.Message, opts ...Option) (*Result, error)

Run executes the agent loop and returns the final result. This is a blocking call that runs until the agent completes.

func (*Agent) RunStream

func (a *Agent) RunStream(ctx context.Context, messages []ai.Message, opts ...Option) <-chan Event

RunStream executes the agent loop and returns a channel of events. The channel is closed when the agent completes or encounters a fatal error. Callers should drain the channel to ensure proper cleanup.

type ApproverFunc

type ApproverFunc func(ctx context.Context, call ai.ToolCall) (approved bool, reason string)

ApproverFunc is called when a tool call requires approval. It returns true to approve the call, or false with a reason to reject it. The rejection reason is sent back to the model as an error result.

type ChatClient

type ChatClient interface {
	ChatStream(ctx context.Context, messages []ai.Message, opts ...ai.Option) (<-chan ai.StreamEvent, error)
}

ChatClient is the interface for chat capabilities needed by the agent.

type ErrToolAlreadyRegistered

type ErrToolAlreadyRegistered struct {
	Name string
}

ErrToolAlreadyRegistered is returned when registering a tool with a duplicate name.

func (*ErrToolAlreadyRegistered) Error

func (e *ErrToolAlreadyRegistered) Error() string

Error returns a formatted error message including the duplicate tool name.

type ErrToolExecution

type ErrToolExecution struct {
	Name string
	Err  error
}

ErrToolExecution wraps errors from tool handler execution.

func (*ErrToolExecution) Error

func (e *ErrToolExecution) Error() string

Error returns a formatted error message including the tool name and cause.

func (*ErrToolExecution) Unwrap

func (e *ErrToolExecution) Unwrap() error

Unwrap returns the underlying error for use with errors.Is and errors.As.

type ErrToolNotFound

type ErrToolNotFound struct {
	Name string
}

ErrToolNotFound is returned when a tool call references an unregistered tool.

func (*ErrToolNotFound) Error

func (e *ErrToolNotFound) Error() string

Error returns a formatted error message including the tool name.

type Event

type Event struct {
	// Type identifies the kind of event.
	Type EventType

	// Step is the current iteration number (1-indexed).
	Step int

	// Delta contains streaming content for EventStreamDelta events.
	Delta string

	// ToolCall contains the tool call for tool-related events.
	ToolCall *ai.ToolCall

	// ToolResult contains the result for EventToolResult events.
	ToolResult *ai.ToolResult

	// Response contains the model response for EventStepComplete and EventAgentComplete.
	Response *ai.Response

	// Error contains the error for EventError events.
	Error error

	// Message contains additional context (e.g., rejection reason).
	Message string

	// Timestamp is when the event occurred.
	Timestamp time.Time
}

Event represents an observable occurrence during agent execution.

type EventType

type EventType string

EventType identifies the kind of event occurring during agent execution.

const (
	// EventStepStart fires at the beginning of each agent iteration.
	EventStepStart EventType = "step_start"

	// EventStreamDelta fires for each streaming token during model response.
	EventStreamDelta EventType = "stream_delta"

	// EventToolCallRequested fires when the model requests a tool call.
	EventToolCallRequested EventType = "tool_call_requested"

	// EventToolCallApproved fires when a tool call is approved (human-in-the-loop).
	EventToolCallApproved EventType = "tool_call_approved"

	// EventToolCallRejected fires when a tool call is rejected by the approver.
	EventToolCallRejected EventType = "tool_call_rejected"

	// EventToolCallStarted fires before executing a tool handler.
	EventToolCallStarted EventType = "tool_call_started"

	// EventToolResult fires after a tool handler completes.
	EventToolResult EventType = "tool_result"

	// EventStepComplete fires at the end of each agent iteration.
	EventStepComplete EventType = "step_complete"

	// EventAgentComplete fires when the agent finishes execution.
	EventAgentComplete EventType = "agent_complete"

	// EventError fires when an error occurs during execution.
	EventError EventType = "error"
)

type Handler

type Handler func(ctx context.Context, call ai.ToolCall) (string, error)

Handler is a function that executes a tool call and returns a result. The context supports cancellation and timeout. The call contains the tool name, ID, and arguments as a JSON string. Returns the result content string, or an error if execution failed.

type Option

type Option func(*Options)

Option is a functional option for configuring agent execution.

func WithApprovalRequired

func WithApprovalRequired(tools ...string) Option

WithApprovalRequired specifies which tools require approval. If not called but WithApprover is used, all tools require approval. Call with specific tool names to only require approval for those tools.

func WithApprover

func WithApprover(fn ApproverFunc) Option

WithApprover sets the human-in-the-loop approval function. The function is called before each tool execution and must return an approval decision.

func WithChatOptions

func WithChatOptions(opts ...ai.Option) Option

WithChatOptions passes options through to the ChatProvider. These options are applied to every chat call made by the agent.

func WithHandlerTimeout

func WithHandlerTimeout(d time.Duration) Option

WithHandlerTimeout sets the timeout for each individual tool handler. Default is 30 seconds. Set to 0 for no per-handler timeout.

func WithMaxSteps

func WithMaxSteps(n int) Option

WithMaxSteps sets the maximum number of agent iterations. Default is 10. Set to 0 for unlimited (not recommended).

func WithMaxTokens

func WithMaxTokens(n int) Option

WithMaxTokens is a convenience option to set max tokens for chat calls.

func WithModel

func WithModel(model ai.Model) Option

WithModel is a convenience option to set the model for chat calls.

func WithParallelToolCalls

func WithParallelToolCalls(enabled bool) Option

WithParallelToolCalls enables or disables concurrent tool execution. Default is true.

func WithStopPredicate

func WithStopPredicate(fn StopFunc) Option

WithStopPredicate sets a custom termination condition. The predicate is called after each step with the step number and response. Return true to stop the agent.

func WithTemperature

func WithTemperature(t float64) Option

WithTemperature is a convenience option to set temperature for chat calls.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets a deadline for the entire agent execution.

type Options

type Options struct {
	// MaxSteps limits the number of agent iterations.
	// Set to 0 for unlimited (not recommended). Default is 10.
	MaxSteps int

	// Timeout sets a deadline for the entire agent execution.
	// A value of 0 means no timeout (context deadline applies).
	Timeout time.Duration

	// HandlerTimeout sets the timeout for each individual tool handler.
	// A value of 0 means no per-handler timeout. Default is 30 seconds.
	HandlerTimeout time.Duration

	// ParallelToolCalls enables concurrent execution of multiple tool calls.
	// Default is true.
	ParallelToolCalls bool

	// Approver enables human-in-the-loop approval for tool calls.
	// If nil, all tool calls are automatically approved.
	Approver ApproverFunc

	// ApprovalRequired specifies which tool names require approval.
	// If empty and Approver is set, all tools require approval.
	// If non-empty, only the listed tools require approval.
	ApprovalRequired []string

	// StopPredicate is a custom termination condition.
	// Called after each step; return true to stop the agent.
	StopPredicate StopFunc

	// ChatOptions are passed through to the underlying ChatProvider.
	ChatOptions []ai.Option
}

Options contains configuration for agent execution.

func ApplyOptions

func ApplyOptions(opts ...Option) *Options

ApplyOptions applies functional options to an Options struct with defaults.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry manages registered tools and their handlers. It is safe for concurrent use.

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates an empty tool registry.

func (*Registry) Execute

func (r *Registry) Execute(ctx context.Context, call ai.ToolCall) (ai.ToolResult, error)

Execute runs the handler for a tool call and returns a ToolResult. If the tool is not found, returns ErrToolNotFound. If the handler returns an error, the error is captured in ToolResult.IsError and the error message is returned as the content (allowing the model to recover).

func (*Registry) Get

func (r *Registry) Get(name string) (Handler, bool)

Get retrieves a handler by tool name. Returns the handler and true if found, or nil and false if not found.

func (*Registry) GetTool

func (r *Registry) GetTool(name string) (ai.Tool, bool)

GetTool retrieves a tool definition by name. Returns the tool and true if found, or empty tool and false if not found.

func (*Registry) Len

func (r *Registry) Len() int

Len returns the number of registered tools.

func (*Registry) MustRegister

func (r *Registry) MustRegister(tool ai.Tool, handler Handler)

MustRegister is like Register but panics on error.

func (*Registry) Names

func (r *Registry) Names() []string

Names returns the names of all registered tools.

func (*Registry) Register

func (r *Registry) Register(tool ai.Tool, handler Handler) error

Register adds a tool with its handler to the registry. Returns an error if a tool with the same name is already registered.

func (*Registry) Tools

func (r *Registry) Tools() []ai.Tool

Tools returns all registered tool definitions. This is used to pass the tools to the ChatProvider.

func (*Registry) Unregister

func (r *Registry) Unregister(name string)

Unregister removes a tool from the registry. It is a no-op if the tool is not registered.

type Result

type Result struct {
	// Response is the final response from the model.
	Response *ai.Response

	// Steps is the number of iterations completed.
	Steps int

	// Termination indicates why execution stopped.
	Termination TerminationReason

	// TotalUsage aggregates token usage across all steps.
	TotalUsage ai.Usage

	// Error contains any error that caused termination (if applicable).
	Error error
	// contains filtered or unexported fields
}

Result represents the final outcome of an agent execution.

func (*Result) LastMessages

func (r *Result) LastMessages(n int) []ai.Message

LastMessages returns the last n messages from the conversation history. If n exceeds the total message count, all messages are returned.

func (*Result) MessageCount

func (r *Result) MessageCount() int

MessageCount returns the number of messages in the conversation history.

func (*Result) Messages

func (r *Result) Messages() []ai.Message

Messages returns the conversation history as a slice.

type StopFunc

type StopFunc func(step int, response *ai.Response) bool

StopFunc is a custom predicate to determine if the agent should stop. It receives the current step number and the latest response. Return true to stop the agent.

type TerminationReason

type TerminationReason string

TerminationReason indicates why the agent stopped execution.

const (
	// TerminationComplete indicates normal completion (no more tool calls).
	TerminationComplete TerminationReason = "complete"

	// TerminationMaxSteps indicates the step limit was reached.
	TerminationMaxSteps TerminationReason = "max_steps"

	// TerminationTimeout indicates the context deadline was exceeded.
	TerminationTimeout TerminationReason = "timeout"

	// TerminationCustom indicates a custom stop predicate returned true.
	TerminationCustom TerminationReason = "custom"

	// TerminationRejected indicates all tool calls were rejected.
	TerminationRejected TerminationReason = "rejected"

	// TerminationError indicates an unrecoverable error occurred.
	TerminationError TerminationReason = "error"

	// TerminationCancelled indicates context cancellation.
	TerminationCancelled TerminationReason = "cancelled"
)

type TypedHandler

type TypedHandler[T any] func(ctx context.Context, args T) (string, error)

TypedHandler is a function that executes a tool call with typed arguments. The args parameter is automatically unmarshaled from the tool call's JSON arguments.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL