Lede. "Qual é o melhor banco vetorial?" é a pergunta errada, e ela não tem resposta. A pergunta certa é "que tipo de pergunta o usuário faz sobre esta base". Este é o artigo que fecha a série: ele reúne as decisões dos treze em uma função executável,
recomendar(), que recebe um perfil da sua base e devolve a arquitetura com a justificativa escrita. No fim, a ordem de implantação em quatro fases e o que medir no fim de cada uma. Onde você está na linha. Este é o passo 12 de 13 — o fechamento. Ele pressupõe que você leu a linha, ou pelo menos o 01, que dá o mapa e os quatro contratos. Se você chegou aqui sem ler nada: a funçãorecomendar()funciona sozinha, e o artigo 13 fecha a série. Se você quer entender por que a função recomenda o que recomenda, ela cita o artigo de cada decisão.
1. A pergunta errada, e por que ela não tem resposta
Existe um post inteiro na internet sobre "o melhor banco vetorial em 2026", e ele
refaz a mesma lista todo ano: Qdrant, pgvector, Weaviate, Milvus, Chroma, um
índice aproximado, uma métrica de similaridade. A lista muda pouco, porque a
pergunta é quase sem conteúdo.
Um banco vetorial é uma peça. A peça não decide nada sozinha: ela responde "quais
trechos se parecem com esta pergunta", e a qualidade da resposta depende de como
os trechos foram cortados, do modelo que gerou os vetores, do filtro aplicado e do
que foi para a conversa. Trocar de banco sem mexer em nada disso entrega o mesmo
resultado com mais trabalho. O que muda o resultado é o formato da pergunta —
e é isso que a tabela da seção 3 lê.
Formato, aqui, é uma de cinco coisas:
-
Local — a resposta está em um trecho: "qual o prazo de garantia".
-
Por termo — a resposta está em um trecho, mas a pergunta contém uma
string exata que o modelo não vai acertar por significado: "o que diz o
contrato E-4021", "qual o código do produto 88.310". Termo técnico:
needle in a haystack é o caso extremo, em que a única pista é o termo.
-
Por relação — a resposta só existe se você seguir um caminho: "quem
assinou o contrato que substituiu este". Nenhum trecho isolado responde isso.
É o formato que o GraphRAG da Microsoft
atende, e é o único em que o grafo ganha.
-
Global — a pergunta é sobre o acervo inteiro, não sobre um documento:
"quais são os três temas dominantes desta base". Não há resposta pronta no
acervo; ela precisa ser sintetizada antes da consulta, e essa síntese é o
resumo de comunidade.
-
Multi-passo — a resposta exige mais de uma ação: buscar, filtrar por um
resultado, buscar de novo, e só então responder.
Cada formato tem uma arquitetura que resolve, e as doze perguntas da seção 2 dizem
se você tem algum deles. Nem toda base tem os cinco. A maior parte tem um, e é o
único que importa.
⚠️ Arquitetura escolhida antes do perfil é o caminho mais curto para
um sistema que não medeO sintoma é o time passar um trimestre construindo grafo de conhecimento para
uma base em que ninguém pergunta por relação. As peças de grafo não estão
erradas — estão respondendo a uma pergunta que ninguém fez. E como nada falha,
o sistema parece pronto. É por isso que a função desta seção é pura e testável:
ela devolve a decisão com a premissa que a justificou, e você pode
discordar dela em um teste.
2. As doze perguntas sobre a sua base
As doze perguntas não são uma entrevista de vendas: são os eixos que mudam a
arquitetura. Responda com um número ou um sim/não, não com adjetivo. "Base
grande" não é resposta; "quatrocentos mil trechos" é.
1. Que formato de pergunta domina? (seção 1). É a pergunta que mais pesa: os
outros onze apenas ajustam.
2. Quantos documentos, e quantos trechos depois do corte? A distinção importa
porque o banco indexa trecho, não documento. Mil documentos com corte de 200
caracteres já são vinte mil vetores — o volume que decide se busca exata ainda
vence, o assunto do artigo 03.
3. Com que frequência a base muda? Se o conteúdo muda uma vez por trimestre,
reindexar do zero é uma wget. Se muda toda hora, reindexar do zero é uma
tragédia, e você precisa de fila e de idempotência (reindexar não pode duplicar
nada) — assunto do artigo 13.
4. A permissão é por usuário, por time, ou não existe? Quando existe, ela
precisa estar dentro da consulta, e não depois. Um tenant é o nome do
cliente, da empresa ou da unidade: a fronteira que separa o que uma pessoa pode
ler do que ela não pode. Errar aqui não é resposta ruim, é resposta que não
deveria existir, e o artigo 01 mostra as duas
versões do código lado a lado.
5. Qual a latência que a tela aguenta? Metade de segundo e um segundo não
são a mesma promessa. Esse número decide se cabe um segundo modelo reordenando os
candidatos — o reranker,
a peça mais cara da recuperação em tempo, e a mais cara em chamada de modelo — e
se cabe chamada em segunda etapa.
6. Qual o orçamento mensal? Ele decide quantas chamadas de modelo por
requisição você aguenta. Cache e degradação são as duas alavancas, e ambas têm
custo de implementação.
7. Quantas pessoas vão manter isso? Uma pessoa e um time de cinco tomam
arquiteturas diferentes. Uma pessoa não mantém grafo de conhecimento, porque o
custo é manutenção, e não o dia em que ele ficou pronto.
8. A governança exige o quê? Dado pessoal no corpus, retenção, direito de
apagamento, auditoria de quem pediu o quê. Isso não muda a arquitetura: muda o
que entra no registro de observabilidade e o que sai da resposta, e é barato
resolver antes e caro resolver depois.
9. Quantos idiomas? Uma base em dois idiomas muda a normalização, muda o
indexador léxico e muda o modelo de vetor. Uma base em cinco muda a estratégia
inteira.
10. A resposta precisa ser auditável? Se alguém vai contestar o que o sistema
afirmou, a citação é obrigatória e o registro por requisição não é opcional — o
artigo 11 é a base disso.
11. Existe SLA? Um SLA é o combinado por escrito sobre disponibilidade e
tempo de resposta. A pergunta que importa não é "tem SLA?", é "o que eu
respondo quando o modelo cai e alguém cobra o SLA?". Se a resposta é "não sei",
você ainda não tem degradação, e degradação é feature (artigo 13).
12. Precisa de resposta sem modelo? Se sim — busca, e-mail automático, o
primeiro retorno de um atendimento —, você precisa do caminho que não chama
modelo nenhum. Esse caminho é mais barato, mais rápido e mais burro, e é ele
que segura o sistema quando o provedor está fora.
A melhor medida do formato de pergunta é uma lista de dez perguntas que as pessoas
realmente já fizeram. Formato ambíguo em documento pequeno quase sempre é, na
verdade, um formato local com vocabulário ruim. Formato ambíguo em base grande é
quase sempre relação — e ninguém escreve "por relação" num formulário.
3. A tabela de decisão
Agora que as doze têm resposta, a decisão cabe numa tabela. As colunas são as
quatro arquiteturas que esta série cobre; a tabela diz, para cada pergunta, o que
pesa a favor e o que pesa contra.
| Sinal na sua base | Vetorial | Híbrido (vetorial + léxico) | Grafo | Agente |
|---|---|---|---|---|
| Pergunta local ("qual o prazo") | resolve, com menos peças | resolve, com folga | não ajuda | é exagero |
Pergunta com termo exato (E-4021) | erra: vetor não distingue "4021" de "4022" | resolve: BM25 acha a string | não ajuda | não resolve sozinho |
| Pergunta por relação | não responde | não responde | resolve, é o motivo de existir | resolve, e de mais jeito |
| Pergunta global ("quais são os temas") | não responde | não responde | resolve com resumo de comunidade | resolve com mais custo |
| Multi-passo (filtrar e buscar de novo) | não | não | não | resolve, é o motivo de existir |
| Volume alto (> 100 mil trechos) | funciona, mas exige índice aproximado | melhora com fusão por ranking (RRF) | escala pior: mais upkeep | custo por passo |
| Base muda muito | reindex caro | mesmo custo | custo de construção do grafo | idem |
| Permissão por usuário | filtro no motor, obrigatório | mesmo filtro, dos dois lados | mesmo filtro, nos nós | mesmo filtro, em toda ferramenta |
| Latência apertada (1 s ou menos) | melhor caso | soma o custo da segunda busca | travessia multi-hop custa | pior caso |
| Orçamento apertado | melhor caso | custo quase igual | custo de construção alto | pior caso |
| Time pequeno | cabe no bolso de uma pessoa | cabe | não cabe | não cabe |
| Auditoria obrigatória | exige citação e registro | idem | idem | exige registrar cada passo |
| SLA com resposta garantida | precisa de degradação | idem | idem | a degradação é mais cara: cada passo pode falhar |
Três leituras que a tabela dá de graça. A primeira: híbrido é o ponto de partida de quase tudo — ele nunca é o pior caso em nenhuma linha, e é o que resolve a
pergunta por termo exato, que é a falha mais visível e mais comum da busca por
vetor. A segunda: grafo e agente não são o próximo passo do vetorial. São
respostas a perguntas específicas, e a coluna deles está cheia de "não ajuda" —
isso é o sinal de que você não tem aquela pergunta. A terceira: as duas últimas colunas da tabela (auditoria e SLA) não escolhem arquitetura. Elas escolhem
obrigação, e as duas Cortam architecture só depois de ela existir.
⚠️ A linha da latência é a que trava a maior parte dos projetos
Não é o modelo que é lento: é o número de chamadas em série. Uma resposta quebusca, depois reordena, depois monta, depois gera, tem o custo somado. Baixar
o
top-ké a alavanca mais barata que existe, e é a que oLost in the Middle dá apoio: o conteúdo que
sobrevive a um corte cego no meio do contexto tende a ser justamente o que não
responde à pergunta. Mais contexto não é mais resposta.
4. A função recomendar(): o artigo sendo executável
Uma tabela que você lê é uma opinião. Uma função é um contrato: ela recebe
fatos, devolve decisão e justificativa, e dá para testar. As regras abaixo estão em
uma lista, na ordem em que são avaliadas, e a primeira que bate decide. Isso
importa: sem ordem explícita, um dia alguém insere uma regra no meio e o
resultado muda para quem não mexeu.
A função é pura. Ela não lê arquivo, não chama modelo, não consulta banco. Dada a
mesma entrada, devolve a mesma saída — e é por isso que dá para escrever o teste
antes do sistema existir.
from __future__ import annotations from dataclasses import dataclass, field from enum import StrEnum # As premissas que a função assume. Elas não são verdade: são as hipóteses que # você confirma (ou corrige) lendo a justificativa que ela devolve. Se uma # premissa é falsa para a sua base, a regra que depende dela está errada, e a # correção é na regra -- não em um número mágico dentro da função. PREMISSAS: tuple[str, ...] = ( "O corte do trecho já está resolvido; arquitetura não conserta corte ruim.", "Uma resposta com citação é exigível em qualquer arquitetura.", "Permissão por tenant é filtro dentro da consulta, nunca depois dela.", "Volume é número de trechos indexados, não de documentos.", "Uma pessoa sozinha não mantém grafo de conhecimento.", ) # Um segundo já é aperto para uma tela de consulta: a pessoa já está esperando. # Abaixo disso, peça que soma latência (segunda busca, reordenação, segunda # chamada de modelo) precisa sair do caminho síncrono ou virar degradação. LATENCIA_APERTADA_MS = 1000 class Arquitetura(StrEnum): """As quatro arquiteturas que esta série cobre.""" VETORIAL = "vetorial" HIBRIDO = "hibrido" GRAFO = "grafo" AGENTE = "agente" @dataclass(frozen=True) class PerfilBase: """As doze perguntas, em um objeto só. Os nomes são autoexplicativos de propósito: este dataclass é a interface entre a conversa com o time e a decisão automática. Se um campo não pode ser preenchido com um número ou um sim/não, ele não deveria existir. """ formato_dominante: str # "local" | "termo" | "relacao" | "global" | "multi_passo" documentos: int atualizacoes_por_mes: int multi_tenant: bool latencia_maxima_ms: int orcamento_mensal_usd: float tamanho_do_time: int exige_auditoria: bool idiomas: int sla: bool precisa_sem_modelo: bool @dataclass(frozen=True) class Recomendacao: """A decisão, e tudo que vem junto dela para poder ser contestada.""" arquitetura: Arquitetura extras: tuple[str, ...] = () # o que soma por cima da arquitetura justificativa: str = "" medir: tuple[str, ...] = () # o que medir antes de mudar qualquer coisa risco: str = "" # o conflito conhecido desta combinação bloqueadores: tuple[str, ...] = () # o que fazer ANTES da arquitetura # Cada regra é (condição, arquitetura, motivo). A ordem é a ordem de decisão. REGRAS: tuple[tuple, ...] = ( ( lambda p: p.formato_dominante == "relacao", Arquitetura.GRAFO, "A pergunta só se responde seguindo um caminho entre entidades, e " "nenhum trecho isolado contém o caminho.", ), ( lambda p: p.formato_dominante == "global", Arquitetura.GRAFO, "A pergunta é sobre o acervo inteiro, então a resposta precisa ser " "sintetizada antes da consulta (resumo de comunidade).", ), ( lambda p: p.formato_dominante == "multi_passo", Arquitetura.AGENTE, "A resposta exige mais de uma ação encadeada, com o resultado de uma " "virando entrada da próxima.", ), ( lambda p: p.formato_dominante == "termo", Arquitetura.HIBRIDO, "A pergunta carrega uma string exata que similaridade de significado " "não distingue; a busca léxica acha a string, e a fusão por ranking " "junta as duas listas.", ), ( lambda p: p.tamanho_do_time <= 1, Arquitetura.VETORIAL, "Uma pessoa não mantém grafo nem agente. Comece pelo que cabe.", ), ( lambda p: p.latencia_maxima_ms <= LATENCIA_APERTADA_MS or p.orcamento_mensal_usd < 50, Arquitetura.VETORIAL, "Latência apertada ou orçamento apertado pedem o caminho mais curto " "entre a pergunta e a resposta.", ), ) # O que se adiciona à arquitetura escolhida. Aqui não há ordem: cada item é # uma condição independente, e a ordem de construção é da seção 7. EXTRAS: tuple[tuple, ...] = ( ( lambda p: p.documentos >= 100_000, "reranker: com base grande, a ordenação inicial erra mais vezes", ), ( lambda p: p.atualizacoes_por_mes >= 20, "fila de indexacao: reindexar do zero toda vez nao fecha", ), ( lambda p: p.multi_tenant, "escopo obrigatorio na assinatura da funcao de busca", ), ( lambda p: p.idiomas > 1, "indexador lexico por idioma e normalizacao no corte", ), ( lambda p: p.sla, "degradacao declarada: o que a resposta faz sem modelo", ), ) # O que precisa estar pronto antes da arquitetura escolhida funcionar. BLOQUEADORES: tuple[tuple, ...] = ( (lambda p: p.multi_tenant, "filtro por tenant dentro da consulta, testado"), (lambda p: p.exige_auditoria, "citacao obrigatoria no prompt"), (lambda p: p.sla, "caminho de resposta sem modelo, medido"), (lambda p: p.latencia_maxima_ms <= LATENCIA_APERTADA_MS, "orcamento de caracteres por bloco"), ) # O que medir para saber se a escolha foi boa. Todos os nomes sao do artigo 11. MEDIR: dict[Arquitetura, tuple[str, ...]] = { Arquitetura.VETORIAL: ("cobertura@10", "taxa de recusa", "recusa indevida"), Arquitetura.HIBRIDO: ("cobertura@10", "mrr", "fracao de acerto que veio do lexico"), Arquitetura.GRAFO: ("cobertura@10", "mrr", "profundidade media do caminho percorrido"), Arquitetura.AGENTE: ("passos por requisicao", "taxa de erro por passo", "custo por requisicao"), } def recomendar(perfil: PerfilBase) -> Recomendacao: """Transforma um perfil em arquitetura, com a justificativa e o risco. Função pura: mesma entrada, mesma saída. Se o resultado não faz sentido, o ajuste é em `REGRAS` ou em `PREMISSAS` -- nunca em um número dentro do corpo, porque aí a regra deixa de explicar o próprio resultado. """ arquitetura, motivo = Arquitetura.VETORIAL, "nenhuma regra especial; padrão da série" for condicao, candidata, porque in REGRAS: if condicao(perfil): arquitetura, motivo = candidata, porque break extras = tuple(porque for condicao, porque in EXTRAS if condicao(perfil)) bloqueadores = tuple(porque for condicao, porque in BLOQUEADORES if condicao(perfil)) risco = _risco(perfil, arquitetura) return Recomendacao( arquitetura=arquitetura, extras=extras, justificativa=motivo, medir=MEDIR[arquitetura], risco=risco, bloqueadores=bloqueadores, ) def _risco(perfil: PerfilBase, arquitetura: Arquitetura) -> str: """O conflito conhecido desta combinação. Um por perfil, o mais caro. Conflito é quando duas coisas que você pediu se atrapalham. A tabela da seção 3 mostra as colunas; ela não mostra que a coluna do híbrido e a da latência apertada não podem ser compradas juntas sem custo. """ if ( perfil.latencia_maxima_ms <= LATENCIA_APERTADA_MS and perfil.documentos >= 100_000 ): return ("latencia apertada com base grande: o reranker sai do caminho " "sincrono, ou a resposta passa a degradar") if arquitetura in (Arquitetura.GRAFO, Arquitetura.AGENTE) and perfil.tamanho_do_time <= 1: return "arquitetura que uma pessoa so nao mantem: o custo e manutencao, nao construcao" if arquitetura is Arquitetura.AGENTE and perfil.sla: return "cada passo do agente e um ponto de falha: o SLA precisa de corte, nao de sorte" if perfil.orcamento_mensal_usd < 50 and perfil.documentos >= 100_000: return "base grande com orcamento apertado: o custo esta na geracao, nao na busca" return "nenhum conflito conhecido: valide medindo"
A função tem uma propriedade que vale mais do que as regras: ela devolve o risco junto com a decisão. Uma ferramenta que só diz "use híbrido" e engole o
conflito é metade da ferramenta. A sua diz "use híbrido, e a sua latência apertada
com base grande vai empurrar o reranker para fora da resposta".
A segunda propriedade é o campo bloqueadores: é o que separa "arquitetura
certa" de "arquitetura possível". Uma base multi-tenant com auditoria obrigatória
não tem um problema de arquitetura — tem dois bloqueios que precisam estar
resolvidos antes, e ambos custam menos que a arquitetura.
⚠️
recomendar()é heurística com premissas declaradas, e ela não
conhece a sua baseAs cinco premissas em
PREMISSASsão as únicas coisa que a função sabe. Se asua base tem corte ruim, a função vai recomendar uma arquitetura excelente
para um sistema que não acha nada. É por isso que o último campo de todo
resultado é sempre "meça": a função é o começo da conversa com a base, e a
última palavra é sempre do artigo 11.
5. Quatro bases, quatro rotas
A função só prova alguma coisa se a entrada variar. Estes quatro perfis caem em
rotas diferentes de propósito: o quarto é o caso em que a função recomenda contra o que a maioria quer, e é o mais instructive de ler.
# Perfil 1: manual interno de uma empresa, uma pessoa mantendo, 900 documentos. MANUAL_INTERNO = PerfilBase( formato_dominante="local", documentos=900, atualizacoes_por_mes=2, multi_tenant=False, latencia_maxima_ms=1500, orcamento_mensal_usd=30, tamanho_do_time=1, exige_auditoria=False, idiomas=1, sla=False, precisa_sem_modelo=False, ) # Perfil 2: catálogo com código de produto e número de contrato, cinco pessoas, # permissão por cliente, tela de busca com 800 ms de orçamento. CATALOGO_COM_CODIGO = PerfilBase( formato_dominante="termo", documentos=180_000, atualizacoes_por_mes=60, multi_tenant=True, latencia_maxima_ms=800, orcamento_mensal_usd=400, tamanho_do_time=5, exige_auditoria=True, idiomas=1, sla=True, precisa_sem_modelo=True, ) # Perfil 3: normas e contratos que se referenciam, com pergunta por relação. NORMAS_COM_REFERENCIA = PerfilBase( formato_dominante="relacao", documentos=6_000, atualizacoes_por_mes=30, multi_tenant=False, latencia_maxima_ms=3000, orcamento_mensal_usd=200, tamanho_do_time=2, exige_auditoria=True, idiomas=1, sla=False, precisa_sem_modelo=False, ) # Perfil 4: suporte interno, onde a resposta exige buscar, filtrar por um # resultado e buscar de novo -- e o orçamento é de 40 dólares por mês. SUPORTE_MULTI_PASSO = PerfilBase( formato_dominante="multi_passo", documentos=15_000, atualizacoes_por_mes=90, multi_tenant=True, latencia_maxima_ms=12_000, orcamento_mensal_usd=40, tamanho_do_time=3, exige_auditoria=False, idiomas=2, sla=True, precisa_sem_modelo=True, ) for nome, perfil in [ ("manual interno", MANUAL_INTERNO), ("catalogo com codigo", CATALOGO_COM_CODIGO), ("normas com referencia", NORMAS_COM_REFERENCIA), ("suporte multi-passo", SUPORTE_MULTI_PASSO), ]: r = recomendar(perfil) print(f"\n== {nome}: {r.arquitetura}") print(f" porque: {r.justificativa}") print(f" extras : {', '.join(r.extras) or 'nenhum'}") print(f" risco : {r.risco}") print(f" medir : {', '.join(r.medir)}") print(f" antes : {', '.join(r.bloqueadores) or 'nada bloqueante'}")
Quatro saídas, e vale ler cada uma procurando o conflito:
-
manual interno sai como vetorial, por causa do tamanho do time. É a rota
que uma pessoa aguenta, e a única das quatro em que o bloco "extras" é
vazio. A justificativa é a segunda regra da lista, e é a que mais surpreende:
o time decide, não o volume.
-
catálogo com código sai como híbrido, e é o caso em que os quatro extras
aparecem de uma vez. Repare no risco: 800 ms com 180 mil documentos empurra o
reranker para fora do caminho síncrono. A função avisa em vez de escolher.
-
normas com referência sai como grafo. Os bloqueadores incluem citação
obrigatória, e o
extraspede fila de indexação — porque grafo que sereconstrói do zero toda semana não é grafo, é um exercício.
-
suporte multi-passo sai como agente, e o risco muda: cada passo é um
ponto de falha, e o SLA precisa de corte, não de sorte. É aqui que a pergunta
12 ("precisa de resposta sem modelo?") deixa de ser recomendação e vira
obrigação.
O que nenhum dos quatro perfis produziu é um agente para resolver uma pergunta
local, nem um grafo para uma base de manual. Isso não é falta de cobertura: é o
resultado que a coluna "não ajuda" da tabela produz quando ela é lida a sério.
6. O que não fazer ainda
A série inteira descreve peças que valem a pena. Isso não significa que você
precisa de todas, e a ordem de errar é previsível.
Não comece pelo agente. Um agente com busca ruim produz uma resposta ruim que
pode estar em três lugares diferentes. Aí você otimiza o prompt — a única parte
rápida de editar — e o prompt nunca foi o problema. O artigo 10
tem os três casos em que multiagente é erro, e a
API do LangGraph mostra
o que a coluna do agente exige de fato: estado explícito, checkpoint e retomada.
A frase que resume o resto: um agente raciocina sobre o que a busca devolveu, e se
a observação é ruim, o raciocínio é elegante sobre nada.
Não construa o grafo antes de ter a pergunta de relação. Construir o grafo
é um projeto de dois meses com a terceira pergunta respondida no fim. A
alternativa honesta é rodar a busca vetorial + léxica durante um mês, anotar as
perguntas que ficaram sem resposta, e ver se "quem assinou o que substituiu o quê"
aparece na lista. Se aparecer, o grafo se justifica com dados. Se não aparece,
você economiza dois meses.
Não comece pela reordenação. O reranker melhora a ordenação de uma lista que
a busca já montou. Se o trecho certo não está entre os 50 candidatos, a
reordenação escolhe melhor entre os errados — e o resultado fica um pouco melhor
sem que ninguém perceba que o defeito continua o mesmo. Meça a cobertura antes: se
ela está em 0,4, o problema é recuperação, e reordenar é enfeite.
Não multi-tenant por filtro em Python. Já está no artigo 01 e vale repetir
porque é o erro que custa mais caro: filtrar depois não vaza na resposta, vaza no
registro. O payload filter do Qdrant
é a cláusula WHERE dentro da mesma consulta que ordena o resultado.
💡 O que fazer enquanto não decide: uma coisa só, e medida
Indexe 50 documentos, mande cinco perguntas que você sabe responder, e meça acobertura. Isso cabe numa tarde, não em um trimestre, e o número que sai é a
única base honesta para qualquer uma das decisões desta seção.
7. A ordem de implantação, em quatro fases
A ordem abaixo não é a ordem dos artigos. É a ordem em que cada decisão fica
barata de revisar, porque cada fase tem um número que a justifica — e é esse
número, não a arquitetura preferida, que decide se você avança.
Fase 1 — vetorial, e nada mais. Indexe, corte, busque, responda, e meça
cobertura@10 com um conjunto de dez perguntas anotadas. Esta fase existe para
dar um número: sem ele, nenhuma fase depois tem critério de parada, e você vai
sentir que está melhorando sem saber. As métricas de trace e de avaliação que
medem isso estão no
no artigo 11.
Fase 2 — lexa, se a fase 1 não cobriu. É a fase da busca por palavra exata:
código, número de contrato, nome próprio. Meça de novo a mesma cobertura e o
mrr. Se o ganho for pequeno, a sua base não tem pergunta por termo — e a
pergunta 1 estava errada, o que é uma descoberta barata.
Fase 3 — corte, e depois o resto. A ordem aqui é contraintuitiva e é a que
mais economiza tempo: antes de adicionar peça, conserte a peça que existe. Corte
melhor muda mais a qualidade do que qualquer modelo de vetor. Só depois de
cobertura alta é que reranker, filtro por idioma e o resto entram.
Fase 4 — degradação, e só então as ramificações. Um caminho que responde sem
modelo, medido e com resposta, antes de grafo e antes de agente. A ordem não é
porque degradação é mais importante que grafo: é porque degradação é a única
fase que é obrigatória quando existe SLA, e ela é a mais barata de fazer bem.
O que a fase 4 decide é se o seu sistema precisa de grafo ou de agente — e essa
decisão, diferente das outras, não se responde com número. Responde-se com a lista
de perguntas que ficaram sem resposta depois das fases 1 a 3. Se a lista está
vazia, você terminou. Acabou cedo ser um resultado legítimo.
8. O custo operacional, honesto e qualitativo
O custo de uma arquitetura não é a diária do servidor. É quanto trabalho humano
ela pede por mês, depois de pronta — e é por isso que a ordem da seção 7 é esta.
Vetorial é o mais barato e o mais opaco. Um índice, um filtro, um
registrador. O custo que ninguém vê é o da qualidade: base grande com ordenação
ruim faz o usuário parar de confiar, e confiança não se recupera com modelo
melhor. Ele se recupera com citação correta.
Híbrido custa uma segunda consulta e um pouco de raciocínio. A segunda
busca é a parte barata; a parte cara é decidir a fusão, e a decisão errada (somar
escores de escalas diferentes) produz um resultado pior que vetorial puro sem
que nenhum alerta apareça. Custa uma métrica: a fração de acertos que veio do
léxico, que o artigo 11 já mede.
Grafo custa manutenção, que é o custo que não volta. Toda mudança no
documento pode virar aresta nova, e o grafo precisa de uma rotina que descubra
isso. Quem chama isso de "custo de construção" está descrevendo o primeiro mês.
Agente custa por requisição e por incidente. Cada passo é uma chance de
falhar, uma chance de gastar uma chamada de modelo, e uma chance de entrar em
ciclo. A métrica que importa não é "o agente acerta?", é "quantos passos por
requisição e quantos deles falharam" — o resto é otimização.
E há um custo que não aparece em planilha: a resposta errada. Uma resposta com
erro em base de política, contrato ou clínico não é um bug comum, é um incidente,
e o custo de responder errado depois de responder bem é assimétrico. Esse é o
argumento por trás de três decisões que parecem conservadoras neste artigo —
citação obrigatória, registro por requisição e degradação declarada. Nenhuma delas
aumenta a qualidade da resposta; as três diminuem a chance de você ficar sem
saber o que aconteceu.
TL;DR
-
A pergunta errada é "qual o melhor banco vetorial". A certa é "que tipo de
pergunta o usuário faz", e existem cinco formatos: local, por termo, por
relação, global e multi-passo.
-
Quatro arquiteturas, um formato de pergunta cada. Por relação é grafo; global
é grafo com resumo de comunidade; multi-passo é agente; por termo é híbrido; o
resto é vetorial, e time de uma pessoa decide vetorial.
-
A função recomendar() é o artigo executável: pura, testável, com a regra
em ordem explícita e o risco no mesmo retorno da decisão.
-
Auditoria e SLA não escolhem arquitetura — escolhem obrigação. Citação
obrigatória, registro por requisição e degradação declarada vêm antes de grafo e
de agente.
-
A última resposta é sempre "meça". As cinco premissas da função são
hipóteses, e a única que confirma ou derruba a recomendação é a cobertura do
conjunto dourado.
Referências
- Hybrid Queries — Qdrant — a fusão por ranking que sustenta a coluna do híbrido e o
top-kdo reranker - Payload filtering — Qdrant — o filtro dentro da consulta, que é bloqueador antes de qualquer arquitetura
- Graph API — LangGraph — o que a coluna do agente exige de fato
- GraphRAG — Microsoft — o resumo de comunidade que a pergunta global exige
- Lost in the Middle: How Language Models Use Long Contexts — por que a latência apertada e o
top-kgrande são o mesmo problema - SDK overview — Langfuse — as métricas que a fase 1 usa para justificar a fase 2