Structured Invoice Parsing: Typed Output Contracts with @ Operator

Tip

What you’ll learn How to use operator syntax for composing agents.

Source: 31_typed_output.py

from adk_fluent import Agent, Pipeline

# @ binds a Pydantic model as the output schema — the LLM must return
# data matching this structure, enabling downstream type-safe processing
parser_fluent = (
    Agent("invoice_parser")
    .model("gemini-2.5-flash")
    .instruct("Parse the uploaded invoice image and extract structured data.")
    @ Invoice
)

# @ is immutable — original unchanged, so you can build variants
base_extractor = Agent("extractor").model("gemini-2.5-flash").instruct("Extract financial data.")
typed_extractor = base_extractor @ Invoice
# base_extractor has no schema, typed_extractor does


class PurchaseOrder(BaseModel):
    order_id: str
    line_items: list[str]
    subtotal: float


# Composes with >> — typed parser feeds structured data into downstream agents
accounts_pipeline = (
    Agent("ocr_agent").model("gemini-2.5-flash").instruct("Perform OCR on the uploaded document and extract raw text.")
    >> Agent("invoice_parser").model("gemini-2.5-flash").instruct("Parse the raw text into structured invoice fields.")
    @ Invoice
    >> Agent("bookkeeper").model("gemini-2.5-flash").instruct("Record the parsed invoice in the general ledger.")
)

# @ preserves all existing config — output_key and schema coexist
detailed_parser = (
    Agent("detailed_parser")
    .model("gemini-2.5-flash")
    .instruct("Extract every line item from the purchase order with amounts.")
    .writes("parsed_po")
    @ PurchaseOrder
)
from google.adk.agents.llm_agent import LlmAgent
from pydantic import BaseModel


class Invoice(BaseModel):
    vendor: str
    total_amount: float
    due_date: str


# Native: pass output_schema directly to enforce structured output
parser_native = LlmAgent(
    name="invoice_parser",
    model="gemini-2.5-flash",
    instruction="Parse the uploaded invoice image and extract structured data.",
    output_schema=Invoice,
)
        graph TD
    n1[["ocr_agent_then_invoice_parser_then_bookkeeper (sequence)"]]
    n2["ocr_agent"]
    n3["invoice_parser"]
    n4["bookkeeper"]
    n2 --> n3
    n3 --> n4
    

Equivalence

# @ wires into ADK's native output_schema
built = parser_fluent.build()
assert built.output_schema is Invoice
assert parser_native.output_schema is Invoice

# Original unchanged (immutable)
assert "_output_schema" not in base_extractor._config
assert typed_extractor._config["_output_schema"] is Invoice

# Composes with >>
assert isinstance(accounts_pipeline, Pipeline)

# Preserves config
assert detailed_parser._config["instruction"] == "Extract every line item from the purchase order with amounts."
assert detailed_parser._config["output_key"] == "parsed_po"
assert detailed_parser._config["_output_schema"] is PurchaseOrder