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
- func IsBoundaryMarker(m Message) bool
- func IsTodoReminder(m Message) bool
- func LastBoundaryIndex(msgs []Message) int
- func SkillInvocationName(m Message) (string, bool)
- type Accumulator
- type BlockType
- type BoundaryMeta
- type CompletionRequest
- type Config
- type ContentBlock
- type Format
- type Message
- func BoundaryMessage(meta BoundaryMeta) Message
- func MessagesAfterBoundary(msgs []Message) []Message
- func MessagesForAPI(msgs []Message) []Message
- func PairToolBlocks(msgs []Message) []Message
- func SkillInvocationMessage(name, path, body string) Message
- func TodoReminderMessage(listText string) Message
- func TruncateSkillMessage(m Message, maxChars int) Message
- func UserText(s string) Message
- type Provider
- type RateLimit
- type Role
- type StreamEvent
- type StreamEventType
- type ToolSchema
- type Usage
Constants ¶
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 ¶
IsBoundaryMarker reports whether m is a compact-boundary marker.
func IsTodoReminder ¶
IsTodoReminder reports whether m is a todo-reminder message (used to derive the throttle "turns since last reminder").
func LastBoundaryIndex ¶
LastBoundaryIndex returns the index of the last boundary marker, or -1.
func SkillInvocationName ¶
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 ¶
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 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 ToolResultText ¶
func ToolResultText(toolUseID, text string, isError bool) ContentBlock
ToolResultText builds a tool_result block carrying a single text block.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
TodoReminderMessage builds the <system-reminder> user message carrying the current todo list. listText is the rendered list (see tool.RenderTodoReminder).
func TruncateSkillMessage ¶
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 (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 ¶
NewProvider builds a built-in provider for cfg.Format. Missing APIKey/BaseURL fall back to standard environment variables and public endpoints.
func RateLimited ¶
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 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.