Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Implementación de AWS Bedrock Knowledge Base: lo que aprendí al construir un sistema de auditoría de políticas en producción

By Cloud Intelligence™Jun 8, 202611 min read

Esta página también está disponible en English, Deutsch, Français, Italiano, 日本語 y Português.

Por Dima Kramskoy — Senior Cloud Architect en DoiT International


Por qué escribí este post

La mayoría de los tutoriales de Bedrock Knowledge Base siguen el mismo guion: subes un PDF a S3, creas un Knowledge Base y le haces una pregunta. Listo. Post publicado.

Eso sirve para una demo. No sirve para producción.

Hace poco desarrollé un sistema de auditoría de políticas para una fintech de LATAM, capaz de evaluar gastos contra las políticas internas en tiempo real con IA. El sistema procesa unas 500 consultas diarias sobre más de 100 documentos de políticas en dos idiomas. Está corriendo en producción. Los reportes de gastos de las personas se aprueban o rechazan según lo que diga.

Acá va lo que realmente aprendí: las decisiones de arquitectura, los errores de chunking, las sorpresas en los costos y las trampas de las que ninguna documentación de AWS te advierte.


Tu primer Bedrock Knowledge Base (5 minutos)

Antes de entrar en profundidad, asegurémonos de que tengas lo básico. Si ya armaste un KB, salta a la siguiente sección. Si no, este es el camino más rápido para llegar al "funciona":

Paso 1: crea un bucket de S3 con tus documentos

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

Paso 2: crea el Knowledge Base (Consola)

  • Entra a Amazon Bedrock → Knowledge Bases → Create
  • Ponle un nombre y elige un modelo de embeddings (Titan Embeddings v2 viene por defecto y sirve para arrancar)
  • Apúntalo a tu bucket de S3
  • Para el vector store: elige S3 Vectors (serverless, sin configuración) o deja que te cree uno
  • Haz clic en Create → espera 2-3 minutos a que sincronice

Paso 3: consúltalo

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'])

Listo. Ya tienes un sistema RAG funcionando. Responde preguntas a partir de tus documentos.

Ahora bien: con esto cubres el 60% del camino. El 40% restante —donde está la calidad de producción— es de lo que trata el resto del post. Estrategia de chunking, control de costos, latencia, el pipeline de transformación y las trampas que te van a complicar la vida cuando escales.


El caso de uso

El cliente tenía un problema que toda empresa en crecimiento termina enfrentando: políticas internas complejas que nadie lee, aplicadas de forma inconsistente, en varios países y en varios idiomas.

En concreto: una empresa con más de 100 documentos de políticas internas (en inglés y español) necesitaba que la IA evaluara los gastos de los empleados contra esas políticas en tiempo real. No "buscar una política", sino decidir si un gasto cumple, citar la regla correspondiente y explicar el porqué.

Los requisitos eran claros:

  • Latencia de decisión por debajo de 3 segundos
  • Cada decisión rastreable hasta un párrafo de la política de origen
  • Aprobación humana para los cambios de política (responsabilidad legal)
  • Soporte multilingüe (inglés/español)
  • Costo controlado a escala (~500 consultas/día, en crecimiento)

Vista general de la arquitectura

Este es el flujo a alto nivel:

Ruta de evaluación de transacciones:

API Gateway → SQS → Step Functions → Bedrock Nova Pro (validación del recibo)
→ Bedrock AgentCore (decisión de política vía KB) → Aurora MySQL (persistir la decisión)

Ruta de ingesta de políticas:

Subida a S3 → Step Functions (aprobación humana vía task token)
→ Transformación con LLM (reestructurar la política, extraer gastos, estructurar la nueva vista)
→ Chunks transformados guardados en S3 (estado de transición)
→ La API expone los chunks para revisión → Aprobación humana
→ Ingesta en Bedrock Knowledge Base

Dos rutas distintas. Una para decisiones en tiempo real, otra para la gestión del ciclo de vida de las políticas. Step Functions orquesta ambas, y eso no es casualidad. Las máquinas de estado te dan justo la visibilidad y la semántica de reintentos que necesitas cuando hay cumplimiento legal de por medio.


