Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

AWS Bedrock Knowledge Base: cosa ho imparato realizzando un sistema di audit delle policy in produzione

By Cloud Intelligence™Jun 8, 202611 min read

Questa pagina è disponibile anche in English, Deutsch, Español, Français, 日本語 e Português.

A cura di Dima Kramskoy — Senior Cloud Architect, DoiT International


Perché questo articolo

La maggior parte dei tutorial su Bedrock Knowledge Base segue lo stesso copione: caricare un PDF su S3, creare una Knowledge Base, porre una domanda. Fatto. Articolo pubblicato.

Per una demo va bene. Per la produzione è inutile.

Di recente ho realizzato un sistema di audit delle policy per una fintech del LATAM: valuta le spese rispetto alle policy interne in tempo reale tramite AI. Il sistema gestisce circa 500 query al giorno su oltre 100 documenti di policy in due lingue. È in produzione. Le note spese delle persone vengono approvate o respinte sulla base di ciò che dice.

Ecco cosa ho davvero imparato: le decisioni architetturali, gli errori di chunking, le sorprese sui costi e le insidie di cui nessuna documentazione AWS ti avverte.


La tua prima Bedrock Knowledge Base (5 minuti)

Prima di entrare nei dettagli, partiamo dalle basi. Se hai già costruito una KB, salta alla sezione successiva. Altrimenti, ecco il percorso più rapido per arrivare a "funziona":

Passo 1: crea un bucket S3 con i tuoi documenti

Terminal window
aws s3 mb s3://my-kb-source-docs
aws s3 cp ./policies/ s3://my-kb-source-docs/ --recursive

Passo 2: crea la Knowledge Base (Console)

  • Vai su Amazon Bedrock → Knowledge Bases → Create
  • Dalle un nome e seleziona un embedding model (Titan Embeddings v2 è quello di default — va benissimo per iniziare)
  • Collegala al tuo bucket S3
  • Per il vector store: scegli S3 Vectors (serverless, zero configurazione) oppure lasciane creare uno automaticamente
  • Clicca Create → attendi 2-3 minuti per la sincronizzazione

Passo 3: interrogala

import boto3
client = boto3.client('bedrock-agent-runtime')
response = client.retrieve_and_generate(
input={'text': 'What is our policy on travel expenses?'},
retrieveAndGenerateConfiguration={
'type': 'KNOWLEDGE_BASE',
'knowledgeBaseConfiguration': {
'knowledgeBaseId': 'YOUR_KB_ID',
'modelArn': 'arn:aws:bedrock:us-east-1::foundation-model/amazon.nova-pro-v1:0'
}
}
)
print(response['output']['text'])

Ecco fatto. Hai un sistema RAG funzionante, che risponde a domande basate sui tuoi documenti.

Ora però il punto è questo: sei arrivato al 60% del percorso. Il restante 40% — dove vive la qualità di produzione — è il tema del resto di questo articolo. Strategia di chunking, controllo dei costi, latenza, pipeline di trasformazione e le insidie che ti faranno male su larga scala.


Il caso d'uso

Il cliente aveva un problema in cui prima o poi si imbatte ogni azienda in crescita: policy interne complesse che nessuno legge, applicate in modo incoerente, su più paesi e lingue.

Nello specifico: un'azienda con oltre 100 documenti di policy interne (in inglese e spagnolo) aveva bisogno dell'AI per valutare in tempo reale le spese dei dipendenti rispetto a quelle policy. Non "cercare una policy", ma decidere davvero se una spesa è conforme, citare la regola pertinente e spiegarne il motivo.

I requisiti erano chiari:

  • Latenza decisionale sotto i 3 secondi
  • Ogni decisione tracciabile fino al paragrafo di policy di origine
  • Gate di approvazione umana per le modifiche alle policy (responsabilità legale)
  • Supporto multilingua (inglese/spagnolo)
  • Costi sostenibili su larga scala (~500 query/giorno, in crescita)

Panoramica dell'architettura

Ecco il flusso ad alto livello:

Percorso di valutazione delle transazioni:

API Gateway → SQS → Step Functions → Bedrock Nova Pro (validazione ricevute)
→ Bedrock AgentCore (decisione sulla policy tramite KB) → Aurora MySQL (persistenza decisione)

Percorso di ingestion delle policy:

Upload su S3 → Step Functions (approvazione umana tramite task token)
→ Trasformazione LLM (ristruttura la policy, estrae le spese, definisce la nuova vista)
→ Chunk trasformati salvati in S3 (stato di transizione)
→ Un'API espone i chunk per la revisione → Approvazione umana
→ Ingestion nella Bedrock Knowledge Base

