llm

package
v0.3.0 Latest Latest
Warning

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

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

Documentation

Overview

Package llm talks to an OpenAI-compatible chat/completions API via a hand-written net/http client (intentionally without an SDK — see docs/TECHNICAL_PLAN.md §2), and provides a deterministic offline provider used when no API key is configured.

Both implementations satisfy the Provider interface, which is justified here because there are genuinely two implementations (project rule: "the interface appears at the second implementation, not the first"). The three supported wire providers — openai, ollama, azure_openai — are branches inside the one Client type (different endpoint/auth shapes, same request/response JSON), not separate Provider implementations.

SSE streaming remains out of scope and lands post-MVP (see docs/TECHNICAL_PLAN.md §6).

Index

Constants

View Source
const (
	ProviderOpenAI = "openai"
	ProviderOllama = "ollama"
	ProviderAzure  = "azure_openai"
)

Supported provider identifiers.

View Source
const (
	RiskLow    = "low"
	RiskMedium = "medium"
	RiskHigh   = "high"
)

Risk levels, lowest → highest. never is not a level a review can carry — it is only a --fail-on threshold meaning "never fail" — but it participates in the ordering below.

Variables

This section is empty.

Functions

func EstimateTokens

func EstimateTokens(text string) int

EstimateTokens approximates the token count of text as ceil(len(bytes)/4). It does NOT apply the safety margin — that is applied once by EstimateCost to the combined total, to avoid double-counting.

func RiskAtLeast

func RiskAtLeast(level, threshold string) bool

RiskAtLeast reports whether level meets or exceeds threshold in the ordering low < medium < high, with "never" below all of them. Both arguments are compared case-insensitively. An unrecognized threshold is treated as "never" (never fail) — the safe direction, since an unknown level's zero-value rank would otherwise sit below every real risk level and make this always true. config.Config.validate() rejects bad policy.fail_on values before they ever reach here; this is a defense-in-depth guard for other callers.

func ValidFailOnLevel added in v0.2.0

func ValidFailOnLevel(s string) bool

ValidFailOnLevel reports whether s is a recognised --fail-on value (never | low | medium | high). It consults the same riskOrder map as RiskAtLeast so config validation and comparison share one source of truth.

func ValidRiskLevel

func ValidRiskLevel(level string) bool

ValidRiskLevel reports whether level is one of low|medium|high.

Types

type AzureConfig

type AzureConfig struct {
	Endpoint   string
	Deployment string
	APIVersion string
}

AzureConfig holds the Azure OpenAI-specific endpoint coordinates, required only when provider == "azure_openai".

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a hand-written net/http client for an OpenAI-compatible chat/completions endpoint. It supports three providers — openai, ollama and azure_openai — that differ only in endpoint URL and auth header; the request and response JSON shape is identical across all three, so there is a single wire format. Requests are retried with exponential backoff + jitter on retryable failures (429/5xx/network), bounded by maxRetries; fatal failures (400/401/403/404) fail immediately.

func NewClient

func NewClient(cfg ClientConfig) (*Client, error)

NewClient constructs a Client for one of the three supported providers. An unknown provider is a real configuration error. Azure requires its endpoint/deployment/api_version sub-block.

func (*Client) Complete

func (c *Client) Complete(ctx context.Context, req Request) (Response, error)

Complete sends a chat/completions request (with retry/backoff) and returns the assistant text plus a risk score. If the model's trailing risk block is missing or invalid, the deterministic heuristic (§7.3) supplies the risk and a warning is logged. The context (carrying the configured timeout and Ctrl-C cancellation) is threaded through the HTTP call and the retry sleeps.

func (*Client) Stream added in v0.3.0

func (c *Client) Stream(ctx context.Context, req Request, w io.Writer) (Response, error)

Stream sends a streaming chat/completions request and writes each token to w as it arrives, accumulating the full text so the risk block can be parsed after the stream completes. Unlike Complete, it performs NO retry: once any byte of the body is read the request cannot be safely replayed. A non-200 status returned before the first byte is surfaced as a *StatusError so the caller may fall back to the non-streaming Complete path.

type ClientConfig

type ClientConfig struct {
	Provider   string
	BaseURL    string
	APIKey     string
	Timeout    time.Duration
	MaxRetries int
	Azure      AzureConfig
}

ClientConfig configures a Client. It mirrors the config.LLMConfig fields the network path needs, without importing internal/config (avoids a cycle and keeps llm reusable).

type Estimate

type Estimate struct {
	Model        string
	InputTokens  int
	OutputTokens int // the configured max_tokens ceiling
	Pricing      Pricing
	CostUSD      float64
}

Estimate is the result of a cost estimation for one review request.

func EstimateCost

func EstimateCost(promptText string, maxOutputTokens int, pricing Pricing) Estimate

