db620d33df
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Blocked by required conditions
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
dotnet-build-and-test / paths-filter (push) Waiting to run
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Blocked by required conditions
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Blocked by required conditions
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Blocked by required conditions
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Blocked by required conditions
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Blocked by required conditions
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Blocked by required conditions
dotnet-build-and-test / dotnet-test-functions (push) Blocked by required conditions
dotnet-build-and-test / dotnet-build-and-test-check (push) Blocked by required conditions
dotnet-build-and-test / Integration Test Report (push) Blocked by required conditions
194 lines
7.0 KiB
Python
194 lines
7.0 KiB
Python
# 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...
|
|
"""
|