Due percorsi distinti. Uno per le decisioni in tempo reale, uno per la gestione del ciclo di vita delle policy. Step Functions orchestra entrambi — e non è un caso. Le state machine offrono esattamente la visibilità e la semantica di retry di cui hai bisogno quando in gioco c'è la conformità legale.


Perché S3 Vectors (e non OpenSearch)

Qui ti risparmio settimane di valutazioni.

Quando ho iniziato questo progetto, il vector store di default per Bedrock KB era OpenSearch Serverless. Funziona, è collaudato. Ma è anche enormemente sovradimensionato per un set documentale sotto i 10K documenti.

S3 Vectors è stato lanciato come alternativa più semplice e, per questo caso d'uso, era la scelta scontata:

Fattore OpenSearch Serverless S3 Vectors
Costo base mensile ~$700+ (minimo 2 OCU) Pay-per-query
Overhead operativo Gestione indici, scaling Zero
Complessità di setup Moderata Minima
Latenza di query (p50) ~200ms ~350ms
Sweet spot 10K+ documenti, query complesse <10K documenti, retrieval lineare

Il criterio di scelta è semplice: se il tuo corpus documentale è sotto i 10K documenti e non ti servono filtri complessi o ricerca ibrida, S3 Vectors ti fa risparmiare soldi e grattacapi operativi. Se ti serve latenza sotto i 200ms o hai decine di migliaia di documenti con query complesse sui metadati, vai su OpenSearch.

Per i nostri circa 100 documenti di policy? S3 Vectors era una scelta obbligata. Paghiamo centesimi al giorno invece di un minimo di $700/mese. Il compromesso sulla latenza (circa 150ms in più) è impercettibile in un workflow che comprende comunque l'inferenza LLM.


Strategie di chunking che contano davvero

Prima di raccontarti cosa ha funzionato per noi, ecco il quadro d'insieme — perché la maggior parte delle guide mostra solo una o due opzioni:

Strategia Come funziona Ideale per A cosa fare attenzione
Fixed-size (default) Divide ogni N caratteri/token Avvio rapido, documenti generici Spezza frasi e regole a metà — genera risposte allucinate
Sentence-based Divide sui confini di frase Documenti semplici, FAQ Non rispetta le sezioni logiche; una regola di policy può estendersi su 5+ frasi
Semantic / Section-based Divide in base alla struttura del documento (intestazioni, sezioni) Documenti strutturati con gerarchia chiara Richiede il parsing della struttura; i chunk variano in dimensione
Hierarchical (parent-child) Chunk genitori (sezione completa) + chunk figli (paragrafi) Massima qualità di retrieval — match sul figlio, restituisce il genitore per il contesto Più complesso, storage maggiore, indicizzazione più lenta
LLM-assisted L'LLM ristruttura il documento PRIMA del chunking Documenti ad alto rischio dove un retrieval errato = decisione sbagliata Aggiunge costo + latenza in ingestion; ne vale la pena quando l'accuratezza è critica

La nostra scelta: LLM-assisted. Per documenti di policy in cui un retrieval sbagliato significa una decisione sbagliata sulla spesa, il costo iniziale della trasformazione LLM si ripaga subito. Approfondiamo nella sezione sulla pipeline qui sotto.

Ma prima lascia che ti mostri la modalità di fallimento che ci ha portato qui:

È qui che, all'inizio, ho commesso il mio errore più costoso.

Cosa non ha funzionato: il chunking di default

Il chunking di default di Bedrock KB divide i documenti per numero di caratteri, con sovrapposizione. Per documenti generici va bene. Per documenti di policy è catastrofico.

Ecco perché: una regola di policy potrebbe recitare "I pasti oltre i 75$ richiedono l'approvazione del manager, tranne durante trasferte con clienti, dove il limite è 150$." Il chunking di default può spezzare questa frase a metà. Il retrieval restituisce quindi "I pasti oltre i 75$ richiedono l'approvazione del manager" senza l'eccezione. L'agente nega una legittima cena con cliente da 100$. I tuoi utenti perdono fiducia nel sistema fin dal primo giorno.

Cosa ha funzionato: chunking per confini semantici

Pre-elaboriamo i documenti prima dell'ingestion, suddividendoli per sezione di policy: ogni regola o sotto-regola diventa un chunk a sé, con il contesto integralmente preservato.