Por qué S3 Vectors (y no OpenSearch)

Acá te voy a ahorrar semanas de deliberación.

Cuando empecé este proyecto, el vector store por defecto para Bedrock KB era OpenSearch Serverless. Funciona. Está probadísimo. Y también está tremendamente sobredimensionado para un conjunto de menos de 10K documentos.

S3 Vectors salió como alternativa más simple y, para este caso de uso, fue la elección obvia:

Factor OpenSearch Serverless S3 Vectors
Costo base mensual ~$700+ (mínimo 2 OCUs) Pago por consulta
Sobrecarga operativa Gestión de índices, escalado Cero
Complejidad de configuración Moderada Mínima
Latencia de consulta (p50) ~200ms ~350ms
Punto ideal Más de 10K docs, consultas complejas <10K docs, recuperación directa

El criterio de decisión es simple: si tu corpus tiene menos de 10K documentos y no necesitas filtrado complejo ni búsqueda híbrida, S3 Vectors te ahorra dinero y dolores de cabeza operativos. Si necesitas latencias por debajo de 200ms o tienes decenas de miles de documentos con consultas de metadatos complejas, ve por OpenSearch.

¿Para nuestros ~100 documentos de políticas? S3 Vectors fue una decisión obvia. Pagamos centavos al día en lugar de un mínimo de $700/mes. El tradeoff de latencia (unos 150ms extra) es invisible dentro de un flujo que ya incluye inferencia de LLM.


Estrategias de chunking que de verdad importan

Antes de contarte qué nos funcionó, este es el panorama, porque la mayoría de las guías solo te muestran una o dos opciones:

Estrategia Cómo funciona Mejor para Cuidado con
Tamaño fijo (por defecto) Divide cada N caracteres/tokens Empezar rápido, documentos genéricos Corta a media frase, a media regla — provoca respuestas alucinadas
Por oraciones Divide en los límites de cada oración Documentos simples, FAQs No respeta las secciones lógicas; una regla puede abarcar más de 5 oraciones
Semántico / por secciones Divide según la estructura del documento (encabezados, secciones) Documentos estructurados con jerarquía clara Hay que parsear la estructura; el tamaño de los chunks varía
Jerárquico (padre-hijo) Chunks padre (sección completa) + chunks hijo (párrafos) Mejor calidad de recuperación — coincide el hijo, devuelve el padre como contexto Más complejo, más almacenamiento, indexación más lenta
Asistido por LLM Un LLM reestructura el documento ANTES del chunking Documentos críticos donde una mala recuperación = mala decisión Suma costo y latencia en la ingesta; vale la pena cuando la precisión importa

Nuestra elección: asistido por LLM. Para documentos de políticas en los que una recuperación incorrecta significa una decisión de gasto incorrecta, el costo inicial de la transformación con LLM se paga solo de inmediato. Más detalles en la sección del pipeline.

Pero antes, déjame mostrarte el modo de falla que nos llevó hasta acá:

Acá cometí mi error más caro al principio.

Lo que no funcionó: el chunking por defecto

El chunking por defecto de Bedrock KB divide los documentos por cantidad de caracteres con solapamiento. Para documentos genéricos, está bien. Para documentos de políticas, es catastrófico.

El motivo: una regla puede decir "Las comidas de más de $75 requieren aprobación del manager, excepto en viajes con clientes, donde el límite es $150." El chunking por defecto puede partir esto a la mitad. La recuperación entonces devuelve "Las comidas de más de $75 requieren aprobación del manager" sin la excepción. El agente rechaza una cena legítima de $100 con un cliente. Tus usuarios pierden la confianza en el sistema el primer día.

Lo que sí funcionó: chunking por límites semánticos

Preprocesamos los documentos antes de la ingesta, dividiéndolos por sección de política: cada regla o subregla se convierte en su propio chunk, conservando el contexto completo.

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

