golem

package module
v0.3.1 Latest Latest
Warning

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

Go to latest
Published: Aug 21, 2026 License: MIT Imports: 8 Imported by: 0

README

golem

Golem

Golem is a Go-first framework for building dependable AI agents: typed dependencies and outputs, explicit tools, composable models, and an observable execution loop.

It takes inspiration from the ergonomics of Python agent frameworks such as Pydantic AI, but follows Go's strengths instead: compile-time contracts, context.Context, explicit error handling, small interfaces, and standard-library-friendly integrations.

Status

v0.3.1 — the core execution contract includes typed agents, evidence-preserving runs, self-correction, retries with fallback models, streaming, structured output, explicit tool deadlines and choice, opt-in ordered parallel tool execution, multimodal image input, history processing with a trim builtin, token/request/tool-call usage bounds, deterministic test models, and provider adapters for OpenAI-compatible APIs (twelve services), Anthropic, Google Gemini, Azure OpenAI, and AWS Bedrock. The guides publish as a documentation site. The public API remains intentionally small; additive changes only until v1.

Direction

  • Make a useful agent the shortest path: configure a model, declare tools, run with dependencies, get a typed result.
  • Make important behavior explicit: model calls, tool execution, iteration limits, usage, and validation are visible in the run result.
  • Keep infrastructure replaceable: applications choose models, tracing, storage, and transport through narrow interfaces.
  • Prefer Go-native composition over ports of Python metaprogramming.

Read the foundation brief before proposing a new public abstraction. Contributor and coding-agent rules live in AGENTS.md, and the development roadmap lives in docs/ROADMAP.md.

Installation

go get github.com/abubakarsiddik31/golem

Golem needs Go 1.26.5 or newer and depends only on the Go standard library.

Quick start

client, err := openai.New(openai.Config{
    APIKey: os.Getenv("OPENAI_API_KEY"),
    Model:  "gpt-4o-mini",
})
agent, err := golem.New[struct{}, string](client,
    golem.DecodeFunc[string](func(_ context.Context, r model.Response) (string, error) {
        return r.Message.Content, nil
    }),
)
result, err := agent.Run(ctx, golem.RunContext[struct{}]{}, "Reply with exactly the word: pong")

Every run returns the typed output, the full normalized conversation (result.Messages, durable additive-only JSON), and cumulative usage — and fails with a RunError carrying an inspectable stage (model, tool, decode, loop, usage) that preserves the cause for errors.Is and errors.As.

Documentation

Guides are the source of truth for each capability; this README only indexes them.

Guide Covers
Getting started The smallest agent, result shape, error stages
Providers OpenAI-compatible and Anthropic adapters, error classification
Tools and dependencies Typed tools, dependencies, and controlled parallel execution
Tool timeouts Context-aware deadlines for individual tool calls
Conversations and history Multi-turn runs, durable message JSON, history trimming
Multimodal input Images in prompts, per-provider mapping
Structured output Output schemas, tool-mode output, DecodeJSON
Self-correction Output and tool rejection budgets (ModelRetry)
Retries Transient model failures, backoff, fallback models
Streaming RunStream, the streaming capability port, SSE adapters
Usage limits Bounding tokens, requests, and tool calls
Testing without a provider Deterministic fakes, contract assertions

Design decisions live in docs/adr/; each guide links the ADR that decided its behavior.

Examples

Runnable programs live in examples/; provider-backed ones print instructions and exit unless their API key is set.

Example Shows
minimal Smallest agent against an OpenAI-compatible API
tools Typed tool with a run dependency
structured-output Output schema + JSON decoding
structured-output-tool Tool-mode structured output
streaming RunStream printing fragments as they arrive
conversation Interactive multi-turn chat with history
self-correction Tool rejecting correctable arguments
fallback Primary model with a fallback and a request bound
anthropic Anthropic Messages API adapter
gemini Google Gemini GenerateContent adapter
azure Azure OpenAI deployment adapter
bedrock AWS Bedrock Converse adapter with SigV4
testing-without-a-provider Scripted fake model, offline and deterministic
OPENAI_API_KEY=sk-... go run ./examples/minimal
go run ./examples/testing-without-a-provider   # no credentials needed

Package shape

golem/        Agent configuration and typed run API
model/        Provider-neutral model request/response contract
tool/         Tool declarations and execution contracts
providers/    Stdlib-only adapters implementing model.Model
internal/     Execution loop and non-public mechanics
examples/     Runnable programs per capability
docs/guides/  Feature guides (source of truth for behavior)
docs/adr/     Decisions that shape the public contracts

Development

go test ./...
go vet ./...

The guides double as the published documentation site; preview it with mkdocs serve — see docs/website.md. Logo usage guidelines and brand assets live in assets/brand/.

Community

License

Released under the MIT License.

Documentation

Overview

Package golem provides typed building blocks for AI agents in Go.

Index

Examples

Constants

View Source
const DefaultMaxIterations = 10

DefaultMaxIterations bounds model turns per run when no explicit limit is configured.

Variables

This section is empty.

