O Eduardo cuida da busca sobre o manual interno de uma operadora de plano de saúde. Alguém cortou os documentos a cada quinhentos tokens, ignorando a estrutura. E um dos pedaços diz só isto.
O prazo é de 21 dias úteis a partir do pedido, e pode ser prorrogado uma vez, com justificativa. Nos casos do item 3.2.
Prazo de quê? Para quem? De qual versão do manual? A busca acha esse trecho para o prazo de autorização de cirurgia. Mas também para o prazo de reembolso, porque o trecho nunca diz de que assunto trata. E o modelo responde com confiança, e às vezes erra o assunto.
Nesta aula, você vai aprender a cortar documentos de um jeito que cada pedaço possa ser entendido, e encontrado, sozinho.
A ideia
Pense numa ficha de arquivo. Uma boa ficha não diz só o que tem dentro. Diz de qual pasta, de qual gaveta e de que ano ela veio. O padrão faz duas mudanças, as duas na hora de indexar.
Primeiro, cortar pela estrutura. Títulos, seções, cláusulas, itens de lista e tabelas são as fronteiras. Um pedaço nunca começa no meio da frase, e nunca separa uma tabela do seu cabeçalho. Ainda existe um tamanho máximo, mas ele é o limite, não a regra.
Segundo, anexar o contexto. Cada pedaço ganha um cabeçalho com o caminho, documento, capítulo e seção, a versão e, opcionalmente, uma frase, escrita por um modelo, que situa o trecho.
Esse cabeçalho entra no texto indexado, para a busca. E no texto que vai para o modelo, para a resposta e a citação.
Como funciona
O documento chega, em PDF, página web ou texto puro.
Um parser encontra os títulos, as seções, as listas e as tabelas.
O corte segue essa estrutura, dentro do tamanho máximo.
Um modelo pequeno lê o documento e o trecho, e escreve uma frase dizendo onde ele se encaixa.
Cada pedaço vira um cabeçalho, com caminho, versão e essa frase, mais o texto original.
E o pedaço completo é indexado, para busca por palavra e busca vetorial.
O mesmo pedaço
Agora o mesmo trecho, depois do padrão. Três coisas mudaram, e cada uma ensina algo.
O caminho. Autorizações, depois cirurgia eletiva, agora entra no embedding e na busca por palavra. Uma pergunta de reembolso não parece mais tão parecida.
A frase de contexto. Ela traduz o trecho vago para o vocabulário da pergunta. Autorização, cirurgia, plano.
E a cláusula vizinha. Se a seção cabe no limite, o item 3.2 vai junto. Se não cabe, o cabeçalho ainda aponta para a seção 4.3, e a citação não finge que o trecho está completo.
Quanto custa? Digamos oitocentos tokens de corpo e oitenta de cabeçalho. Cinco pedaços por pergunta vão de quatro mil tokens para quatro mil e quatrocentos. O custo de verdade fica na ingestão, uma chamada pequena por pedaço, com o documento inteiro em cache. Não na pergunta.
E pôr o caminho no começo ajuda duas vezes. A busca acha o assunto certo. E o modelo, que lê melhor o começo de um contexto do que o meio, vê primeiro de onde o trecho veio.
Um desafio
Uma pergunta para você. Você ligou a frase de contexto, mas gerou lendo só o pedaço, não o documento. No eval, a pergunta de reembolso ainda traz o trecho de autorização. Por quê?
Porque a frase é tão vaga quanto o pedaço. O modelo que escreveu também nunca viu o capítulo. Então ele escreve algo como, este trecho fala de um prazo.
A frase existe para injetar o contexto que o pedaço perdeu. Sem o documento nessa chamada, não tem contexto para injetar. Por isso a chamada leva o documento inteiro, em cache, e reaproveita em todos os pedaços.
Quando não usar
Documentos curtos que cabem num pedaço só. Uma política de trabalho remoto de duas páginas não precisa ser cortada. Título e versão no começo bastam. Cortar só inventa fronteiras, e pode partir a regra no meio.
Texto sem estrutura, como transcrição. Não tem título para seguir. Corte por assunto, ou por janelas com sobreposição.
E material que cabe inteiro no contexto. É o smell de duas aulas atrás. Não corte o que o modelo poderia ler.
Agora as armadilhas comuns. Cortar por caractere e chamar de estrutural. Se a fronteira ignora o título da tabela, você só mudou o tamanho.
Que foi exatamente o que aconteceu na operadora. As linhas com os números dos prazos caíram num pedaço. O cabeçalho, com as colunas, autorização, reembolso, recurso, ficou no anterior. Então o bot começou a citar 21 sem dizer qual prazo. Uma tabela vira um pedaço próprio, com título e colunas.
E deixar versões revogadas no índice. Um cabeçalho que diz versão seis não impede a busca de trazer a versão seis. Filtre por vigência na hora da consulta e remova o documento antigo ao reindexar. Isso se resolve no índice, não no prompt.
Em produção
O que medir. Recall e precisão da busca, antes e depois, no mesmo eval.
Monte perguntas gêmeas de propósito. O prazo de reembolso e o prazo de autorização. Se o trecho de reembolso fica fora dos cinco primeiros na pergunta de autorização, o cabeçalho está fazendo o seu trabalho.
Confira as citações. A seção citada na resposta tem que ser a certa.
E crie um alerta para a taxa de caminho vazio na ingestão. Se trinta por cento dos pedaços saem sem caminho, o parser quebrou num tipo novo de PDF. Não tape com a frase gerada. Corrija a conversão e reindexe.
Um ponto de segurança. Um pedaço pode carregar dado pessoal, o nome de um beneficiário, o número da carteirinha. Pela LGPD, o índice é mais um lugar onde esse dado mora. Se a sua política proíbe mandar esses documentos para o modelo que escreve a frase de contexto, fique no cabeçalho estrutural, montado só com título e seção.
O código, por intenção
Na página, tem duas abas. A de TypeScript faz só a parte estrutural. Ela corta o documento pelos títulos, guarda o caminho conforme desce e põe título, caminho e versão no topo de cada pedaço. Nenhuma chamada a modelo.
A de Python acrescenta a frase de contexto. Ela manda o documento inteiro como um bloco em cache e pergunta a um modelo pequeno, para cada pedaço, de que assunto e de que caso aquele trecho trata.
E essa é a ordem para adotar. Comece pelo cabeçalho estrutural. É barato, e já resolve boa parte do problema.
Ligue a frase gerada só se o eval de confusão de assunto ainda falhar.
Fechamento
Então, a ideia para levar. Um pedaço cortado às cegas é uma frase sem endereço. Corte pela estrutura do próprio documento e dê a cada pedaço o caminho, a versão e, quando ajuda, uma frase dizendo do que ele trata. Aí a busca e o modelo sabem onde estão.
Esse caminho no cabeçalho é feito de palavras exatas, e palavra exata é o que a busca por palavra-chave adora. Na próxima aula, a gente junta busca por palavra-chave com embeddings: hybrid search. Até lá.
17Cc
PadrãoContexto e conhecimento
Chunking com Contexto
Contextual Chunking
Chunking com Contexto (Contextual Chunking) é um padrão de projeto de IA para apps com LLM e agentes, do grupo Contexto e conhecimento. Cortar documentos respeitando a estrutura e anexar a cada pedaço o contexto de onde ele veio.
Dividir documentos seguindo sua estrutura (seções, cláusulas, tabelas) e anexar a cada pedaço o contexto de onde ele veio: documento, seção, data e uma frase que situa o trecho. Serve para RAGs em que os pedaços, sozinhos, perdem o sentido.
Em uma frase
Uma ficha de arquivo que diz não só o conteúdo, mas de qual pasta, gaveta e ano ela saiu.
Problema
Eduardo mantém a busca do manual interno de uma operadora de saúde. Os documentos foram cortados a cada 500 tokens, sem olhar a estrutura. Um dos pedaços diz apenas:
...o prazo é de 21 dias úteis, contados a partir da solicitação, e pode serprorrogado uma vez mediante justificativa. Nos casos previstos no item 3.2...
Prazo de quê? Para quem? De qual versão do manual? A busca acha esse trecho para a pergunta “qual o prazo de autorização de cirurgia eletiva?”, mas também para “qual o prazo de reembolso?”, porque o trecho não diz de que assunto trata. O modelo responde com confiança e às vezes erra o assunto.
O corte cego também separou uma tabela do seu título. As linhas com números de prazo ficaram num pedaço, o cabeçalho com as colunas (autorização, reembolso, recurso) ficou no pedaço anterior. O bot passou a citar “21” sem dizer de qual prazo.
Há ainda o item 3.2. Ele está em outro pedaço. Quem lê só o trecho acima não tem como saber o que o item autoriza. Na operadora, isso mistura regras da ANS com regras internas do plano.
A pergunta que este padrão responde: como dividir documentos de modo que cada pedaço seja compreensível e encontrável sozinho?
Solução
Duas mudanças no momento da indexação:
Dividir pela estrutura: usar títulos, seções, cláusulas, itens de lista e tabelas como fronteiras. Um pedaço nunca começa no meio de uma frase nem separa uma tabela do seu cabeçalho. Tamanho máximo existe, mas é o limite, não a regra.
Anexar contexto: cada pedaço ganha um cabeçalho com o caminho (documento, capítulo, seção), a data ou versão e, opcionalmente, uma frase gerada por um modelo que situa o trecho (“Este trecho trata do prazo de autorização de cirurgias eletivas para planos individuais”).
O cabeçalho entra no texto indexado (para a busca) e no texto enviado ao modelo (para a resposta e a citação).
A Anthropic descreveu essa ideia no texto Contextual Retrieval (setembro de 2024). Antes de indexar, um modelo lê o documento e o pedaço e escreve uma frase que situa o trecho. Essa frase entra no texto que vai para o embedding e para a busca por palavra-chave.
Exemplo trabalhado
Pegue o trecho do prazo e o documento de onde ele saiu. Depois do corte cego, o índice guarda só o miolo. Depois deste padrão, o mesmo miolo ganha um cabeçalho:
[Manual do Beneficiário | Autorizações | 4.1 Cirurgias eletivas | v7, vigência 2026-03]Este trecho trata do prazo de autorização de cirurgias eletivas em planos individuais.O prazo é de 21 dias úteis, contados a partir da solicitação, e pode serprorrogado uma vez mediante justificativa. Nos casos previstos no item 3.2(urgência e emergência, ver seção 4.3), o prazo não se aplica.
Três coisas mudaram, e cada uma ensina uma lição.
O caminho. “Autorizações > 4.1 Cirurgias eletivas” entra no embedding e no BM25. A pergunta sobre reembolso deixa de competir com a mesma similaridade.
A frase de contexto. Ela traduz o trecho vago para o vocabulário da pergunta (“autorização”, “cirurgia eletiva”, “plano individual”).
A cláusula vizinha. Se a seção 4.1 cabe no limite, o item 3.2 viaja junto. Se não cabe, o cabeçalho ainda aponta para 4.3, e a citação não finge que o trecho é completo.
A conta de tokens, com números ilustrativos. Suponha 800 tokens de miolo e 80 de cabeçalho. Em 2.000 pedaços, o índice engorda uns 160 mil tokens. Na consulta, cinco pedaços passam de 4.000 para 4.400 tokens. O custo extra está na ingestão (uma chamada pequena por pedaço, com o documento em cache), não na pergunta.
O paper “Lost in the Middle” (Liu et al., 2023) mostra que modelos usam melhor o começo e o fim do contexto do que o meio. Colocar o caminho no início do pedaço ajuda duas vezes: a busca acha o assunto certo, e o modelo lê primeiro de onde o trecho veio.
Estrutura
flowchart LR
D(["1. Documento"]):::io --> P["2. Analisar a estrutura"]:::tool
P --> S["3. Dividir por seção e cláusula"]:::tool
S --> C(["4. Gerar frase de contexto"]):::llm
C --> H["5. Montar cabeçalho e trecho"]:::tool
H --> I[("6. Índice")]:::store
O documento chega em PDF, HTML ou texto.
Um parser identifica títulos, seções, listas e tabelas.
A divisão segue essa estrutura, respeitando um tamanho máximo.
Um modelo pequeno lê o documento e o trecho e escreve uma frase que situa o trecho.
Cada pedaço vira cabeçalho (caminho, versão, frase de contexto) mais o texto original.
O pedaço completo é indexado para busca lexical e vetorial.
Pseudocódigo
schema ChunkContext { summary: string /* uma frase */ }function index_document(doc): tree = parse_structure(doc) # 2. títulos, seções, tabelas for section in tree.leaves(max_tokens=800): # 3. pela estrutura path = " > ".join(section.ancestors_titles()) ctx = llm(CONTEXT_PROMPT, doc.summary, section.text, model="small") -> ChunkContext # 4 header = f"[{doc.title} | {path} | versão {doc.version}] {ctx.summary}" index.add(id=section.id, text=header + "\n" + section.text, metadata={doc: doc.id, path: path, version: doc.version}) # 5 e 6
Cenários reais
Chat com a base de conhecimento interna. Políticas divididas por seção, com o caminho e a versão em cada trecho. A busca para “prazo de reembolso” não traz mais o prazo de autorização.
Assistente jurídico. Contratos divididos por cláusula e item, com o número da cláusula no cabeçalho, para citação exata. Em Mata v. Avianca (2023), advogados protocolaram decisões inventadas pelo ChatGPT. Um índice que guarda o número da cláusula não inventa a cláusula, mas só ajuda se o trecho certo for o que a busca devolve.
Manuais da Receita e da ANS. Normas brasileiras citam umas às outras (“nos termos do art. 12”). Sem o caminho no pedaço, o modelo trata a remissão como se fosse a regra.
Aplicabilidade
Documentos longos e estruturados (manuais, políticas, contratos, normas).
Trechos que, sozinhos, usam termos vagos (“o prazo”, “neste caso”).
Várias versões ou vários documentos parecidos no mesmo índice.
Respostas que precisam citar a seção exata.
Quando não usar
Documentos curtos, que cabem num pedaço só. Título e versão no começo bastam; cortar só cria fronteiras artificiais.
Conteúdo sem estrutura (transcrições de conversa). Divida por tema ou por janelas com sobreposição.
Olhe os documentos reais e identifique as marcas de estrutura (títulos numerados, cláusulas, tabelas).
Escreva o parser por tipo de documento; converta PDF para um formato com estrutura antes.
Defina o tamanho máximo e o que fazer com seções maiores (dividir por parágrafo, mantendo o título).
Monte o cabeçalho com caminho e versão.
Gere a frase de contexto com um modelo pequeno, com cache de prompt para o documento inteiro.
Reindexe e compare recall e precisão no eval de busca.
Use o caminho do cabeçalho nas citações da resposta.
Armadilhas comuns
Cortar por caracteres e chamar de estrutural. Se a fronteira ignora o título da tabela, você só mudou o tamanho.
Gerar a frase só com o pedaço. Sem o documento, o modelo escreve “este trecho fala de um prazo”, tão vago quanto o miolo. A chamada precisa ver o caminho e um resumo do documento.
Deixar versões revogadas no índice. O cabeçalho com “v6” não impede a busca de devolver o v6. Filtre por vigência na consulta e remova o documento antigo na reindexação.
Confiar na frase e relaxar o parser. Uma frase errada envenena o embedding. Amostra de pedaços na ingestão pega isso antes do usuário.
Variações
Só cabeçalho estrutural. Caminho e versão, sem frase gerada. Barato e já resolve boa parte.
Frase de contexto gerada. Melhora a busca para trechos vagos; custa uma chamada pequena por pedaço na indexação.
Pedaços em níveis. Pedaços pequenos para a busca, e a seção inteira (pai) enviada ao modelo quando o pequeno é encontrado.
Tabelas como pedaços próprios. Cada tabela vira um pedaço, com título e legenda, em formato de texto.
Comece pelo cabeçalho estrutural. Só ligue a frase gerada se o eval de confusão de assunto continuar falhando.
Custo, latência e falhas
Aspecto
O que acontece
Como aparece
Custo
Na indexação: uma chamada pequena por pedaço, com cache do documento
custo de ingestão por documento
Latência
Nenhuma na consulta; o trabalho é feito ao indexar
tempo de ingestão
Tamanho
Cabeçalhos aumentam um pouco cada pedaço
tokens por pedaço
Falha: parser errado
Estrutura mal lida gera pedaços sem sentido
pedaços com caminho vazio ou trocado
Falha: frase de contexto errada
Situa o trecho no assunto errado
resultados de busca fora do tema
Falha: versão antiga
Documentos revogados continuam indexados
respostas citando versão anterior
Como avaliar
Recall e precisão da busca antes e depois, no mesmo eval.
Confusão de assunto: perguntas parecidas sobre temas diferentes (“prazo de reembolso” e “prazo de autorização”).
Citações: a seção citada na resposta é a certa.
Fronteira de tabela e cláusula: casos em que o corte cego partia um bloco.
{ "question": "Qual o prazo de autorização de cirurgia eletiva?", "relevant": ["manual-v7#autorizacoes-4.1"], "not_relevant": ["manual-v7#reembolso-2.3"] }
Monte o par de perguntas gêmeas de propósito. Se o trecho relevante do reembolso não está no top 5 da pergunta de autorização, o cabeçalho está funcionando.
Em produção
Registro do caminho e da versão dos trechos usados em cada resposta.
Rotina de reindexação quando um documento muda, removendo versões antigas.
Amostra de pedaços para conferir parser e frases de contexto.
Alerta para taxa de caminho vazio na ingestão: parser quebrado depois de um novo tipo de PDF.
Segurança
O pedaço indexado pode carregar dados pessoais (nome de beneficiário, número de carteirinha). Na versão brasileira, isso cai na LGPD: o índice é mais um lugar onde esses dados moram. Não mande documentos com dados pessoais para o modelo da frase de contexto se a política da empresa não permitir. Nesses casos, fique no cabeçalho estrutural, gerado só com título e seção.
Prós e contras
Prós
Contras
Cada pedaço é compreensível sozinho
Parser por tipo de documento
Busca acerta o assunto certo
Custo de indexação com a frase gerada
Citação com seção exata
Pedaços um pouco maiores
Versões distinguíveis no índice
Exige reindexar quando a estrutura muda
Relações
Padrão de destino:RAG. Chunking com contexto é a etapa de ingestão que faz a busca devolver o trecho certo.
Vizinhos:Busca Híbrida junta palavra-chave e embedding, o que aproveita bem o caminho no cabeçalho. Embeddings explica por que um trecho vago vira um vetor vago.
As duas abas dividem um documento pelos títulos e montam o cabeçalho com o caminho; a aba Python também gera a frase de contexto, com o documento inteiro em cache.
Python. Use quando os documentos estão em Markdown ou foram convertidos para um formato com títulos.
import reimport anthropicclient = anthropic.Anthropic()def split_by_headings(markdown: str, max_chars: int = 3000) -> list[dict]: # 2 e 3 chunks, path = [], [] for block in re.split(r"\n(?=#{1,4} )", markdown): m = re.match(r"(#{1,4}) (.+)", block) if m: level = len(m.group(1)) path = path[: level - 1] + [m.group(2).strip()] for i in range(0, len(block), max_chars): # seção grande: divide mantendo o caminho chunks.append({"path": " > ".join(path), "text": block[i : i + max_chars]}) return chunksdef situate(doc_text: str, chunk_text: str) -> str: # 4 reply = client.messages.create( model="claude-haiku-4-5", max_tokens=100, system=[{"type": "text", "text": f"<documento>\n{doc_text}\n</documento>", "cache_control": {"type": "ephemeral"}}], # documento cacheado entre os pedaços messages=[{"role": "user", "content": "Em uma frase, diga de que assunto e caso trata este trecho, " f"para ajudar a encontrá-lo numa busca.\n<trecho>{chunk_text}</trecho>"}], ) return reply.content[0].text.strip()def index_document(doc: dict) -> list[dict]: out = [] for c in split_by_headings(doc["markdown"]): header = f"[{doc['title']} | {c['path']} | versão {doc['version']}] {situate(doc['markdown'], c['text'])}" out.append({"id": f"{doc['id']}#{c['path']}", "text": f"{header}\n{c['text']}", "path": c["path"]}) # 5 return out
O que você ganhou: pedaços com caminho, versão e frase de contexto, e o documento lido do cache a cada pedaço. O que você perdeu: o parser por títulos depende de o documento ter títulos; PDFs sem marcação precisam de conversão melhor antes.
TypeScript. Use quando você quer começar só com o cabeçalho estrutural, sem frase gerada.
type Chunk = { id: string; path: string; text: string };export function chunkByHeadings(doc: { id: string; title: string; version: string; markdown: string }, maxChars = 3000): Chunk[] { const chunks: Chunk[] = []; let path: string[] = []; for (const block of doc.markdown.split(/\n(?=#{1,4} )/)) { const m = block.match(/^(#{1,4}) (.+)/); if (m) path = [...path.slice(0, m[1].length - 1), m[2].trim()]; const p = path.join(" > "); const header = `[${doc.title} | ${p} | versão ${doc.version}]`; // 5. cabeçalho estrutural for (let i = 0; i < block.length; i += maxChars) { chunks.push({ id: `${doc.id}#${p}#${i}`, path: p, text: `${header}\n${block.slice(i, i + maxChars)}` }); } } return chunks;}
O que você ganhou: um passo de indexação sem custo de modelo, que já resolve a maior parte da perda de contexto. O que você perdeu: trechos muito vagos continuam difíceis de achar; acrescente a frase gerada se o eval mostrar falhas.
Teste seu entendimento
Checagem da aula
Responda as 6 perguntas para concluir a aula. Só assistir não conclui: a checagem é o que conta para a trilha e para o certificado.
Perguntas rápidas
O que é Chunking com Contexto?
Chunking com Contexto (Contextual Chunking) é um padrão de projeto de IA para apps com LLM e agentes, do grupo Contexto e conhecimento. Cortar documentos respeitando a estrutura e anexar a cada pedaço o contexto de onde ele veio.