Skip to main content

Structured Output

Use structuredOutput: true on any Object or Array node to collapse that entire subtree into a single constrained LLM call instead of decomposing it into per-field calls. This is ObjectWeaver's hybrid mode — you choose the strategy per field, not per definition.

When to Use It

The fundamental trade-off is between cost and depth. OW's normal orchestration fires one LLM call per field, repeating the input document each time. Structured output fires one call for the whole subtree, but the model has to produce all the fields in a single pass.

Use structuredOutput: true

  • Extraction arrays — lists of entities, dates, citations, transactions. You're finding structured facts, not reasoning about them.
  • Simple objects — metadata, classification, tagging where each property is low-complexity.
  • Wide objects — 8+ properties that would otherwise each trigger a separate LLM call.
  • Cost-sensitive workloads — when running thousands of documents and deep reasoning isn't required.

Leave as normal OW decomposition

  • Reasoning fields — risk assessment, strategic analysis, recommendations where each sub-field deserves the model's full output window.
  • Fields with selectFields chains — when a field's quality depends on specific prior results injected as context.
  • Fields needing different models — when you want to route sub-fields to different models or temperatures.
Rule of thumb

If a field is about finding things → use structured output. If a field is about thinking about things → use orchestration.

Basic Usage

Add structuredOutput: true to any object or array node in your definition:

{
"type": "object",
"properties": {
"parties": {
"type": "array",
"structuredOutput": true,
"instruction": "Extract all parties mentioned in the document",
"items": {
"type": "object",
"properties": {
"name": { "type": "string", "instruction": "Full legal name" },
"role": { "type": "string", "instruction": "Role in the matter" },
"represented_by": { "type": "string", "instruction": "Legal counsel if mentioned" }
}
}
},
"risk_assessment": {
"type": "object",
"instruction": "Analyse litigation risk in depth",
"properties": {
"vulnerabilities": {
"type": "string",
"instruction": "Identify specific legal vulnerabilities"
},
"recommendation": {
"type": "string",
"instruction": "Recommend next steps based on identified risks",
"selectFields": ["risk_assessment.vulnerabilities"]
}
}
}
}
}

parties fires one API call and returns a JSON array. risk_assessment uses normal OW decomposition — vulnerabilities gets its own call, then recommendation gets a separate call with vulnerability analysis injected via selectFields.

How It Works

When OW encounters a node with structuredOutput: true, the StructuredOutputProcessor:

  1. Converts the subtree to JSON Schema — walks all child properties, nested objects, and array item types and builds the response_format constraint.
  2. Sends a single constrained call — builds one prompt from the field's instruction and submits it with the JSON Schema response format. The LLM returns structured JSON.
  3. Stores the result normally — the parsed value is stored in the execution context exactly like any other field. Downstream fields can reference it via selectFields.
  4. Falls back automatically — if the structured call fails (context too large, schema too complex), OW silently retries using normal per-field decomposition. No configuration needed.

The Hybrid Pattern

The most powerful use of structured output is mixing it with orchestration in a single definition. This is the hybrid approach:

{
"type": "object",
"properties": {
"metadata": {
"type": "object",
"structuredOutput": true,
"instruction": "Extract document metadata",
"properties": {
"title": { "type": "string" },
"date": { "type": "string" },
"jurisdiction": { "type": "string" }
}
},
"parties": {
"type": "array",
"structuredOutput": true,
"instruction": "Extract all named parties",
"items": { "type": "object", "properties": {
"name": { "type": "string" },
"role": { "type": "string" }
}}
},
"strategic_brief": {
"type": "object",
"instruction": "Produce a strategic brief",
"properties": {
"summary": { "type": "string", "instruction": "Executive summary of key issues" },
"recommendation": {
"type": "string",
"instruction": "Strategic recommendation",
"selectFields": ["parties", "strategic_brief.summary"]
}
}
}
}
}

metadata and parties — extraction — collapse to 2 LLM calls. strategic_brief — reasoning — expands to multiple calls with full context per field.

Cost Impact

Normal OW orchestration repeats the entire input document in every LLM call. For a 4,400-token document with 228 fields, that's ~1M input tokens. Hybrid mode collapses extraction fields to a handful of structured calls:

ApproachAPI callsEst. input tokensInput cost (Gemini Flash Lite)
Pure orchestration~228~1,010,000$0.0757
Hybrid~25~35,000$0.0026
Pure structured1~3,755$0.0003

The hybrid approach used 97% fewer input tokens than pure orchestration in the document analysis benchmark, while maintaining orchestration's depth advantage on reasoning fields (2.1× more content, 7 vs 4 vulnerabilities identified in risk analysis).

At scale across 1,000 documents:

ApproachTotal input costQuality on reasoning
Pure orchestration$75.70Deep
Hybrid$2.60Deep
Pure structured$0.28Compressed

Constraints

  • structuredOutput: true is valid on object and array nodes only, not on leaf string/number/boolean fields.
  • The JSON Schema sent to the provider must be within the provider's schema complexity limits. OW falls back to decomposition automatically if this limit is hit.
  • Fields inside a structuredOutput subtree cannot use selectFields to reference fields outside that subtree. Cross-subtree context must be passed at the parent level.

Further Reading