Functions

This section is empty.

Types

type Agent

type Agent[Deps any, Output any] struct {
	// contains filtered or unexported fields
}

Agent combines a model, instructions, tools, and a typed output boundary. Deps is the dependency value tools receive on every run.

Example

ExampleAgent demonstrates an agent that executes a typed tool with an explicit dependency value and returns the full run evidence.

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"

	"github.com/abubakarsiddik31/golem"
	"github.com/abubakarsiddik31/golem/model"
	"github.com/abubakarsiddik31/golem/tool"
)

// diceModel scripts the tool exchange: it requests the player-name tool
// once, then produces a final answer. Real applications implement
// model.Model with a provider adapter.
type diceModel struct{ requests []model.Request }

func (m *diceModel) Generate(ctx context.Context, request model.Request) (model.Response, error) {
	m.requests = append(m.requests, request)
	for _, message := range request.Messages {
		if message.Role == model.RoleTool {
			return model.Response{
				Message: model.Message{Role: model.RoleAssistant, Content: fmt.Sprintf("winner: %s", message.Content)},
				Usage:   model.Usage{InputTokens: 54, OutputTokens: 2},
			}, nil
		}
	}
	return model.Response{
		Message: model.Message{Role: model.RoleAssistant, ToolCalls: []model.ToolCall{
			{ID: "call-1", Name: "get_player_name", Args: json.RawMessage(`{}`)},
		}},
		Usage: model.Usage{InputTokens: 54, OutputTokens: 2},
	}, nil
}

func main() {
	getPlayerName := tool.MustNew(tool.Tool[string]{
		Name:        "get_player_name",
		Description: "Get the player's name.",
		Schema:      json.RawMessage(`{"type":"object"}`),
		Exec: func(ctx context.Context, playerName string, args json.RawMessage) (string, error) {
			return playerName, nil
		},
	})

	agent, err := golem.New[string, string](
		&diceModel{},
		golem.DecodeFunc[string](func(ctx context.Context, response model.Response) (string, error) {
			return response.Message.Content, nil
		}),
		golem.WithTools[string, string](getPlayerName),
	)
	if err != nil {
		log.Fatal(err)
	}

	result, err := agent.Run(context.Background(), golem.RunContext[string]{Deps: "Anne"}, "My guess is 4")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(result.Output)
	fmt.Println(len(result.Messages), "messages,", result.Usage.OutputTokens, "output tokens")
}
Output:
winner: Anne
4 messages, 4 output tokens

func New

func New[Deps any, Output any](
	modelClient model.Model,
	decoder OutputDecoder[Output],
	options ...Option[Deps, Output],
) (*Agent[Deps, Output], error)

New creates an Agent. A model and decoder are both required: Golem never guesses how untrusted model output becomes a typed application value.

func (*Agent[Deps, Output]) Run

func (a *Agent[Deps, Output]) Run(ctx context.Context, runCtx RunContext[Deps], prompt string, opts ...RunOption) (Result[Output], error)

Run executes the agent: it asks the configured model to answer prompt, executing requested tools along the way, and decodes the final response. Model calls are attempted up to the configured attempt limit; exhausted retries fail with the model stage, preserving the provider cause. Output the decoder rejects with *model.ModelRetry is fed back for correction up to the configured output retry budget, and tool calls a tool rejects with *model.ModelRetry are fed back up to the tool retry budget.

Errors are wrapped in RunError with the failing stage. Cancellation and deadline errors are returned unwrapped so callers can match them directly with errors.Is.

func (*Agent[Deps, Output]) RunStream

func (a *Agent[Deps, Output]) RunStream(ctx context.Context, runCtx RunContext[Deps], prompt string, onDelta func(model.Delta) error, opts ...RunOption) (Result[Output], error)

RunStream executes the agent like Run while streaming progress: every model fragment — text, tool-call arguments, and re-streamed correction rounds — is forwarded to onDelta in arrival order, across tool turns. The returned Result is identical in shape to Run's; deltas are advisory progress on top of the canonical run.

The model must implement model.StreamingModel; otherwise RunStream fails up front with a plain error, before any stage runs — there is no silent fallback to non-streaming generation. Streamed model turns are single-attempt: retryable failures fail the run at the model stage instead of being retried, because a retried stream would replay fragments the caller already saw. An error returned from onDelta stops the run and surfaces at the model stage with the original error reachable via errors.Is. A nil onDelta is allowed and discards fragments.

Example

ExampleAgent_RunStream shows a run that streams every fragment to the callback while producing the same typed result as Run.

package main

import (
	"context"
	"fmt"
	"log"
	"strings"

	"github.com/abubakarsiddik31/golem"
	"github.com/abubakarsiddik31/golem/model"
)

// morningModel streams its answer as two fragments.
type morningModel struct{}

func (m *morningModel) Generate(ctx context.Context, request model.Request) (model.Response, error) {
	return model.Response{Message: model.Message{Role: model.RoleAssistant, Content: "good morning"}}, nil
}

