llm

package
v0.4.3 Latest Latest
Warning

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

Go to latest
Published: Nov 9, 2026 License: AGPL-3.0, Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package llm defines a vendor-neutral message model and a pluggable Provider interface, with adapters that speak the Anthropic Messages API and the OpenAI Chat Completions API. The internal model mirrors Anthropic's content-block shape (the richer superset, including thinking and structured tool results); the OpenAI adapter translates at its boundary. This lets the harness be written once against a single representation while remaining compatible with any model reachable through either wire format.

Index

Constants

View Source
const MaxTokensFieldCompletion = "max_completion_tokens"

MaxTokensFieldCompletion is the Config.MaxTokensField value that moves the OpenAI Chat Completions output cap onto "max_completion_tokens". Any other value keeps the classic "max_tokens".

Variables

This section is empty.

Functions

func IsBoundaryMarker

func IsBoundaryMarker(m Message) bool

IsBoundaryMarker reports whether m is a compact-boundary marker.

func IsTodoReminder

func IsTodoReminder(m Message) bool

IsTodoReminder reports whether m is a todo-reminder message (used to derive the throttle "turns since last reminder").

func LastBoundaryIndex

func LastBoundaryIndex(msgs []Message) int

LastBoundaryIndex returns the index of the last boundary marker, or -1.

func SkillInvocationName

func SkillInvocationName(m Message) (string, bool)

SkillInvocationName reports whether m is a skill-invocation message and, if so, returns the invoked skill's name (used by compaction to dedupe to the latest invocation per skill).

Types

type Accumulator

type Accumulator struct {
	StopReason string
	Usage      Usage
	// contains filtered or unexported fields
}

Accumulator reassembles a complete assistant Message from a stream of normalized StreamEvents. The harness feeds every event to Add; once the stream ends, Message returns the finished assistant turn (text, thinking, and tool_use blocks in arrival order).

func NewAccumulator

func NewAccumulator() *Accumulator

NewAccumulator returns an empty Accumulator.

func (*Accumulator) Add

func (a *Accumulator) Add(ev StreamEvent)

Add folds one event into the accumulator.

func (*Accumulator) Message

func (a *Accumulator) Message() Message

Message finalizes and returns the assembled assistant message. Partial tool inputs are validated; malformed JSON falls back to "{}".

type BlockType

type BlockType string

BlockType enumerates the kinds of content a message block may carry.

const (
	BlockText       BlockType = "text"
	BlockThinking   BlockType = "thinking"
	BlockToolUse    BlockType = "tool_use"
	BlockToolResult BlockType = "tool_result"
)
const BlockCompactBoundary BlockType = "compact_boundary"

BlockCompactBoundary marks a compaction point. A message consisting solely of this block is a boundary marker; the summary that follows is a normal message.

type BoundaryMeta

type BoundaryMeta struct {
	// Trigger is what caused compaction: "auto" (proactive threshold),
	// "reactive" (recovery from a prompt-too-long error), or "manual".
	Trigger string `json:"trigger,omitempty"`
	// PreTokens is the token count just before compaction.
	PreTokens int `json:"pre_tokens"`
	// MessagesSummarized is how many messages were condensed into the summary.
	MessagesSummarized int `json:"messages_summarized,omitempty"`
	// ActiveSkills names the invoked skills re-injected verbatim after this
	// boundary (diagnostics; the instructions themselves ride in the messages).
	ActiveSkills []string `json:"active_skills,omitempty"`
}

BoundaryMeta is the structured payload of a compact boundary. It records why compaction ran and what it condensed, for diagnostics, analytics, and transcript replay.

func ParseBoundaryMeta

func ParseBoundaryMeta(m Message) (BoundaryMeta, bool)

ParseBoundaryMeta extracts the metadata from a boundary marker. It accepts both the structured object form and the legacy bare-integer form (which encoded only the pre-compaction token count).

type CompletionRequest

