Documentation
¶
Overview ¶
Package llmrec implements a recording decorator for llm.Provider. It intercepts every Stream call, captures the full request and accumulated response, and persists them to PostgreSQL for later inspection.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func TaskIDFrom ¶
TaskIDFrom returns the explicit task registry id attached by the task runtime.
func WithTaskID ¶
WithTaskID attaches the owning task registry id to an LLM call. Session ids are based on exploration ids, which are not interchangeable with task ids.
func WithWorker ¶
WithWorker overrides the agent-lane ("worker") label for an LLM call. The lane is normally parsed from the transcript session id (exp<N>-<role>); calls made outside the engine's worker/planner sessions — e.g. the intercept fallback judge — carry no such session, so they attach their lane explicitly here. This lets the usage ledger single out that spend (worker='judge') for the config page.
Types ¶
type Capture ¶
type Capture struct {
// contains filtered or unexported fields
}
Capture collects the untouched wire bodies of one logical LLM call. The Recorder creates it and puts it on the context; the HTTP transport that norma dials through (agent.quotaAwareTransport) finds it there and fills it in.
This exists because everything the Recorder itself sees is already normalized: llm.CompletionRequest is re-serialized rather than the body buildBody() sent, and the response arrives as decoded StreamEvents, not the SSE frames. For debugging a live provider, the bytes on the wire are the only ground truth.
norma's doStream retries the request-establishment phase, so one Stream can issue several HTTP attempts. Each attempt is kept: the discarded ones (see norma/llm/retry.go, which closes non-final bodies unread) are exactly what makes rate-limit and gateway failures diagnosable.
The transport writes from norma's stream-reading goroutine while the Recorder snapshots at stream end, so all state is mutex-guarded.
func CaptureFrom ¶
CaptureFrom returns the Capture attached to ctx, or nil when raw capture is off. Callers must tolerate nil — recording is a toggle, and non-recorded providers dial through the same transport.
func NewCapture ¶
NewCapture returns a context carrying a fresh Capture, plus the Capture itself.
func (*Capture) Attempts ¶
Attempts returns every round trip in order. Unlike RawResponse — which drops the header line for a lone attempt so the bytes stay replayable — this always carries the status code, for callers that must report "HTTP 401 + body".
func (*Capture) RawRequest ¶
RawRequest returns the request body as sent, or "" if nothing was captured.
func (*Capture) RawResponse ¶
RawResponse returns the response bytes as received. A single attempt yields the untouched original (copy-pasteable straight into a replay); multiple attempts are concatenated behind per-attempt header lines so a retry sequence stays readable.
func (*Capture) SetRequest ¶
SetRequest stores the outgoing request body. Retries re-send identical bytes, so only the first attempt's body is kept.
func (*Capture) TeeResponse ¶
func (c *Capture) TeeResponse(status int, rc io.ReadCloser) io.ReadCloser
TeeResponse opens a new attempt and wraps rc so everything read from it is mirrored into that attempt. It tees rather than reads because a successful response is an SSE stream that must keep streaming to the caller.
type Recorder ¶
type Recorder struct {
// contains filtered or unexported fields
}
Recorder wraps an llm.Provider and records every completion call.
func Wrap ¶
func Wrap(inner llm.Provider, pg *db.DB, model, profName, thinkingType, reasoningEffort string, enabled func() bool) *Recorder
Wrap returns a Provider that records calls to pg when enabled() reports true. profName is the LLM profile name (e.g. "default"); may be empty. thinkingType / reasoningEffort are the config-level thinking params actually sent to the API (empty = not sent). A nil enabled predicate records unconditionally.
func (*Recorder) Complete ¶
func (r *Recorder) Complete(ctx context.Context, req llm.CompletionRequest) (llm.Message, string, llm.Usage, error)
Complete implements the atomic completion path while preserving the same usage metering, normalized body recording and raw wire capture as Stream.
func (*Recorder) Stream ¶
func (r *Recorder) Stream(ctx context.Context, req llm.CompletionRequest) iter.Seq2[llm.StreamEvent, error]
Stream implements llm.Provider. It delegates to the inner provider, accumulates the streamed events to reconstruct the response, and records the full exchange.
The session identifier (e.g. "exp1-worker-i3") is carried on ctx by the norma harness via transcript.WithSessionID; reading it per-call is race-free even when planner and multiple workers share one Recorder instance.