func (m *morningModel) GenerateStream(ctx context.Context, request model.Request, onDelta func(model.Delta) error) (model.Response, error) {
	for _, fragment := range []string{"good ", "morning"} {
		if err := onDelta(model.Delta{Content: fragment}); err != nil {
			return model.Response{}, err
		}
	}
	return model.Response{Message: model.Message{Role: model.RoleAssistant, Content: "good morning"}}, nil
}

func main() {
	agent, err := golem.New[struct{}, string](&morningModel{},
		golem.DecodeFunc[string](func(ctx context.Context, response model.Response) (string, error) {
			return response.Message.Content, nil
		}))
	if err != nil {
		log.Fatal(err)
	}

	var fragments []string
	result, err := agent.RunStream(context.Background(), golem.RunContext[struct{}]{}, "greet me",
		func(d model.Delta) error {
			fragments = append(fragments, d.Content)
			return nil
		})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(strings.Join(fragments, "|"))
	fmt.Println(result.Output)
}
Output:
good |morning
good morning

func (*Agent[Deps, Output]) RunStreamWithHistory

func (a *Agent[Deps, Output]) RunStreamWithHistory(ctx context.Context, runCtx RunContext[Deps], history []model.Message, prompt string, onDelta func(model.Delta) error, opts ...RunOption) (Result[Output], error)

RunStreamWithHistory continues a conversation like RunWithHistory while streaming progress; see RunStream for the streaming contract.

func (*Agent[Deps, Output]) RunWithHistory

func (a *Agent[Deps, Output]) RunWithHistory(ctx context.Context, runCtx RunContext[Deps], history []model.Message, prompt string, opts ...RunOption) (Result[Output], error)

RunWithHistory continues a conversation. history — typically the Result.Messages of a previous run — is sent before a fresh user prompt, and the result carries the full reconstructed conversation so runs chain. The agent's current instructions govern the request: any system messages in history are replaced by them, so guidance is re-evaluated per run and never duplicated.

History is repaired before the request is built so it keeps the call/result pairing providers require: a tool call that never received a result — from a crashed or cancelled run, or hand-built history — gets a synthesized result stating no outcome was produced, and a result whose call is absent is dropped.

Example

ExampleAgent_RunWithHistory continues a conversation across two runs: the first result's messages become the second run's history, and the second result carries the full chained conversation.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/abubakarsiddik31/golem"
	"github.com/abubakarsiddik31/golem/model"
)

// conversationModel answers with the last user prompt it has seen.
type conversationModel struct{}

func (m *conversationModel) Generate(ctx context.Context, request model.Request) (model.Response, error) {
	last := ""
	for _, message := range request.Messages {
		if message.Role == model.RoleUser {
			last = message.Content
		}
	}
	return model.Response{
		Message: model.Message{Role: model.RoleAssistant, Content: fmt.Sprintf("heard: %s", last)},
	}, nil
}

func main() {
	agent, err := golem.New[struct{}, string](&conversationModel{},
		golem.DecodeFunc[string](func(ctx context.Context, response model.Response) (string, error) {
			return response.Message.Content, nil
		}))
	if err != nil {
		log.Fatal(err)
	}
	runCtx := golem.RunContext[struct{}]{}

	first, err := agent.Run(context.Background(), runCtx, "hello")
	if err != nil {
		log.Fatal(err)
	}
	second, err := agent.RunWithHistory(context.Background(), runCtx, first.Messages, "goodbye")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(first.Output)
	fmt.Println(second.Output)
	fmt.Println(len(second.Messages), "messages in the chained conversation")
}
Output:
heard: hello
heard: goodbye
4 messages in the chained conversation

type DecodeFunc

type DecodeFunc[Output any] func(context.Context, model.Response) (Output, error)

DecodeFunc adapts a function to an OutputDecoder.

func (DecodeFunc[Output]) Decode

func (f DecodeFunc[Output]) Decode(ctx context.Context, response model.Response) (Output, error)

Decode converts response using f.

type HistoryProcessor

type HistoryProcessor func(ctx context.Context, history []model.Message) ([]model.Message, error)

HistoryProcessor rewrites the history of one run before the request is built. It receives the history exactly as the caller supplied it — before validation and repair — and returns the history to send; the returned messages are then part-validated, repaired, and sent. The processor runs once per run; an error fails the run before any model call. Processors must be deterministic enough for their caller's purposes: nothing re-runs them.

func TrimHistory

func TrimHistory(maxMessages int) HistoryProcessor

TrimHistory returns a HistoryProcessor that keeps the newest maxMessages messages of a conversation. After the cut it advances past messages that cannot open a request: tool results whose requesting call was trimmed, and assistant turns carrying tool calls whose results were trimmed — repair would otherwise reattach synthesized results, paying tokens for evidence the trim meant to drop. A budget below 1, or a history with nothing left after the boundary rule, fails the run.

type InstructionsFunc

type InstructionsFunc[Deps any] func(ctx context.Context, runCtx RunContext[Deps]) string

InstructionsFunc builds the instructions of one run. ctx is the caller's run context, so a builder that consults external state can honor cancellation.