type CompletionRequest struct {
	// System is the host-controlled system prompt, in segments. The SDK injects
	// no presets (design decision 1). Segments are joined with blank lines.
	System []string
	// DynamicBoundary is the index in System separating cacheable static prefix
	// from per-request dynamic content; providers may set a cache breakpoint
	// there (FR-02.2). 0 disables.
	DynamicBoundary int
	Messages        []Message
	Tools           []ToolSchema
	MaxTokens       int
	Temperature     *float64
	Stop            []string
	// Thinking overrides the provider's thinking mode for THIS request only:
	// "" inherits Config.ThinkingType; "enabled"/"disabled" force that mode.
	// Compaction's summary call sets "disabled" so the summarizer never spends
	// tokens on reasoning and never replays thinking blocks (which Anthropic
	// rejects when thinking is off).
	Thinking string
}

CompletionRequest is a single, provider-neutral model invocation.

type Config

type Config struct {
	Format     Format
	BaseURL    string
	APIKey     string
	Model      string
	APIVersion string // Anthropic only; default 2023-06-01
	HTTPClient *http.Client
	// Proxy, when set, routes all model requests through the given proxy URL —
	// "http://", "https://" or "socks5://" (optionally with user:pass@). It only
	// affects the built-in client: when empty, the built-in client falls back to
	// the standard HTTP_PROXY / HTTPS_PROXY / NO_PROXY environment variables.
	// Ignored when the caller supplies its own HTTPClient.
	Proxy string
	// MaxRetries bounds transient-failure retries on the model request
	// establishment (connection reset / timeout / 429 / 5xx). 0 = default 3;
	// negative disables retrying. Only the pre-stream phase is retried (safe);
	// a mid-stream drop is surfaced as an error.
	MaxRetries int
	// RetryInterval, when > 0, replaces the exponential backoff between
	// establishment retries with this FIXED wait. 0 (default) keeps the
	// 0.5s/1s/2s… ladder capped at 8s. A host that wants a predictable,
	// operator-set cadence sets both MaxRetries and this.
	RetryInterval time.Duration
	// EmptyResponseRetries bounds how many times a completed-but-EMPTY response
	// is re-requested (FormatOpenAI only; see emptyResponseRetries). 0 = default
	// 2; negative disables. Separate from MaxRetries because an empty-response
	// retry re-sends the whole prompt rather than re-establishing a request.
	EmptyResponseRetries int
	// EmptyResponseInterval, when > 0, replaces the exponential backoff before
	// each empty-response retry with this FIXED wait. 0 keeps the ladder.
	EmptyResponseInterval time.Duration
	// RateLimit, when set, bounds the request rate shared across every caller of
	// the built Provider (per-second and/or per-minute). Retries do not count.
	RateLimit *RateLimit

	// ThinkingType toggles extended/reasoning mode via the request's
	// "thinking":{"type":...} field, for both formats. "" omits it (provider
	// default); "enabled"/"disabled" are sent verbatim.
	ThinkingType string
	// ReasoningEffort sets the reasoning strength. "" omits it. For OpenAI it maps
	// to "reasoning_effort"; for Anthropic to "output_config":{"effort":...}.
	// Typical values: low / medium / high / max.
	ReasoningEffort string
	// MaxTokensField selects which request key carries the output cap on
	// FormatOpenAI (Chat Completions). "" (default) sends the classic
	// "max_tokens"; MaxTokensFieldCompletion sends "max_completion_tokens"
	// instead. OpenAI's reasoning models reject "max_tokens" outright
	// (unsupported_parameter) and accept only the newer key, whose budget covers
	// reasoning tokens plus visible output; most OpenAI-compatible gateways still
	// take the old one, hence the opt-in default. Exactly one key is ever sent.
	// Ignored by the Anthropic and Responses formats, which name the field
	// themselves ("max_tokens" / "max_output_tokens").
	MaxTokensField string
	// contains filtered or unexported fields
}

Config configures the built-in providers (FR-03.4).

type ContentBlock

