Nous.OutputSchema (nous v0.13.3)

View Source

Structured output support for Nous agents.

Converts Ecto schemas and other type specifications into JSON Schema, generates provider-specific settings, and validates LLM responses against the declared output type.

Inspired by instructor_ex.

Output Type Variants

  • :string — raw text (default, no processing)
  • module() — Ecto schema → JSON schema + changeset validation
  • %{atom() => atom()} — schemaless Ecto types (e.g. %{name: :string})
  • %{String.t() => map()} — raw JSON schema (string keys, passed through)
  • {:regex, pattern} — regex-constrained output (vLLM/SGLang)
  • {:grammar, ebnf} — grammar-constrained output (vLLM)
  • {:choice, choices} — choice-constrained output (vLLM/SGLang)

Modes

ModeMechanismProviders
:autoPick best for providerAll
:tool_callSynthetic tool + tool_choiceAll (native for Anthropic)
:json_schemaresponse_format: {type: "json_schema", ...}OpenAI, vLLM, SGLang
:jsonresponse_format: {type: "json_object"}OpenAI-compatible
:md_jsonPrompt + markdown fence + stop tokenAll (fallback)

Summary

Functions

Cast parsed JSON data and validate it against the output type.

Extract the response text and matched schema from a {:one_of, schemas} message.

Extract the response text from a provider response message.

Find the schema module whose synthetic tool name matches tool_name.

Format a validation error into a human-readable string for LLM retry.

Parse raw LLM response text and validate it against the output type.

Return a snake_case name for the given output type.

Return true if name is a synthetic structured output tool name.

Generate system prompt suffix describing the expected output schema.

Convert an output type specification to a JSON Schema map.

Generate provider-specific model settings for structured output.

Build the synthetic tool name for a given schema module.

Functions

cast_and_validate(parsed, output_type)

@spec cast_and_validate(map(), Nous.Types.output_type()) ::
  {:ok, any()} | {:error, Nous.Errors.ValidationError.t()}

Cast parsed JSON data and validate it against the output type.

For Ecto schemas, uses Ecto.Changeset.cast/3 + validate_changeset/1. For schemaless types, uses Ecto.Changeset.cast/4 with {data, types}. For raw JSON schema maps, returns the parsed data as-is.

extract_response_for_one_of(msg, schemas)

@spec extract_response_for_one_of(Nous.Message.t(), [module()]) ::
  {String.t(), module() | nil}

Extract the response text and matched schema from a {:one_of, schemas} message.

If the message contains a synthetic tool call matching one of the schemas, returns {json_text, schema_module}. Otherwise returns {text, nil}.

extract_response_text(msg, provider)

@spec extract_response_text(Nous.Message.t(), atom()) :: String.t()

Extract the response text from a provider response message.

For :tool_call mode on Anthropic, extracts the __structured_output__ tool call arguments. For all other modes, extracts the text content.

find_schema_for_tool_name(tool_name, schemas)

@spec find_schema_for_tool_name(String.t(), [module()]) :: module() | nil

Find the schema module whose synthetic tool name matches tool_name.

Returns nil if no schema matches.

format_errors(validation_error)

@spec format_errors(Nous.Errors.ValidationError.t()) :: String.t()

Format a validation error into a human-readable string for LLM retry.

parse_and_validate(text, output_type)

@spec parse_and_validate(String.t(), Nous.Types.output_type()) ::
  {:ok, any()} | {:error, Nous.Errors.ValidationError.t()}

Parse raw LLM response text and validate it against the output type.

Returns {:ok, result} where result is a struct, map, or string, or {:error, %ValidationError{}} on failure.

schema_name(module)

@spec schema_name(Nous.Types.output_type()) :: String.t()

Return a snake_case name for the given output type.

For Ecto schema modules, returns the last segment of the module name underscored (e.g. MyApp.SentimentResult"sentiment_result"). For maps or other types, returns "output".

synthetic_tool_name?(name)

@spec synthetic_tool_name?(String.t()) :: boolean()

Return true if name is a synthetic structured output tool name.

Matches both the standard "__structured_output__" and per-schema names like "__structured_output_sentiment_result__".

system_prompt_suffix(output_type, opts)

@spec system_prompt_suffix(
  Nous.Types.output_type(),
  keyword()
) :: String.t() | nil

Generate system prompt suffix describing the expected output schema.

to_json_schema(output_type)

@spec to_json_schema(Nous.Types.output_type()) :: map() | nil

Convert an output type specification to a JSON Schema map.

Returns nil for types that don't produce JSON schema (:string, tuples).

to_provider_settings(output_type, provider, opts \\ [])

@spec to_provider_settings(Nous.Types.output_type(), atom(), keyword()) :: map()

Generate provider-specific model settings for structured output.

Returns a map of settings to merge into the model request. The map may contain special keys prefixed with __structured_output that the AgentRunner handles separately (e.g. synthetic tool injection).

Options

  • :mode — output mode (:auto, :tool_call, :json_schema, :json, :md_json)
  • :has_other_tools — whether the agent has non-synthetic tools

tool_name_for_schema(module)

@spec tool_name_for_schema(module()) :: String.t()

Build the synthetic tool name for a given schema module.

Example

iex> OutputSchema.tool_name_for_schema(SentimentResult)
"__structured_output_sentiment_result__"