type Option

type Option[Deps any, Output any] func(*Agent[Deps, Output])

Option configures an Agent during construction.

func WithHistoryProcessor

func WithHistoryProcessor[Deps any, Output any](processor HistoryProcessor) Option[Deps, Output]

WithHistoryProcessor configures a processor applied to the history of every run, before validation and repair, on Run and its history-aware and streaming variants. See TrimHistory for a builtin.

func WithInstructions

func WithInstructions[Deps any, Output any](instructions string) Option[Deps, Output]

WithInstructions configures stable system instructions for every run.

func WithInstructionsFunc

func WithInstructionsFunc[Deps any, Output any](fn InstructionsFunc[Deps]) Option[Deps, Output]

WithInstructionsFunc configures instructions evaluated at the start of every run, so guidance can depend on runtime state such as the run's dependency value. The result joins static instructions — static text first, separated by a blank line — and an empty result contributes nothing. History system messages are replaced by the resolved instructions of the current run, exactly as for static instructions. Register one function; compose closures when several sources apply.

Example

ExampleWithInstructionsFunc shows instructions resolved per run: the function's result joins the static instructions, and both flow to the model as the run's system guidance.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/abubakarsiddik31/golem"
	"github.com/abubakarsiddik31/golem/model"
)

// instructedModel echoes the instructions it was given, if any.
type instructedModel struct{}

func (m *instructedModel) Generate(ctx context.Context, request model.Request) (model.Response, error) {
	for _, message := range request.Messages {
		if message.Role == model.RoleSystem {
			return model.Response{
				Message: model.Message{Role: model.RoleAssistant, Content: message.Content},
			}, nil
		}
	}
	return model.Response{Message: model.Message{Role: model.RoleAssistant, Content: ""}}, nil
}

func main() {
	type player struct{ Name string }
	agent, err := golem.New[player, string](&instructedModel{},
		golem.DecodeFunc[string](func(ctx context.Context, response model.Response) (string, error) {
			return response.Message.Content, nil
		}),
		golem.WithInstructions[player, string]("Always greet the player."),
		golem.WithInstructionsFunc[player, string](
			func(ctx context.Context, runCtx golem.RunContext[player]) string {
				return "The player's name is " + runCtx.Deps.Name + "."
			}),
	)
	if err != nil {
		log.Fatal(err)
	}

	result, err := agent.Run(context.Background(), golem.RunContext[player]{Deps: player{Name: "Anne"}}, "greet")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(result.Output)
}
Output:
Always greet the player.

The player's name is Anne.

func WithMaxAttempts

func WithMaxAttempts[Deps any, Output any](attempts int) Option[Deps, Output]

WithMaxAttempts bounds how many times each model call may be attempted, including the first, when the model reports a retryable failure (408, 429, 5xx, transport faults). Tool and decode failures are never retried. The default is 1 — retries are opt-in — and values below 1 fail New.

func WithMaxIterations

func WithMaxIterations[Deps any, Output any](iterations int) Option[Deps, Output]

WithMaxIterations bounds model turns per run. It must be at least 1; otherwise New fails.

func WithOutputRetries

func WithOutputRetries[Deps any, Output any](retries int) Option[Deps, Output]

WithOutputRetries sets how many correction rounds a decoder may request by returning *model.ModelRetry: each round appends the rejection reason to the conversation and asks the model again. The default is 0 — self-correction is opt-in — and negative values fail New.

Example

ExampleWithOutputRetries shows a decoder rejecting a correctable response: the run feeds the rejection back to the model, which answers again within the configured budget.

package main

import (
	"context"
	"fmt"
	"log"
	"strconv"
	"strings"

	"github.com/abubakarsiddik31/golem"
	"github.com/abubakarsiddik31/golem/model"
)

// pickyModel answers with a word first, then with the digit once corrected.
type pickyModel struct{ calls int }

func (m *pickyModel) Generate(ctx context.Context, request model.Request) (model.Response, error) {
	m.calls++
	content := "seven"
	if m.calls > 1 {
		content = "7"
	}
	return model.Response{Message: model.Message{Role: model.RoleAssistant, Content: content}}, nil
}

func main() {
	agent, err := golem.New[struct{}, int](&pickyModel{},
		golem.DecodeFunc[int](func(ctx context.Context, response model.Response) (int, error) {
			value, err := strconv.Atoi(strings.TrimSpace(response.Message.Content))
			if err != nil {
				return 0, &model.ModelRetry{Err: fmt.Errorf("answer must be an integer, got %q", response.Message.Content)}
			}
			return value, nil
		}),
		golem.WithOutputRetries[struct{}, int](2),
	)
	if err != nil {
		log.Fatal(err)
	}

	result, err := agent.Run(context.Background(), golem.RunContext[struct{}]{}, "pick a number")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(result.Output)
	fmt.Println(len(result.Messages), "messages in the corrected conversation")
}
Output:
7
4 messages in the corrected conversation

func WithOutputSchema

func WithOutputSchema[Deps any, Output any](schema json.RawMessage) Option[Deps, Output]