type ContentBlock struct {
	Type BlockType `json:"type"`

	// Text block.
	Text string `json:"text,omitempty"`

	// Thinking block (extended thinking). Anthropic requires the field to be
	// named "thinking" (not "text") and requires "signature" when replaying
	// the block in multi-turn history.
	Thinking  string `json:"thinking,omitempty"`
	Signature string `json:"signature,omitempty"`

	// ToolUse: a model request to invoke a tool.
	ID    string          `json:"id,omitempty"`
	Name  string          `json:"name,omitempty"`
	Input json.RawMessage `json:"input,omitempty"`

	// ToolResult: the outcome of a tool, fed back to the model.
	ToolUseID string         `json:"tool_use_id,omitempty"`
	Content   []ContentBlock `json:"content,omitempty"`
	IsError   bool           `json:"is_error,omitempty"`
}

ContentBlock is one piece of message content. Which fields are populated depends on Type. This is the Anthropic content-block union.

func TextBlock

func TextBlock(s string) ContentBlock

TextBlock builds a text content block.

func ToolResultText

func ToolResultText(toolUseID, text string, isError bool) ContentBlock

ToolResultText builds a tool_result block carrying a single text block.

type Format

type Format string

Format selects the wire protocol of a Provider.

const (
	FormatAnthropic Format = "anthropic"
	FormatOpenAI    Format = "openai"
	// FormatOpenAIResponses speaks the OpenAI Responses API (POST /v1/responses),
	// distinct from FormatOpenAI's Chat Completions (/chat/completions).
	FormatOpenAIResponses Format = "openai-responses"
)

type Message

type Message struct {
	Role    Role           `json:"role"`
	Content []ContentBlock `json:"content"`
}

Message is a single conversation turn.

func BoundaryMessage

func BoundaryMessage(meta BoundaryMeta) Message

BoundaryMessage builds a compact-boundary marker carrying its metadata. It carries no model-visible text (the marker is stripped before the API call).

func MessagesAfterBoundary

func MessagesAfterBoundary(msgs []Message) []Message

MessagesAfterBoundary returns the slice from the last boundary marker onward (boundary marker included), or all messages if there is no boundary. This is the post-compaction "working set"; everything before it is retained history.

func MessagesForAPI

func MessagesForAPI(msgs []Message) []Message

MessagesForAPI returns the messages actually sent to the model: the working set after the last boundary, with boundary markers stripped (they are position markers, not content) and any tool block left unpaired by the boundary cut dropped (PairToolBlocks). The summary that follows a boundary is kept.

func PairToolBlocks

func PairToolBlocks(msgs []Message) []Message

PairToolBlocks drops tool blocks left unpaired within msgs: a tool_result whose tool_use is absent, or a tool_use whose tool_result is absent. The working set is sliced at the last boundary, and that cut can fall inside a tool exchange — leaving the post-boundary view starting with a tool_result whose tool_use was summarized away (or ending with a tool_use whose result is beyond the tail). Sent verbatim, the first makes the gateway 400 ("a message with role 'tool' must be a response to a preceding message with 'tool_calls'"). Well-formed histories have no unpaired blocks and pass through unchanged; this also repairs a resumed transcript that already baked in an orphan from an earlier compaction. Drops (not synthesizes), matching the summarizer's pairForReplay.

func SkillInvocationMessage

func SkillInvocationMessage(name, path, body string) Message

SkillInvocationMessage builds the independent user message that carries a skill's instructions. name is required; path (the skill's base directory) is optional and, when present, is surfaced both as an attribute (for detection) and as a human-readable "Base directory:" line. body is the instructions plus any caller-supplied context.

func TodoReminderMessage

func TodoReminderMessage(listText string) Message

TodoReminderMessage builds the <system-reminder> user message carrying the current todo list. listText is the rendered list (see tool.RenderTodoReminder).

func TruncateSkillMessage

func TruncateSkillMessage(m Message, maxChars int) Message

TruncateSkillMessage returns m with its instructions body clipped so the whole message stays within roughly maxChars, preserving the envelope tags and appending a truncation note. Messages already within budget are returned unchanged.

func UserText

func UserText(s string) Message

UserText builds a user message with one text block.

func (Message) Text

