v0.4.8
Feature: Runtime.invoke_structured — schema-validated LLM output. PAR-xd5 + PAR-5cc.
New Features
- Structured output API (PAR-xd5, P0):
Runtime.invoke_structured ~agent_id ~message ~response_schemareturns schema-validated JSON instead of free text. The function returnsstructured_invoke_result = { value; raw_response; conversation; attempts }so callers can chain follow-up turns and observe repair-loop behavior. - OpenAI native structured output (WU-3):
Openai_provider.complete_structuredemitsresponse_format: { type: "json_schema", json_schema: { name, schema, strict: true } }per the OpenAI Structured Outputs spec. - Schema normalization for OpenAI strict mode (Oracle D5 must-fix):
Openai_provider.normalize_for_openai_strictwarns about silent server-side rewrites (forcesadditionalProperties: false, marks all propertiesrequired, convertsconst → enum). Without local normalization, users get semantically wrong output while PAR reports success. - Anthropic native structured output (WU-4):
Anthropic_provider.complete_structureduses the 2026 GAoutput_config.formatfield. JSON lands incontent[0].textas a JSON-encoded string. - Engine feedback repair loop (WU-2.2): on JSON parse or schema-validation failure,
Engine.run_structuredappends repair messages to conversation and retries up tomax_repair_attempts(default 3). Cancellation token is checked at the top of each iteration (Oracle BS-1 must-fix). Middlewareon_before_llm/on_after_llmhooks still fire (Oracle D2 must-fix); onlyon_erroris bypassed because the loop is the repair authority. - Mock provider structured support (WU-5):
Mock_provider.createnow accepts?structured_response:Yojson.Safe.tfor test override, or synthesizes a minimal valid object from the request schema's top-level properties. - Generic fallback path: when
llm_service.complete_structured_fn = None(e.g. Ollama, Custom providers), the engine prepends a JSON Schema directive to the system prompt, callscomplete_fn, then locally validates the response against the schema. Falls back gracefully without rejecting the provider. - Python FFI binding (WU-7):
Runtime.invoke_structured(agent_id, message, response_schema)accepts a Python dict schema, returns a parsed dict, raisesPARInvokeErroron failure. C ABIpar_invoke_structuredexposed alongsidepar_invoke. - New event type:
Structured_output_completed of { attempts; schema_valid; task_id }fires after every structured call for observability subscribers. - Lenient JSON extraction (WU-2.1):
Json_extract.extract_json_from_textstrips markdown fences (```json,```), extracts balanced{...}/[...]blocks from prose, and skips over string literals to avoid false-depth scans.
API Changes
Types.llm_service(additive, non-breaking): new optional fieldcomplete_structured_fn : (... -> Yojson.Safe.t -> (llm_response, error_category) result) option. DefaultNonemeans fallback path. Existing custom providers keep compiling; they just don't get native structured support until they populate the field.Types.structured_invoke_result(new type):{ value : Yojson.Safe.t; raw_response : llm_response; conversation : conversation; attempts : int }.Types.event(additive variant): new constructorStructured_output_completed of { attempts : int; schema_valid : bool; task_id : Task_id.t }. Exhaustivematch event with ...consumers without a catch-all arm must add an explicit branch.Runtime.invoke_structured(new public val): signature inruntime.mliafterinvoke.Engine.run_structured(new public val): signature inengine.mliafterrun_agent.Mock_provider.create(additive, optional param): new?structured_response:Yojson.Safe.t.
Limitations (documented per Oracle D4)
The in-tree JSON Schema validator (Validation.validate_value) checks top-level object properties only in the fallback path (type, required, enum, minimum/maximum, minLength/maxLength). Array items, nested object properties, and oneOf / anyOf are NOT validated locally. Native providers (OpenAI strict mode, Anthropic output_config.format) validate deeply server-side — the local limitation only affects the fallback path. Full nested validation deferred to v0.5.
Test Count
- 987 OCaml tests (+13 new: engine_structured feedback loop, cancellation, middleware hooks, json_extract variants).
- 33 Python tests (+2 new: invoke_structured signature + error path).
Install
curl -fsSL https://raw.githubusercontent.com/jcz2020/par/main/install.sh | bashOr upgrade: par update
macOS: binary is unsigned. Run
xattr -cr ""once after install.
Full changelog: https://github.com/jcz2020/par/blob/main/CHANGES.md