
Este artigo compartilha a implementação de engenharia de Q&A zero alucinação no nosso leitor com IA: as respostas se baseiam estritamente no texto do livro aberto e as afirmações-chave podem ser rastreadas em um clique até a passagem exata. Se você está desenvolvendo leitura com IA, Q&A de documentos ou apps no estilo RAG, esperamos que três iterações de lições e a arquitetura final sejam úteis.
I. Evolução em três estágios
O Q&A zero alucinação não foi projetado de forma perfeita no primeiro dia. Evoluiu sob tensão entre custo, latência e precisão. Abaixo está uma visão cronológica dos três estágios—contexto útil para entender por que a arquitetura atual tem esta forma.
Estágio 1: Inserir o livro inteiro no contexto (o mais simples—e o primeiro a falhar)
Abordagem: Quando o usuário abre um livro e faz uma pergunta, colocar todo o texto extraído no System Prompt ou na mensagem do usuário e deixar o modelo de conversa responder. Se o livro exceder cerca de 400 mil caracteres, aplicar truncamento rígido—só se mantém o início; os capítulos posteriores ficam invisíveis para o modelo.
Vantagens:
- Custo de implementação muito baixo; quase sem pré-processamento;
- Funciona razoavelmente em livros curtos e documentos simples—o modelo realmente «viu o livro inteiro»;
- UX simples: pergunta e resposta, sem estado «aguarde enquanto analisamos».
Desvantagens (rapidamente inaceitáveis):
- Respostas lentas: Cada pergunta reenvia um payload enorme; o tempo até o primeiro token e a latência total crescem com o tamanho do livro;
- Custo alto de tokens: Você paga o input do livro inteiro em cada pergunta;
- Livros longos distorcem muito: Após 400 mil caracteres, a segunda metade, apêndices e conclusões podem não existir para o modelo—e a UI muitas vezes não indica claramente que houve truncamento;
- Granularidade de recuperação zero: O modelo precisa «procurar uma agulha no palheiro» em centenas de milhares de caracteres—fácil perder detalhes e mais fácil produzir resumos plausíveis sem base—exatamente o que apps de leitura devem evitar.
O estágio 1 serve para um MVP, não para um produto de qualidade.
Estágio 2: LLM mais leve para extrair frases-chave (comprimir contexto—mas demais)
Abordagem: Antes do Q&A (ou na primeira abertura do livro), usar um modelo mais barato sobre o corpo: dividir por capítulo do Spine (ou segmentar o livro), extrair frases-chave, manter marcas de posição como [fArquivo-início-fim], depois concatenar trechos em um contexto mais curto para o Q&A posterior.
Pipeline típico: Extract → Cache → Chat. Extrair uma vez (offline ou sob demanda), guardar um «conjunto de frases-chave», reutilizar em cada pergunta—a mesma ideia de muitos protótipos de Q&A de documentos que comprimem primeiro e depois respondem.
Vantagens:
- Cada pergunta envia muito menos texto; o consumo de tokens por requisição cai em relação ao estágio 1;
- O pré-processamento pode ser em cache; sem re-extrair por pergunta no mesmo livro;
- As marcas de posição lançam bases para citações.
Desvantagens (ainda falha em livros longos):
- Perda massiva de detalhe: As «frases-chave» são selecionadas pelo modelo; qualificadores, contraexemplos e cadeias argumentativas são frequentemente descartados—as respostas ficam «corretas mas parciais»;
- Contexto ainda grande em livros longos: Mesmo bundles de frases-chave em obras grandes são consideráveis—latência e custo são aliviados, não resolvidos;
- Erro duplo de LLM: A extração pode falhar; o Q&A pode interpretar mal trechos—os erros se acumulam;
- Contexto estático: Quer o usuário pergunte sobre um capítulo ou sobre a estrutura do livro, o modelo sempre recebe o mesmo blob pré-extraído—sem estreitamento dinâmico pela pergunta.
Lição: o problema não é «se comprimimos», mas se a compressão é sob demanda e se podemos voltar ao texto fonte.
Estágio 3: Índice de segmentos + Tool retrieval sob demanda + texto fonte de volta (atual)
Abordagem: Inspirado em PageIndex. Em relação ao estágio 2, três mudanças centrais:
- O pré-processamento produz um índice estruturado (resumos no nível do índice + spans exatos de caracteres), não trechos usados diretamente como contexto de Q&A;
- Cada pergunta usa Tool Calling para recuperar sob demanda, depois puxa texto fonte com marcas de posição para responder;
- System Prompt + frontend impõem formato de citação e suportam saltos com clique e destaque no leitor.
Comparação dos três estágios:
| Dimensão | Estágio 1 (texto integral) | Estágio 2 (frases-chave) | Estágio 3 (atual) |
|---|---|---|---|
| Contexto por pergunta | Livro inteiro (ou metade frontal truncada) | Frases-chave pré-extraídas | Apenas trechos de texto fonte relevantes para a pergunta |
| Precisão em livros longos | Colapsa após ~400k chars | Depende da extração; perde detalhe | Recuperação por TOC/span; sem truncamento rígido do livro inteiro |
| Velocidade de resposta | Lenta | Um pouco melhor; livros longos ainda lentos | Recuperação + contexto curto—visivelmente mais rápido |
| Custo de tokens | Muito alto | Médio-alto | Pré-processamento amortizado + pagar sob necessidade |
| Rastreabilidade | Fraca (difícil citar) | Tags existem mas conteúdo já filtrado | Notas de rodapé mapeiam spans reais de fonte |
| Complexidade de engenharia | Baixa | Média | Alta |
Por que paramos no estágio 3: Para leitura, zero alucinação não é «mostrar ao modelo o máximo de texto possível», mas «antes de responder, obter evidência fonte para a pergunta». Os estágios 1–2 lutaram o tamanho do contexto; o estágio 3 divide o pipeline em índice (pré-processar) → recuperar (Tool) → evidência (fonte) → responder (geração restrita)—equilibrando precisão, custo e rastreabilidade.
A seguir o detalhe do estágio 3.
II. Definição do problema: Em Q&A de livros, alucinação dói mais que no chat genérico
Os usuários perdoam erros ocasionais em um chatbot geral. Em Q&A de livros, o custo é maior:
- Perguntam o que este livro diz—não o que vive na memória paramétrica do modelo;
- Uma «opinião do livro» plausível pode enganar notas, citações e compartilhamentos;
- Sem fontes, não podem verificar—a confiança é difícil de construir.
Assim, «zero alucinação» torna-se três regras executáveis:
- Perguntas sobre o livro devem consultar o livro primeiro: Tudo o que possa ser sobre o livro aberto deve passar por recuperação (Tool) antes da resposta;
- Respostas devem ser rastreáveis: Afirmações-chave com marcas de posição que a UI possa analisar e saltar;
- Dizer quando não encontrar: Se o livro não contém, dizer—não disfarçar conhecimento geral como «o que o livro diz».
O resto segue o fluxo de dados do estágio 3 e como essas regras se implementam.
III. Arquitetura: Pré-processar → Tool retrieval → Geração restrita → Citações clicáveis
Ideia central: não deixar o modelo «responder de memória»—obrigá-lo a «reunir evidência, responder e marcar fontes».
IV. Pré-processamento: Transformar o livro inteiro em um índice de segmentos pesquisável
Se cada pergunta ainda usasse contexto de livro integral do estágio 1, livros longos estouram o orçamento de tokens e a recuperação é grossa demais. Estágio 3: no primeiro chat de IA para um livro, rodar em segundo plano uma tarefa de resumo de segmentos—dividir por TOC ou comprimento de texto em Segments, resumir cada um, persistir em IndexedDB local.
Cada Segment contém resumo mais posição física no corpo:
| Campo | Significado |
|---|---|
startFileIndex / endFileIndex | Índice de arquivo Spine (PDF: um arquivo por página) |
startOffset / endOffset | Início/fim em caracteres |
sequence | Ordem linear de leitura |
title | Título do TOC |
A divisão equilibra precisão e custo: se o corpo de um nó TOC tiver menos de ~20KB, resumir só esse nó; nós irmãos podem fundir-se em lotes (15–20KB) antes de chamadas LLM; blocos longos sem estrutura dividem-se em intervalos de ~30–40 mil caracteres.
O System Prompt de resumo exige manter marcas de posição inline ([fNúmero-Número-Número]) para o texto fonte obtido por Tool alinhar com offsets do Spine. Restrição central:
Se o conteúdo do resumo se relacionar com uma passagem, manter a marca de posição no final [fNúmero-Número-Número] (ex. [f1-90-109]).
As marcas são atômicas—não alterar, fundir ou omitir qualquer caractere ou dígito.
Após o pré-processamento, o Q&A depende de um índice estruturado de segmentos, não do contexto do livro inteiro—pré-requisito de engenharia para zero alucinação em livros longos.
V. Sistema de marcas de posição: Codificar «onde» no texto
Zero alucinação exige conteúdo da fonte e proveniência analisável por máquina e saltável na UI. Usamos marcas inline:
[f{fileIndex}-{startChar}-{endChar}]
Exemplo: [f5-123-165] = arquivo Spine 5 (base 0), caracteres 123–165.
5.1 Como as marcas entram no corpo do texto
A camada de extração acrescenta [f{fileIndex}-{start}-{end}] no fim dos segmentos:
const position = `[f${fileIndex}-${absOffset}-${absOffset + segment.length}]`;
fileLines.push(segment.text.trim() + position);
Quer resumos de pré-processamento quer trechos de Tool, as posições alinham com offsets de caracteres do Spine—não números de página estimados pelo modelo.
5.2 Restrições na saída do modelo
O System Prompt inclui Position Citation Rules—cinco pontos centrais:
- Formato padrão: Deve usar
[f_fileIndex-startChar-endChar]; as três partes numéricas são obrigatórias; - Copiar só das fontes atuais: Notas de rodapé verbatim das mensagens system/user ou retornos Tool deste turno;
- Sem fabricação: Não calcular, editar ou inventar posições;
- Preferir omissão: Se não houver marca válida no contexto, responder normalmente—não emitir marcas de posição;
- Inline com afirmações: Marcas seguem a frase relevante; sem listas de citações no final.
A UI também filtra marcas inválidas de duas partes ocasionais (ex. [f1-293]) antes de renderizar.