Recomendaciones prácticas

  • Tamaño óptimo de chunk: 200–1500 caracteres para documentos de políticas. Los chunks más chicos mejoran la precisión; los más grandes preservan el contexto. Encuentra tu balance.
  • Solapamiento: si tienes que usar chunking por caracteres, usa al menos un 20% de solapamiento. Pero en serio: divide por límites semánticos.
  • Los metadatos son recuperación: etiqueta cada chunk con language, policy_type, effective_date y department. Vas a filtrar por estos campos después; no es opcional.

El pipeline de ingesta

Los documentos de políticas no son posts de blog. No los puedes simplemente tirar a un vector store y cruzar los dedos. Una interpretación incorrecta de una política tiene consecuencias legales.

Este es el pipeline:

El patrón Transformación con LLM + Human-in-the-Loop

La idea clave del diseño: transformar ANTES de aprobar. El flujo:

Subida a S3 (PDF de política en bruto)
Transformación con LLM (reestructurar, extraer reglas de gastos, estructurar en la vista deseada)
Chunks transformados guardados en S3 (estado de transición — cacheados para reutilizar)
La API expone los chunks estructurados para revisión
El revisor aprueba / rechaza (un clic)
Los chunks aprobados se ingieren en Bedrock Knowledge Base

Esto es una compuerta lógica de aprobación, no una orquestación pesada. El LLM hace el trabajo duro al inicio: parsear PDFs de varias páginas, extraer reglas de gasto individuales y estructurarlas de forma consistente. Para cuando el revisor ve la salida, está mirando chunks limpios y estructurados, no documentos en crudo.

Por qué esto importa en producción:

  • La reingesta es rápida — la transformación queda cacheada en S3. Las actualizaciones de políticas no exigen reprocesar desde cero.
  • Los revisores ven salidas de calidad — aprueban reglas estructuradas, no muros de texto en PDF.
  • Menos errores al recuperar — como el LLM preestructura el contenido, el KB recibe chunks con formato consistente cada vez.

Por qué importa el human-in-the-loop

He visto equipos saltarse la compuerta de aprobación porque "confiamos en nuestro equipo de políticas". Después alguien sube un documento en borrador, se embebe y la IA empieza a aplicar reglas en borrador. Un incidente así y ya perdiste la confianza de la organización en el sistema.

Antipatrón: auto-ingerir al subir a S3. Nunca hagas esto con documentos sensibles a cumplimiento.

Patrón: Subida → el LLM transforma y estructura la política → los chunks transformados quedan cacheados en S3 → la API expone los chunks para revisión → un humano aprueba/rechaza → entonces se ingiere al KB. Así, la reingesta es rápida (la transformación ya está hecha y cacheada) y los aprobadores ven salidas limpias y estructuradas, no PDFs en crudo.


Latencia y costo de recuperación

Números reales de producción (workload de 500 consultas/día):

Latencia (S3 Vectors)

p50 = tiempo de respuesta mediano (experiencia típica). p99 = percentil 99 (peor caso excluyendo outliers extremos).

  • p50: 340ms (solo recuperación, sin contar la inferencia del LLM)
  • p99: 890ms
  • Decisión end-to-end (incluyendo AgentCore): p50 ~2.1s, p99 ~4.8s

Desglose de costo mensual

Componente Costo mensual
S3 Vectors (almacenamiento + consultas) ~$12
Llamadas a la API de Bedrock KB ~$8
Titan Embeddings (ingesta) ~$3
Nova Pro (validación de recibos) ~$45
AgentCore (decisiones de política) ~$120
Step Functions ~$5
Aurora MySQL (persistencia) ~$65
Total ~$258/mes

Compáralo con OpenSearch Serverless solo, con un mínimo de $700/mes. Las decisiones de arquitectura se acumulan.


Trampas de las que nadie te advierte

