# Copyright (c) Microsoft. All rights reserved. # /// script # requires-python = ">=3.10" # dependencies = [ # "agent-framework-azure-contentunderstanding", # "agent-framework-foundry", # "azure-identity", # "pydantic", # ] # /// # Run with: uv run packages/azure-contentunderstanding/samples/01-get-started/04_invoice_processing.py import asyncio import os from pathlib import Path from agent_framework import Agent, AgentSession, Content, Message from agent_framework.foundry import ContentUnderstandingContextProvider, FoundryChatClient from azure.identity import AzureCliCredential from dotenv import load_dotenv from pydantic import BaseModel, Field load_dotenv() """ Invoice Processing — Structured output with prebuilt-invoice analyzer This sample demonstrates CU's structured field extraction combined with LLM structured output (Pydantic model). The prebuilt-invoice analyzer extracts typed fields (VendorName, InvoiceTotal, DueDate, LineItems, etc.) with confidence scores. We use output_sections=["fields"] only (no markdown needed) since we want the LLM to produce a structured JSON response from the extracted fields, not summarize document text. Environment variables: FOUNDRY_PROJECT_ENDPOINT — Azure AI Foundry project endpoint FOUNDRY_MODEL — Model deployment name (e.g. gpt-4.1) AZURE_CONTENTUNDERSTANDING_ENDPOINT — CU endpoint URL """ SAMPLE_PDF_PATH = Path(__file__).resolve().parents[1] / "shared" / "sample_assets" / "invoice.pdf" # Structured output model — the LLM will return JSON matching this schema # Structured output models — the LLM returns JSON matching this schema. # # Note: the prebuilt-invoice analyzer extracts an extensive set of fields # (VendorName, BillingAddress, ShippingAddress, TaxDetails, PONumber, etc.). # This sample defines a simplified schema to extract only the fields of # interest to the caller. The LLM maps the full CU field output to this # subset automatically. # Learn more about prebuilt analyzers: https://learn.microsoft.com/azure/ai-services/content-understanding/concepts/prebuilt-analyzers class LineItem(BaseModel): description: str quantity: float | None = None unit_price: float | None = None amount: float | None = None class LowConfidenceField(BaseModel): field_name: str confidence: float class InvoiceResult(BaseModel): vendor_name: str total_amount: float | None = None currency: str = "USD" due_date: str | None = None line_items: list[LineItem] = Field(default_factory=list) low_confidence_fields: list[LowConfidenceField] = Field( default_factory=list, description="Fields with confidence < 0.8, including their confidence score", ) async def main() -> None: # 1. Set up credentials and CU context provider credential = AzureCliCredential() # Default analyzer is prebuilt-documentSearch (RAG-optimized). # Per-file override via additional_properties["analyzer_id"] lets us # use prebuilt-invoice for structured field extraction on specific files. # # Only request "fields" (not "markdown") — we want the extracted typed # fields for structured output, not the raw document text. cu = ContentUnderstandingContextProvider( endpoint=os.environ["AZURE_CONTENTUNDERSTANDING_ENDPOINT"], credential=credential, analyzer_id="prebuilt-documentSearch", # default for all files max_wait=None, # wait until CU analysis finishes output_sections=["fields"], # fields only — structured output doesn't need markdown ) # 2. Set up the LLM client client = FoundryChatClient( project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], model=os.environ["FOUNDRY_MODEL"], credential=credential, ) # 3. Create agent and session async with cu: agent = Agent( client=client, name="InvoiceProcessor", instructions=( "You are an invoice processing assistant. Extract invoice data from " "the provided CU fields (JSON with confidence scores). Return structured " "output matching the requested schema. Flag fields with confidence < 0.8 " "in the low_confidence_fields list." ), context_providers=[cu], ) session = AgentSession() # 4. Upload an invoice PDF — uses structured output (Pydantic model) print("--- Upload Invoice (Structured Output) ---") pdf_bytes = SAMPLE_PDF_PATH.read_bytes() response = await agent.run( Message( role="user", contents=[ Content.from_text( "Process this invoice. Extract the vendor name, total amount, due date, and all line items." ), Content.from_data( pdf_bytes, "application/pdf", # Per-file analyzer override: use prebuilt-invoice for # structured field extraction (VendorName, InvoiceTotal, etc.) # instead of the provider default (prebuilt-documentSearch). additional_properties={ "filename": SAMPLE_PDF_PATH.name, "analyzer_id": "prebuilt-invoice", }, ), ], ), session=session, options={"response_format": InvoiceResult}, ) # Parse the structured output from JSON text try: invoice = InvoiceResult.model_validate_json(response.text) print(f"Vendor: {invoice.vendor_name}") print(f"Total: {invoice.currency} {invoice.total_amount}") print(f"Due date: {invoice.due_date}") print(f"Line items ({len(invoice.line_items)}):") for item in invoice.line_items: print(f" - {item.description}: {item.amount}") if invoice.low_confidence_fields: print("⚠ Low confidence fields:") for f in invoice.low_confidence_fields: print(f" - {f.field_name}: {f.confidence:.3f}") except Exception: print(f"Agent (raw): {response.text}\n") # 5. Follow-up: free-text question about the invoice print("\n--- Follow-up (Free Text) ---") response = await agent.run( "What is the payment term? Are there any fields with low confidence?", session=session, ) print(f"Agent: {response}\n") if __name__ == "__main__": asyncio.run(main()) """ Sample output: --- Upload Invoice (Structured Output) --- Vendor: CONTOSO LTD. Total: USD 110.0 Due date: 2019-12-15 Line items (3): - Consulting Services: 60.0 - Document Fee: 30.0 - Printing Fee: 10.0 ⚠ Low confidence: VendorName, CustomerName --- Follow-up (Free Text) --- Agent: The payment terms are not explicitly stated on the invoice... """