VI. Tool Calling: Recuperar primeiro, responder depois
Quando o chat está ligado a um livro (resourceId presente, chatType === 'chat'), registramos duas Tools com executors antes de cada geração—ciclo function calling compatível com OpenAI.
6.1 get_related_segment_summaries — Busca segmentada direcionada
Para: conceitos, personagens, enredo, detalhes de capítulo—intenção clara de recuperação.
Fluxo:
- O modelo reescreve a formulação do usuário em termos prováveis no livro («Optimize Search Queries» no System Prompt);
- Chamar Tool com
question; - Agrupar todos os resumos de segmentos por orçamento de tokens (~30k tokens por lote, máx. 5 lotes);
- Cada lote: requisição LLM separada escolhe IDs de segmentos relevantes (máx. 5) de
{ id, title, summary }, JSON como{"Thinking":"...","answer":["1","3"]}; - Para segmentos selecionados, puxar texto fonte com marcas do Spine—não resumos—como resultado Tool.
Design-chave: Tool devolve fonte, não resumos. O modelo responde a partir de parágrafos reais com [f…] inline, evitando deriva «resumo → re-resumo».
6.2 get_full_book_segment_summaries — Visão geral do livro
Para: «resumir o livro», «avaliar este livro», «estrutura/temas globais»—visão global.
Concatenar todos os campos summary dos segmentos por ordem de leitura—evitar perder capítulos-chave só com relevância por bloco.
6.3 System Prompt: Livro primeiro, tools primeiro
Com livro ligado, aplica-se Core Principles for Reading Assistant:
1. Book First, Tool First
- Qualquer pergunta possivelmente sobre o livro deve chamar tools primeiro;
- Respostas devem basear-se sobretudo na recuperação—nunca inventar «conteúdo do livro» sem recuperação.
2. General Knowledge as Fallback Only
- Só para: conversa casual / usuário pede explicitamente ignorar o livro / tools sem resultado;
- Se o livro não tem, dizer «não mencionado neste livro» antes de conhecimento geral.
3. Direct Style
- Ir ao assunto—evitar «com base nos materiais fornecidos…» e frases vazias semelhantes.
A geração roda o ciclo de tools: tool_calls → executar → acrescentar role: tool → continuar até texto final. Com tools ativas, o canal thinking desliga-se para evitar conflitos de protocolo.
VII. Rastreabilidade no frontend: Da nota de rodapé ao destaque
A saída [f5-123-165] do modelo não se mostra em bruto; a camada de render transforma em citações clicáveis.
7.1 Renderização de notas
Normalizar marcas para links Markdown como [1]([f5-123-165]), renderizar como notas numeradas; deduplicar a mesma posição para evitar sobrecarga na UI.
7.2 Interação por clique
- Primeiro clique: Analisar
[f…]→ fileIndex + offsets → extrair texto do Spine → pré-visualização (título TOC opcional); - Mesma nota de novo: Fechar pré-visualização;
- Confirmar salto: Abrir vista de leitura, destacar intervalo de caracteres.
Da marca copiada do modelo ao texto visível pelo usuário, a cadeia nunca passa por outra chamada LLM—determinística e reproduzível.
VIII. Casos limite e degradação honesta
Zero alucinação ≠ «sempre há resposta»—significa sem evidência, sem fabricação:
| Cenário | Comportamento |
|---|---|
| Resumos de segmentos ainda não prontos | Extrair texto integral e resumir primeiro |
| Tool não encontra nada | Devolver (No relevant segment excerpts found…); o modelo deve dizer que não está no livro |
| Marcas inválidas de duas partes do modelo | Frontend filtra; sem notas quebradas |
| Conversa casual | System Prompt permite conhecimento geral fora do livro |
| Exportar conversa | Notas podem virar deep links do leitor para compartilhar/arquivar |