import re
from dataclasses import dataclass
@dataclass
class PolicyChunk:
content: str
metadata: dict
def chunk_policy_document(text: str, doc_id: str, language: str) -> list[PolicyChunk]:
"""Chunk policy documents by semantic boundaries (section headers)."""
# Split on policy section patterns (numbered rules, headers)
section_pattern = r'\n(?=\d+\.\s|\#{1,3}\s|Article\s+\d+|Artículo\s+\d+)'
sections = re.split(section_pattern, text)
chunks = []
for i, section in enumerate(sections):
section = section.strip()
if len(section) < 50: # Skip trivial sections
continue
# Keep chunks between 200-1500 chars for optimal retrieval
if len(section) > 1500:
# Sub-chunk by paragraph, preserving section header
header = section.split('\n')[0]
paragraphs = section.split('\n\n')
for j, para in enumerate(paragraphs[1:], 1):
chunks.append(PolicyChunk(
content=f"{header}\n\n{para}",
metadata={
"doc_id": doc_id,
"section_index": i,
"sub_index": j,
"language": language,
"chunk_type": "policy_rule"
}
))
else:
chunks.append(PolicyChunk(
content=section,
metadata={
"doc_id": doc_id,
"section_index": i,
"sub_index": 0,
"language": language,
"chunk_type": "policy_rule"
}
))
return chunks

Indicazioni pratiche

  • Dimensione ottimale dei chunk: 200–1500 caratteri per i documenti di policy. Chunk più piccoli aumentano la precisione; chunk più grandi preservano il contesto. Trova il tuo equilibrio.
  • Overlap: se proprio devi usare un chunking basato sui caratteri, usa almeno il 20% di sovrapposizione. Ma fidati: usa i confini semantici.
  • I metadati sono il retrieval: etichetta ogni chunk con language, policy_type, effective_date e department. Dovrai filtrare su questi più avanti — non è opzionale.

La pipeline di ingestion

I documenti di policy non sono articoli di blog. Non puoi limitarti a buttarli in un vector store e sperare che vada bene. Un'interpretazione sbagliata di una policy ha conseguenze legali.

Ecco la pipeline:

Il pattern Trasformazione LLM + Human-in-the-Loop

L'intuizione progettuale chiave: trasformare PRIMA dell'approvazione. Il flusso:

Upload su S3 (PDF grezzo della policy)
Trasformazione LLM (ristruttura, estrae le regole di spesa, organizza nella vista desiderata)
Chunk trasformati salvati in S3 (stato di transizione — in cache per il riutilizzo)
Un'API espone i chunk strutturati per la revisione
Il revisore approva / rifiuta (un click)
I chunk approvati vengono ingeriti nella Bedrock Knowledge Base

Questo è un gate logico di approvazione, non un'orchestrazione pesante. L'LLM fa il lavoro duro a monte: analizza PDF di più pagine, estrae le singole regole di spesa e le struttura in modo coerente. Quando il revisore vede l'output, davanti ha chunk puliti e strutturati, non documenti grezzi.

Perché è importante in produzione:

  • La re-ingestion è veloce: la trasformazione è in cache su S3. Gli aggiornamenti delle policy non richiedono di rifare l'elaborazione da zero.
  • I revisori vedono output di qualità: approvano regole strutturate, non muri di testo PDF.
  • Meno errori in fase di retrieval: poiché l'LLM pre-struttura il contenuto, la KB riceve ogni volta chunk formattati in modo coerente.

Perché human-in-the-loop conta

Ho visto team saltare il gate di approvazione perché "ci fidiamo del nostro team policy". Poi qualcuno carica un documento in bozza, viene fatto l'embedding, e l'AI inizia ad applicare regole che erano solo una bozza. Basta un incidente del genere e hai perso la fiducia di tutta l'organizzazione nel sistema.

Anti-pattern: ingestion automatica all'upload su S3. Da non fare mai per documenti sensibili alla compliance.

Pattern: Upload → l'LLM trasforma e struttura la policy → chunk trasformati in cache su S3 → un'API espone i chunk per la revisione → l'umano approva/rifiuta → solo allora ingestion nella KB. In questo modo la re-ingestion è veloce (la trasformazione è già fatta e in cache) e gli approvatori vedono output pulito e strutturato — non PDF grezzi.


Latenza e costi del retrieval

Numeri reali dalla produzione (workload di 500 query/giorno):

Latenza (S3 Vectors)

