LLM interaction utilities for structured and unstructured outputs.
SVAR = Structured Validated Automated Reasoning
Scope: structured LLM output + provider routing. Main functions:
ask! - Structured output using the spec DSLask-code! - native tool-calling completion (the model acts via tools)models! - Fetch available models from the LLM APIRe-exports the spec DSL (field, spec, str->data, str->data-with-spec,
data->str, validate-data, spec->prompt, build-ref-registry) and
make-router so users can require only this namespace.
Configuration: LLM calls route automatically via the router.
Example: (ask! router {:spec my-spec :messages [(system "Help the user.") (user "What is 2+2?")] :model "gpt-4o"})
LLM interaction utilities for structured and unstructured outputs.
SVAR = Structured Validated Automated Reasoning
Scope: structured LLM output + provider routing. Main functions:
- `ask!` - Structured output using the spec DSL
- `ask-code!` - native tool-calling completion (the model acts via tools)
- `models!` - Fetch available models from the LLM API
Re-exports the spec DSL (`field`, `spec`, `str->data`, `str->data-with-spec`,
`data->str`, `validate-data`, `spec->prompt`, `build-ref-registry`) and
`make-router` so users can require only this namespace.
Configuration:
LLM calls route automatically via the router.
Example:
(ask! router {:spec my-spec
:messages [(system "Help the user.")
(user "What is 2+2?")]
:model "gpt-4o"})Extension-facing helpers for router limits and parse diagnostics.
Preserved-thinking handoff is provider-agnostic via the
:assistant-message field on ask! / ask-code! results. The
value is a canonical svar message — {:role "assistant" :content [<canonical-blocks>]} — which callers append to :messages on
the next call. Canonical {:type "thinking"} content blocks
carry the per-provider preserved-reasoning state under
:thinking-signature; svar's wire serializers transform them into
native shapes (Anthropic signed thinking blocks, z.ai
reasoning_content field, OpenAI Responses reasoning input items).
Plain chat models without preserved thinking just don't surface
:assistant-message, so the same caller pipeline
(keep :assistant-message results) works uniformly across every
provider with zero per-provider branching.
Extension-facing helpers for router limits and parse diagnostics.
Preserved-thinking handoff is provider-agnostic via the
`:assistant-message` field on `ask!` / `ask-code!` results. The
value is a canonical svar message — `{:role "assistant" :content
[<canonical-blocks>]}` — which callers append to `:messages` on
the next call. Canonical `{:type "thinking"}` content blocks
carry the per-provider preserved-reasoning state under
`:thinking-signature`; svar's wire serializers transform them into
native shapes (Anthropic signed thinking blocks, z.ai
`reasoning_content` field, OpenAI Responses reasoning input items).
Plain chat models without preserved thinking just don't surface
`:assistant-message`, so the same caller pipeline
`(keep :assistant-message results)` works uniformly across every
provider with zero per-provider branching.Single source of truth for provider/gateway failure classification.
Why this namespace exists: llm (low-level HTTP retry) and router
(provider fallback) each grew their own copy of the same substring/status
heuristics, so a newly observed transient had to be added twice and the two
layers could disagree about whether the very same exception was retryable.
Everything about "what kind of failure is this and is it safe to retry"
now lives here, and both layers delegate.
The driving deployment is a self-hosted LiteLLM gateway in front of many upstreams (Bedrock, Azure, vLLM). Under load it produces a steady drizzle of failures that are NOT the caller's fault and NOT the model's answer:
litellm.Timeout: BedrockException: Timeout Error - litellm.Timeout: Connection timed out. Timeout passed=Timeout(connect=5.0, read=600.0 ...) Received Model Group=claude-sonnet-5 Available Model Group Fallbacks=None
HTTP/1.1 header parser received no bytes
No deployments available for selected model group
Every one of those clears on a retry seconds later. Agents that feel "bulletproof" against such a gateway are not doing anything clever: they simply classify the whole transient family as retryable and back off with jitter, instead of surfacing the first blip as a hard failure.
Classification is deliberately EVIDENCE-ORDERED, most specific first: account state (quota/billing) and definitive client errors win over any transient-looking wording, so a 400 whose body merely contains the word "timeout" is never retried.
All predicates are pure: they take an already-lower-cased haystack plus an optional status, or a Throwable, and return data. No IO, no sleeping.
Single source of truth for provider/gateway failure classification. Why this namespace exists: `llm` (low-level HTTP retry) and `router` (provider fallback) each grew their own copy of the same substring/status heuristics, so a newly observed transient had to be added twice and the two layers could disagree about whether the very same exception was retryable. Everything about "what kind of failure is this and is it safe to retry" now lives here, and both layers delegate. The driving deployment is a self-hosted LiteLLM gateway in front of many upstreams (Bedrock, Azure, vLLM). Under load it produces a steady drizzle of failures that are NOT the caller's fault and NOT the model's answer: litellm.Timeout: BedrockException: Timeout Error - litellm.Timeout: Connection timed out. Timeout passed=Timeout(connect=5.0, read=600.0 ...) Received Model Group=claude-sonnet-5 Available Model Group Fallbacks=None HTTP/1.1 header parser received no bytes No deployments available for selected model group Every one of those clears on a retry seconds later. Agents that feel "bulletproof" against such a gateway are not doing anything clever: they simply classify the whole transient family as retryable and back off with jitter, instead of surfacing the first blip as a hard failure. Classification is deliberately EVIDENCE-ORDERED, most specific first: account state (quota/billing) and definitive client errors win over any transient-looking wording, so a 400 whose body merely contains the word "timeout" is never retried. All predicates are pure: they take an already-lower-cased haystack plus an optional status, or a Throwable, and return data. No IO, no sleeping.
Wrapper for the JsonishParser Java class.
Provides SAP (Schemaless Adaptive Parsing) for malformed JSON from LLMs. Handles unquoted keys/values, trailing commas, markdown code blocks, etc.
Wrapper for the JsonishParser Java class. Provides SAP (Schemaless Adaptive Parsing) for malformed JSON from LLMs. Handles unquoted keys/values, trailing commas, markdown code blocks, etc.
LLM client layer: HTTP transport, message construction, and all LLM interaction functions (ask!, abstract!, eval!, refine!, models!, sample!).
LLM client layer: HTTP transport, message construction, and all LLM interaction functions (ask!, abstract!, eval!, refine!, models!, sample!).
models.dev catalog loader.
Reads the bundled resources/models.dev.json snapshot (refreshed via
make refresh-models) and exposes a normalized view that downstream
router code merges with KNOWN_PROVIDERS wire/policy overlay.
Catalog wins for: pricing, context, modalities, capability flags, release dates, family. svar overlay wins for: api-style, reasoning-style, llm-headers, env-keys, base-url, paths, extra-body, exclude-models, rate budgets, default-models.
Plan-vs-retail pricing — per-provider entries on models.dev already
reflect plan zeros (e.g. github-copilot, zai-coding-plan ship
{input:0, output:0}). For svar's :openai-codex and
:anthropic-coding-plan we explicitly want retail pricing
(the user pays at API rates once metered), so the overlay declares
:pricing-source to redirect catalog lookup to the retail provider.
models.dev catalog loader.
Reads the bundled `resources/models.dev.json` snapshot (refreshed via
`make refresh-models`) and exposes a normalized view that downstream
router code merges with `KNOWN_PROVIDERS` wire/policy overlay.
Catalog wins for: pricing, context, modalities, capability flags,
release dates, family.
svar overlay wins for: api-style, reasoning-style, llm-headers,
env-keys, base-url, paths, extra-body, exclude-models, rate budgets,
default-models.
Plan-vs-retail pricing — per-provider entries on models.dev already
reflect plan zeros (e.g. `github-copilot`, `zai-coding-plan` ship
{input:0, output:0}). For svar's `:openai-codex` and
`:anthropic-coding-plan` we explicitly want **retail** pricing
(the user pays at API rates once metered), so the overlay declares
`:pricing-source` to redirect catalog lookup to the retail provider.Provider rate-limit header parsing — single source of truth for the
:rate-limit map surfaced on ask! / ask-code! results.
Providers send their quota-reset clock on EVERY response (success or 429) via headers; svar used to throw those headers away, so status renderers (vis footer, CLI) had no reset date to show. This ns parses the per-provider header families into ONE canonical shape:
{:resets-at-ms <epoch-ms> ;; soonest effective reset across windows :remaining <long> ;; requests remaining in the primary window :limit <long> ;; primary-window limit :windows {:requests {:resets-at-ms :remaining :limit} :tokens {:resets-at-ms :remaining :limit} :unified {:resets-at-ms :remaining :status}} :raw {<header> <value>}} ;; the rate-limit headers, verbatim
:resets-at-ms is the caller-facing 'effective date of reset' (epoch
millis, UTC). Two provider dialects:
Anthropic (:anthropic — Claude API + coding-plan OAuth): reset
values are ABSOLUTE — either unix epoch seconds
(anthropic-ratelimit-unified-reset) or RFC-3339 timestamps
(anthropic-ratelimit-{requests,tokens}-reset). The coding plan's
5h/weekly window rides on the -unified- family. CAVEAT: the
Claude coding-plan OAuth proxy (sk-ant-oat* tokens) does NOT
forward the anthropic-ratelimit-* headers, so parse returns
nil there. For the coding plan callers must prefer the OAuth
usage endpoint (/api/oauth/usage) as the source of truth and
treat this header parse as best-effort fallback only.
OpenAI / Codex (:openai-compatible-*): reset values are RELATIVE
Go-style durations (x-ratelimit-reset-requests = "6m0s",
"1s", "100ms", "1h2m3s"); resets-at-ms = now + duration.
Returns nil when no rate-limit headers are present (never throws — a quirky header must not break the LLM call).
Provider rate-limit header parsing — single source of truth for the
`:rate-limit` map surfaced on `ask!` / `ask-code!` results.
Providers send their quota-reset clock on EVERY response (success or
429) via headers; svar used to throw those headers away, so status
renderers (vis footer, CLI) had no reset date to show. This ns parses
the per-provider header families into ONE canonical shape:
{:resets-at-ms <epoch-ms> ;; soonest effective reset across windows
:remaining <long> ;; requests remaining in the primary window
:limit <long> ;; primary-window limit
:windows {:requests {:resets-at-ms :remaining :limit}
:tokens {:resets-at-ms :remaining :limit}
:unified {:resets-at-ms :remaining :status}}
:raw {<header> <value>}} ;; the rate-limit headers, verbatim
`:resets-at-ms` is the caller-facing 'effective date of reset' (epoch
millis, UTC). Two provider dialects:
- Anthropic (`:anthropic` — Claude API + coding-plan OAuth): reset
values are ABSOLUTE — either unix epoch seconds
(`anthropic-ratelimit-unified-reset`) or RFC-3339 timestamps
(`anthropic-ratelimit-{requests,tokens}-reset`). The coding plan's
5h/weekly window rides on the `-unified-` family. CAVEAT: the
Claude *coding-plan* OAuth proxy (sk-ant-oat* tokens) does NOT
forward the `anthropic-ratelimit-*` headers, so `parse` returns
nil there. For the coding plan callers must prefer the OAuth
usage endpoint (`/api/oauth/usage`) as the source of truth and
treat this header parse as best-effort fallback only.
- OpenAI / Codex (`:openai-compatible-*`): reset values are RELATIVE
Go-style durations (`x-ratelimit-reset-requests` = "6m0s",
"1s", "100ms", "1h2m3s"); resets-at-ms = now + duration.
Returns nil when no rate-limit headers are present (never throws — a
quirky header must not break the LLM call).Router: provider/model registry, circuit breakers, retry routing, budget tracking, and routing resolution.
Extracted from defaults.clj (provider/model metadata) and llm.clj (routing logic) to provide a single cohesive namespace for all routing concerns.
Router: provider/model registry, circuit breakers, retry routing, budget tracking, and routing resolution. Extracted from defaults.clj (provider/model metadata) and llm.clj (routing logic) to provide a single cohesive namespace for all routing concerns.
Structured output specification system for LLM responses.
This namespace provides a DSL for defining expected output structures, converting specs to LLM prompts, and parsing LLM responses back to Clojure data.
Primary functions:
field - Define a field with name, type, cardinality, and descriptionspec - Create a spec from field definitionsbuild-ref-registry - Build a registry of referenced specs for nested typesspec->prompt - Generate LLM prompt text from a spec (sent to LLM)str->data - Parse LLM response string to Clojure data (schemaless)str->data-with-spec - Parse LLM response with spec-based type coercionvalidate-data - Validate parsed data against a specdata->str - Serialize Clojure data to JSON stringData Flow:
spec and field functionsspec->prompt (sent to LLM)str->data-with-spec (LLM response -> typed Clojure map)validate-datadata->strStructured output specification system for LLM responses. This namespace provides a DSL for defining expected output structures, converting specs to LLM prompts, and parsing LLM responses back to Clojure data. Primary functions: - `field` - Define a field with name, type, cardinality, and description - `spec` - Create a spec from field definitions - `build-ref-registry` - Build a registry of referenced specs for nested types - `spec->prompt` - Generate LLM prompt text from a spec (sent to LLM) - `str->data` - Parse LLM response string to Clojure data (schemaless) - `str->data-with-spec` - Parse LLM response with spec-based type coercion - `validate-data` - Validate parsed data against a spec - `data->str` - Serialize Clojure data to JSON string Data Flow: 1. Define spec with `spec` and `field` functions 2. Generate prompt with `spec->prompt` (sent to LLM) 3. Parse response with `str->data-with-spec` (LLM response -> typed Clojure map) 4. Optionally validate with `validate-data` 5. Optionally serialize with `data->str`
Canonical token-usage shape — single source of truth across providers.
Phase A of svar 0.6.0. Replaces the hybrid pre-0.6 shape that emitted
:prompt_tokens with provider-dependent semantics (Anthropic
additive, OpenAI inclusive) under the same key. Every provider
normalizer now produces the SAME shape; downstream consumers read
one set of keys regardless of which model served the call.
Canonical shape — INVARIANT: regular + cache-write + cache-read = input-tokens:
{:input-tokens <long> ;; TOTAL prompt tokens (always inclusive) :output-tokens <long> ;; TOTAL completion tokens :input-tokens-details {:regular <long> ;; not from cache, not written :cache-write <long> ;; written this request (1.25× input rate, anthropic; 0 else) :cache-read <long>} ;; served from cache (0.1× input rate, anthropic; ~10-50% off, openai) :output-tokens-details {:reasoning <long>} ;; subset of output-tokens :total-tokens <long> ;; convenience = input-tokens + output-tokens :raw <map>} ;; original provider envelope (debug / forensics)
Provider differences:
Anthropic Messages API (:anthropic api-style): RAW
input_tokens excludes cached AND cache-creation. Canonical
:input-tokens adds all three so the value is TOTAL.
OpenAI Chat / Responses (:openai-compatible-* api-styles): RAW
prompt_tokens / input_tokens IS the total. Cached subset lives
under prompt_tokens_details.cached_tokens /
input_tokens_details.cached_tokens. No native cache-write
concept (server-managed implicit caching), so :cache-write is
always 0 here UNLESS the provider proxies Anthropic via OpenRouter
and surfaces cache_creation_input_tokens as a pydantic extra
field.
Z.ai (GLM coding-plan / OpenAI-compatible-chat): same as OpenAI.
Industry alignment (May 2026):
inputTokens always TOTAL
with inputTokensDetails {regular, cacheWrite, cacheRead}.gen_ai.usage.input_tokens (≥ v1.37): SHOULD be
inclusive (all kinds of input tokens).context_window.total_input_tokens = input + cache_creation + cache_read.Reject: the additive convention (e.g. litellm PR #23342) leaves
total_tokens inconsistent with prompt_tokens and breaks naive
aggregation downstream.
Canonical token-usage shape — single source of truth across providers.
Phase A of svar 0.6.0. Replaces the hybrid pre-0.6 shape that emitted
`:prompt_tokens` with provider-dependent semantics (Anthropic
additive, OpenAI inclusive) under the same key. Every provider
normalizer now produces the SAME shape; downstream consumers read
one set of keys regardless of which model served the call.
Canonical shape — INVARIANT: `regular + cache-write + cache-read = input-tokens`:
{:input-tokens <long> ;; TOTAL prompt tokens (always inclusive)
:output-tokens <long> ;; TOTAL completion tokens
:input-tokens-details {:regular <long> ;; not from cache, not written
:cache-write <long> ;; written this request (1.25× input rate, anthropic; 0 else)
:cache-read <long>} ;; served from cache (0.1× input rate, anthropic; ~10-50% off, openai)
:output-tokens-details {:reasoning <long>} ;; subset of output-tokens
:total-tokens <long> ;; convenience = input-tokens + output-tokens
:raw <map>} ;; original provider envelope (debug / forensics)
Provider differences:
- Anthropic Messages API (`:anthropic` api-style): RAW
`input_tokens` excludes cached AND cache-creation. Canonical
`:input-tokens` adds all three so the value is TOTAL.
- OpenAI Chat / Responses (`:openai-compatible-*` api-styles): RAW
`prompt_tokens` / `input_tokens` IS the total. Cached subset lives
under `prompt_tokens_details.cached_tokens` /
`input_tokens_details.cached_tokens`. No native `cache-write`
concept (server-managed implicit caching), so `:cache-write` is
always 0 here UNLESS the provider proxies Anthropic via OpenRouter
and surfaces `cache_creation_input_tokens` as a pydantic extra
field.
- Z.ai (GLM coding-plan / OpenAI-compatible-chat): same as OpenAI.
Industry alignment (May 2026):
- Vercel AI SDK V3 spec (vercel/ai#9921): `inputTokens` always TOTAL
with `inputTokensDetails {regular, cacheWrite, cacheRead}`.
- OpenTelemetry `gen_ai.usage.input_tokens` (≥ v1.37): SHOULD be
inclusive (all kinds of input tokens).
- Claude Code official statusline JSON:
`context_window.total_input_tokens = input + cache_creation + cache_read`.
Reject: the additive convention (e.g. litellm PR #23342) leaves
`total_tokens` inconsistent with `prompt_tokens` and breaks naive
aggregation downstream.Shared internal utilities.
Shared internal utilities.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |