Como usar o Jev para escolher qual fonte o seu RAG vai consultar?

diagrama mostrando o Jev para RAG escolhendo entre fontes de dados
Resposta rápida

Roteador de fonte no RAG não é tarefa de redação, é escolha dentro de uma lista fechada. Usar Jev para RAG significa transformar essa decisão numa pergunta Choice: cada índice ou coleção vira uma opção, você inclui um no_search e um other, manda a pergunta do usuário como state e recebe de volta a opção escolhida, a probabilidade de cada uma e um confidence de 0 a 1. O roteamento em si fica no seu código, com limiar. Entrada custa US$ 0,042 por 1M de tokens e a saída é gratuita, então dá pra rotear toda consulta

Fala aí, beleza? Se o seu RAG dispara busca em todo índice que existe pra depois "ver no que dá", você está pagando contexto pra receber ruído

E o pior: quando a coleção errada entra no prompt, o modelo que responde não te avisa, ele só responde bonito e errado

A decisão de QUAL fonte consultar é uma escolha dentro de uma lista que você já conhece, e é exatamente aí que o Jev entra. Ele é o primeiro modelo público da TypeSafe AI, de uma classe que a empresa chama de System One: não gera texto, recebe um estado não estruturado e devolve decisões tipadas com probabilidades

Ou seja, o roteador de busca vira uma pergunta Choice, e não um prompt torcendo pra IA responder {"index": "faq"} sem inventar aspas no meio 😀

Bora montar isso na prática?

O que você precisa antes de montar o roteador

Do lado da TypeSafe, o setup é curto

  • conta no console da TypeSafe (console.typesafe.ai). O Jev deixou de ter waitlist, o cadastro está aberto pra todo mundo
  • quem cria conta recebe US$ 5 em crédito gratuito, algo em torno de 120 milhões de tokens de entrada
  • a variável de ambiente TYPESAFE_API_KEY, que os dois SDKs leem sozinhos
  • o SDK oficial da sua stack

Python:

pip install typesafe-sdk

Exige Python 3.10 ou superior, e o cliente chama jev-latest por padrão. O repositório fica em typesafe-sdk-python

JavaScript/TypeScript:

npm install @typesafe-ai/sdk

O pacote é o @typesafe-ai/sdk, com TypeSafeClient e o método client.systemOne(), também lendo TYPESAFE_API_KEY. Código em typesafe-sdk-js

E do lado do seu RAG, você precisa de duas coisas que provavelmente já tem, só que bagunçadas:

  1. a lista real de índices ou coleções que o sistema pode consultar
  2. uma descrição honesta do que cada um cobre, e do que ele NÃO cobre

Essa segunda parte é a que todo mundo pula, e é a que decide a qualidade do roteador. "documentos" não é descrição, beleza? "contratos assinados a partir de 2023, com cláusulas e aditivos, sem proposta comercial" é

Passo a passo: montando o roteador de busca com uma pergunta Choice

A primitiva aqui é a Choice: ela seleciona uma opção dentro de um conjunto fixo definido por você e retorna a opção escolhida, a probabilidade de cada opção e um valor de confiança

E tem espaço de sobra: uma Choice aceita até 255 opções, e a própria documentação recomenda passar a lista COMPLETA em vez de uma lista curta, porque cada opção custa poucos tokens

  1. Liste suas fontes como opções, uma por índice ou coleção

Cada opção pode carregar campos descritivos pra explicar o escopo. Um detalhe que economiza dor de cabeça: os nomes que aparecem nos exemplos da doc (question, focus, what, not_for, examples) não fazem parte da API e não são reservados. Quem escolhe o nome dos campos é você, do mesmo jeito que escolhe o nome das opções

import os
from typesafe import TypeSafeClient, Choice

client = TypeSafeClient()  # le TYPESAFE_API_KEY do ambiente

fontes = [
    {
        "name": "docs_produto",
        "what": "documentacao publica do produto, guias de uso, referencia de API",
        "not_for": "preco, contrato, dados de cliente especifico",
        "examples": ["como configuro webhook?", "quais parametros o endpoint aceita?"],
    },
    {
        "name": "base_contratos",
        "what": "contratos assinados, aditivos e clausulas a partir de 2023",
        "not_for": "proposta comercial nao assinada, documentacao tecnica",
        "examples": ["qual o prazo de rescisao do cliente X?"],
    },
    {
        "name": "tickets_suporte",
        "what": "historico de chamados resolvidos, com sintoma e solucao aplicada",
        "not_for": "politica oficial, texto normativo",
        "examples": ["ja aconteceu esse erro de timeout antes?"],
    },
]

O erro comum deste passo: mandar três opções genéricas do tipo interno, externo, outro. Descrição vaga vira probabilidade espalhada, e aí você culpa o modelo por uma lista que ninguém entenderia

  1. Adicione a opção de não buscar e a opção de escape

Nem toda pergunta merece busca. "bom dia, tudo bem?" não precisa varrer índice nenhum

E a documentação recomenda incluir uma opção do tipo other ou none of the above quando a lista pode não cobrir toda entrada, justamente pro modelo poder dizer que nenhuma das outras serve

fontes += [
    {
        "name": "no_search",
        "what": "saudacao, agradecimento ou pergunta que o assistente responde sem recuperar nada",
        "examples": ["oi", "obrigado!", "voce consegue me ajudar?"],
    },
    {
        "name": "other",
        "what": "nenhuma das fontes acima cobre essa pergunta",
    },
]

roteador_fonte = Choice(
    name="fonte_de_busca",
    question="Qual fonte deve ser consultada para responder a pergunta do usuario?",
    options=fontes,
)

O erro comum deste passo: esquecer o escape e obrigar o modelo a empurrar a pergunta pra alguma fonte à força. Sem other, toda entrada fora do escopo vira falso positivo em cima da opção mais parecida

  1. Monte o state com contexto mínimo

O state é o conteúdo que você pede pro modelo avaliar, e ele aceita string, objeto JSON ou array. Em Python você passa direto em client.system_one(state=...)

A orientação oficial é dar a cada pergunta apenas o contexto de que ela precisa, e usar o JSON que você já tem (schema, taxonomia, linha de banco) em vez de serializar tudo num template de string

state = {
    "pergunta": "qual o prazo de rescisao que fechamos com a Acme?",
    "ultimo_turno": "usuario perguntou sobre renovacao do contrato da Acme",
    "cliente_logado": True,
}

O erro comum deste passo: despejar a conversa inteira no state "por garantia". Fora o custo, você tem teto: 64k tokens cobrem o state mais todas as perguntas juntas, e 32k se aplicam ao state mais a pergunta mais longa. Histórico longo também dilui o sinal, porque o turno de três mensagens atrás briga com a pergunta atual

  1. Chame o modelo

Cada requisição avalia um único state contra uma ou mais perguntas. Todas veem o mesmo state, são avaliadas de forma independente e você pode misturar Choice, Score e Noul na mesma chamada

resposta = client.system_one(
    state=state,
    questions=[roteador_fonte],
)

# para cada pergunta, a resposta traz: opcao escolhida,
# a probabilidade de cada opcao e um confidence de 0 a 1

Em JavaScript, mesma ideia com client.systemOne():

import { TypeSafeClient } from "@typesafe-ai/sdk";

const client = new TypeSafeClient(); // le TYPESAFE_API_KEY

const resposta = await client.systemOne({
  state: {
    pergunta: "qual o prazo de rescisao que fechamos com a Acme?",
    ultimoTurno: "usuario perguntou sobre renovacao do contrato da Acme",
    clienteLogado: true,
  },
  questions: [roteador_fonte],
});

O erro comum deste passo: esperar que o modelo execute a busca. Modelos System One não geram código nem escolhem sozinhos a própria próxima ação. Ele te entrega a decisão, quem chama o retriever é você

  1. Roteie no seu código

A parte mais chata de aceitar e a mais libertadora: fluxo de controle, regras determinísticas e efeitos colaterais ficam do lado do código

RETRIEVERS = {
    "docs_produto": buscar_docs,
    "base_contratos": buscar_contratos,
    "tickets_suporte": buscar_tickets,
}

if fonte_escolhida == "no_search":
    passagens = []
elif fonte_escolhida == "other":
    passagens = fallback_humano(state)
else:
    passagens = RETRIEVERS[fonte_escolhida](state["pergunta"])

Simples assim, e totalmente auditável. Sem parser de string, sem regex torcendo pra IA ter fechado a chave 🙂

Roteamento por confiança: o que fazer quando o Jev fica na dúvida

Tem pergunta ambígua. "quanto tempo isso dura?" pode ser SLA de suporte ou prazo de contrato, e o modelo vai te dizer isso: as probabilidades ficam divididas e o confidence cai

A resposta curta: você não chuta e nem manda buscar em tudo, você define um limiar de confiança e deixa o código decidir o caminho

A TypeSafe calcula e retorna um valor de confiança de 0 a 1 em toda resposta de Choice e de Score, além das probabilidades completas, justamente pra você aplicar limiar sem refazer a conta

E o que é limiar (threshold)? É só um número de corte que VOCÊ escolhe, nada de mágica: acima dele a decisão segue sozinha, abaixo dele ela toma outro caminho. O confidence é o valor que entra nessa comparação, então na prática o seu roteador vira um if comparando dois números

Dá pra ter mais de um corte também: um limiar alto pra seguir direto numa fonte só, um limiar do meio pra buscar nas duas fontes mais prováveis, e abaixo disso escalar. Se liga

O padrão na documentação se chama confidence-gated routing: o código toma caminhos diferentes pra respostas confiantes e pra não confiantes, escalando os casos incertos pra uma pessoa ou pra um modelo de raciocínio mais caro

THRESHOLDS = {
    "auto": 0.85,
    "multi": 0.55,
}

def rotear(fonte_escolhida, confianca, probabilidades):
    if confianca >= THRESHOLDS["auto"]:
        return [fonte_escolhida]
    if confianca >= THRESHOLDS["multi"]:
        # dividida entre duas fontes? busca nas duas mais provaveis
        return top_n(probabilidades, 2)
    return "escalar"

Repare numa coisa boa: os limiares vivem numa constante e a função de roteamento lê só as respostas já armazenadas. É o mesmo desenho do cookbook de classificação de passagens de RAG, onde os limiares ficam numa constante THRESHOLDS e a função route() lê apenas o que já está salvo

Tradução prática: reavaliar os limiares em cima de mil decisões antigas não custa uma chamada de API. Você guarda a resposta crua, mexe no número, roda de novo e compara. Massa demais pra calibrar sem queimar crédito

Tome cuidado com um detalhe: limiar alto demais no começo joga metade do tráfego no fallback e você conclui que "não funcionou". Comece medindo a distribuição de confiança nas suas perguntas reais, e só depois escolha o corte

Além do roteador: outras decisões do pipeline que o Jev resolve

O roteador é só a primeira porta. Como uma requisição avalia um state contra N perguntas, em paralelo e misturando primitivas, dá pra tomar várias decisões na mesma ida

As três primitivas de pergunta são Choice, Score e Noul:

Primitiva O que devolve Onde encaixa no RAG
Choice opção escolhida, probabilidade de cada opção e confiança escolher índice, coleção ou não buscar
Score valor escalar calibrado de 0.0 a 1.0 pontuar relevância de passagem recuperada
Noul probabilidade de a resposta ser "sim" "esta passagem responde a pergunta?"

Classificar as passagens que voltaram

A documentação tem um cookbook chamado "Classifying RAG passages": você pontua cada passagem recuperada numa requisição e decide no código quais chegam ao modelo que responde

A ideia é separar evidência aceita de evidência conflitante, e descartar passagem com instrução escondida (prompt injection). Isso último é ouro: documento de origem duvidosa com "ignore as instruções anteriores" no meio do texto morre antes de entrar no prompt

Re-ranking com Noul

Tem também um cookbook de re-ranking que usa um Noul por par pergunta/candidato e ordena os candidatos pela probabilidade, sem limiar nenhum. Nada de "nota de 1 a 10" em texto livre, é probabilidade mesmo

No experimento desse cookbook, com shortlists BM25 de 30 passagens para 40 consultas do CLERC:

Métrica Antes Depois do re-ranking
Acurácia top-1 5% 18%
Acurácia top-10 38% 62%

É o mesmo recall bruto do BM25, só que ordenado direito. Barato de testar

Quando as fontes têm níveis

Se o seu acervo é hierárquico (área, depois subárea, depois coleção), a doc tem um cookbook de classificação hierárquica. Ajuda quando 255 opções numa Choice única viraria uma sopa de nomes parecidos

E olha, esse mesmo desenho de decisão tipada não vive só dentro de RAG: usar o Jev pra qualificar e priorizar leads no funil é literalmente o mesmo padrão, trocando índice por estágio

Por que essa decisão não precisa de texto livre

Aqui está o pulo do gato do post

Escolher fonte é seleção dentro de um conjunto fechado, não redação. Você já sabe todos os resultados possíveis antes de perguntar. Então pra que pedir uma string?

Quando você usa um LLM generativo pra isso, ganha três problemas de brinde:

  • precisa parsear a saída, e ela varia de formato quando o modelo resolve ser prestativo
  • ele pode devolver uma fonte que não existe, com a maior convicção
  • você não recebe probabilidade calibrada, só um texto seguro de si

Com Choice, o conjunto de respostas é o que você definiu, e vem acompanhado da distribuição inteira mais o confidence. Dá pra decidir o que fazer com a dúvida em vez de descobrir a dúvida em produção

Tem outro ponto no guia oficial que fecha o raciocínio: ele manda quebrar julgamentos amplos em perguntas estreitas, tipadas e atômicas. A própria doc cita "Analise esta mensagem e determine o melhor curso de ação" como exemplo de pergunta ruim, sinal de que a tarefa precisa ser fatiada em perguntas menores compostas no código

