llm

package
v0.16.0 Latest Latest
Warning

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

Go to latest
Published: Jul 13, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package llm handles LLM-based generation tasks: prompt rendering, template embedding, runner invocation, and output parsing. Finders (read side) and handlers (write side) both consume this package — it owns the "call an LLM with a structured prompt" concern.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FormatEntryForPrompt

func FormatEntryForPrompt(e *model.Entry) string

FormatEntryForPrompt formats an entry as readable text for inclusion in a prompt.

Refs rendering is conditional on the entry's ref shape, per d-tac-4ub:

  • All-legacy (every ref has Kind == RefKindUnknown — bare-string YAML fallback): render the flat `Refs: id1, id2` format. Legacy refs carry no kind or desc, so the object form would only add empty `(kind: unknown)` noise.
  • Object-form (any ref carries a capturable kind): render multi-line ` - id (kind: K): desc` so the LLM sees ref metadata, enabling the ref-meta consistency check (see ref_meta_consistency.tmpl).

Mixed entries (some refs legacy, some object) get the multi-line form — the presence of any object-form ref signals an entry authored under the new contract; rendering uniformly preserves clarity.

This renders canonical (parse-resolved) ref kinds — the single form both pre-flight and summary generation consume.

func RecordEmbedCall added in v0.8.0

func RecordEmbedCall(ctx context.Context, stat CallStat)

RecordEmbedCall logs one embedding batch at debug level and hands its metrics to the StatsSink on ctx (if any). It mirrors logCallResult for the embedding path, which carries no LLMMetadata: embeddings report input tokens and an item count, never output tokens or a cache breakdown. The caller supplies a fully-populated CallStat (op, provider, model, items, input tokens, duration). Best-effort — a nil sink is a no-op and a sink error never reaches the caller.

func WithStatsSink added in v0.7.1

func WithStatsSink(ctx context.Context, sink StatsSink) context.Context

WithStatsSink returns a context carrying the sink, retrieved later by the call-logging path. A nil sink is permitted and makes recording a no-op.

Types

type CallStat added in v0.7.1

type CallStat struct {
	Op       string
	Provider string
	Model    string
	// Items is the number of inputs in this call. The embedding path sets it
	// (one batch = N texts) so throughput (items or tokens per second) is
	// derivable from DurationMS; chat calls are single-prompt and leave it 0.
	Items             int
	InputTokens       int
	OutputTokens      int
	CacheReadTokens   int
	CacheCreateTokens int
	DurationMS        int64
}

CallStat is one LLM call's metrics, handed to a StatsSink for durable collection. The timestamp is added by the sink implementation. Cost is deliberately omitted: the provider APIs on the active (gollm) path report tokens and the prompt-cache breakdown but not a dollar cost, and we do not maintain a pricing table (see d-tac-zis).

type Embedder added in v0.4.0

type Embedder interface {
	// EmbedDocuments embeds texts as index-side passages, applying the
	// configured DocumentTemplate. Used by the indexer at build /
	// lazy-fill time.
	EmbedDocuments(ctx context.Context, texts []string) ([][]float32, error)
	// EmbedQueries embeds texts as retrieval-side queries, applying the
	// configured QueryTemplate. Used by the search finder at query time.
	EmbedQueries(ctx context.Context, texts []string) ([][]float32, error)
	Dimensions() int
	Fingerprint() string
	// BatchSize is the per-call input cap the embedder targets. Drivers
	// the indexer's outer bucketing so each Embed call corresponds to
	// exactly one HTTP round-trip — progress callbacks fire per batch
	// instead of "all-at-once at the end" of a giant cross-entry call.
	// Larger inputs still embed correctly; the embedder splits
	// internally and the indexer just sees a slower individual call.
	BatchSize() int
}

Embedder turns a batch of texts into dense vectors. Implementations are thin transport adapters around an embedding service (OpenAI-compatible `/v1/embeddings`, Ollama `/api/embed`); construction is via embed.New.

The interface splits embedding into a document-side and a query-side because instruction-tuned encoders (Qwen3, E5, Nomic, BGE) want asymmetric prefixes — `passage:` on documents, `query:` on queries, or `Instruct: …\nQuery:…` only on queries. The split makes the call-site intent explicit so the wrong template can't silently slip onto the wrong side. Untemplated models (OpenAI text-embedding-3) treat both methods as equivalent.

Dimensions returns the vector length the implementation produces — must be stable across calls so the index can validate row shape.

Fingerprint is an opaque identifier for the (provider + model + truncation + document template) tuple. Used by the search index to detect rows whose embedding is stale after a configuration change. The format is implementation-defined, but implementations must guarantee that two embedders that produce distribution-comparable document vectors share a fingerprint, and two that don't, don't. The query template deliberately does NOT factor into the fingerprint — query template changes affect retrieval quality but never invalidate indexed embeddings, so they're a free-tweak knob (see EmbeddingConfig).

type Finding

type Finding struct {
	Severity    Severity
	Category    string
	Observation string
}

Finding is a single observation from pre-flight validation.

type LLMMetadata

type LLMMetadata struct {
	// Provider is the configured LLM provider that served the call
	// (e.g. "anthropic", "ollama", "openai", "claude-cli").
	Provider          string
	TotalCostUSD      float64
	InputTokens       int
	OutputTokens      int
	CacheReadTokens   int
	CacheCreateTokens int
	NumTurns          int
	Duration          time.Duration
	DurationAPI       time.Duration
	Models            map[string]ModelUsage
}

