Liking cljdoc? Tell your friends :D

com.blockether.svar.internal.spec

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 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
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 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`
raw docstring

build-ref-registryclj

(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.
sourceraw docstring

coerce-data-with-specclj

(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:

  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.

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.
sourceraw docstring

data->strclj

(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\"}"
sourceraw docstring

fieldclj

(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:

  • :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).

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).
sourceraw docstring

specclj

(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.
sourceraw docstring

spec->promptclj

(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, }"
sourceraw docstring

str->dataclj

(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.
sourceraw docstring

str->data-with-specclj

(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:

  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.

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.
sourceraw docstring

validate-dataclj

(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:

  • 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.

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.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close