WithOutputSchema declares the JSON Schema document describing the agent's expected final answer. Adapters that support structured output map it to their native mechanism; adapters that do not ignore it. The schema describes the expected shape to the model — the decoder remains the validation boundary. An empty schema disables the behavior; a non-empty schema that is not valid JSON fails New. Mutually exclusive with WithOutputTool, which expresses the same intent through an output tool call.

Example

ExampleWithOutputSchema pairs a declared output schema — sent to the model as structured-output instructions by adapters that support them — with the JSON decoder that validates the response content.

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"

	"github.com/abubakarsiddik31/golem"
	"github.com/abubakarsiddik31/golem/model"
)

// forecastModel answers with JSON shaped by the output schema.
type forecastModel struct{}

func (m *forecastModel) Generate(ctx context.Context, request model.Request) (model.Response, error) {
	return model.Response{
		Message: model.Message{Role: model.RoleAssistant, Content: `{"city":"Lagos","celsius":31}`},
		Usage:   model.Usage{InputTokens: 20, OutputTokens: 6},
	}, nil
}

func main() {
	type weather struct {
		City    string `json:"city"`
		Celsius int    `json:"celsius"`
	}
	agent, err := golem.New[struct{}, weather](&forecastModel{}, golem.DecodeJSON[weather](),
		golem.WithOutputSchema[struct{}, weather](json.RawMessage(`{
			"type": "object",
			"properties": {"city": {"type": "string"}, "celsius": {"type": "integer"}},
			"required": ["city", "celsius"],
			"additionalProperties": false
		}`)),
	)
	if err != nil {
		log.Fatal(err)
	}

	result, err := agent.Run(context.Background(), golem.RunContext[struct{}]{}, "forecast for Lagos")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%s: %d°C\n", result.Output.City, result.Output.Celsius)
}
Output:
Lagos: 31°C

func WithOutputTool

func WithOutputTool[Deps any, Output any](name, description string, schema json.RawMessage) Option[Deps, Output]

WithOutputTool declares tool-mode structured output: schema becomes the parameters of a synthesized output tool offered to the model, and the run ends on the model's first call to it. The call's arguments reach the decoder as the final response content, so DecodeJSON validates them like any other response — the decoder remains the validation boundary.

Tool mode reaches every model with tool calling, including those without native JSON-schema output support. Calls co-emitted with the output call are not executed; they are closed with an interrupted result so the conversation keeps the call/result pairing providers require. The output call itself is closed in the result evidence after decoding: a recorded result on success, a rejection bound to the call when the decoder asks for correction. Mutually exclusive with WithOutputSchema. name must not collide with a registered tool; description may be empty; schema must be a non-empty valid JSON document.

Example

ExampleWithOutputTool declares tool-mode structured output: the schema becomes the parameters of a synthesized output tool, the run ends on the model's first call to it, and the call's arguments reach the decoder as the final response content.

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"

	"github.com/abubakarsiddik31/golem"
	"github.com/abubakarsiddik31/golem/model"
)

// reportingModel calls the output tool with its final arguments.
type reportingModel struct{}

func (m *reportingModel) Generate(ctx context.Context, request model.Request) (model.Response, error) {
	return model.Response{
		Message: model.Message{Role: model.RoleAssistant, ToolCalls: []model.ToolCall{
			{ID: "out-1", Name: "record_weather", Args: json.RawMessage(`{"city":"Lagos","celsius":31}`)},
		}},
		Usage: model.Usage{InputTokens: 20, OutputTokens: 6},
	}, nil
}

func main() {
	type weather struct {
		City    string `json:"city"`
		Celsius int    `json:"celsius"`
	}
	agent, err := golem.New[struct{}, weather](&reportingModel{}, golem.DecodeJSON[weather](),
		golem.WithOutputTool[struct{}, weather]("record_weather",
			"Record the final weather report.", json.RawMessage(`{
				"type": "object",
				"properties": {"city": {"type": "string"}, "celsius": {"type": "integer"}},
				"required": ["city", "celsius"],
				"additionalProperties": false
			}`)),
	)
	if err != nil {
		log.Fatal(err)
	}

	result, err := agent.Run(context.Background(), golem.RunContext[struct{}]{}, "forecast for Lagos")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%s: %d°C\n", result.Output.City, result.Output.Celsius)
}
Output:
Lagos: 31°C

func WithParallelToolCalls

func WithParallelToolCalls[Deps any, Output any]() Option[Deps, Output]

WithParallelToolCalls lets independent calls returned in one model response run concurrently. Result messages remain in model emission order. A tool marked Sequential is a barrier: earlier calls finish, it runs alone, then later calls begin. The default is false for compatibility and predictable side effects.

func WithRetryBackoff

func WithRetryBackoff[Deps any, Output any](backoff func(attempt int) time.Duration) Option[Deps, Output]

WithRetryBackoff overrides the wait between retried model calls. backoff receives the 1-based number of the attempt that just failed. When attempts are enabled without an explicit backoff, runs wait with exponential backoff: 500 ms doubling, capped at 30 s.

