Agentic Document Intelligence
This document describes the current agentic metadata extraction pipeline.
Entry Point
The current extraction entry point is HybridMetadataExtractor.
Behavior:
- if no supported LLM provider is configured, it uses deterministic extraction
- if at least one of OpenAI, Gemini, or Mistral is configured, it delegates to
AgenticDocumentIntelligenceService
Framework Choice
The agentic pipeline is implemented with LangGraph.
Current internal pipeline marker:
- framework:
langgraph-ready - version:
v1
The design goal is orchestrated, bounded intelligence rather than free-form autonomous behavior.
Supported LLM Providers
The current provider order inside the agentic pipeline is:
mistralgeminiopenai
LlmService supports explicit fallback order resolution and returns provider/model metadata together with completion results.
Workflow Shape
The AgenticDocumentIntelligenceService builds a LangGraph state graph with nodes for:
- routing
- title and summary generation
- typed metadata extraction
- correspondent resolution
- tagging
- normalization and validation
The final result is converted into the shared MetadataExtractionResult shape expected by the rest of the processing pipeline.
Structured Outputs
All four agent LLM calls (routing, title/summary, per-type extraction, tagging) send a
JSON schema from apps/api/src/processing/agent-schemas.ts:
- Mistral and OpenAI enforce it via
response_format: { type: "json_schema", strict: true }; the schemas are strict-compatible (every property required, nullable where optional in spirit,additionalProperties: false) - Gemini's schema dialect rejects JSON-Schema type unions, so Gemini falls back to plain JSON mode; caller-side validation covers the gap
- the per-type extraction schema is generated from the registry's relevant fields; its
fieldConfidenceentries are required but nullable, and null placeholders are accepted by the response validator and ignored during normalization - every response is validated with Zod (
parseWithSchema) before use; a schema-violating payload falls back to the deterministic extraction path, and the brace-slice JSON heuristic remains only as the last-ditch parse fallback - merged fields are confidence-aware: an LLM value only replaces a deterministic regex hit when the deterministic slot is empty or the LLM reported at least the deterministic confidence, and field provenance follows the winner
Parse-Provider Annotation Hints
When MISTRAL_OCR_DOCUMENT_ANNOTATIONS=true (default off), the Mistral OCR request
additionally asks for a document annotation: a vision-capable model extracts a generic
metadata schema (document type from the registry enum, title, summary, the union of
relevant fields, each with confidence) inside the same OCR call. The validated result
lands on parsed.preExtracted — a capability of the parse output, not a new interface,
so other providers are unaffected.
The graph shape is unchanged; nodes consume the hint conditionally:
- routing skips its LLM call when the annotated type is valid and its confidence is at
least 0.7 (provider recorded as
mistral-annotation) - title/summary skips its LLM call when the annotation carries a title AND the parse provider reported a document language that matches the configured processing language. Mistral OCR reports no language, so in practice titles are regenerated through the language-aware LLM path — the annotation request carries no language preference, so a mismatch would persist titles in the document's language instead of the one selected in Settings
- typed extraction seeds from the annotation fields via the same confidence-aware merge
used for LLM values (provenance
provider_annotation). The seed is filtered to the routed type's relevant fields, so the generic annotation schema cannot inject values that type could never produce. The LLM call is skipped only when the annotation itself supplied every required field with sufficient annotation confidence (a value scored below 0.5, or with no score at all, does not count as coverage) — deterministic parsing filling the gaps does not count. Type-specific refiners still run afterwards, and confidence/provenance are rebuilt for the values they replace - correspondent resolution, tagging, and validation are unchanged — they need archive context (candidate lists, deterministic seeds) the annotation cannot provide
Cost is roughly neutral (annotations ~+$1/1000 pages vs. up to three saved chat calls
per document); latency drops because up to three sequential round-trips disappear.
Documents longer than ~8 pages get the warning annotation_hint_partial_document
because annotations only consider the leading pages. Every node keeps its LLM and
deterministic fallback, so the flag can be toggled per environment and compared.
Routing Stage
The routing stage determines the likely document type and stores:
- selected document type
- subtype when present
- confidence
- reasoning hints
- provider and model metadata
This data is later exposed to the frontend through metadata.intelligence.routing.
Supported Document Types
Current document type registry entries:
invoicereceiptcontracttax_documentutility_billbank_statementpayslipinsurance_documentgeneric_letter
The registry defines:
- canonical names
- aliases
- summaries
- required fields
- relevant fields
- label hints for dates, references, and similar extraction targets
Type-Specific Extraction
The typed extraction stage uses dedicated modules under:
apps/api/src/processing/type-specific-extractors/
This keeps extraction logic structured per document family instead of relying on one generic prompt path.
Correspondent Resolution
Correspondent resolution is not a separate ad-hoc step in the UI. It is part of the extraction workflow.
The pipeline feeds extracted or candidate correspondent data into CorrespondentResolutionService, which returns:
- resolved correspondent name
- confidence
- match strategy metadata
That metadata is preserved under metadata.correspondentExtraction and metadata.intelligence.correspondentResolution.
Tagging
The tagging stage produces normalized tag suggestions and confidence metadata.
These are surfaced through metadata.intelligence.tagging.
Validation and Normalization
The final validation stage normalizes extracted fields and produces:
- normalized field values
- warnings
- errors
- duplicate signals
This stage is also responsible for shaping data that will later affect:
- confidence
- review reasons
- review evidence
Final Output Shape
The agentic pipeline returns the standard extraction fields expected elsewhere in the backend, including:
- title
- summary
- issue date
- due date
- expiry date
- amount and currency
- reference number
- holder name
- issuing authority
- correspondent name
- document type name
- tags
- confidence
- review reasons
It also writes detailed intelligence metadata into metadata.intelligence.
Frontend Exposure
The web app currently exposes agentic output in several places:
- explorer row badges and summaries
- review queue badges and summaries
- document detail intelligence tab
The document detail page currently exposes:
- routing
- generated summary
- type-specific fields
- field confidence and provenance
- tagging and correspondent resolution
- validation warnings and errors
- pipeline metadata and durations
Design Constraints
Current design constraints intentionally keep the system predictable:
- agent stages are fixed and orchestrated
- extracted data is normalized before it is trusted
- review remains explicit and separate from processing
- manual overrides still outrank automated output