Después de tres meses en producción, esta es mi lista:

  1. Retraso de sincronización después de subir. Después de llamar a StartIngestionJob, el KB no queda consultable de inmediato con el contenido nuevo. Espera entre 30 y 90 segundos para actualizaciones pequeñas. Considéralo en tu UX: muestra estados del tipo "procesando actualización de política".

  2. El filtrado por metadatos es solo coincidencia exacta (S3 Vectors). No puedes hacer consultas por rango ni coincidencias parciales en los metadatos. Diseña tu esquema en torno a filtros de igualdad. Si necesitas "todas las políticas actualizadas después de enero de 2025", vas a necesitar otro enfoque.

  3. La elección del modelo de embeddings es permanente. Una vez que creas un KB con Titan Embeddings v2, no puedes cambiar a Cohere sin recrear todo el Knowledge Base. Elige con cuidado desde el inicio. (Nosotros fuimos con Titan v2: buen balance entre costo y calidad para contenido bilingüe.)

  4. La recuperación multilingüe no es magia. Una consulta en español recupera bien chunks en español, pero la recuperación cruzada entre idiomas (consulta en español → política en inglés) es poco confiable. Lo resolvimos manteniendo chunks paralelos en ambos idiomas y filtrando por el idioma detectado en la consulta.

  5. Picos de costo por reindexación. Si sincronizas todo tu KB con frecuencia (en vez de hacer actualizaciones incrementales), los costos de embeddings se disparan. Una reindexación completa de 100 documentos cuesta ~$2. Hazlo cada hora por error y estarás quemando $1,400/mes solo en embeddings.

  6. Las cuotas del KB son sorprendentemente bajas. Jobs de ingesta concurrentes por defecto: 1. Tamaño de documento por defecto: 50MB. Pide aumentos de cuota antes de llegar a escala de producción.

  7. El problema de la "respuesta incorrecta con confianza". Cuando el agente recupera un chunk parecido pero no correcto, aplica la regla equivocada con total convicción. Mitígalo fijando un umbral de score de similitud (nosotros usamos 0.7) y enrutando las recuperaciones de baja confianza a revisión humana.


Cómo empezar

Cinco pasos hacia tu primera implementación de Bedrock KB en producción:

  1. Empieza con 10 documentos, no con 100. Afina tu estrategia de chunking en un conjunto pequeño. Valida manualmente la calidad de la recuperación antes de escalar.

  2. Elige S3 Vectors a menos que tengas una razón para no hacerlo. Para la mayoría de casos de uso de Knowledge Base por debajo de 10K documentos, es más barato y más simple. Pasa a OpenSearch cuando realmente lo necesites.

  3. Invierte en chunking antes que en nada más. El chunking por defecto es una trampa para documentos estructurados. Dedícale una semana a tu estrategia de chunking: es el trabajo de mayor apalancamiento que vas a hacer.

  4. Construye el pipeline de aprobación desde el día uno. Aunque hoy no necesites human-in-the-loop, lo vas a necesitar cuando suban las apuestas. El patrón de task token en Step Functions lo hace trivial de agregar después, pero caro de retroadaptar.

  5. Instrumenta todo. Registra los scores de recuperación, los IDs de chunks y la confianza de cada decisión. No puedes mejorar lo que no puedes medir. Cuando la calidad de recuperación se degrade (y lo hará, a medida que crezca tu corpus), vas a necesitar datos para diagnosticar por qué.


Para cerrar

Bedrock Knowledge Base es, genuinamente, buena infraestructura para sistemas RAG en producción. Pero la distancia entre "demo" y "producción" es donde vive cada decisión interesante de ingeniería: estrategias de chunking, pipelines de ingesta, optimización de costos, cadenas de evidencia, modos de falla.

S3 Vectors hizo este proyecto económicamente viable a una escala donde OpenSearch habría sido exagerado. Step Functions nos dio las garantías de orquestación que exige el cumplimiento. Y AgentCore convirtió la recuperación en decisiones estructuradas y auditables.

El stack funciona. La parte difícil nunca fueron los servicios de AWS, sino la ingeniería de datos a su alrededor.

Hazlo bien desde la primera vez. Tu yo del futuro (y el equipo legal de tu cliente) te lo van a agradecer.


Dima Kramskoy es Senior Cloud Architect en DoiT International, con más de 20 años en ingeniería de software, 10 certificaciones de AWS, y es AWS Community Builder (2026). Ayuda a las organizaciones a construir sistemas de IA/ML en producción sobre AWS.