func WithToolChoice

func WithToolChoice[Deps any, Output any](name string) Option[Deps, Output]

WithToolChoice restricts this agent's advertised tools to name. It is a provider-neutral availability boundary: the selected tool is the only function sent to the model, so models that do not support a provider-native forced-choice flag still cannot request another registered tool. An empty or unregistered name fails New.

func WithToolRetries

func WithToolRetries[Deps any, Output any](retries int) Option[Deps, Output]

WithToolRetries sets how many tool rejections a run feeds back to the model: a tool signals correctable arguments by returning an error wrapping *model.ModelRetry, and the run delivers the rejection as the call's tool result so the model can try again. The default is 0 — self-correction is opt-in — and negative values fail New. The budget counts total rejections per run and is additionally bounded by the model turn limit.

Example

ExampleWithToolRetries shows a tool rejecting correctable arguments: the run delivers the rejection as the call's tool result, and the model calls again with fixed arguments within the configured budget.

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"strings"

	"github.com/abubakarsiddik31/golem"
	"github.com/abubakarsiddik31/golem/model"
	"github.com/abubakarsiddik31/golem/tool"
)

// learningModel requests the roll tool with an invalid argument first,
// then corrects the call once it sees the rejection come back.
type learningModel struct{ calls int }

func (m *learningModel) Generate(ctx context.Context, request model.Request) (model.Response, error) {
	last := request.Messages[len(request.Messages)-1]
	if last.Role == model.RoleTool && !strings.Contains(last.Content, "rejected") {
		return model.Response{
			Message: model.Message{Role: model.RoleAssistant, Content: fmt.Sprintf("the die %s", last.Content)},
		}, nil
	}
	m.calls++
	n := 0
	if m.calls > 1 {
		n = 4
	}
	return model.Response{Message: model.Message{Role: model.RoleAssistant, ToolCalls: []model.ToolCall{
		{ID: fmt.Sprintf("call-%d", m.calls), Name: "roll", Args: json.RawMessage(fmt.Sprintf(`{"n":%d}`, n))},
	}}}, nil
}

func main() {
	roll := tool.MustNew(tool.Tool[struct{}]{
		Name:        "roll",
		Description: "Roll a die; n must be positive.",
		Schema:      json.RawMessage(`{"type":"object","properties":{"n":{"type":"integer"}}}`),
		Exec: func(ctx context.Context, deps struct{}, args json.RawMessage) (string, error) {
			var input struct {
				N int `json:"n"`
			}
			if err := json.Unmarshal(args, &input); err != nil {
				return "", err
			}
			if input.N <= 0 {
				return "", &model.ModelRetry{Err: fmt.Errorf("n must be positive, got %d", input.N)}
			}
			return fmt.Sprintf("rolled %d", input.N), nil
		},
	})

	agent, err := golem.New[struct{}, string](&learningModel{},
		golem.DecodeFunc[string](func(ctx context.Context, response model.Response) (string, error) {
			return response.Message.Content, nil
		}),
		golem.WithTools[struct{}, string](roll),
		golem.WithToolRetries[struct{}, string](2),
	)
	if err != nil {
		log.Fatal(err)
	}

	result, err := agent.Run(context.Background(), golem.RunContext[struct{}]{}, "roll a 4")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(result.Output)
	fmt.Println(len(result.Messages), "messages in the corrected run")
}
Output:
the die rolled 4
6 messages in the corrected run

func WithToolTimeout

func WithToolTimeout[Deps any, Output any](timeout time.Duration) Option[Deps, Output]

WithToolTimeout sets the default deadline for one tool execution. A tool's non-zero Timeout takes precedence. The zero value disables the default; negative values fail New. Tools must honor their context so work ends when the deadline expires.

func WithTools

func WithTools[Deps any, Output any](tools ...tool.Tool[Deps]) Option[Deps, Output]

WithTools registers tools the model may request. Tools should be built with tool.New; New rejects invalid or duplicate declarations.

func WithUsageLimit

func WithUsageLimit[Deps any, Output any](limit UsageLimit) Option[Deps, Output]

WithUsageLimit bounds the tokens a single run may consume and the model requests and tool executions it may make, counted across every model turn, retried call, and correction round. The check runs after each model response against the run's cumulative usage: the response that crosses a bound fails the run at the usage stage, even when it would have decoded successfully. Negative values fail New.

Example

ExampleWithUsageLimit shows a run stopped at the usage stage: the response that crosses the bound fails the run, with the crossed dimension inspectable through the typed cause.

package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	"github.com/abubakarsiddik31/golem"
	"github.com/abubakarsiddik31/golem/model"
)

// verboseModel reports heavy usage on every response.
type verboseModel struct{}

func (m *verboseModel) Generate(ctx context.Context, request model.Request) (model.Response, error) {
	return model.Response{
		Message: model.Message{Role: model.RoleAssistant, Content: "an expensive answer"},
		Usage:   model.Usage{InputTokens: 1200, OutputTokens: 800},
	}, nil
}