IX. Compromisso de design: Por que não «vector RAG»?
Colegas em Q&A de documentos perguntam com frequência: se fazem retrieval-augmented generation, por que não Embedding + vector DB Top-K?
Estamos fazendo RAG—recuperar antes de gerar. A diferença: «RAG» na comunidade implica muitas vezes similaridade vetorial; nosso estágio 3 é índice de segmentos + Tool com pull de fonte sob demanda—sem camada vetorial por design. Seguem razões arquiteturais, sem negar o valor do vector RAG.
Escopo: não «sem recuperação», mas «sem recuperação vetorial»
- RAG amplo: recuperar → gerar → fazemos isso;
- Vector RAG: recall via similaridade de embedding → não nesta versão.
O pré-processamento constrói um índice de resumos de segmentos; o modelo escolhe segmentos via Tools e obtém texto fonte. A recuperação existe sem modelo de embedding separado nem manutenção de índice vetorial.
Razão 1: Providers LLM personalizados—manter a superfície de integração pequena
Os usuários podem usar suas API keys, URLs base personalizadas ou Ollama local—o modelo de chat é escolha deles; custo e caminho de dados sob controle.
O vector RAG típico alarga a integração:
- Além do modelo de chat, costuma precisar de modelo de embedding (outro nome, às vezes outro endpoint);
- Ollama local precisa de modelo de embedding à parte e compatibilidade de dimensão/API;
- Mais modos de falha: chat OK mas recuperação vazia—embedding, índice ou dimensão inconsistentes; mais difícil depurar que um provider de ponta a ponta.
Aqui, seleção de segmentos e resposta compartilham a mesma configuração de provider—sem «chat em A, índice em B». Para apps com LLM plugável, isso muitas vezes supera alguns pontos de recall.