É o oposto do agente que decide sozinho o que fazer em seguida. Modelos System One não geram código nem escolhem a própria próxima ação, e nesse caso isso é uma feature, não uma limitação. Seu roteador fica determinístico, testável e versionável

Se você está na dúvida entre isso e treinar um modelo próprio pra decidir, vale olhar o comparativo entre Jev e classificador treinado em casa, porque a conta muda bastante conforme o volume

E por falar em conta: tokens de entrada custam US$ 0,042 por 1 milhão, e os tokens de saída são gratuitos. Isso muda o cálculo de "vale a pena rotear?" pra "por que eu não estaria roteando toda consulta?"

Conclusão

Recapitulando o caminho: sua lista de índices vira opções de uma Choice, você acrescenta no_search e other, manda a pergunta do usuário como state com contexto mínimo, chama o modelo e roteia no seu código lendo escolha, probabilidades e confidence

O próximo passo concreto, na ordem que evita retrabalho:

  1. cria a conta no console e usa o crédito de US$ 5 pra rodar o roteador com as opções REAIS do seu índice, não com exemplo de blog
  2. monta um conjunto de 30 a 50 perguntas que você já sabe a fonte correta e mede o acerto
  3. calibra os limiares em cima das respostas salvas, que isso não custa chamada nova
  4. só depois plugue o re-ranking em cima do que voltar

Último lembrete que já pegou gente desprevenida: aliases de modelo se movem quando sai uma versão nova. Hoje jev-preview aponta pro mesmo modelo de jev-latest, e o campo model da resposta informa o ID versionado que respondeu. Loga esse campo junto com suas métricas, senão você não vai saber se o acerto mudou por causa do seu prompt ou por causa de uma versão nova atrás do alias 😉

Até o próximo post!

Perguntas frequentes

Quantas fontes posso colocar em uma única pergunta Choice do Jev?

Uma pergunta Choice aceita até 255 opções, e a documentação recomenda listar todas as suas fontes reais em vez de resumir em três ou quatro categorias genéricas. Cada opção custa poucos tokens, então não tem motivo pra cortar índice da lista.

O Jev decide sozinho pra qual índice mandar a busca depois de escolher a fonte?

Não. O Jev não gera código nem escolhe sozinho a próxima ação, ele só devolve a opção escolhida, as probabilidades e a confiança. Quem dispara a busca no índice certo é o seu código, lendo essa resposta.

Quanto custa em dólar rodar o roteador de busca com o Jev?

Os tokens de entrada custam US$ 0,042 por 1 milhão e a saída é gratuita, então cada Choice de roteamento fica bem barato. Quem cria conta ainda ganha US$ 5 de crédito, o equivalente a cerca de 120 milhões de tokens de entrada.

Dá pra rotear a busca e checar outra coisa na mesma chamada, tipo se a pergunta precisa de contexto extra?

Dá sim. Uma requisição avalia um único state contra várias perguntas ao mesmo tempo, de forma independente, e você pode misturar Choice, Score e Noul na mesma chamada. Então o roteamento de fonte pode andar junto com um Noul de "precisa de mais contexto?", por exemplo.

O que o Jev faz quando a pergunta do usuário não se encaixa em nenhuma fonte cadastrada?

Se você incluiu a opção other ou none of the above na lista, o modelo pode sinalizar isso em vez de forçar a pergunta pra fonte mais parecida. A documentação recomenda incluir essa opção sempre que a lista pode não cobrir toda entrada.

Como saber se a decisão do roteador é confiável o suficiente pra buscar direto, sem revisão?

Toda resposta de Choice vem com um campo confidence de 0 a 1 e as probabilidades completas de cada opção. É esse valor que alimenta o padrão de confidence-gated routing: o código segue direto quando a confiança é alta e escala pra revisão humana ou pra um modelo mais caro quando não é.



Escrito por | Matheus Battisti

Matheus Battisti
Fundador da Hora de Codar

Programador apaixonado pelo mundo das tecnologias, sempre buscando em aprender e se aprofundar em linguagens, frameworks e o que mais for necessário para executar um bom trabalho. Agora tem uma nova missão que é de passar seu conhecimento adiante para formar novos programadores e especializar mais os que já são.

Subscribe
Notify of
guest

0 Comentários
Oldest
Newest Most Voted

Formações

Formação Claude Code

Formação Claude Code

Domine Claude Code do absoluto zero até o avançado

  • 120 aulas
  • 4 projetos
  • 9h 48min

Blog | Mais populares