func main() {
	agent, err := golem.New[struct{}, string](&verboseModel{},
		golem.DecodeFunc[string](func(ctx context.Context, response model.Response) (string, error) {
			return response.Message.Content, nil
		}),
		golem.WithUsageLimit[struct{}, string](golem.UsageLimit{TotalTokens: 1000}),
	)
	if err != nil {
		log.Fatal(err)
	}

	_, err = agent.Run(context.Background(), golem.RunContext[struct{}]{}, "answer")
	var runErr *golem.RunError
	if !errors.As(err, &runErr) {
		log.Fatal(err)
	}
	fmt.Println(runErr.Stage)
	fmt.Println(runErr.Err)
}
Output:
usage
run exceeded the total token limit of 1000 (used 2000)

type OutputDecoder

type OutputDecoder[Output any] interface {
	Decode(ctx context.Context, response model.Response) (Output, error)
}

OutputDecoder validates and converts a provider response to the agent's declared result type. It is the boundary at which model-produced data becomes application data. Returning *model.ModelRetry rejects a response the model can correct; with an output retry budget configured, the run feeds the rejection back to the model.

func DecodeJSON

func DecodeJSON[Output any]() OutputDecoder[Output]

DecodeJSON returns an OutputDecoder that decodes the final response's message content as JSON into Output. Content that is not valid JSON for Output is rejected as *model.ModelRetry — a correctable rejection — so with an output retry budget configured the run asks the model to fix the response instead of failing. Pair it with WithOutputSchema so the model is told the expected shape up front.

type Result

type Result[Output any] struct {
	Output   Output
	Messages []model.Message
	Usage    model.Usage
}

Result preserves the typed output and the normalized model evidence that produced it, including every tool-call exchange in execution order. This makes testing and observability possible without a tracing backend.

type RunContext

type RunContext[Deps any] struct {
	Deps Deps
}

RunContext carries explicit application dependencies for a run. Its Deps value flows to every tool executed during the run.

type RunError

type RunError struct {
	Stage Stage
	Err   error
}

RunError adds an inspectable execution stage while preserving the source error for errors.Is and errors.As.

func (*RunError) Error

func (e *RunError) Error() string

func (*RunError) Unwrap

func (e *RunError) Unwrap() error

Unwrap exposes the originating model or decoder error.

type RunOption

type RunOption func(*runOptions)

RunOption customizes a single run. Options are evaluated once, at run start; invalid input fails the run before any model call.

func WithPromptImageData

func WithPromptImageData(mediaType string, data []byte) RunOption

WithPromptImageData attaches one inline image with its media type, such as "image/png". Data is application-owned: treat it as immutable once attached.

func WithPromptImageURL

func WithPromptImageURL(url string) RunOption

WithPromptImageURL attaches one image reachable at url; the provider fetches it. See the multimodal support each adapter documents — not every provider accepts image URLs.

func WithPromptParts

func WithPromptParts(parts ...model.Part) RunOption

WithPromptParts appends non-text parts, such as images, after the prompt text of this run's user message. Parts must be well-formed (see model.Part.Validate); a malformed part, or parts on a history message other than a user message, fails the run up front.

type Stage

type Stage string

Stage identifies the run phase that returned an error.

const (
	// StageModel means the model could not generate a response.
	StageModel Stage = "model"
	// StageDecode means a generated response could not become the declared type.
	StageDecode Stage = "decode"
	// StageTool means a tool execution failed; the run aborted.
	StageTool Stage = "tool"
	// StageLoop means the run exceeded its model-turn limit before producing
	// a final response.
	StageLoop Stage = "loop"
	// StageUsage means the run crossed a configured usage bound.
	StageUsage Stage = "usage"
)

type UsageLimit

type UsageLimit struct {
	InputTokens  int
	OutputTokens int
	TotalTokens  int
	// Requests bounds model calls, retried attempts included.
	Requests int
	// ToolCalls bounds tool executions.
	ToolCalls int
}

UsageLimit bounds a run's provider-recorded token consumption and its model-request and tool-execution activity. The zero value disables the limit; each dimension is independent, and zero within a set limit means that dimension is unbounded. Providers that do not report usage count as zero tokens, so a token limit never trips without provider-reported usage; requests and tool executions are counted by the run itself.

type UsageLimitError

type UsageLimitError struct {
	// Kind names the crossed dimension, e.g. "output token".
	Kind string
	// Limit is the configured bound.
	Limit int
	// Actual is the run's cumulative value when the run failed.
	Actual int
}

UsageLimitError reports that a run's cumulative usage crossed one of its configured bounds. It is wrapped in a RunError with the usage stage.

func (*UsageLimitError) Error

func (e *UsageLimitError) Error() string

Directories

