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`
(build-ref-registry spec-def)Builds a registry of referenced specs from a spec's ::refs.
Recursively collects all refs from nested specs and returns a map of spec-name -> spec-def. Detects duplicate spec names and throws error.
Params:
spec-def - Map. Spec definition with optional ::refs key.
Returns: Map. Registry mapping spec names (keywords) to spec definitions. Empty map if no refs.
Throws: ExceptionInfo if duplicate spec names are found.
Builds a registry of referenced specs from a spec's ::refs. Recursively collects all refs from nested specs and returns a map of spec-name -> spec-def. Detects duplicate spec names and throws error. Params: `spec-def` - Map. Spec definition with optional ::refs key. Returns: Map. Registry mapping spec names (keywords) to spec definitions. Empty map if no refs. Throws: ExceptionInfo if duplicate spec names are found.
(coerce-data-with-spec data spec-def)Coerces Clojure data according to spec type definitions.
Unlike str->data-with-spec which parses JSON text, this operates on existing Clojure data structures (e.g., from SCI eval). Applies:
Does NOT apply ::key-ns namespacing — data keys are expected to already be in their final form. Designed for pre-parsed Clojure data that needs type normalization but not JSON parsing or key restructuring.
Params:
data - Clojure data (map, vector, or primitive) to coerce.
spec-def - Map. Spec definition with ::fields, ::refs, etc.
Returns: Coerced data with keyword fields converted and defaults applied.
Coerces Clojure data according to spec type definitions. Unlike str->data-with-spec which parses JSON text, this operates on existing Clojure data structures (e.g., from SCI eval). Applies: 1. Array normalization (vector wrapping a single object → unwrapped map) 2. Keyword type coercion (strings → keywords for :spec.type/keyword fields) 3. Spec field defaults (nil :many → [], missing keys → nil) Does NOT apply ::key-ns namespacing — data keys are expected to already be in their final form. Designed for pre-parsed Clojure data that needs type normalization but not JSON parsing or key restructuring. Params: `data` - Clojure data (map, vector, or primitive) to coerce. `spec-def` - Map. Spec definition with ::fields, ::refs, etc. Returns: Coerced data with keyword fields converted and defaults applied.
(data->str data)Serializes Clojure data to JSON string.
Converts dates/datetimes to ISO 8601 strings.
Params:
data - Map. Clojure data structure with keyword keys.
Returns: String. JSON representation of the data.
Examples: (data->str {:name "John" :age 42}) => "{"name":"John","age":42}"
(data->str {:date (LocalDate/of 2024 1 15)}) => "{"date":"2024-01-15"}"
Serializes Clojure data to JSON string.
Converts dates/datetimes to ISO 8601 strings.
Params:
`data` - Map. Clojure data structure with keyword keys.
Returns:
String. JSON representation of the data.
Examples:
(data->str {:name "John" :age 42})
=> "{\"name\":\"John\",\"age\":42}"
(data->str {:date (LocalDate/of 2024 1 15)})
=> "{\"date\":\"2024-01-15\"}"(field &
{the-description :com.blockether.svar.internal.spec/description
the-name :com.blockether.svar.internal.spec/name
:or {the-required true the-humanize? false}
the-type :com.blockether.svar.internal.spec/type
the-values :com.blockether.svar.internal.spec/values
the-required :com.blockether.svar.internal.spec/required
the-cardinality :com.blockether.svar.internal.spec/cardinality
the-target :com.blockether.svar.internal.spec/target
the-humanize? :com.blockether.svar.internal.spec/humanize?})Defines a spec field with namespaced keyword options.
Params:
::name - Keyword, required. Field path/name as Datomic-style keyword.
::type - Keyword, required. One of:
::cardinality - Keyword, required.::description - String, required. Human-readable description (no reserved chars).
::required - Boolean, optional. false if field can be nil (default: true).
::values - Map, optional. Enum constraint with value->description pairs.
Every enum value MUST have a description explaining its meaning.
Example: {"admin" "Full system access" "user" "Standard access"}
::target - Keyword or vector of keywords, optional. Required when ::type is :spec.type/ref.
Single keyword for simple ref, vector for union types (e.g., [:Heading :Paragraph :Image]).
::humanize? - Boolean, optional. When true, marks this field for humanization
when a :humanizer fn is passed to ask!. Defaults to false.Returns: Map. Field definition with keys ::name, ::type, ::cardinality, ::description, and optionally ::union (for optional fields), ::values (for enums), ::target (for refs), and ::humanize? (for humanization marking).
Defines a spec field with namespaced keyword options.
Params:
`::name` - Keyword, required. Field path/name as Datomic-style keyword.
`::type` - Keyword, required. One of:
- :spec.type/string - String value
- :spec.type/int - Integer value
- :spec.type/float - Floating point value
- :spec.type/bool - Boolean value
- :spec.type/date - ISO date (YYYY-MM-DD)
- :spec.type/datetime - ISO datetime
- :spec.type/keyword - Clojure keyword (rendered as string, keywordized on parse)
- :spec.type/ref - Reference to another spec
- :spec.type/int-v-N - Fixed-size integer vector (e.g., :spec.type/int-v-4 for 4 ints)
- :spec.type/string-v-N - Fixed-size string vector (e.g., :spec.type/string-v-2)
- :spec.type/double-v-N - Fixed-size double vector (e.g., :spec.type/double-v-3)
`::cardinality` - Keyword, required.
- :spec.cardinality/one - Single value
- :spec.cardinality/many - Vector of values
`::description` - String, required. Human-readable description (no reserved chars).
`::required` - Boolean, optional. false if field can be nil (default: true).
`::values` - Map, optional. Enum constraint with value->description pairs.
Every enum value MUST have a description explaining its meaning.
Example: {"admin" "Full system access" "user" "Standard access"}
`::target` - Keyword or vector of keywords, optional. Required when ::type is :spec.type/ref.
Single keyword for simple ref, vector for union types (e.g., [:Heading :Paragraph :Image]).
`::humanize?` - Boolean, optional. When true, marks this field for humanization
when a :humanizer fn is passed to ask!. Defaults to false.
Returns:
Map. Field definition with keys ::name, ::type, ::cardinality, ::description,
and optionally ::union (for optional fields), ::values (for enums), ::target (for refs),
and ::humanize? (for humanization marking).(spec & args)Creates a spec from field definitions.
Params:
name - Keyword, optional. Name of the spec (e.g., :Person, :Address).
opts - Map, optional. Options map that may contain:
:refs - Vector of referenced specs for union types.
::key-ns - String. Namespace to add to all keys during parsing.
Example: "page.node" transforms :type to :page.node/type.
fields - Field definitions. Variadic list of field maps created with field.
Examples: (spec (field ...)) ; Anonymous spec (spec :Person (field ...)) ; Named spec (spec :Person {:refs [addr-spec]} (field ...)) ; Named spec with refs (spec :section {::key-ns "page.node"} (field ...)) ; Keys namespaced as :page.node/*
Returns: Map. Spec definition with ::fields key and optionally ::spec-name, ::refs, and ::key-ns keys.
Creates a spec from field definitions.
Params:
`name` - Keyword, optional. Name of the spec (e.g., :Person, :Address).
`opts` - Map, optional. Options map that may contain:
`:refs` - Vector of referenced specs for union types.
`::key-ns` - String. Namespace to add to all keys during parsing.
Example: "page.node" transforms :type to :page.node/type.
`fields` - Field definitions. Variadic list of field maps created with `field`.
Examples:
(spec (field ...)) ; Anonymous spec
(spec :Person (field ...)) ; Named spec
(spec :Person {:refs [addr-spec]} (field ...)) ; Named spec with refs
(spec :section {::key-ns "page.node"} (field ...)) ; Keys namespaced as :page.node/*
Returns:
Map. Spec definition with ::fields key and optionally ::spec-name, ::refs, and ::key-ns keys.(spec->prompt the-spec)Converts a spec to a full prompt for LLM with BAML-style schema.
Uses simple, direct format without XML tags.
Params:
the-spec - Map. Spec definition created with spec.
Returns: String. Prompt with BAML-style schema.
Examples: "Answer in JSON using this schema:\n{ field: type, }"
Converts a spec to a full prompt for LLM with BAML-style schema.
Uses simple, direct format without XML tags.
Params:
`the-spec` - Map. Spec definition created with `spec`.
Returns:
String. Prompt with BAML-style schema.
Examples:
"Answer in JSON using this schema:\n{ field: type, }"(str->data text)Parses JSON response from LLM into Clojure data structure.
Uses jsonish parser to handle malformed JSON (unquoted keys/values, trailing commas, etc.).
Params:
text - String. JSON response from LLM (may be malformed).
Returns: Map. Parsed Clojure data with keywords as keys, nested maps/vectors as values.
Examples: (str->data "{"name": "John", "age": 30}") => {:name "John", :age 30}
(str->data "{name: John, age: 30}") ; Unquoted - still works => {:name "John", :age 30}
Throws: RuntimeException if JSON cannot be parsed.
Parses JSON response from LLM into Clojure data structure.
Uses jsonish parser to handle malformed JSON (unquoted keys/values, trailing commas, etc.).
Params:
`text` - String. JSON response from LLM (may be malformed).
Returns:
Map. Parsed Clojure data with keywords as keys, nested maps/vectors as values.
Examples:
(str->data "{\"name\": \"John\", \"age\": 30}")
=> {:name "John", :age 30}
(str->data "{name: John, age: 30}") ; Unquoted - still works
=> {:name "John", :age 30}
Throws:
RuntimeException if JSON cannot be parsed.(str->data-with-spec text spec-def)Parses JSON response from LLM into Clojure data structure with spec-aware processing.
Uses jsonish parser to handle malformed JSON, then:
Params:
text - String. JSON response from LLM (may be malformed).
spec-def - Map. Spec definition used to generate the prompt.
Returns: Map. Parsed Clojure data with keys matching original spec field names, keyword-typed fields converted to Clojure keywords, and keys namespaced if ::key-ns is configured.
Examples: (def my-spec (spec (field ::name :valid? ::type :spec.type/bool ...))) (str->data-with-spec "{"valid": true}" my-spec) => {:valid? true} ; Note: key is :valid? not :valid
(def kw-spec (spec (field ::name :status ::type :spec.type/keyword ...))) (str->data-with-spec "{"status": "active"}" kw-spec) => {:status :active} ; String converted to keyword
(def ns-spec (spec :node {::key-ns "page.node"} (field ::name :type ...))) (str->data-with-spec "{"type": "heading"}" ns-spec) => {:page.node/type "heading"} ; Key namespaced
Throws: RuntimeException if JSON cannot be parsed.
Parses JSON response from LLM into Clojure data structure with spec-aware processing.
Uses jsonish parser to handle malformed JSON, then:
1. Remaps keys to match original spec field names (preserves ?!*+ chars)
2. Converts :spec.type/keyword fields from strings to Clojure keywords
3. Applies ::key-ns namespace to keys if configured in spec
Params:
`text` - String. JSON response from LLM (may be malformed).
`spec-def` - Map. Spec definition used to generate the prompt.
Returns:
Map. Parsed Clojure data with keys matching original spec field names,
keyword-typed fields converted to Clojure keywords, and keys namespaced
if ::key-ns is configured.
Examples:
(def my-spec (spec (field ::name :valid? ::type :spec.type/bool ...)))
(str->data-with-spec "{\"valid\": true}" my-spec)
=> {:valid? true} ; Note: key is :valid? not :valid
(def kw-spec (spec (field ::name :status ::type :spec.type/keyword ...)))
(str->data-with-spec "{\"status\": \"active\"}" kw-spec)
=> {:status :active} ; String converted to keyword
(def ns-spec (spec :node {::key-ns "page.node"} (field ::name :type ...)))
(str->data-with-spec "{\"type\": \"heading\"}" ns-spec)
=> {:page.node/type "heading"} ; Key namespaced
Throws:
RuntimeException if JSON cannot be parsed.(validate-data the-spec data)Validates parsed data against a spec. Returns {:valid? true} if valid, or {:valid? false :errors [...]} with error details.
Handles array of objects pattern where a field with cardinality many contains objects with nested fields (e.g., :books with :books.title, :books.year).
Recursively validates TYPE_REF fields against their target sub-specs.
Checks:
Params:
the-spec - Spec definition (created with spec/spec and spec/field)
data - Parsed Clojure data to validate
Returns: Map with :valid? boolean and optional :errors vector of error maps.
Validates parsed data against a spec.
Returns {:valid? true} if valid, or {:valid? false :errors [...]} with error details.
Handles array of objects pattern where a field with cardinality many contains
objects with nested fields (e.g., :books with :books.title, :books.year).
Recursively validates TYPE_REF fields against their target sub-specs.
Checks:
- Required fields are present (unless ::union contains ::nil)
- Values match expected types
- Enum values are valid (if ::values is specified)
- Ref values are validated against their target sub-specs
Params:
`the-spec` - Spec definition (created with spec/spec and spec/field)
`data` - Parsed Clojure data to validate
Returns:
Map with :valid? boolean and optional :errors vector of error maps.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 |