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.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.Router: provider/model registry, circuit breakers, rate limiting, 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, rate limiting, 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 |