Le compagnie assicurative elaborano milioni di documenti ogni anno: verbali di polizia, cartelle cliniche, fatture, preventivi di riparazione. Tradizionalmente, operatori umani leggono ogni documento, lo classificano, ne estraggono i campi rilevanti e inseriscono i dati nel sistema dei sinistri. Questo è lento, costoso e soggetto a errori.
In questo articolo descrivo l’architettura di una pipeline di elaborazione documenti in produzione che ho contribuito a costruire. Il sistema ingestisce documenti di sinistro, estrae testo usando LLM con capacità visiva, raggruppa e classifica le sezioni del documento, estrae dati strutturati e genera embedding vettoriali per la ricerca semantica. Tutto su un’architettura AWS completamente serverless senza costi di infrastruttura idle.
Lo Spazio del Problema#
Un tipico sinistro assicurativo arriva come archivio ZIP contenente più documenti: un verbale di polizia, certificati medici, preventivi di riparazione, fotografie, corrispondenza. Ogni documento deve essere:
- Analizzato in testo leggibile dalla macchina
- Classificato per tipo (verbale di polizia, referto medico, fattura, ecc.)
- Strutturato estraendo campi specifici per tipo di documento
- Indicizzato per ricerca semantica
- Riassunto per dare al responsabile del caso una panoramica
Panoramica dell’Architettura#
L’architettura segue un pattern event-driven, completamente serverless:
Upload ZIP → S3 → EventBridge → Step Functions → [Pipeline Lambda] → DynamoDB + Aurora
↓
Servizio Sinistri (ECS) ← ALB ← UtentiI componenti chiave:
- S3: riceve upload ZIP tramite URL pre-firmati
- EventBridge: attiva la pipeline quando arriva un file
- Step Functions: orchestra il workflow di elaborazione multi-fase
- Lambda: esegue ogni fase (stateless, parallelo)
- DynamoDB: traccia lo stato e archivia i risultati di estrazione
- Aurora PostgreSQL: archivia gli embedding vettoriali per RAG
La Pipeline Step Functions#
Il cuore del sistema è una macchina a stati AWS Step Functions che orchestra l’elaborazione dei documenti attraverso una fase lineare seguita da due rami paralleli.
Fase Lineare: Analisi ed Estrazione Testo#
Stage 1: Start — Valida lo ZIP, estrae i file, crea record di tracciamento in DynamoDB. Gestisce anche la deduplicazione.
Stage 2: Split — Ogni documento viene convertito in PDF (se è un DOCX), poi suddiviso in pagine singole salvate come immagini PNG.
Stage 3: Parse — Qui entra in gioco l’AI. Ogni immagine di pagina viene inviata a Claude 3.5 Sonnet (via Amazon Bedrock) per l’estrazione del testo basata su visione. L’LLM legge l’immagine e produce testo Markdown pulito.
def parse_page(image_bytes: bytes, page_number: int) -> str:
bedrock = boto3.client("bedrock-runtime")
response = bedrock.invoke_model(
modelId="anthropic.claude-3-5-sonnet-20240620-v1:0",
body=json.dumps({
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 4096,
"messages": [{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/png",
"data": base64.b64encode(image_bytes).decode(),
},
},
{
"type": "text",
"text": "Estrai tutto il testo da questa pagina del documento. "
"Preserva la struttura usando la formattazione Markdown. "
"Includi tabelle, intestazioni e qualsiasi testo scritto a mano.",
},
],
}],
}),
)
result = json.loads(response["body"].read())
return result["content"][0]["text"]Perché Claude 3.5 Sonnet invece di Amazon Textract? Gli LLM visivi gestiscono documenti reali (timbri, scrittura a mano, layout misti) significativamente meglio dell’OCR tradizionale. E l’output è già Markdown strutturato.
Stage 4: Page Checker — Implementa la logica di retry: raccoglie i risultati da tutte le chiamate di parsing, identifica i fallimenti, e ri-dispatcha le pagine fallite fino a 3 volte.
def check_pages(document_id: str, page_results: list[dict]) -> dict:
successful = [p for p in page_results if p["status"] == "success"]
failed = [p for p in page_results if p["status"] == "error"]
retryable = [p for p in failed if p.get("retry_count", 0) < MAX_RETRIES]
if retryable:
for page in retryable:
page["retry_count"] = page.get("retry_count", 0) + 1
return {"status": "retry", "pages": retryable}
return {
"status": "success",
"parsed_pages": len(successful),
"failed_pages": len(failed),
}Ramo Superiore: Pipeline di Embedding#
Crea embedding vettoriali per la ricerca semantica. I chunk del documento vengono embeddati usando text-embedding-ada-002 di OpenAI e archiviati in Aurora PostgreSQL con l’estensione pgvector.
Questo alimenta un’interfaccia RAG dove i responsabili dei casi possono fare domande tipo “Qual era il costo stimato delle riparazioni?” e ottenere risposte ancorate ai documenti reali.
Ramo Inferiore: Pipeline di Classificazione ed Estrazione#
Clusterizzazione: Identifica range di pagine consecutive che appartengono allo stesso argomento. Usiamo Gemini 2.0 Flash per questo perché è veloce, economico e funziona bene per il ragionamento di classificazione.
Classificazione: Ogni cluster viene etichettato con un tipo di documento (verbale di polizia, certificato medico, fattura, preventivo riparazione, ecc.).
Estrazione: In base all’etichetta di classificazione, vengono estratti campi specifici. Un verbale di polizia ottiene targa, nome conducente, data e luogo. Un referto medico ottiene diagnosi, trattamento e medico.
La Strategia Multi-Modello#
Una delle decisioni architetturali più interessanti è stata usare tre diversi provider LLM, ognuno selezionato per un compito specifico:
| Compito | Modello | Perché |
|---|---|---|
| Estrazione testo (OCR) | Claude 3.5 Sonnet (Bedrock) | Migliori capacità visive per documenti complessi |
| Embedding | text-embedding-ada-002 (OpenAI) | Embedding di alta qualità a costi contenuti |
| Clusterizzazione/Classificazione | Gemini 2.0 Flash (Vertex AI) | Veloce ed economico per compiti di ragionamento |
Tutte le chiamate LLM passano attraverso un servizio gateway interno che astrae le differenze tra provider. Questo approccio multi-modello ci permette di ottimizzare per costo e qualità per compito invece di essere vincolati a un singolo provider.
Performance e Costi#
Alcuni numeri dalla produzione:
- Tempo di elaborazione: un documento di 30 pagine richiede circa 3-5 minuti end-to-end
- Throughput: il sistema gestisce 500+ documenti per ora nei periodi di punta
- Costo per documento: circa EUR 0.15-0.30, a seconda del numero di pagine
- Costo infrastruttura a riposo: quasi zero (serverless)
Lezioni Imparate#
Gli LLM visivi sono pronti per la produzione per l’OCR. Claude 3.5 Sonnet gestisce documenti assicurativi reali (timbri, scrittura a mano, scansioni di bassa qualità, lingue miste) molto meglio dell’OCR tradizionale.
Step Functions è lo strumento giusto per le pipeline documentali. La logica di retry integrata, gli stati Map paralleli, la gestione degli errori e il debug visivo rendono Step Functions ideale per l’elaborazione multi-fase.
Il retry non è opzionale. Le API LLM falliscono più spesso delle API tradizionali. Il pattern Page Checker (fino a 3 tentativi per pagina) è ciò che rende la pipeline abbastanza affidabile per la produzione.
Batch invece di real-time quando puoi. Elaborare gli upload ogni 10 minuti invece di immediatamente ha semplificato significativamente l’architettura. Per l’elaborazione dei sinistri assicurativi, qualche minuto di latenza è perfettamente accettabile.