**Structured Generation in Enterprise RAG – The Contract (Article 8A)**
*Part II, “The Four Bricks” — Article 8 of the Enterprise Document Intelligence series*
—
In an Enterprise RAG system, the fourth and final brick is **generation**. But generation cannot succeed on raw text and vague prompts—it requires a **contract**: a typed answer schema that turns free-form language into structured, auditable output. This article defines that contract and explains how it powers reliable, production-grade RAG.
> Source: Insight Media Group, *Enterprise Document Intelligence: Four Bricks Series*, Article 8A: The Contract (https://contributor.insightmediagroup.io/articles/enterprise-document-intelligence-four-bricks-generation-contract/)
—
### Why generation needs a contract
A language model is a probabilistic next-token predictor, not a database query engine. Left unconstrained, it will **hallucinate** with confidence. You cannot train hallucination away; you can only limit the space where it can operate. That reduction is achieved through a **structured contract** between the pipeline and the model:
– Document parsing produces clean, structured tables instead of raw text.
– Question parsing defines a precise question plus an explicit answer shape and type.
– Retrieval supplies only the minimal, line-level passages needed.
– Generation then maps those inputs to a **typed schema**, with citations for every claim.
The lever is not a smarter prompt (“don’t hallucinate” rarely helps). It is **controlled execution**: structured input goes in, structured output comes out, and every field is checked against the source material.
—
### The contract in practice
At a high level, the contract is an **Answer schema** enforced at decoding time. The schema serves two roles:
1. A **programmable contract** that downstream code can consume without re-parsing.
2. An **instruction set** that tells the model exactly which questions it must answer using only the provided passages.
The contract is built in layers, from primitive values to full answers, and includes explicit self-assessment fields so the pipeline can decide what to do next.
#### 2.1 Typed values and reusable schemas
Each answer type is defined as a typed primitive (a **Value**), with one Pydantic class per concept:
– `Amount(value, currency, unit)`
– `DateValue(iso, original)`
– `TableValue(headers, rows)`
– `text` for definitions and narrative answers
– `bool` for yes/no questions
Every value is wrapped in an **Item** that pairs it with a list of `Span` citations (line_start, line_end, optional quote), for example:
“`python
class Span:
line_start: int
line_end: int
quote: str | None = None
class AmountItem:
amount: Amount
spans: list[Span] = Field(default_factory=list)
“`
This multi-span capability allows a single answer to pull non-contiguous evidence from across the document.
#### 2.2 Multi-element answers and multi-span citations
Real questions often expect lists, not single values. The schema handles this with cardinality-aware answer shapes:
– `answer_shape: “single” | “listing” | “table” | “tree” | “nested_json”`
– `answer_type: “amount” | “date” | “text” | “boolean” | …`
A list answer is represented as `items: list[XItem]`, where each item carries its own value and evidence spans. Even a single-element answer can have multiple spans, so `spans: list[Span]` is always present.
Because evidence is recovered after generation from line-level indices, the model never needs to return raw text—only structured references.
#### 2.3 Self-assessment and pipeline feedback
To steer the pipeline, every answer includes **self-assessment** and **pipeline-feedback** fields:
– `confidence: float ∈ [0, 1]`
– `caveats: list[str]`
– `extraction_method: “verbatim” | “computed” | “inferred” | “na”`
– `answer_found: bool`
– `complete_answer_found: bool`
– `context_completeness_weak: float`
– `context_structured: bool`
– `llm_discovered_keywords: list[str]`
– `keywords_found: list[str]`
– `conflicting_evidence: bool`
– `suggested_clarification: str | None`
Crucially, one signal is **not** asked of the model: completeness determined by retrieval scope. That is handled by the pipeline using section-level overlap (see below).
These fields allow the dispatcher (Article 8C) to decide whether to:
– Ship the answer
– Retry with broader retrieval
– Return “not found”
– Ask for clarification
—
### Programmatic completeness: the “strong” signal
Some failures cannot be detected by the model alone. For example, a clean sentence ending on page 5 may look complete, while items (f), (g), and (h) reside on page 6. This is handled by **retrieval scope**, not by self-rating.
The retrieval module can include a one-page overlap beyond the inferred section boundary. It then checks a derived flag:
– `context_completeness_strong = True` only when retrieval scope aligns with a section boundary.
This check is deterministic and model-independent, buying robustness where the model cannot see.
—
### How the contract is enforced
Constrained decoding makes the contract real:
– Preferred: **Pydantic + `responses.parse`** (or equivalent native structured output). The model cannot return malformed output.
– Fallbacks: JSON Schema in prompt, or plain “return JSON” (less reliable).
Because the schema is Pydantic-native, it integrates cleanly with Python downstream consumers, while still aligning with JSON-based ecosystems.
—
### A unified registry and vocabulary
Across the system:
– `answer_type` → maps to a Value class via `ANSWER_REGISTRY`
– `answer_shape` → controls cardinality (single vs list vs table)
– `Shape` → the string key emitted by question parsing
– `Item` → one Value + citations
– `Answer` → top-level schema passed to `responses.parse`
– `GenerationResult` → pairs the Answer with model metadata
This consistent vocabulary makes it straightforward to add new types—such as jurisdiction flags or redaction notices—without breaking existing pipelines.
—
### Concrete examples
| Scenario | Signals | Dispatcher action |
|———|——–|——————|
| Full list of NIST Functions | `complete_answer_found=True`, high confidence | Ship answer |
| Partial list with “continued in Section 7” | `complete_answer_found=False`, `llm_discovered_keywords` | Broader retrieval |
| No answer in retrieved text | `answer_found=False`, `extraction_method=”na”` | “Not found” path or rephrase |
| Conflicting dates | `conflicting_evidence=True` | Present conflict to the user |
—
### Conclusion
The contract turns generation from a creative gamble into a **checked engineering step**. By declaring typed answers, multi-span citations, and explicit self-assessment fields—and by enforcing the schema at decoding time—the pipeline delivers results that are:
– **Programmatically usable**
– **Auditable via citations**
– **Safe downstream** through deterministic checks
Article 8B explains how to assemble the calls that fill this contract, and Article 8C describes how validation uses these signals to control the loop. Together, they close the loop between retrieval and generation without relying on unreliable prompts.
—
**References**
– Willard & Louf, *Efficient Guided Generation for Large Language Models* (Outlines), 2023
– Asai et al., *Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection*, ICLR 2024
– Bohnet et al., *Attributed Question Answering*, 2022
– OpenAI, *Structured Outputs* documentation (Aug 2024)



