Por Dima Kramskoy — Senior Cloud Architect na DoiT International
Por que este post existe
A maioria dos tutoriais sobre Bedrock Knowledge Base segue o mesmo roteiro: subir um PDF para o S3, criar um Knowledge Base, fazer uma pergunta. Pronto. Post publicado.
Isso até funciona para uma demo. Mas é inútil em produção.
Recentemente, construí um sistema de auditoria de políticas para uma fintech latino-americana — um sistema que avalia despesas em relação às políticas internas em tempo real usando IA. Ele processa cerca de 500 consultas por dia em mais de 100 documentos de política, em dois idiomas. Está rodando em produção. Relatórios de despesas reais são aprovados ou negados com base no que ele diz.
Aqui vai o que realmente aprendi — as decisões de arquitetura, os erros de chunking, as surpresas de custo e as pegadinhas que nenhuma documentação da AWS te avisa.
Seu primeiro Bedrock Knowledge Base (5 minutos)
Antes de me aprofundar, vamos garantir que você tem o básico. Se já criou um KB, pule para a próxima seção. Se não, este é o caminho mais rápido até o "funcionou":
Passo 1: crie um bucket S3 com seus documentos
aws s3 mb s3://my-kb-source-docsaws s3 cp ./policies/ s3://my-kb-source-docs/ --recursivePasso 2: crie o Knowledge Base (no Console)
- Vá em Amazon Bedrock → Knowledge Bases → Create
- Dê um nome e selecione um modelo de embedding (o Titan Embeddings v2 é o padrão — serve bem para começar)
- Aponte para o seu bucket S3
- Para o vector store: escolha S3 Vectors (serverless, zero configuração) ou deixe que ele crie um para você
- Clique em Create → aguarde de 2 a 3 minutos pela sincronização
Passo 3: consulte
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'])É isso. Você tem um sistema RAG funcionando. Ele responde perguntas a partir dos seus documentos.
Mas tem um detalhe: isso te leva a 60% do caminho. Os 40% restantes — onde mora a qualidade de produção — são o que o resto deste post aborda. Estratégia de chunking, controle de custo, latência, pipeline de transformação e as pegadinhas que vão te derrubar em escala.
O caso de uso
O cliente tinha um problema que toda empresa em crescimento acaba enfrentando: políticas internas complexas que ninguém lê, aplicadas de forma inconsistente, em vários países e idiomas.
Mais especificamente: uma empresa com mais de 100 documentos de política interna (em inglês e espanhol) precisava que a IA avaliasse despesas de funcionários em relação a essas políticas em tempo real. Não era "buscar uma política" — era de fato decidir se uma despesa está em conformidade, citar a regra aplicável e explicar o porquê.
Os requisitos eram claros:
- Latência de decisão abaixo de 3 segundos
- Cada decisão rastreável até um parágrafo de política de origem
- Aprovação humana obrigatória para mudanças de política (responsabilidade legal)
- Suporte a múltiplos idiomas (inglês/espanhol)
- Custo viável em escala (cerca de 500 consultas/dia, em crescimento)
Visão geral da arquitetura
Este é o fluxo em alto nível:
Fluxo de avaliação de transações:
API Gateway → SQS → Step Functions → Bedrock Nova Pro (validação de recibo)→ Bedrock AgentCore (decisão de política via KB) → Aurora MySQL (persiste a decisão)Fluxo de ingestão de políticas:
Upload no S3 → Step Functions (aprovação humana via task token)→ Transformação via LLM (reestrutura a política, extrai despesas, monta a nova visão)→ Chunks transformados armazenados no S3 (estado de transição)→ API expõe chunks para revisão → Aprovação humana→ Ingestão no Bedrock Knowledge BaseDois fluxos distintos. Um para decisões em tempo real, outro para o ciclo de vida das políticas. O Step Functions orquestra os dois — e isso não é por acaso. Máquinas de estado dão exatamente a visibilidade e a semântica de retry que você precisa quando há conformidade legal em jogo.
Por que S3 Vectors (e não OpenSearch)
É aqui que eu te poupo semanas de discussão.
Quando comecei este projeto, o vector store padrão do Bedrock KB era o OpenSearch Serverless. Funciona. É testado em batalha. E também é drasticamente superdimensionado para um conjunto com menos de 10 mil documentos.
O S3 Vectors surgiu como uma alternativa mais simples e, para este caso de uso, foi a escolha óbvia:
| Fator | OpenSearch Serverless | S3 Vectors |
|---|---|---|
| Custo base mensal | ~US$ 700+ (mínimo de 2 OCUs) | Pagamento por consulta |
| Sobrecarga operacional | Gestão de índice, escalabilidade | Zero |
| Complexidade de setup | Moderada | Mínima |
| Latência de consulta (p50) | ~200ms | ~350ms |
| Cenário ideal | Mais de 10K docs, consultas complexas | <10K docs, recuperação direta |
O critério de decisão é simples: se o seu corpus tem menos de 10 mil documentos e você não precisa de filtragem complexa nem busca híbrida, o S3 Vectors economiza dinheiro e dor de cabeça operacional. Se precisa de latência abaixo de 200ms ou tem dezenas de milhares de documentos com consultas complexas em metadados, vá de OpenSearch.
Para os nossos cerca de 100 documentos de política? S3 Vectors foi escolha imediata. Pagamos centavos por dia em vez de no mínimo US$ 700/mês. O tradeoff de latência (uns 150ms a mais) some em um workflow que já inclui inferência de LLM.
Estratégias de chunking que realmente importam
Antes de compartilhar o que funcionou para nós, vale o panorama — porque a maioria dos guias mostra só uma ou duas opções:
| Estratégia | Como funciona | Ideal para | Atenção |
|---|---|---|---|
| Tamanho fixo (padrão) | Divide a cada N caracteres/tokens | Começo rápido, documentos genéricos | Quebra no meio da frase, no meio da regra — gera respostas alucinadas |
| Por sentença | Quebra nos limites das frases | Documentos simples, FAQs | Ignora seções lógicas; uma regra de política pode ocupar mais de 5 frases |
| Semântico / por seção | Quebra pela estrutura do documento (cabeçalhos, seções) | Documentos estruturados com hierarquia clara | Exige fazer parsing da estrutura; chunks variam de tamanho |
| Hierárquico (pai-filho) | Chunks pais (seção completa) + chunks filhos (parágrafos) | Melhor qualidade de recuperação — bate no filho, devolve o pai para o contexto | Mais complexo, mais armazenamento, indexação mais lenta |
| Assistido por LLM | O LLM reestrutura o documento ANTES do chunking | Documentos críticos em que recuperação errada = decisão errada | Adiciona custo + latência na ingestão; vale a pena quando precisão importa |
Nossa escolha: assistido por LLM. Para documentos de política em que uma recuperação errada significa uma decisão errada sobre despesa, o custo inicial da transformação via LLM se paga imediatamente. Mais sobre isso na seção do pipeline, mais abaixo.
Mas antes — deixa eu mostrar o cenário de falha que nos trouxe até aqui:
Foi nesse ponto que cometi meu erro mais caro lá no começo.
O que não funcionou: chunking padrão
O chunking padrão do Bedrock KB divide documentos por contagem de caracteres, com sobreposição. Para documentos genéricos, tudo bem. Para documentos de política, é catastrófico.
Veja por quê: uma regra de política pode dizer "Refeições acima de US$ 75 exigem aprovação do gerente, exceto em viagens com cliente, em que o limite é US$ 150." O chunking padrão pode quebrar isso no meio da frase. A recuperação então devolve "Refeições acima de US$ 75 exigem aprovação do gerente", sem a exceção. O agente nega um legítimo jantar de US$ 100 com cliente. E seus usuários perdem a confiança no sistema logo no primeiro dia.
O que funcionou: chunking por fronteiras semânticas
Pré-processamos os documentos antes da ingestão, dividindo por seção de política — cada regra ou sub-regra vira seu próprio chunk, com o contexto completo preservado.
import refrom dataclasses import dataclass
@dataclassclass 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 chunksOrientações práticas
- Tamanho ideal de chunk: 200–1500 caracteres para documentos de política. Chunks menores melhoram a precisão; chunks maiores preservam contexto. Encontre o equilíbrio.
- Sobreposição: se for usar chunking por caracteres, use no mínimo 20% de sobreposição. Mas, sério, divida por fronteiras semânticas.
- Metadados são recuperação: marque todo chunk com
language,policy_type,effective_dateedepartment. Você vai filtrar por eles depois — isso não é opcional.
O pipeline de ingestão
Documentos de política não são posts de blog. Você não pode simplesmente jogá-los num vector store e torcer pelo melhor. Uma interpretação errada de política tem consequências legais.
Este é o pipeline:
O padrão de transformação via LLM + human-in-the-loop
A grande sacada de design: transformar ANTES da aprovação. O fluxo:
Upload no S3 (PDF de política bruto) ↓Transformação via LLM (reestrutura, extrai regras de despesa, monta a visão desejada) ↓Chunks transformados armazenados no S3 (estado de transição — em cache para reuso) ↓API expõe chunks estruturados para revisão ↓Revisor aprova / rejeita (um clique) ↓Chunks aprovados são ingeridos no Bedrock Knowledge BaseIsso é um gate lógico de aprovação, não uma orquestração pesada. O LLM faz o trabalho duro lá no início — fazendo o parsing de PDFs de várias páginas, extraindo regras de despesa individuais e estruturando tudo de forma consistente. Quando o revisor vê a saída, ele está olhando para chunks limpos e estruturados — não para documentos brutos.
Por que isso importa em produção:
- A reingestão é rápida — a transformação fica em cache no S3. Atualizações de política não exigem reprocessamento do zero.
- Os revisores veem saída de qualidade — eles aprovam regras estruturadas, não paredes de texto de PDF.
- Menos erros no momento da recuperação — como o LLM pré-estrutura o conteúdo, o KB recebe chunks formatados de maneira consistente, sempre.
Por que human-in-the-loop importa
Já vi times pulando o gate de aprovação porque "confiamos no nosso time de políticas". Aí alguém sobe um documento em rascunho, ele é embedado e a IA começa a aplicar regras de rascunho. Um incidente desses e você perdeu a confiança organizacional no sistema.
Antipadrão: ingestão automática no upload do S3. Nunca faça isso com documentos sensíveis à conformidade.
Padrão: Upload → o LLM transforma e estrutura a política → chunks transformados ficam em cache no S3 → API expõe os chunks para revisão → humano aprova/rejeita → aí sim ingere no KB. Isso significa que a reingestão é rápida (a transformação já está feita e em cache), e os aprovadores veem saída limpa e estruturada — não PDFs brutos.
Latência e custo de recuperação
Números reais de produção (workload de 500 consultas/dia):
Latência (S3 Vectors)
p50 = tempo de resposta mediano (experiência típica). p99 = percentil 99 (pior caso, excluindo outliers extremos).
- p50: 340ms (só recuperação, sem incluir inferência do LLM)
- p99: 890ms
- Decisão fim a fim (incluindo AgentCore): p50 ~2,1s, p99 ~4,8s
Composição do custo mensal
| Componente | Custo mensal |
|---|---|
| S3 Vectors (armazenamento + consultas) | ~US$ 12 |
| Chamadas à API do Bedrock KB | ~US$ 8 |
| Titan Embeddings (ingestão) | ~US$ 3 |
| Nova Pro (validação de recibo) | ~US$ 45 |
| AgentCore (decisões de política) | ~US$ 120 |
| Step Functions | ~US$ 5 |
| Aurora MySQL (persistência) | ~US$ 65 |
| Total | ~US$ 258/mês |
Compare isso com o OpenSearch Serverless sozinho, que custa no mínimo US$ 700/mês. Decisões de arquitetura se acumulam.
Pegadinhas que ninguém te avisa
Depois de três meses em produção, esta é a minha lista:
Atraso de sincronização após o upload. Depois de chamar o
StartIngestionJob, o KB não fica consultável imediatamente com o novo conteúdo. Espere de 30 a 90 segundos para atualizações pequenas. Considere isso na UX — mostre estados de "atualização de política em processamento".A filtragem de metadados aceita só correspondência exata (S3 Vectors). Você não consegue fazer consultas por faixa nem correspondência parcial em metadados. Projete seu schema de metadados em torno de filtros de igualdade. Se você precisa de "todas as políticas atualizadas após janeiro de 2025", vai precisar de outra abordagem.
A escolha do modelo de embedding é permanente. Depois de criar um KB com Titan Embeddings v2, não dá para trocar para Cohere sem recriar o Knowledge Base inteiro. Escolha com cuidado lá no início. (Fomos de Titan v2 — bom equilíbrio entre custo e qualidade para conteúdo bilíngue.)
Recuperação multilíngue não é mágica. Uma consulta em espanhol recupera bem chunks em espanhol, mas a recuperação cross-language (consulta em espanhol → política em inglês) é instável. Resolvemos isso mantendo chunks paralelos nos dois idiomas e filtrando pelo idioma detectado da consulta.
Picos de custo de reindexação. Se você sincronizar o KB inteiro com frequência (em vez de atualizações incrementais), o custo de embeddings dispara. Uma reindexação completa de 100 documentos custa cerca de US$ 2. Faça isso de hora em hora por engano, e estará queimando US$ 1.400/mês só em embeddings.
As cotas do KB são surpreendentemente baixas. Jobs de ingestão concorrentes padrão: 1. Tamanho de documento padrão: 50MB. Solicite aumento de cota antes de chegar à escala de produção.
O problema da "resposta errada com confiança". Quando o agente recupera um chunk que está perto, mas não certo, ele aplica a regra errada com toda a convicção. Mitigue isso definindo um threshold de score de similaridade (usamos 0,7) e roteando recuperações de baixa confiança para revisão humana.
Como começar
Cinco passos para a sua primeira implementação de Bedrock KB em produção:
Comece com 10 documentos, não 100. Acerte sua estratégia de chunking num conjunto pequeno. Valide a qualidade da recuperação manualmente antes de escalar.
Escolha S3 Vectors, a não ser que tenha um motivo para não escolher. Para a maioria dos casos de uso de Knowledge Base com menos de 10 mil documentos, é mais barato e mais simples. Migre para o OpenSearch quando realmente precisar.
Invista em chunking antes de qualquer outra coisa. O chunking padrão é uma armadilha para documentos estruturados. Dedique uma semana à sua estratégia de chunking — é o trabalho de maior alavancagem que você vai fazer.
Monte o pipeline de aprovação desde o primeiro dia. Mesmo que você não precise de human-in-the-loop hoje, vai precisar quando o risco aumentar. O padrão de task token no Step Functions torna isso trivial de adicionar depois, mas caro de retrofitar.
Instrumente tudo. Logue scores de recuperação, IDs de chunk e nível de confiança da decisão. Você não pode melhorar o que não mede. Quando a qualidade da recuperação cair (e vai cair, conforme seu corpus crescer), você vai precisar de dados para diagnosticar o porquê.
Para fechar
O Bedrock Knowledge Base é, de fato, uma boa infraestrutura para sistemas RAG em produção. Mas a distância entre "demo" e "produção" é onde mora toda decisão de engenharia interessante — estratégias de chunking, pipelines de ingestão, otimização de custo, cadeias de evidência, modos de falha.
O S3 Vectors tornou este projeto economicamente viável em uma escala em que o OpenSearch seria exagero. O Step Functions deu as garantias de orquestração que a conformidade exige. E o AgentCore transformou recuperação em decisões estruturadas e auditáveis.
O stack funciona. A parte difícil nunca foram os serviços AWS — foi a engenharia de dados em volta deles.
Faça certo da primeira vez. Seu eu do futuro (e o time jurídico do seu cliente) vai agradecer.
Dima Kramskoy é Senior Cloud Architect na DoiT International, com mais de 20 anos em engenharia de software, 10 certificações AWS e é AWS Community Builder (2026). Ele ajuda organizações a construir sistemas de IA/ML em produção na AWS.