EstimateCost estimates the USD cost of a request whose full prompt (system + user, exactly what goes on the wire) is promptText, capping output at maxOutputTokens (the configured llm.max_tokens). pricing is the price table entry (from LookupPricing or a config override). The ×1.15 safety margin is applied once, to the combined input+output token totals (§8.1).

func (Estimate) String

func (e Estimate) String() string

String renders a human-readable one-line summary of an estimate.

type Offline

type Offline struct {
	// contains filtered or unexported fields
}

Offline is a deterministic, network-free Provider used when no API key is configured. Given the same commits and diff it always produces the same Markdown review — no time-based randomness, no external calls — so tests stay stable.

It is a genuinely useful fallback (not a "no key" stub): it summarizes commit subjects, lists changed files grouped by status, and reports rough size stats. It always carries a structured risk score (low|medium|high) computed by the shared deterministic heuristic (§7.3) — there is no model to ask.

func NewOffline

func NewOffline(commits []gitlog.Commit, diff string) *Offline

NewOffline builds an Offline provider from the structured review input. The provider works from this data directly; the Request passed to Complete is ignored (there is no model to prompt).

func (*Offline) Complete

func (o *Offline) Complete(ctx context.Context, _ Request) (Response, error)

Complete returns a deterministic Markdown review with a heuristic risk score. The ctx is honored for cancellation; the Request's prompt fields are unused (offline has no model to prompt), though its Commits/Diff would be equivalent to the ones NewOffline was built from.

type Pricing

type Pricing struct {
	InputPer1M  float64
	OutputPer1M float64
}

Pricing is the per-1M-token price of a model, in USD.

func LookupPricing

func LookupPricing(model string) (Pricing, bool)

LookupPricing returns the built-in pricing for a model and whether it was found. Callers use the returned bool to decide between the table, a config override, or the permissive "no pricing data" path.

type Provider

type Provider interface {
	Complete(ctx context.Context, req Request) (Response, error)
}

Provider produces a completion for a Request. Implemented by both the network Client and the deterministic Offline provider.

type Request

type Request struct {
	// System is the system prompt (role=system); may be empty.
	System string
	// User is the user prompt (role=user).
	User string
	// Model, MaxTokens and Temperature come from config; the offline provider
	// ignores them.
	Model       string
	MaxTokens   int
	Temperature float64

	// Commits and Diff are pass-through context (not sent over the wire) so the
	// risk heuristic (§7.3) can be computed inside the provider without
	// re-deriving structure from the prompt text: the offline provider always
	// uses them, and the network Client uses them as a fallback when the
	// model's risk block is missing or invalid.
	Commits []gitlog.Commit
	Diff    string
}

Request is a provider-agnostic completion request.

type Response

type Response struct {
	// Content is the assistant message text (Markdown for review), with any
	// trailing risk block already stripped (§7.2).
	Content string
	// Risk is always populated: the model supplies it, or the heuristic
	// fallback does (§7.2, §7.3). Consumers never need to handle an empty risk.
	Risk Risk
}

Response is a provider-agnostic completion response.

type Risk

type Risk struct {
	Level     string
	Summary   string
	Heuristic bool
}

Risk is the structured risk score attached to a review (§7). Level is one of "low", "medium", "high"; Summary is a one-sentence explanation. Heuristic is true when the score was computed by the deterministic fallback (offline mode, or the model omitted a valid risk block) rather than parsed from the model's own fenced risk block.

func HeuristicRisk

func HeuristicRisk(commits []gitlog.Commit, diff string) Risk

HeuristicRisk computes a deterministic risk score from commits and diff. It is the single shared implementation used both by the offline provider (always) and by the network Client as a fallback when the model's risk block is missing or invalid (§7.3). Same input → same output, no randomness, no I/O.

func ParseRisk

func ParseRisk(content string) (stripped string, risk Risk, ok bool)

ParseRisk extracts the model's risk score from a review body (§7.2). It looks for the LAST fenced block tagged `risk`, validates the level, and returns the review content with that block stripped. ok is false when no valid risk block is found, in which case the caller should fall back to the heuristic; the returned content is the original text with any partially-matched, invalid risk blocks left intact (only a valid block is stripped). Only ` ```risk ` blocks are accepted — ` ```json ` is not, to avoid accidentally treating a generic JSON block as a risk payload.

type StatusError

type StatusError struct {
	StatusCode int
	Retryable  bool
	Provider   string
	Message    string
}

StatusError is a typed HTTP error carrying the status code and whether the request is worth retrying. It is used with errors.As so retry classification never relies on string matching.

func (*StatusError) Error

func (e *StatusError) Error() string

type Streamer added in v0.3.0

type Streamer interface {
	Stream(ctx context.Context, req Request, w io.Writer) (Response, error)
}

Streamer is an optional interface for providers that support token-by-token SSE streaming. Client implements it; Offline does not.

Jump to

Keyboard shortcuts

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