Razão 2: Embeddings ligam-se ao índice—trocar de provider é caro
Em vector RAG, vetores não são formato intermediário universal—são coordenadas sob um modelo de embedding. Índice com A, consulta com B: similaridade normalmente incomparável—muitas vezes re-embedding completo, e dimensões (768 / 1024 / 1536 …) fixam o schema de armazenamento.
O estágio 3 persiste resumos estruturados + spans de caracteres, não vetores; trocar modelo de chat não reconstrói o índice; a cadeia de evidência (posições fonte) se mantém—alinhado com «experimentar LLMs diferentes a qualquer momento».
Razão 3: Roteamento estruturado costuma bastar para documentos longos com TOC
E-books e PDF têm normalmente estrutura de capítulos; o pré-processamento produz títulos + resumos de segmentos. Para «o que diz o capítulo X» ou «como o livro define Y», escolher segmentos no catálogo e puxar fonte funciona bem na prática; a Tool devolve fonte com [f…], zero alucinação ancora-se em spans de caracteres.
Vetores ajudam em semântica difusa, multilíngue, desajuste literal em passagens longas; para leitores com TOC + pré-processamento + rastreabilidade forte, investir em Tool + devolução de fonte + regras de citação costuma ter melhor ROI.
Futuro: Recall híbrido, não reescrita
Podemos acrescentar recall grosso vetorial (embedding só para Top-N capítulos candidatos), terminando sempre em escolher segmento → fonte → rastreio clicável—regras zero alucinação inalteradas. Se adicionado: embedding opcional, avisos explícitos de re-indexar ao mudar modelos—evitar recuperação errada silenciosa.
Até lá: qualquer API de chat compatível com OpenAI funciona; trocar modelo de chat não reconstrói o índice local.
X. Resumo
| Etapa | Método | Papel |
|---|---|---|
| Pré-processar | Dividir por TOC/comprimento + cache de resumos | Livros longos pesquisáveis e localizáveis |
| Marcas de posição | [fArquivo-início-fim] na fonte | Proveniência analisável |
| Tool retrieval | Segmentos por pergunta / resumos do livro, devolver fonte | Forçar evidência antes de responder |
| System Prompt | Livro primeiro, sem marcas falsas, dizer quando falta | Restringir geração |
| Frontend | Nota → pré-visualização → saltar e destacar | Usuário verifica evidência |
| Sem recuperação vetorial | Provider único; trocar modelo de chat sem re-indexar | Menor custo de integração e migração |
«Zero alucinação» não significa que o modelo nunca erra—significa engenharia que prende a saída a uma cadeia de evidência: sem recuperação → não fingir conteúdo do livro; com recuperação → dar posições fonte verificáveis.
Se você desenvolve leitura com IA ou Q&A de documentos, esperamos que o caminho texto integral → frases-chave → Tool-first sob demanda, mais marcas inline + devolução de fonte, seja uma implementação de referência útil.
Estas são lições do desenvolvimento do leitor com IA Foxycape—apenas para referência. Experimente o leitor na página de download.