p50 = tempo di risposta mediano (esperienza tipica). p99 = 99° percentile (caso peggiore, esclusi gli outlier estremi).

  • p50: 340ms (solo retrieval, esclusa l'inferenza LLM)
  • p99: 890ms
  • Decisione end-to-end (incluso AgentCore): p50 ~2,1s, p99 ~4,8s

Ripartizione dei costi mensili

Componente Costo mensile
S3 Vectors (storage + query) ~$12
Chiamate API Bedrock KB ~$8
Titan Embeddings (ingestion) ~$3
Nova Pro (validazione ricevute) ~$45
AgentCore (decisioni sulle policy) ~$120
Step Functions ~$5
Aurora MySQL (persistenza) ~$65
Totale ~$258/mese

Confrontalo con il solo OpenSearch Serverless, a un minimo di $700/mese. Le scelte architetturali si sommano.


Insidie di cui nessuno ti avverte

Dopo tre mesi in produzione, ecco la mia lista:

  1. Ritardo di sincronizzazione dopo l'upload. Dopo aver chiamato StartIngestionJob, la KB non è subito interrogabile sui nuovi contenuti. Aspettati 30–90 secondi per piccoli aggiornamenti. Tienine conto nella UX, mostrando stati come "aggiornamento policy in elaborazione".

  2. Il filtro sui metadati è solo exact-match (S3 Vectors). Non puoi fare query su intervalli o match parziali sui metadati. Progetta lo schema dei metadati per filtri di uguaglianza. Se ti serve "tutte le policy aggiornate dopo gennaio 2025", dovrai adottare un altro approccio.

  3. La scelta dell'embedding model è definitiva. Una volta creata una KB con Titan Embeddings v2, non puoi passare a Cohere senza ricreare l'intera Knowledge Base. Scegli con cura fin da subito. (Noi abbiamo optato per Titan v2: buon equilibrio tra costo e qualità per contenuti bilingue.)

  4. Il retrieval multilingua non è magia. Una query in spagnolo recupera bene i chunk in spagnolo, ma il retrieval cross-lingua (query in spagnolo → policy in inglese) è inaffidabile. L'abbiamo risolto mantenendo chunk paralleli in entrambe le lingue e filtrando in base alla lingua rilevata della query.

  5. Picchi di costo per la re-indicizzazione. Se sincronizzi spesso l'intera KB (invece di fare aggiornamenti incrementali), i costi di embedding schizzano alle stelle. Una re-indicizzazione completa di 100 documenti costa circa $2. Falla per errore ogni ora e bruci $1.400/mese solo in embedding.

  6. Le quote KB sono sorprendentemente basse. Job di ingestion concorrenti di default: 1. Dimensione documento di default: 50MB. Richiedi gli aumenti delle quote prima di arrivare alla scala di produzione.

  7. Il problema della "risposta sbagliata data con sicurezza". Quando l'agente recupera un chunk vicino ma non corretto, applica con sicurezza la regola sbagliata. Mitiga questo rischio impostando una soglia di similarity score (noi usiamo 0,7) e instradando i retrieval a bassa confidenza alla revisione umana.


Come iniziare

Cinque passi verso la tua prima implementazione di Bedrock KB in produzione:

  1. Inizia con 10 documenti, non 100. Affina la strategia di chunking su un set ristretto. Valida manualmente la qualità del retrieval prima di scalare.

  2. Scegli S3 Vectors, salvo motivi specifici per non farlo. Per la maggior parte dei casi d'uso Knowledge Base sotto i 10K documenti è più economico e semplice. Passa a OpenSearch solo quando ne avrai davvero bisogno.

  3. Investi nel chunking prima di ogni altra cosa. Il chunking di default è una trappola per i documenti strutturati. Dedica una settimana alla tua strategia di chunking: è il lavoro a più alta leva che farai.

  4. Costruisci la pipeline di approvazione fin dal primo giorno. Anche se oggi non ti serve l'human-in-the-loop, ti servirà quando la posta in gioco si alzerà. Il pattern del task token in Step Functions rende banale aggiungerlo dopo, ma costoso da retrofittare.

  5. Strumenta tutto. Logga score di retrieval, ID dei chunk, confidenza delle decisioni. Non puoi migliorare ciò che non misuri. Quando la qualità del retrieval calerà (e lo farà, man mano che il corpus crescerà), avrai bisogno di dati per capirne il motivo.


In conclusione

Bedrock Knowledge Base è un'infrastruttura davvero solida per i sistemi RAG in produzione. Ma è proprio nello scarto tra "demo" e "produzione" che vivono tutte le decisioni ingegneristiche interessanti: strategie di chunking, pipeline di ingestion, ottimizzazione dei costi, catene di evidenze, modalità di fallimento.

S3 Vectors ha reso questo progetto economicamente sostenibile a una scala in cui OpenSearch sarebbe stato sovradimensionato. Step Functions ci ha dato le garanzie di orchestrazione che la compliance richiede. E AgentCore ha trasformato il retrieval in decisioni strutturate e verificabili.

Lo stack funziona. La parte difficile non sono mai stati i servizi AWS: è stata la data engineering attorno a essi.

Fallo bene la prima volta. Il tuo te stesso futuro (e il team legale del tuo cliente) ti ringrazieranno.


Dima Kramskoy è Senior Cloud Architect presso DoiT International, con oltre 20 anni di esperienza nello sviluppo software, 10 certificazioni AWS, ed è AWS Community Builder (2026). Aiuta le aziende a costruire sistemi AI/ML di produzione su AWS.