LLMMetadata holds agent-neutral per-call metrics from the LLM provider.

type ModelUsage

type ModelUsage struct {
	InputTokens       int
	OutputTokens      int
	CacheReadTokens   int
	CacheCreateTokens int
	CostUSD           float64
}

ModelUsage holds per-model token and cost metrics.

type PreflightResult

type PreflightResult struct {
	Findings []Finding
}

PreflightResult holds the parsed findings from a pre-flight validator run. An empty Findings slice means the validator reported no findings.

func Preflight

func Preflight(ctx context.Context, runner Runner, entry *model.Entry, graph *model.Graph, configuredLanguage string) (*PreflightResult, error)

Preflight runs the pre-flight validator against the given entry and graph. Returns the parsed result regardless of finding severity. Returns an error only for infrastructure failures (runner error, template error, parse error).

Participant validation is handled by mechanical checks in the finders layer (see finders.mechanicalPreflight) — the LLM participant-drift rubric is retired per plan d-cpt-d34 AC 9.

configuredLanguage is the graph authoring language (locale code) from `.sdd/config.yaml` (empty string when unset — English default). It feeds the language-drift check which flags entries whose description prose does not match the configured language.

func (*PreflightResult) HasBlocking

func (r *PreflightResult) HasBlocking() bool

HasBlocking reports whether any finding blocks entry creation. Currently only SeverityHigh blocks.

type Request added in v0.2.0

type Request struct {
	SystemPrompt string
	UserPrompt   string
}

Request carries the two-part prompt submitted to a Runner. SystemPrompt holds the stable portion (instructions, structural rules) — providers that support prompt caching treat this as the cacheable prefix. UserPrompt holds the per-call variable portion (entry content, refs). Runners that can't distinguish system from user concatenate them with SystemPrompt first.

func RenderSummaryPrompt

func RenderSummaryPrompt(entry *model.Entry, graph *model.Graph) (Request, error)

RenderSummaryPrompt renders the summary prompt for an entry. Returns a Request with the full rendered prompt in UserPrompt; the system/user split is introduced when templates are refactored (see the plan decision).

func (Request) Combined added in v0.2.0

func (r Request) Combined() string

Combined returns SystemPrompt followed by UserPrompt separated by a blank line when both are non-empty. Runners without native system-prompt support use this to flatten the Request into a single payload.

type RunResult

type RunResult struct {
	Text string
	Meta *LLMMetadata
}

RunResult holds the LLM response text and optional metadata.

func Run added in v0.2.0

func Run(ctx context.Context, runner Runner, req Request, op string) (*RunResult, error)

Run executes a pre-rendered Request against the Runner and emits the standard debug log entry. Callers that orchestrate prompt rendering themselves (e.g. the parallel summarize handler) use this instead of Runner.Run directly so logging stays uniform across call sites.

type Runner

type Runner interface {
	Run(ctx context.Context, req Request) (*RunResult, error)
}

Runner executes a structured LLM request and returns the response with metadata. The implementation decides which model and transport to use. Injected so tests can substitute fakes.

type Severity

type Severity string

Severity classifies a pre-flight finding. Mirrored in the query package; templates describe severity in purely semantic terms.

const (
	SeverityHigh   Severity = "high"
	SeverityMedium Severity = "medium"
	SeverityLow    Severity = "low"
)

type StatsSink added in v0.7.1

type StatsSink interface {
	RecordCall(CallStat)
}

StatsSink durably records per-call LLM metrics (e.g. to a local JSONL file). Implementations must be safe for concurrent use — batch operations like `sdd summarize --all` call them from multiple goroutines.

type SummarizeResult

type SummarizeResult struct {
	Summary string
}

SummarizeResult holds the generated summary.

func Summarize

func Summarize(ctx context.Context, runner Runner, entry *model.Entry, graph *model.Graph) (*SummarizeResult, error)

Summarize generates a summary for a single entry using the LLM runner. Summaries are derived on demand with no staleness tracking (d-cpt-4qi), so this always regenerates — the caller decides when to invoke it.

Directories

Path Synopsis
Package claude implements llm.Runner by invoking the Claude CLI.
Package claude implements llm.Runner by invoking the Claude CLI.
Package embed provides Embedder implementations for OpenAI-compatible (`/v1/embeddings`) and Ollama (`/api/embeddings`) endpoints, plus a factory that dispatches by configured provider and wraps remote providers with a rate.Limiter (paralleling the chat-runner factory).
Package embed provides Embedder implementations for OpenAI-compatible (`/v1/embeddings`) and Ollama (`/api/embeddings`) endpoints, plus a factory that dispatches by configured provider and wraps remote providers with a rate.Limiter (paralleling the chat-runner factory).
Package factory resolves an llm.Runner from model.LLMConfig.
Package factory resolves an llm.Runner from model.LLMConfig.
Package gollm implements llm.Runner on top of github.com/teilomillet/gollm, providing a unified adapter for Anthropic API, OpenAI, Ollama, and other providers supported by gollm.
Package gollm implements llm.Runner on top of github.com/teilomillet/gollm, providing a unified adapter for Anthropic API, OpenAI, Ollama, and other providers supported by gollm.

Jump to

Keyboard shortcuts

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