func (m Message) Text() string

Text concatenates all text blocks of a message.

func (Message) ToolUses

func (m Message) ToolUses() []ContentBlock

ToolUses returns the tool_use blocks of a message.

type Provider

type Provider interface {
	Stream(ctx context.Context, req CompletionRequest) iter.Seq2[StreamEvent, error]
	Complete(ctx context.Context, req CompletionRequest) (Message, string, Usage, error)
}

Provider produces a completion, either streamed (Stream) or in a single non-streaming round-trip (Complete). Hosts may implement this interface to plug in private RPC backends (FR-03.7). Stream's iterator yields normalized events; a terminal error is delivered as (zero, err) and ends iteration. Complete performs a real non-streaming request (the wire carries stream:false and the whole JSON body is parsed at once) and returns the fully assembled assistant message, the provider stop reason, and cumulative usage.

func NewProvider

func NewProvider(cfg Config) (Provider, error)

NewProvider builds a built-in provider for cfg.Format. Missing APIKey/BaseURL fall back to standard environment variables and public endpoints.

func RateLimited

func RateLimited(p Provider, rl RateLimit) Provider

RateLimited wraps any Provider so that all of its Stream calls share one rate limit. Use it for custom Provider implementations; the built-in providers take a RateLimit via Config. Returns p unchanged if rl imposes no limit.

type RateLimit

type RateLimit struct {
	PerSecond float64 // max requests per second (0 = unlimited)
	PerMinute float64 // max requests per minute (0 = unlimited)
	// Burst is the per-second bucket capacity (how many may go at once before
	// smoothing). 0 = default to PerSecond.
	Burst float64
	// OnWait, when set, is called once per acquired request with how long the
	// caller blocked on the limiter (0 if it passed immediately). Observability
	// hook; nil disables it.
	OnWait func(blocked time.Duration)
}

RateLimit configures a shared request-rate limit for a Provider. A window with a zero value is unlimited. The limit is enforced across ALL callers that share the same Provider instance (one limiter per provider), so any number of concurrent agents using one Provider are bounded by a single limit.

Only the logical request is counted: retries inside a single Stream call do not consume additional tokens.

type Role

type Role string

Role identifies the author of a message.

const (
	RoleUser      Role = "user"
	RoleAssistant Role = "assistant"
)

type StreamEvent

type StreamEvent struct {
	Type       StreamEventType
	Text       string // text/thinking/tool_input JSON fragment
	ToolID     string // tool_use_start
	ToolName   string // tool_use_start
	StopReason string // message_delta
	Usage      Usage  // message_start / message_delta
}

StreamEvent is one normalized model-level streaming event (FR-03.5).

type StreamEventType

type StreamEventType string

StreamEventType enumerates the normalized streaming events every adapter emits, regardless of wire format.

const (
	SEMessageStart      StreamEventType = "message_start"
	SETextDelta         StreamEventType = "text_delta"
	SEThinkingDelta     StreamEventType = "thinking_delta"
	SEThinkingSignature StreamEventType = "thinking_signature"
	SEToolUseStart      StreamEventType = "tool_use_start"
	SEToolInputJSON     StreamEventType = "tool_input_delta"
	SEMessageDelta      StreamEventType = "message_delta" // carries stop_reason / usage
	SEMessageStop       StreamEventType = "message_stop"
)

type ToolSchema

type ToolSchema struct {
	Name        string         `json:"name"`
	Description string         `json:"description"`
	InputSchema map[string]any `json:"input_schema"`
}

ToolSchema is a tool definition exposed to the model.

type Usage

type Usage struct {
	InputTokens      int `json:"input_tokens"`
	OutputTokens     int `json:"output_tokens"`
	CacheReadTokens  int `json:"cache_read_tokens"`
	CacheWriteTokens int `json:"cache_write_tokens"`
}

Usage reports token accounting, including prompt-cache tokens (FR-03.8).

func (*Usage) Add

func (u *Usage) Add(o Usage)

Add accumulates token counts from another Usage.

Jump to

Keyboard shortcuts

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