Path Synopsis
examples
anthropic command
Command anthropic runs a minimal agent against the Anthropic Messages API: explicit configuration, including the MaxTokens bound the API requires.
Command anthropic runs a minimal agent against the Anthropic Messages API: explicit configuration, including the MaxTokens bound the API requires.
azure command
Command azure runs a minimal agent against Azure OpenAI: the wire format matches OpenAI chat completions, but requests target a named deployment with an explicit API version and the api-key header.
Command azure runs a minimal agent against Azure OpenAI: the wire format matches OpenAI chat completions, but requests target a named deployment with an explicit API version and the api-key header.
bedrock command
Command bedrock runs a minimal agent against the AWS Bedrock Runtime Converse API, with requests signed using AWS Signature Version 4.
Command bedrock runs a minimal agent against the AWS Bedrock Runtime Converse API, with requests signed using AWS Signature Version 4.
conversation command
Command conversation chains runs into a multi-turn chat: each result's messages become the next run's history, and instructions are re-applied per run.
Command conversation chains runs into a multi-turn chat: each result's messages become the next run's history, and instructions are re-applied per run.
fallback command
Command fallback runs a prompt against a primary model with a backup model behind it: when the primary fails with a retryable error — rate limits, 5xx, transport faults — the run continues on the backup instead of failing.
Command fallback runs a prompt against a primary model with a backup model behind it: when the primary fails with a retryable error — rate limits, 5xx, transport faults — the run continues on the backup instead of failing.
gemini command
Command gemini runs a minimal agent against the Google Gemini GenerateContent API.
Command gemini runs a minimal agent against the Google Gemini GenerateContent API.
minimal command
Command minimal runs the smallest agent: an OpenAI-compatible model, a decoder that takes the response text, and one run.
Command minimal runs the smallest agent: an OpenAI-compatible model, a decoder that takes the response text, and one run.
multimodal-input command
Command multimodal-input attaches an inline image to a run's prompt and asks the model to describe it.
Command multimodal-input attaches an inline image to a run's prompt and asks the model to describe it.
self-correction command
Command self-correction shows a tool that rejects correctable arguments: the die roll requires a positive count, and when the model gets it wrong the run feeds the rejection back so the model calls again within the configured budget.
Command self-correction shows a tool that rejects correctable arguments: the die roll requires a positive count, and when the model gets it wrong the run feeds the rejection back so the model calls again within the configured budget.
streaming command
Command streaming prints a response as it arrives: RunStream forwards every model fragment across tool turns and correction rounds while producing the same Result as Run.
Command streaming prints a response as it arrives: RunStream forwards every model fragment across tool turns and correction rounds while producing the same Result as Run.
structured-output command
Command structured-output extracts a typed value: the agent declares a JSON Schema the adapter sends as structured-output instructions, and DecodeJSON turns the response content into the declared type.
Command structured-output extracts a typed value: the agent declares a JSON Schema the adapter sends as structured-output instructions, and DecodeJSON turns the response content into the declared type.
structured-output-tool command
Command structured-output-tool extracts a typed value through tool-mode structured output: the schema becomes the parameters of a synthesized output tool, and the run ends on the model's first call to it.
Command structured-output-tool extracts a typed value through tool-mode structured output: the schema becomes the parameters of a synthesized output tool, and the run ends on the model's first call to it.
testing-without-a-provider command
Command testing-without-a-provider runs an agent against a scripted fake model: no network, no credentials, fully deterministic.
Command testing-without-a-provider runs an agent against a scripted fake model: no network, no credentials, fully deterministic.
tools command
Command tools runs an agent whose tool receives a typed dependency value: the model requests the lookup, Golem executes it with the run's Deps, and the model answers from the result.
Command tools runs an agent whose tool receives a typed dependency value: the model requests the lookup, Golem executes it with the run's Deps, and the model answers from the result.
internal
runner
Package runner orchestrates the sequential model/tool execution loop.
Package runner orchestrates the sequential model/tool execution loop.
Package model defines the provider-neutral contract used by Golem agents.
Package model defines the provider-neutral contract used by Golem agents.
providers
anthropic
Package anthropic adapts the Anthropic Messages API to Golem's provider-neutral model contract.
Package anthropic adapts the Anthropic Messages API to Golem's provider-neutral model contract.
azure
Package azure adapts Azure OpenAI chat completions to Golem's provider-neutral model contract.
Package azure adapts Azure OpenAI chat completions to Golem's provider-neutral model contract.
bedrock
Package bedrock adapts the AWS Bedrock Runtime Converse API to Golem's provider-neutral model contract.
Package bedrock adapts the AWS Bedrock Runtime Converse API to Golem's provider-neutral model contract.
gemini
Package gemini adapts the Google Gemini GenerateContent API to Golem's provider-neutral model contract.
Package gemini adapts the Google Gemini GenerateContent API to Golem's provider-neutral model contract.
openai
Package openai adapts OpenAI-compatible chat-completions APIs to Golem's provider-neutral model contract.
Package openai adapts OpenAI-compatible chat-completions APIs to Golem's provider-neutral model contract.
Package testmodel provides model implementations for testing agents without provider credentials or network access.
Package testmodel provides model implementations for testing agents without provider credentials or network access.
Package tool defines Golem's typed tool declaration and execution contract.
Package tool defines Golem's typed tool declaration and execution contract.

Jump to

Keyboard shortcuts

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