Documentation
¶
Overview ¶
Package goagent provides a Go-idiomatic framework for building AI agents with a ReAct loop and pluggable providers.
Overview ¶
goagent orchestrates the interaction between a language model and a set of tools. On each iteration of the ReAct loop the model either produces a final text answer or requests one or more tool calls. The agent dispatches those calls in parallel, feeds the results back, and repeats until the model stops or the iteration budget (WithMaxIterations) is exhausted.
The framework is built around three small interfaces:
- Provider — wraps an LLM backend (Ollama, Anthropic, OpenAI-compatible, …).
- Tool — a capability the model can invoke (calculator, web search, …).
- ShortTermMemory / LongTermMemory — optional conversation persistence.
All configuration uses functional options passed to New.
Basic usage ¶
provider := ollama.New(ollama.WithBaseURL("http://localhost:11434"))
add := goagent.ToolFunc("add", "Sum two numbers",
map[string]any{
"type": "object",
"properties": map[string]any{
"a": map[string]any{"type": "number"},
"b": map[string]any{"type": "number"},
},
"required": []string{"a", "b"},
},
func(ctx context.Context, args map[string]any) (string, error) {
a, _ := args["a"].(float64)
b, _ := args["b"].(float64)
return fmt.Sprintf("%g", a+b), nil
},
)
agent := goagent.New(
goagent.WithProvider(provider),
goagent.WithTool(add),
goagent.WithMaxIterations(5),
)
answer, err := agent.Run(ctx, "What is 2 + 3?")
Multimodal content ¶
[RunBlocks] accepts images and documents alongside text:
answer, err := agent.RunBlocks(ctx,
goagent.ImageBlock(pngBytes, "image/png"),
goagent.TextBlock("Describe this image"),
)
Memory ¶
By default each Agent.Run call is stateless. To maintain conversation context across calls, configure a ShortTermMemory via WithShortTermMemory. For semantic retrieval across sessions, add a LongTermMemory via WithLongTermMemory. Implementations live in the memory sub-package.
Sub-packages ¶
- memory — ShortTermMemory and LongTermMemory with pluggable storage and policies.
- memory/storage — persistence backends (in-memory; bring your own).
- memory/policy — read-time filters: FixedWindow, TokenWindow, NoOp.
- providers/anthropic — Provider for the Anthropic Messages API (Claude).
- providers/ollama — Provider for Ollama (OpenAI-compatible API).
- internal/testutil — mock implementations for testing agents.
Example ¶
Example demonstrates creating an Agent with a mock provider and running it.
package main
import (
"context"
"fmt"
"log"
"github.com/Germanblandin1/goagent"
"github.com/Germanblandin1/goagent/internal/testutil"
)
func main() {
agent, err := goagent.New(
goagent.WithProvider(testutil.NewMockProvider(
goagent.CompletionResponse{
Message: goagent.AssistantMessage("4"),
StopReason: goagent.StopReasonEndTurn,
},
)),
)
if err != nil {
log.Fatal(err)
}
result, err := agent.Run(context.Background(), "what is 2+2?")
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
}
Output: 4
Index ¶
- Variables
- func TextFrom(blocks []ContentBlock) string
- func ValidDocumentMediaType(mediaType string) bool
- func ValidImageMediaType(mediaType string) bool
- type Agent
- type CompletionRequest
- type CompletionResponse
- type ContentBlock
- type ContentType
- type DocumentData
- type Embedder
- type Hooks
- type ImageData
- type LongTermMemory
- type MCPConnectorFn
- type MaxIterationsError
- type Message
- type Option
- func WithAdaptiveThinking() Option
- func WithEffort(level string) Option
- func WithHooks(h Hooks) Option
- func WithLogger(l *slog.Logger) Option
- func WithLongTermMemory(m LongTermMemory) Option
- func WithLongTermTopK(k int) Option
- func WithMCPConnector(fn MCPConnectorFn) Option
- func WithMaxIterations(n int) Option
- func WithModel(model string) Option
- func WithName(name string) Option
- func WithProvider(p Provider) Option
- func WithShortTermMemory(m ShortTermMemory) Option
- func WithShortTermTraceTools(include bool) Option
- func WithSystemPrompt(prompt string) Option
- func WithThinking(budgetTokens int) Option
- func WithTool(t Tool) Option
- func WithWritePolicy(p WritePolicy) Option
- type Provider
- type ProviderError
- type Role
- type ShortTermMemory
- type StopReason
- type ThinkingConfig
- type ThinkingData
- type Tool
- type ToolCall
- type ToolDefinition
- type ToolExecutionError
- type ToolResult
- type UnsupportedContentError
- type Usage
- type VectorStore
- type WritePolicy
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrInvalidMediaType = errors.New("invalid media type")
ErrInvalidMediaType is returned when a content block has a MIME type that is not in the set of supported types for its content kind.
var ErrToolNotFound = errors.New("tool not found")
ErrToolNotFound is returned when the model requests a tool that was not registered with the agent.
var ErrUnsupportedContent = errors.New("unsupported content type")
ErrUnsupportedContent is returned when the provider does not support a content type present in the request (e.g. documents on an OpenAI-compatible provider, or images on a text-only model).
Functions ¶
func TextFrom ¶
func TextFrom(blocks []ContentBlock) string
TextFrom extracts and concatenates the text from a slice of ContentBlocks. Non-text blocks are ignored. Adjacent text values are separated by a space.
Intended as a convenience for Embedder implementations that only handle text:
func (e *myEmbedder) Embed(ctx context.Context, content []goagent.ContentBlock) ([]float32, error) {
return e.client.Embed(ctx, goagent.TextFrom(content))
}
Example ¶
ExampleTextFrom shows how TextFrom extracts text from a mixed slice of ContentBlocks, ignoring non-text blocks like images.
package main
import (
"fmt"
"github.com/Germanblandin1/goagent"
)
func main() {
blocks := []goagent.ContentBlock{
goagent.TextBlock("hello"),
goagent.ImageBlock([]byte{0xFF}, "image/png"),
goagent.TextBlock("world"),
}
fmt.Println(goagent.TextFrom(blocks))
}
Output: hello world
func ValidDocumentMediaType ¶
ValidDocumentMediaType reports whether mediaType is a supported document MIME type.
func ValidImageMediaType ¶
ValidImageMediaType reports whether mediaType is a supported image MIME type.
Types ¶
type Agent ¶
type Agent struct {
// contains filtered or unexported fields
}
Agent runs a ReAct loop: it alternates between calling the LLM provider and dispatching tool calls until the model produces a final answer or the iteration budget is exhausted.
By default an Agent is stateless — each Run call is independent and carries no memory of previous calls. To persist conversation history across calls, configure a ShortTermMemory via WithShortTermMemory. For semantic retrieval across sessions, configure a LongTermMemory via WithLongTermMemory.
Use WithName to assign a stable identity to the agent. When a LongTermMemory is configured, the name is used as the session namespace so that multiple agents sharing the same memory backend can only see their own entries.
Concurrency ¶
Agent itself holds no mutable state after construction; all fields are set once by New and never written again. Whether concurrent calls to Run are safe depends entirely on the implementations injected by the caller:
- Provider: safe if the implementation is. All built-in providers are.
- ShortTermMemory: concurrent Run calls produce undefined message ordering. See WithShortTermMemory.
- LongTermMemory: concurrent Retrieve and Store calls produce undefined ordering. See WithLongTermMemory.
- Logger: slog.Logger is documented as safe for concurrent use.
If no memory backend is configured (the default), Run is safe to call from multiple goroutines simultaneously.
func New ¶
New creates an Agent with the provided options applied over sensible defaults. A Provider must be supplied via WithProvider before calling Run.
If any WithMCP* options are present, New establishes the MCP connections, discovers their tools, and returns an error if any connection fails. On error, all already-opened connections are closed before returning.
Call Close to release MCP connections when the agent is no longer needed:
agent, err := goagent.New(...)
if err != nil {
log.Fatal(err)
}
defer agent.Close()
func (*Agent) Close ¶
Close releases all MCP connections opened during New. For stdio transports: terminates the subprocess. For SSE transports: closes the HTTP connection. Idempotent — multiple calls are safe. Errors are logged at Warn level; the method always returns nil.
func (*Agent) Run ¶
Run executes the ReAct loop for the given prompt and returns the model's final text response.
This is the main entry point for text interactions. For sending images, documents, or other multimodal content, use RunBlocks.
If a ShortTermMemory is configured via WithShortTermMemory, Run loads the conversation history before calling the provider and persists the new turn after producing an answer (or exhausting iterations).
If a LongTermMemory is configured via WithLongTermMemory, Run retrieves semantically relevant context before building the request, and stores the completed turn (subject to WritePolicy) after each Run.
Memory write failures are logged at Warn level and do not cause Run to return an error.
Possible errors:
- *ProviderError — the provider returned an error
- *MaxIterationsError — the iteration budget was exhausted
- context.Canceled / context.DeadlineExceeded — context was cancelled
Example (WithMemory) ¶
ExampleAgent_Run_withMemory shows that completed turns are persisted to ShortTermMemory and available for the next Run call.
package main
import (
"context"
"fmt"
"log"
"github.com/Germanblandin1/goagent"
"github.com/Germanblandin1/goagent/internal/testutil"
"github.com/Germanblandin1/goagent/memory"
)
func main() {
mem := memory.NewShortTerm()
agent, err := goagent.New(
goagent.WithProvider(testutil.NewMockProvider(
goagent.CompletionResponse{
Message: goagent.AssistantMessage("pong"),
StopReason: goagent.StopReasonEndTurn,
},
)),
goagent.WithShortTermMemory(mem),
)
if err != nil {
log.Fatal(err)
}
if _, err := agent.Run(context.Background(), "ping"); err != nil {
log.Fatal(err)
}
msgs, err := mem.Messages(context.Background())
if err != nil {
log.Fatal(err)
}
fmt.Println(len(msgs))
fmt.Println(msgs[0].TextContent())
fmt.Println(msgs[1].TextContent())
}
Output: 2 ping pong
Example (WithTools) ¶
ExampleAgent_Run_withTools shows how a tool is registered and invoked during a ReAct loop. The mock provider first requests a tool call, then produces the final answer after receiving the tool result.
package main
import (
"context"
"fmt"
"log"
"github.com/Germanblandin1/goagent"
"github.com/Germanblandin1/goagent/internal/testutil"
)
func main() {
echo := goagent.ToolFunc("echo", "returns the input text unchanged", nil,
func(_ context.Context, args map[string]any) (string, error) {
return fmt.Sprintf("%v", args["text"]), nil
},
)
mock := testutil.NewMockProvider(
// Iteration 1: model requests the echo tool.
goagent.CompletionResponse{
Message: goagent.Message{
Role: goagent.RoleAssistant,
ToolCalls: []goagent.ToolCall{
{ID: "call-1", Name: "echo", Arguments: map[string]any{"text": "hello"}},
},
},
StopReason: goagent.StopReasonToolUse,
},
// Iteration 2: model produces the final answer.
goagent.CompletionResponse{
Message: goagent.AssistantMessage("hello"),
StopReason: goagent.StopReasonEndTurn,
},
)
agent, err := goagent.New(
goagent.WithProvider(mock),
goagent.WithTool(echo),
)
if err != nil {
log.Fatal(err)
}
result, err := agent.Run(context.Background(), "echo hello")
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
}
Output: hello
func (*Agent) RunBlocks ¶
RunBlocks executes the ReAct loop with multimodal content and returns the model's final text response.
It accepts one or more ContentBlock in any combination of types. For text-only prompts, prefer Run which is more ergonomic.
Example:
result, err := agent.RunBlocks(ctx,
goagent.ImageBlock(imgData, "image/png"),
goagent.TextBlock("What animal is this?"),
)
Possible errors:
- error if no content blocks are provided
- ErrInvalidMediaType if a content block has an unsupported MIME type
- *ProviderError — the provider returned an error
- *UnsupportedContentError — the provider does not support a content type
- *MaxIterationsError — the iteration budget was exhausted
- context.Canceled / context.DeadlineExceeded — context was cancelled
Example ¶
ExampleAgent_RunBlocks shows how to use RunBlocks with multimodal content. Here we send a text block; in practice you would combine text with image or document blocks.
package main
import (
"context"
"fmt"
"log"
"github.com/Germanblandin1/goagent"
"github.com/Germanblandin1/goagent/internal/testutil"
)
func main() {
agent, err := goagent.New(
goagent.WithProvider(testutil.NewMockProvider(
goagent.CompletionResponse{
Message: goagent.AssistantMessage("It's a cat"),
StopReason: goagent.StopReasonEndTurn,
},
)),
)
if err != nil {
log.Fatal(err)
}
result, err := agent.RunBlocks(context.Background(),
goagent.TextBlock("What animal is this?"),
)
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
}
Output: It's a cat
type CompletionRequest ¶
type CompletionRequest struct {
// Model is the exact model identifier forwarded to the provider
// (e.g. "llama3", "qwen3"). Interpretation is provider-specific;
// the framework passes it through without validation.
Model string
// SystemPrompt is the system-level instruction for the model.
// Providers must forward it using their native mechanism — for
// OpenAI-compatible APIs this means prepending a system message;
// for Anthropic it maps to the top-level "system" field.
// Empty string means no system prompt.
SystemPrompt string
// Messages is the conversation history to send, in chronological order.
// The slice is never nil when sent by Agent, but Provider implementations
// must handle a nil or empty slice without panicking.
Messages []Message
// Tools is the list of tools the model may call during this completion.
// Nil or empty means no tool use is available for this request.
// Providers must not error when this field is nil.
Tools []ToolDefinition
// Thinking configures extended thinking for this request.
// nil means thinking is disabled (default behaviour).
// Providers that do not support thinking must ignore this field.
Thinking *ThinkingConfig
// Effort controls the overall effort the model puts into its response,
// affecting text, tool calls, and thinking (when enabled).
// Valid values: "high", "medium", "low". Empty string means the model's
// default (equivalent to "high"). Thinking and Effort are orthogonal —
// each can be set independently.
// Providers that do not support effort must ignore this field.
Effort string
}
CompletionRequest is the input to a provider's Complete call.
type CompletionResponse ¶
type CompletionResponse struct {
// Message is the model's reply. Role is always RoleAssistant.
// Content may be empty if the model produced only tool calls.
// ToolCalls is non-empty when StopReason is StopReasonToolUse.
Message Message
// StopReason indicates why the model stopped generating.
StopReason StopReason
// Usage reports token consumption for this completion.
// Providers should populate this when the API returns it;
// zero values are valid if the backend does not expose token counts.
Usage Usage
}
CompletionResponse is the output from a provider's Complete call.
type ContentBlock ¶
type ContentBlock struct {
Type ContentType
Text string
Image *ImageData
Document *DocumentData
Thinking *ThinkingData
}
ContentBlock represents a unit of content within a message. Exactly one of Text, Image, Document, or Thinking is valid depending on the value of Type. The others are zero value.
Use the helpers TextBlock, ImageBlock, DocumentBlock, and ThinkingBlock to construct content blocks instead of building the struct directly.
func DocumentBlock ¶
func DocumentBlock(data []byte, mediaType, title string) ContentBlock
DocumentBlock creates a document ContentBlock from raw bytes. mediaType must be one of: "application/pdf", "text/plain". title is optional — if non-empty, it gives the model context about the document.
func ImageBlock ¶
func ImageBlock(data []byte, mediaType string) ContentBlock
ImageBlock creates an image ContentBlock from raw bytes. mediaType must be one of: "image/jpeg", "image/png", "image/gif", "image/webp".
func ThinkingBlock ¶
func ThinkingBlock(thinking, signature string) ContentBlock
ThinkingBlock creates a ContentBlock that carries the model's internal reasoning. signature is the opaque cryptographic token from the Anthropic API; pass an empty string for local models that do not use this mechanism.
type ContentType ¶
type ContentType string
ContentType identifies the kind of content in a ContentBlock.
const ( // ContentText indicates the block contains plain text. ContentText ContentType = "text" // ContentImage indicates the block contains an image. // Supported formats: JPEG, PNG, GIF, WebP. ContentImage ContentType = "image" // ContentDocument indicates the block contains a document. // Supported formats: PDF, plain text. ContentDocument ContentType = "document" // ContentThinking indicates the block contains the model's internal // reasoning produced before the final response or a tool call. // The text may be a summary (Claude 4+) or the full chain-of-thought // (Claude Sonnet 3.7, local models). // // Thinking blocks are produced by the model and must not be constructed // by callers except when echoing them back to the provider (which the // Agent does automatically). Use ThinkingBlock to build one if needed. ContentThinking ContentType = "thinking" )
type DocumentData ¶
type DocumentData struct {
MediaType string // MIME type: "application/pdf", "text/plain"
Data []byte // raw document content
Title string // optional — gives the model context about the document
}
DocumentData holds a document to send to the model. For PDFs, Claude processes both text and visual content (tables, charts, embedded images) page by page.
Supported formats: application/pdf, text/plain. Anthropic limit: 32 MB per document.
type Embedder ¶
type Embedder interface {
Embed(ctx context.Context, content []ContentBlock) ([]float32, error)
}
Embedder converts message content into a dense vector representation suitable for semantic similarity search. Implementations receive the full []ContentBlock so they can handle text, image, and document blocks natively — for example, by routing ContentImage blocks to a vision embedding model.
The vector for a given content slice must be consistent across calls (deterministic given the same model and input).
Use TextFrom to extract and concatenate all text blocks in implementations that only support text.
type Hooks ¶
type Hooks struct {
// OnIterationStart se invoca al inicio de cada iteración del loop ReAct,
// antes de llamar al provider.
// iteration es 0-indexed: la primera iteración es 0.
OnIterationStart func(iteration int)
// OnThinking se invoca cuando el modelo produce un bloque de thinking.
// text es el contenido del razonamiento — puede ser un resumen en Claude 4+
// o el razonamiento completo en modelos locales y Claude Sonnet 3.7.
//
// Se invoca una vez por cada thinking block en la respuesta del modelo.
// Si la respuesta tiene múltiples thinking blocks (interleaved thinking),
// se invoca una vez por cada uno, en orden.
//
// Solo se invoca si el agente tiene thinking habilitado (WithThinking,
// WithAdaptiveThinking) o si el modelo local produce thinking.
OnThinking func(text string)
// OnToolCall se invoca cuando el modelo solicita ejecutar una herramienta,
// antes de que el dispatcher la ejecute.
// Se invoca una vez por cada tool call en la respuesta del modelo.
// Si el modelo pide N tools en paralelo, se invoca N veces antes del dispatch.
OnToolCall func(name string, args map[string]any)
// OnToolResult se invoca después de que una herramienta termina de ejecutarse.
// content es el resultado que se devolverá al modelo.
// duration es el tiempo que tardó la ejecución.
// err es nil si la tool ejecutó exitosamente, o el error si falló.
//
// Se invoca incluso cuando la tool falla — err contiene el error.
// Se invoca una vez por cada tool call, después de que todas terminan.
OnToolResult func(name string, content []ContentBlock, duration time.Duration, err error)
// OnResponse se invoca cuando el modelo produce la respuesta final,
// justo antes de que Run/RunBlocks retorne al caller.
// text es la respuesta textual extraída (sin thinking blocks).
// iterations es la cantidad total de iteraciones que usó el loop (1-indexed).
//
// También se invoca cuando el loop se agota (MaxIterationsError) —
// text puede ser "" si la última iteración terminó en tool use.
OnResponse func(text string, iterations int)
// OnShortTermLoad se invoca después de que el agente carga el historial de la
// memoria de corto plazo al inicio de cada Run, tanto en éxito como en error.
// results es la cantidad de mensajes cargados (0 si err != nil).
// duration es el tiempo que tardó la operación.
// err es nil si la carga fue exitosa.
//
// Solo se invoca si el agente tiene una ShortTermMemory configurada.
OnShortTermLoad func(results int, duration time.Duration, err error)
// OnShortTermAppend se invoca después de que el agente persiste el turno en
// la memoria de corto plazo al finalizar cada Run, tanto en éxito como en error.
// msgs es la cantidad de mensajes que se intentó almacenar.
// duration es el tiempo que tardó la operación.
// err es nil si el almacenamiento fue exitoso.
//
// Solo se invoca si el agente tiene una ShortTermMemory configurada.
OnShortTermAppend func(msgs int, duration time.Duration, err error)
// OnLongTermRetrieve se invoca después de que el agente consulta la memoria
// de largo plazo al inicio de cada Run, tanto en caso de éxito como de error.
// results es la cantidad de mensajes recuperados (0 si err != nil).
// duration es el tiempo que tardó la operación de recuperación.
// err es nil si la recuperación fue exitosa.
//
// Solo se invoca si el agente tiene una LongTermMemory configurada.
OnLongTermRetrieve func(results int, duration time.Duration, err error)
// OnLongTermStore se invoca después de que el agente persiste un turno en la
// memoria de largo plazo al finalizar cada Run, tanto en caso de éxito como
// de error.
// msgs es la cantidad de mensajes que se intentó almacenar.
// duration es el tiempo que tardó la operación de almacenamiento.
// err es nil si el almacenamiento fue exitoso.
//
// Solo se invoca si el agente tiene una LongTermMemory configurada y la
// WritePolicy decidió persistir el turno. No se invoca si la política
// descarta el turno.
OnLongTermStore func(msgs int, duration time.Duration, err error)
}
Hooks permite observar eventos del loop ReAct sin modificar su comportamiento. Todos los campos son opcionales — un hook nil se ignora silenciosamente.
Los hooks se invocan sincrónicamente dentro del loop. Si un hook necesita hacer trabajo pesado (ej: enviar a un servicio externo), debe lanzar una goroutine internamente para no bloquear el loop.
El zero value de Hooks es funcional y no invoca ningún callback.
Ejemplo:
agent := goagent.New(
goagent.WithProvider(provider),
goagent.WithHooks(goagent.Hooks{
OnToolCall: func(name string, args map[string]any) {
fmt.Printf("🔧 %s\n", name)
},
}),
)
type ImageData ¶
type ImageData struct {
MediaType string // MIME type: "image/jpeg", "image/png", "image/gif", "image/webp"
Data []byte // raw image content
}
ImageData holds an image to send to the model. Data is the raw image content — the provider layer encodes it to base64.
Supported formats: image/jpeg, image/png, image/gif, image/webp. Anthropic limit: 5 MB per image, ~1600x1600 px recommended.
type LongTermMemory ¶
type LongTermMemory interface {
// Store persists msgs for future retrieval.
Store(ctx context.Context, msgs ...Message) error
// Retrieve returns the topK messages most semantically similar to the
// given content. The full []ContentBlock is passed so that embedder
// implementations that support vision or documents can build a meaningful
// query vector even when the prompt contains no text.
Retrieve(ctx context.Context, query []ContentBlock, topK int) ([]Message, error)
}
LongTermMemory stores and retrieves messages across sessions by semantic relevance. Unlike ShortTermMemory, retrieval is similarity-based, not positional — the store may contain thousands of messages but only the most relevant ones are surfaced on each Run. Implementations must be safe for concurrent use.
type MCPConnectorFn ¶
MCPConnectorFn is a function that establishes an MCP connection, discovers tools, and returns them along with a Closer for lifecycle management. It is called by New for each WithMCP* option applied to the agent.
type MaxIterationsError ¶
type MaxIterationsError struct {
// Iterations is the budget that was exhausted (set by WithMaxIterations).
Iterations int
// LastThought is the text content of the model's last assistant message
// before the budget ran out. It is empty if the model's last turn
// produced only tool calls with no accompanying text. Useful for
// debugging runaway loops or surfacing a partial answer to the user.
LastThought string
}
MaxIterationsError is returned by Run when the agent exhausts its iteration budget without producing a final answer.
func (*MaxIterationsError) Error ¶
func (e *MaxIterationsError) Error() string
type Message ¶
type Message struct {
Role Role
Content []ContentBlock
// ToolCalls is non-empty when the model requests one or more tool
// invocations. Only set on assistant messages (Role == RoleAssistant).
ToolCalls []ToolCall
// ToolCallID is the ID of the ToolCall this message is a result for.
// Must be set — and must exactly match ToolCall.ID — when Role == RoleTool.
// The Agent sets this automatically when building tool result messages;
// Provider implementations must populate ToolCall.ID for the correlation
// to work correctly.
ToolCallID string
}
Message is a single turn in a conversation.
Content is a slice of ContentBlock that can hold text, images, documents, or any combination. For simple text messages, use the helpers TextMessage() or UserMessage(). To extract concatenated text from all blocks, use TextContent().
func AssistantMessage ¶
AssistantMessage creates an assistant-role Message with text content. Shorthand for TextMessage(RoleAssistant, text).
func TextMessage ¶
TextMessage creates a Message with a single text content block.
func UserMessage ¶
UserMessage creates a user-role Message with text content. Shorthand for TextMessage(RoleUser, text).
func (Message) HasContentType ¶
func (m Message) HasContentType(ct ContentType) bool
HasContentType reports whether the message contains at least one ContentBlock of the given type.
func (Message) TextContent ¶
TextContent returns the concatenation of all ContentText blocks in the message, separated by newlines. Returns an empty string if the message contains no text blocks.
type Option ¶
type Option func(*options)
Option is a functional option for configuring an Agent.
func WithAdaptiveThinking ¶
func WithAdaptiveThinking() Option
WithAdaptiveThinking enables thinking in adaptive mode: the model decides how much to reason based on the complexity of each prompt. Recommended for claude-opus-4-6 and claude-sonnet-4-6.
On models that do not support adaptive mode, the provider may fall back to a manual budget.
func WithEffort ¶
WithEffort controls the overall effort the model puts into its response, affecting text quality, tool call accuracy, and reasoning depth.
Valid values:
- "high": maximum effort — equivalent to the model's default behaviour.
- "medium": balanced quality and cost — suitable for most tasks.
- "low": faster and cheaper responses — best for simple classification or extraction tasks.
Effort and thinking are orthogonal and can be combined freely. Supported models: claude-opus-4-6, claude-sonnet-4-6, claude-opus-4-5. Models that do not support effort silently ignore this setting.
func WithHooks ¶
WithHooks registers observability callbacks for the ReAct loop. All fields of Hooks are optional — only non-nil hooks are invoked.
Example:
agent := goagent.New(
goagent.WithProvider(provider),
goagent.WithHooks(goagent.Hooks{
OnToolCall: func(name string, args map[string]any) {
fmt.Printf("calling tool: %s\n", name)
},
}),
)
func WithLogger ¶
WithLogger sets the structured logger used for debug output.
func WithLongTermMemory ¶
func WithLongTermMemory(m LongTermMemory) Option
WithLongTermMemory configures the agent to retrieve semantically relevant context from past sessions before each Run, and to store the completed turn after each Run (subject to the configured WritePolicy). The default is no long-term memory.
Note: concurrent Run calls on the same Agent will issue concurrent Retrieve and Store calls to this backend. Whether that is safe depends on the LongTermMemory implementation supplied by the caller.
func WithLongTermTopK ¶
WithLongTermTopK sets how many messages the long-term memory retrieves per Run. Default: 3.
func WithMCPConnector ¶
func WithMCPConnector(fn MCPConnectorFn) Option
WithMCPConnector registers an MCP connector that is called during New to establish a connection, discover tools, and obtain a Closer for lifecycle management. The connection is established once during New; if it fails, New returns an error and closes any already-opened connections.
Callers typically use the higher-level helpers in the mcp sub-package (mcp.WithStdio, mcp.WithSSE) rather than calling this directly.
func WithMaxIterations ¶
WithMaxIterations limits how many reasoning iterations the agent may perform. Defaults to 10.
func WithModel ¶
WithModel sets the model identifier forwarded to the provider. Required. The value is provider-specific (e.g. "qwen3" for Ollama, "claude-sonnet-4-6" for Anthropic). If not set, the provider may return an error.
func WithName ¶
WithName assigns a name to the agent. When a LongTermMemory is configured, the name is used as the session namespace: all vectors stored and retrieved during Run are scoped to this name, so two agents sharing the same LongTermMemory backend but different names cannot see each other's entries. If not set, no session filtering is applied.
func WithProvider ¶
WithProvider sets the LLM backend used by the agent.
func WithShortTermMemory ¶
func WithShortTermMemory(m ShortTermMemory) Option
WithShortTermMemory configures the agent to persist and replay conversation history across Run calls using the provided ShortTermMemory. The default is stateless — each Run starts with no history.
Note: sharing a ShortTermMemory across concurrent Run calls produces undefined message ordering. Sequential use (one Run at a time) is safe.
func WithShortTermTraceTools ¶
WithShortTermTraceTools controls whether the full ReAct trace (tool calls and their results) is included when persisting to short-term memory.
- true (default): the complete trace is stored — user message, all intermediate assistant turns, tool results, and final assistant answer. The next Run will see the full reasoning history.
- false: only the user message and the final assistant answer are stored, discarding intermediate tool call steps.
func WithSystemPrompt ¶
WithSystemPrompt sets a system-level instruction sent to the provider on every Run call.
func WithThinking ¶
WithThinking enables extended thinking with a fixed token budget. The model uses up to budgetTokens tokens for internal reasoning before responding or invoking a tool.
Minimum: 1024 tokens. Recommended ranges:
- Simple tasks: 4 000–8 000
- Complex tasks (math, code): 10 000–16 000
- Deep reasoning: 16 000–32 000
For budgets above 32 000 tokens, consider WithAdaptiveThinking instead. Supported models: claude-sonnet-4-6, claude-opus-4-6, claude-sonnet-3-7, claude-opus-4, claude-opus-4-5.
func WithWritePolicy ¶
func WithWritePolicy(p WritePolicy) Option
WithWritePolicy sets the function that decides whether a completed turn (prompt + final response) is stored in long-term memory. Default: StoreAlways. Only effective when WithLongTermMemory is configured.
type Provider ¶
type Provider interface {
Complete(ctx context.Context, req CompletionRequest) (CompletionResponse, error)
}
Provider is the interface that wraps a language model backend. Callers supply a Provider to Agent via WithProvider.
type ProviderError ¶
ProviderError wraps an error returned by a Provider's Complete method.
func (*ProviderError) Error ¶
func (e *ProviderError) Error() string
func (*ProviderError) Unwrap ¶
func (e *ProviderError) Unwrap() error
Unwrap enables errors.Is and errors.As to inspect the underlying cause.
type Role ¶
type Role string
Role identifies who authored a message in a conversation.
const ( RoleUser Role = "user" RoleAssistant Role = "assistant" RoleTool Role = "tool" // RoleSystem is provided for completeness and for callers that build // Provider implementations or raw message slices. When using Agent, // the system prompt is set via WithSystemPrompt and is forwarded to the // provider through CompletionRequest.SystemPrompt — never as a Message // with this role. Callers do not need to construct RoleSystem messages // directly. RoleSystem Role = "system" )
type ShortTermMemory ¶
type ShortTermMemory interface {
// Messages returns the messages to include in the next provider request,
// with the configured read policy applied (e.g. FixedWindow, TokenWindow).
// The returned slice is a defensive copy; callers may modify it freely.
Messages(ctx context.Context) ([]Message, error)
// Append adds msgs to the store in the order provided.
// Filtering occurs at read time (Messages), never at write time.
Append(ctx context.Context, msgs ...Message) error
}
ShortTermMemory manages the active conversation history for an Agent. It is used to maintain context across multiple Run calls within a session. Implementations must be safe for concurrent use.
type StopReason ¶
type StopReason int
StopReason indicates why the model stopped generating.
const ( StopReasonEndTurn StopReason = iota // model produced a final answer StopReasonMaxTokens // token limit reached StopReasonToolUse // model wants to call tools StopReasonError // provider-level error )
func (StopReason) String ¶
func (s StopReason) String() string
String returns a human-readable representation of the stop reason.
type ThinkingConfig ¶
ThinkingConfig configures the model's extended thinking mode.
Manual mode: Enabled true, BudgetTokens > 0. The model uses up to BudgetTokens tokens for internal reasoning before responding or invoking a tool. Minimum: 1024 tokens.
Adaptive mode: Enabled true, BudgetTokens 0. The model decides how much to reason based on prompt complexity. Recommended for Opus 4.6 and Sonnet 4.6.
Disabled: Enabled false (or nil pointer in CompletionRequest).
type ThinkingData ¶
ThinkingData holds the model's internal reasoning produced during extended thinking. The Agent preserves this data within a turn so the provider can echo it back to the API (required by Anthropic for thinking continuity).
Thinking is the reasoning text — may be a summary in Claude 4+ or the full chain-of-thought in Claude Sonnet 3.7 and local models.
Signature is the opaque cryptographic token issued by the Anthropic API to verify the block's authenticity. It must be echoed back unchanged and must never be logged, inspected, or modified. For local models (Ollama) that do not use this mechanism, Signature is an empty string.
type Tool ¶
type Tool interface {
Definition() ToolDefinition
Execute(ctx context.Context, args map[string]any) ([]ContentBlock, error)
}
Tool is the interface that callers implement to give capabilities to an agent.
Execute receives the arguments parsed by the model and returns content that is injected into the conversation as the tool result. The content can be text, images, documents, or any combination.
For tools that only return text (the most common case), use ToolFunc which accepts func(...) (string, error) and wraps it automatically.
func ToolBlocksFunc ¶
func ToolBlocksFunc( name, description string, parameters map[string]any, fn func(ctx context.Context, args map[string]any) ([]ContentBlock, error), ) Tool
ToolBlocksFunc creates a Tool from a plain function that returns multimodal content. Use this when the tool needs to return images, documents, or a combination of content types.
For tools that only return text, prefer ToolFunc which is more ergonomic.
Example ¶
ExampleToolBlocksFunc shows how to create a Tool that returns multimodal content blocks instead of plain text.
package main
import (
"context"
"fmt"
"github.com/Germanblandin1/goagent"
)
func main() {
t := goagent.ToolBlocksFunc("screenshot", "captures the current screen", nil,
func(_ context.Context, _ map[string]any) ([]goagent.ContentBlock, error) {
return []goagent.ContentBlock{
goagent.TextBlock("captured"),
}, nil
},
)
fmt.Println(t.Definition().Name)
fmt.Println(t.Definition().Description)
}
Output: screenshot captures the current screen
func ToolFunc ¶
func ToolFunc( name, description string, parameters map[string]any, fn func(ctx context.Context, args map[string]any) (string, error), ) Tool
ToolFunc creates a Tool from a plain function that returns text. The returned string is wrapped in a single text ContentBlock automatically, avoiding the need to define a new struct for simple tools.
For tools that need to return images, documents, or mixed content, use ToolBlocksFunc instead.
Example ¶
ExampleToolFunc shows how to create a Tool from a plain function.
package main
import (
"context"
"fmt"
"github.com/Germanblandin1/goagent"
)
func main() {
t := goagent.ToolFunc("add", "adds two numbers", nil,
func(_ context.Context, _ map[string]any) (string, error) {
return "42", nil
},
)
fmt.Println(t.Definition().Name)
fmt.Println(t.Definition().Description)
}
Output: add adds two numbers
type ToolCall ¶
type ToolCall struct {
// ID is the opaque identifier assigned by the model to this tool call.
// It must be echoed back in Message.ToolCallID of the corresponding
// tool result message so the model can correlate the result with the
// request. Provider implementations must populate this field; leaving
// it empty will cause the next completion to fail on APIs that enforce
// the tool call / tool result pairing (e.g. Anthropic, OpenAI).
ID string
// Name is the tool name the model wants to invoke, matching the
// Name field of a registered ToolDefinition.
Name string
// Arguments contains the arguments the model supplied for this call,
// decoded from the provider's JSON payload. Keys match the parameter
// names defined in ToolDefinition.Parameters.
Arguments map[string]any
}
ToolCall represents a request from the model to invoke a tool.
type ToolDefinition ¶
ToolDefinition describes a tool's name, purpose, and parameter schema. Parameters must be a valid JSON Schema object as map[string]any.
type ToolExecutionError ¶
ToolExecutionError wraps an error returned by a tool's Execute method, adding the tool name and arguments for diagnosis.
func (*ToolExecutionError) Error ¶
func (e *ToolExecutionError) Error() string
func (*ToolExecutionError) Unwrap ¶
func (e *ToolExecutionError) Unwrap() error
Unwrap enables errors.Is and errors.As to inspect the underlying cause.
type ToolResult ¶
type ToolResult struct {
ToolCallID string
Name string
Content []ContentBlock
Err error
// Duration is how long Execute took. It is zero when the tool was not
// found (ErrToolNotFound) — no execution occurred in that case.
Duration time.Duration
}
ToolResult holds the outcome of a single tool execution dispatched by the agent.
type UnsupportedContentError ¶
type UnsupportedContentError struct {
ContentType ContentType
Provider string
Reason string
}
UnsupportedContentError provides detail about which content type is not supported and by which provider. It wraps ErrUnsupportedContent so callers can match with errors.Is(err, ErrUnsupportedContent).
func (*UnsupportedContentError) Error ¶
func (e *UnsupportedContentError) Error() string
func (*UnsupportedContentError) Unwrap ¶
func (e *UnsupportedContentError) Unwrap() error
Unwrap returns ErrUnsupportedContent so errors.Is works.
type Usage ¶
type Usage struct {
// InputTokens is the number of tokens in the prompt (including history).
InputTokens int
// OutputTokens is the number of tokens in the model's response.
OutputTokens int
}
Usage reports token consumption for a completion.
type VectorStore ¶
type VectorStore interface {
// Upsert stores or updates a message with its embedding vector.
// id must be a stable identifier for the message (e.g. content hash).
Upsert(ctx context.Context, id string, vector []float32, msg Message) error
// Search returns the topK messages most similar to the given vector.
Search(ctx context.Context, vector []float32, topK int) ([]Message, error)
}
VectorStore stores (message, embedding) pairs and supports similarity search. This module does not ship a VectorStore implementation; the caller must supply one (e.g. a pgvector client, a Chroma adapter, or an in-process approximate nearest-neighbour store).
type WritePolicy ¶
WritePolicy decides what to persist after a completed turn. It is called once per Run after the final answer is produced.
Returning nil discards the turn — nothing is written to long-term memory. Returning a non-nil slice (even an empty one) stores exactly those messages.
This design lets policies both filter and transform: a policy may return the original user+assistant pair unchanged (like StoreAlways), a condensed single message (like a summarising judge agent), or any custom set of messages.
prompt is the user Message that opened the turn (may contain images, documents, or other multimodal blocks). response is the final assistant Message. Both are passed in full so policies can inspect or forward binary content without losing it.
var StoreAlways WritePolicy = func(p, r Message) []Message { return []Message{p, r} }
StoreAlways is a WritePolicy that persists every turn as the original user+assistant message pair. It is the default when WithLongTermMemory is configured without an explicit WritePolicy.
func MinLength ¶
func MinLength(n int) WritePolicy
MinLength returns a WritePolicy that stores the original user+assistant pair only when the combined character count of their text content exceeds n. Returns nil (discard) when the combined length is n or fewer characters. Useful for filtering out trivial exchanges ("ok", "gracias", "seguí") that add noise to the long-term store without carrying durable information.
Example ¶
ExampleMinLength shows how MinLength filters out short exchanges from long-term memory storage. A nil return means the turn is discarded; a non-nil slice is stored as-is.
package main
import (
"fmt"
"github.com/Germanblandin1/goagent"
)
func main() {
policy := goagent.MinLength(10)
// 4 chars total ("hi" + "ok") — below threshold, turn is discarded.
fmt.Println(policy(goagent.UserMessage("hi"), goagent.AssistantMessage("ok")) == nil)
// 15 chars total ("hello world" + "fine") — above threshold, returns the
// default user+assistant pair ready to be passed to LongTermMemory.Store.
msgs := policy(goagent.UserMessage("hello world"), goagent.AssistantMessage("fine"))
fmt.Printf("%d messages: %s / %s\n", len(msgs), msgs[0].Role, msgs[1].Role)
}
Output: true 2 messages: user / assistant
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
testutil
Package testutil provides shared test helpers for the goagent module.
|
Package testutil provides shared test helpers for the goagent module. |
|
Package memory provides ShortTermMemory and LongTermMemory implementations for the goagent framework.
|
Package memory provides ShortTermMemory and LongTermMemory implementations for the goagent framework. |
|
policy
Package policy provides read Policy implementations for the goagent memory system.
|
Package policy provides read Policy implementations for the goagent memory system. |
|
storage
Package storage provides Storage implementations for the goagent memory system.
|
Package storage provides Storage implementations for the goagent memory system. |
|
vector
Package vector provides embeddings, chunking, similarity search, and an in-process vector store for goagent's long-term memory subsystem.
|
Package vector provides embeddings, chunking, similarity search, and an in-process vector store for goagent's long-term memory subsystem. |
|
vector/pgvector
module
|
|
|
vector/sqlitevec
module
|
|
|
orchestration
module
|
|
|
rag
module
|