aditya0103's picture
Initial commit: schemas, extraction engine, dataset prep, 54 passing tests
44c2f50
Raw
History Blame Contribute Delete
3.41 kB
"""Base schema types and the ExtractionResult wrapper.
Every extraction returns an ExtractionResult[T] where T is the domain schema
(Invoice, Receipt, or a filing schema in v2). The wrapper carries the extracted
data plus per-field confidence, warnings, and provenance so downstream code
never has to reach into the LLM response directly.
"""
from __future__ import annotations
from typing import Generic, TypeVar
from pydantic import BaseModel, ConfigDict, Field, field_validator
# --- Type variable for the domain schema ---
T = TypeVar("T", bound=BaseModel)
class StrictModel(BaseModel):
"""Base model with strict config — matches OpenAI structured-outputs requirements."""
model_config = ConfigDict(
extra="forbid", # no extra fields (matches OpenAI strict mode)
str_strip_whitespace=True,
validate_assignment=True,
)
class FieldConfidence(StrictModel):
"""Model-reported confidence for a single extracted field."""
field: str = Field(description="Dotted path to the field, e.g. 'total' or 'line_items[0].price'.")
score: float = Field(
ge=0.0,
le=1.0,
description="Model's self-reported confidence for this field (0.0 = uncertain, 1.0 = certain).",
)
reasoning: str | None = Field(
default=None,
description="Brief explanation of why confidence is not 1.0 (only present when score < 0.9).",
)
class ExtractionWarning(StrictModel):
"""A warning surfaced during extraction — non-fatal, human-reviewable."""
field: str | None = Field(default=None, description="Field the warning applies to, if applicable.")
message: str = Field(description="Human-readable warning.")
severity: str = Field(
default="info",
description="One of: info, warning, error. 'error' means the field could not be extracted.",
)
@field_validator("severity")
@classmethod
def _validate_severity(cls, v: str) -> str:
if v not in {"info", "warning", "error"}:
raise ValueError(f"severity must be info/warning/error, got {v!r}")
return v
class ExtractionResult(StrictModel, Generic[T]):
"""Wrapper around any extracted domain schema.
- `data` is the strongly-typed domain object (Invoice, Receipt, etc.)
- `field_confidences` gives per-field confidence
- `overall_confidence` is a single 0-1 rollup for quick UI display
- `warnings` surfaces low-confidence or missing fields to the caller
- `raw_text_snippet` keeps a slice of the source for debugging / traceability
"""
document_type: str = Field(description="Identifier of the domain schema used (e.g. 'invoice').")
data: T = Field(description="The extracted, schema-validated domain object.")
field_confidences: list[FieldConfidence] = Field(
default_factory=list,
description="Per-field confidence scores. Empty list is acceptable but discouraged.",
)
overall_confidence: float = Field(
ge=0.0, le=1.0, description="Rollup confidence across all fields (mean of scores)."
)
warnings: list[ExtractionWarning] = Field(
default_factory=list, description="Non-fatal issues surfaced during extraction."
)
raw_text_snippet: str | None = Field(
default=None,
max_length=2000,
description="First 2K chars of source text for debugging. None for pure-image inputs.",
)