Como testar o código que chama o Jev sem depender da API? Mocks, fixtures e casos de borda

Para testar código que chama o Jev sem depender da rede, isole a chamada do SDK da sua regra de negócio, injete transporte falso (http_client no Python, a propriedade fetch no JS) e mocke a rota POST https://api.typesafe.ai/v1/systemone com RESPX. Depois monte um fixture por primitiva: ChoiceAnswer com choice, confidence e probabilities, ScoreAnswer com score, confidence, probabilities e legend, NoulAnswer com um noul de 0 a 1 e sem confidence. Desligue o retry no ambiente de teste com RetryPolicy(max_retries=0) e escreva um teste por ramo: confiança baixa, empate na distribuição, score entre dois níveis e answer faltando
Fala aí, beleza? Suíte de teste que depende de rede é aquele tipo de dívida que só cobra juros: roda devagar, quebra sozinha quando a API espirra e ainda queima token toda vez que alguém dá push
Com o Jev dá pra fugir disso de um jeito bem confortável
Ele é o primeiro modelo System One da TypeSafe AI: você manda um estado mais perguntas tipadas e ele devolve decisões estruturadas com probabilidades, em vez de texto solto. Ou seja, a resposta tem formato fixo (choice, score, noul) e formato fixo é MUITO fácil de reproduzir em fixture
A ideia deste post é simples: fazer todo ramo do seu fluxo passar por teste sem uma única chamada HTTP de verdade 🙂
O que você precisa antes de começar
Nada de PC da Nasa aqui, só o setup certo
- SDK Python:
pip install typesafe-sdk(ouuv add typesafe-sdk), com Python 3.10 ou superior - SDK JavaScript/TypeScript:
npm install @typesafe-ai/sdk, com Node 20 ou mais novo. O pacote traz ESM, CommonJS e as declarações de tipo
E três variáveis de ambiente que fazem toda a diferença dentro da suíte:
TYPESAFE_API_KEY: a chave que o cliente lê sozinho. No teste, você quer uma chave fake aqui, nunca a de produçãoTYPESAFE_DEFAULT_MODEL: o modelo padrão. Sem isso, o cliente usa o aliasjev-latest, que hoje resolve parajev-1.13.0TYPESAFE_BASE_URL: a URL base, por padrãohttps://api.typesafe.ai. É o seu botão de emergência contra chamada acidental pra internet
O contrato que você vai reproduzir:
Antes de mockar qualquer coisa, entenda o desenho da conversa
O corpo da requisição leva state, model e um mapa de perguntas tipadas nomeadas por você
A resposta volta com uma answer por pergunta, chaveada exatamente pelos mesmos ids que você escolheu
Isso é ótimo pro teste: o id é seu, então o fixture nunca depende de um nome mágico que o modelo inventou
Um aviso de acesso: o Jev entrou em early access em 15 de setembro de 2026 e, até agora, continua por waitlist com chaves liberadas em lotes, sem disponibilidade geral. Mais um motivo pra sua suíte não ficar dependendo de chave pra rodar, né?
Formação Claude Code
Domine Claude Code do absoluto zero até o avançado
- 120 aulas
- 4 projetos
- 9h 45min
Passo a passo: isolando a chamada ao Jev nos testes
- Separe a chamada do SDK da sua regra de negócio
A decisão do modelo é uma coisa, o que o seu sistema FAZ com ela é outra. Se os dois moram na mesma função, você é obrigado a subir transporte pra testar um if
# app/jev_client.py
from typesafe_sdk import Choice, TypeSafeClient
client = TypeSafeClient()
def perguntar_categoria(texto: str):
return client.system_one(
state=texto,
questions={
"categoria": Choice(...), # monte a pergunta conforme a doc de primitivas
},
)
# app/roteamento.py
def rotear_ticket(resposta, limite_confianca: float = 0.7):
answer = resposta.answers["categoria"]
if answer.confidence < limite_confianca:
return "fila_humana"
return f"fila_{answer.choice}"
Agora rotear_ticket é uma função pura: recebe objeto, devolve string, zero rede
Se você vem de modelo de texto, o desenho é diferente do que acontece com function calling no GPT-6 Astra, onde o modelo decide QUAL função chamar. Aqui a pergunta é sua, o formato é seu, e o modelo só preenche
O erro comum deste passo: deixar o if de confiança dentro da mesma função que faz a chamada. Aí todo teste de regra vira teste de integração, sem necessidade
- Injete um transporte falso no cliente
Os clientes Python (síncrono e assíncrono) aceitam api_key, model, base_url, timeout, retry e http_client. Esse último é a porta de entrada do seu transporte de teste
import httpx
from typesafe_sdk import TypeSafeClient
client = TypeSafeClient(
api_key="chave-de-teste",
base_url="https://api.typesafe.ai",
http_client=httpx.Client(timeout=5.0),
)
No JavaScript a mesma ideia tem outro nome: na construção o cliente aceita apiKey e baseURL, e expõe as propriedades de configuração baseURL, defaultHeaders, defaultModel, fetch, logger, logLevel, models, retry e timeout
Ou seja, tu passa o teu próprio fetch e acabou a mágica
import { TypeSafeClient } from "@typesafe-ai/sdk"
const fetchFalso = async () =>
new Response(JSON.stringify(payloadDeTeste), {
status: 200,
headers: { "content-type": "application/json" },
})
const client = new TypeSafeClient({
apiKey: "chave-de-teste",
baseURL: "https://api.typesafe.ai",
fetch: fetchFalso,
})
O erro comum deste passo: esquecer de sobrescrever a base URL no ambiente de teste. Se TYPESAFE_BASE_URL continuar apontando pro host real e o mock falhar em pegar a rota, a chamada sai de verdade e tu só descobre na fatura
- Mocke a rota HTTP, não o método do SDK
Toda chamada ao Jev passa por um único endpoint: POST https://api.typesafe.ai/v1/systemone, com Authorization: Bearer e Content-Type: application/json
Um endpoint só significa uma rota só pra mockar. Pra isso o RESPX resolve bonito: ele faz patch no HTTPX por rotas de requisição
Se você já usou aquelas libs de mock de HTTP de outras linguagens, é bem semelhante: tu registra a rota e diz o que ela responde
import httpx
import respx
from app.jev_client import perguntar_categoria
@respx.mock
def test_chamada_envia_contrato_correto():
rota = respx.post("https://api.typesafe.ai/v1/systemone").mock(
return_value=httpx.Response(
200,
json=PAYLOAD_CHOICE,
headers={"x-typesafe-request-id": "req_teste_1"},
)
)
perguntar_categoria("cliente relata cobrança duplicada")
requisicao = rota.calls.last.request
assert requisicao.headers["content-type"] == "application/json"
assert requisicao.headers["authorization"].startswith("Bearer ")
Repara no que ganhamos aqui: o teste valida que o SDK montou a requisição do jeito combinado, com header de auth e content type. Isso é contrato, não é achismo
O erro comum deste passo: fazer monkeypatch direto no system_one do cliente. Passa verde sempre, mas você deixou de testar serialização, header e endpoint. O dia que o payload mudar de forma, a suíte não vai te avisar
- Monte o payload de resposta reproduzindo o SystemOneResponse
O objeto SystemOneResponse do SDK Python expõe .answers (dict pelo nome da pergunta), as views tipadas .choices, .scores e .nouls, além de .model, .usage (com .input_tokens e .output_tokens), .request_id (vem do header x-typesafe-request-id) e .raw_http_response
Se o seu código lê .usage pra logar custo, ou .request_id pra rastrear, o fixture precisa carregar esses campos. Senão o teste testa metade do mundo
@respx.mock
def test_resposta_expondo_metadados():
respx.post("https://api.typesafe.ai/v1/systemone").mock(
return_value=httpx.Response(
200,
json=PAYLOAD_CHOICE,
headers={"x-typesafe-request-id": "req_teste_1"},
)
)
resposta = perguntar_categoria("cliente relata cobrança duplicada")
assert resposta.request_id == "req_teste_1"
assert resposta.usage.input_tokens > 0
assert resposta.usage.output_tokens == 0 or resposta.usage.output_tokens >= 0
assert "categoria" in resposta.answers
Tome cuidado com um detalhe: o formato exato do JSON bruto é o que a referência da API define, então vale abrir a doc e copiar a forma real pro seu PAYLOAD_CHOICE em vez de chutar campos. Payload inventado é teste verde com produção vermelha
- Asserte sobre o SEU ramo, não sobre o modelo
Esse é o pulo do gato
O teste não existe pra provar que o Jev acerta a categoria. Ele existe pra provar que, dada uma resposta X, o seu fluxo vai pro caminho Y
from typesafe_sdk import ChoiceAnswer
from app.roteamento import rotear_ticket
class RespostaFake:
def __init__(self, answers):
self.answers = answers
def test_confianca_baixa_vai_pra_revisao_humana():
resposta = RespostaFake({
"categoria": ChoiceAnswer(
choice="cobranca",
confidence=0.41,
probabilities={"bug": 0.30, "cobranca": 0.36, "duvida": 0.34},
)
})
assert rotear_ticket(resposta) == "fila_humana"
A mesma lógica vale pra quando você quer testar convenções do seu projeto em vez de só checar se o código roda: o alvo do teste é o comportamento que você prometeu, não o humor do modelo
O erro comum deste passo: escrever asserção do tipo assert answer.choice == "cobranca" em cima de uma chamada real. Isso não é teste, é aposta
Fixtures por primitiva: Choice, Score e Noul
O Jev trabalha com três primitivas de pergunta, e cada uma tem uma cara de resposta. Três fixtures mínimos e tu cobre o SDK inteiro
Choice: escolha, distribuição e confiança
A resposta de Choice traz a opção escolhida, a distribuição de probabilidades de TODAS as opções e um valor de confiança
No SDK Python, os campos são choice, confidence e probabilities
from typesafe_sdk import ChoiceAnswer
choice_confiante = ChoiceAnswer(
choice="cobranca",
confidence=0.93,
probabilities={"bug": 0.03, "cobranca": 0.94, "duvida": 0.03},
)
choice_empatado = ChoiceAnswer(
choice="bug",
confidence=0.35,
probabilities={"bug": 0.34, "cobranca": 0.33, "duvida": 0.33},
)
E aqui vai a parte que muita gente erra no fixture: o confidence é um número de 0 a 1 calculado a partir do FORMATO da distribuição
Distribuição achatada gera confiança baixa
Pico único gera confiança alta
Então não adianta escrever um fixture com probabilities tipo 0,94 em uma opção e confidence de 0,2, porque isso não acontece na vida real. Fixture incoerente ensina o time a confiar em um cenário impossível
Score: posição entre níveis
A resposta de Score traz uma posição ao longo dos níveis que você definiu, e ela PODE cair entre dois níveis
Além disso vêm probabilities, legend e confidence. No SDK Python, probabilities e legend são chaveados por nível inteiro
from typesafe_sdk import ScoreAnswer
score_entre_niveis = ScoreAnswer(
score=1.30,
confidence=0.62,
probabilities={1: 0.70, 2: 0.30},
legend={1: "baixo", 2: "medio", 3: "alto"},
)
O exemplo da doc é justamente esse: score 1.30 significa quase todo nível 1 com um peso no nível 2
Se o seu código faz int(score) sem pensar, esse fixture é o que vai te salvar
Noul: probabilidade pura, sem confidence
A resposta de Noul é um único número: a probabilidade de a proposição ser verdadeira
E ela NÃO tem campo de confiança separado, porque a própria probabilidade já carrega a incerteza
from typesafe_sdk import NoulAnswer
noul_certeiro = NoulAnswer(noul=0.97)
noul_indeciso = NoulAnswer(noul=0.51)
Massa, né? Um fixture de uma linha
Fábrica de fixtures parametrizada por confiança:
Depois do terceiro teste tu vai cansar de escrever dicionário na mão. Monta uma fábrica e parametriza pelo que realmente importa no seu fluxo: o nível de confiança
import pytest
from typesafe_sdk import ChoiceAnswer
def fabricar_choice(vencedora: str, confianca: float, opcoes: list[str]) -> ChoiceAnswer:
resto = (1 - confianca) / (len(opcoes) - 1)
probabilities = {o: (confianca if o == vencedora else resto) for o in opcoes}
return ChoiceAnswer(
choice=vencedora,
confidence=confianca,
probabilities=probabilities,
)
@pytest.mark.parametrize(
"confianca,destino",
[(0.95, "fila_cobranca"), (0.71, "fila_cobranca"), (0.40, "fila_humana")],
)
def test_roteamento_por_faixa_de_confianca(confianca, destino):
resposta = RespostaFake({
"categoria": fabricar_choice("cobranca", confianca, ["bug", "cobranca", "duvida"])
})
assert rotear_ticket(resposta) == destino
Repara que a fábrica mantém probabilities e confidence coerentes entre si sozinha. Menos chance de fixture mentiroso
Casos de borda que todo teste do Jev deveria cobrir
Essa parte é ouro e quase ninguém usa: a documentação oficial publica uma página de jaggedness do jev-1.13 listando onde o modelo falha
Isso não é folclore de fórum, é insumo direto pra escrever caso de borda
Cada fraqueza vira um cenário de fixture, e cada cenário vira uma decisão de produto no SEU código:
| Fraqueza documentada do jev-1.13 | Cenário no fixture | Como seu código deve reagir |
|---|---|---|
| Leitura literal da pergunta (negações e escopo ao pé da letra) | Choice com distribuição quase empatada | Cair pra revisão humana em vez de escolher o topo por 1 ponto |
| Contagem numérica não confiável | Score entre dois níveis, tipo 1.30 | Não arredondar em silêncio, tratar a faixa explicitamente |
| Datas lidas como texto, não como quantidades ordenadas | Noul perto de 0,5 em proposição com data | Fallback determinístico no seu código, comparação de data é trabalho de código, não de modelo |
| State tratado como dado e não como hostil (injeção via conteúdo) | State com instrução embutida no conteúdo do usuário | Teste de regressão garantindo que o ramo perigoso não é acionado só pelo texto |
| Queda de acurácia quando o state cresce com conteúdo irrelevante | Choice com confiança baixa em state inchado | Limitar o que entra no state e logar quando a confiança despenca |
| Conflito entre instructions e criteria | Confiança baixa sem motivo aparente | Erro explícito no build da pergunta, não fallback silencioso |
| Ausência de geração de texto | Answer faltando para um id esperado | KeyError tratado com erro explícito, nunca string vazia pro usuário |
Os cinco fixtures que eu deixaria versionados no repo, sempre:
- confiança baixa (abaixo do teu threshold)
- empate na distribuição (duas opções praticamente iguais)
- score entre dois níveis (o clássico 1.30)
- noul perto de 0,5 (o modelo literalmente não sabe)
- answer faltando para um id que o código espera
Esse último é o mais esquecido e o que mais derruba produção
def test_answer_faltando_estoura_erro_explicito():
resposta = RespostaFake({}) # o id "categoria" não voltou
with pytest.raises(KeyError):
rotear_ticket(resposta)
Se o seu fluxo prefere fallback em vez de exceção, beleza, mas ele precisa estar ESCRITO em algum lugar. O teste é esse lugar
Problemas comuns ao mockar o Jev (e como prevenir)
Sintoma: a suíte ficou lenta, ou o teste de timeout demora uma eternidade
Causa: o SDK tem política de retry ligada por padrão. São 2 retries depois da tentativa inicial em erros de conexão, timeouts e respostas 408, 429 e 5xx, com backoff exponencial de 0,5s a 5s, 25% de jitter, Retry-After respeitado e orçamento total de 30s por chamada
Ou seja: você mockou um 500 pra testar o ramo de erro e o SDK, comportadinho, tentou de novo duas vezes antes de desistir
Solução: desligue o retry no ambiente de teste
client = TypeSafeClient(
api_key="chave-de-teste",
retry=RetryPolicy(max_retries=0), # importe conforme a referência do SDK
)
Como prevenir: deixe a construção do cliente em uma factory única do projeto, que lê o ambiente. Assim ninguém sobe cliente com retry ligado dentro de teste por distração
Sintoma: teste verde, produção vermelha
Causa: o mock não respeita o formato real da resposta. Você inventou um campo, esqueceu outro, e o seu código passou meses conversando com um Jev de mentira que só existe na sua imaginação
Solução: gere os fixtures a partir de respostas reais capturadas uma vez e salvas em arquivo, e mocke no nível HTTP (RESPX) em vez de no método do SDK. Assim a desserialização do SDK continua rodando dentro do teste
Como prevenir: um smoke test separado, fora da suíte principal, que bate no endpoint real de vez em quando e confere que o formato salvo ainda bate
Sintoma: AttributeError em confidence de Noul
Causa: alguém escreveu um helper genérico if answer.confidence < limite e passou um NoulAnswer nele. Noul não tem campo de confiança separado, a incerteza está no próprio número
Solução: trate Noul por outro caminho, comparando o float contra faixas (perto de 0,5 = não sabe). E use as views tipadas do response (.choices, .scores, .nouls) pra não misturar as primitivas por acidente
Como prevenir: um teste que passa um NoulAnswer no helper de roteamento e exige o comportamento correto. Já vi gente se ferrar com helper genérico demais, e o remédio é sempre o mesmo: teste que documenta a diferença
Sintoma: rede batendo no CI mesmo com mock configurado
Causa: TYPESAFE_BASE_URL não foi sobrescrita, ou o mock não casou com a rota (path diferente, host diferente) e a requisição vazou
Solução: aponte TYPESAFE_BASE_URL pra um host que não existe no ambiente de teste. Se algo escapar do mock, a suíte quebra na hora em vez de sair falando com a API
Como prevenir: um fixture global do pytest que seta chave fake e base URL de teste pra TODA a sessão, e um assert de que a rota mockada foi chamada (rota.called)
Ainda dentro de prevenção, vale conhecer o system-one-adapter-python, mantido pela própria TypeSafe AI no GitHub. Ele é um substituto drop-in do TypeSafeClient apoiado por APIs de LLM, com release v0.1.5 de 18/09/2026
Pra quem ainda está na waitlist e quer manter o código rodando enquanto a chave não chega, é um caminho que existe e está no ar
Vídeo: automação e fluxo de trabalho com IA
Pra quem quer começar do zero na ideia de tirar trabalho manual das costas e deixar a IA tocar parte do fluxo, este vídeo do canal mostra o Claude Cowork na prática, com tutorial e três casos reais
Próximo passo: uma suíte que roda offline
Recapitulando o padrão inteiro, que é curtinho de guardar na cabeça:
- Isola a chamada do SDK da regra de negócio
- Injeta transporte falso (
http_clientno Python,fetchno JS) e trava a base URL - Mocka a rota
POST /v1/systemonecom RESPX, conferindoAuthorizationeContent-Type - Escreve um fixture por primitiva, com probabilities e confidence coerentes entre si
- Faz um teste por ramo do SEU fluxo, incluindo os cinco casos de borda
O próximo movimento é montar a fábrica de fixtures no repo e conseguir rodar a suíte inteira sem chave nenhuma no ambiente
Chamada real fica reservada pra um smoke test separado, que roda quando você mandar, não a cada commit
E tem o argumento chato de planilha também: o Jev 1.13 custa US$ 0,042 por milhão de tokens de entrada, com saída gratuita. É barato, sim, mas teste offline não custa nada, roda em milissegundos e não fica instável porque a internet do CI resolveu tossir
Bora deixar a suíte rodando no modo avião? =)
Até o próximo post!
Perguntas frequentes
Como simular uma resposta de Score do Jev num fixture de teste?
Uma resposta de Score traz score, confidence, probabilities e legend, e no SDK Python probabilities e legend são chaveados por nível inteiro. Um exemplo real: score 1.30 significa quase todo o peso no nível 1, com um pouco no nível 2. Monte o fixture reproduzindo esses quatro campos e o teste da sua regra de negócio fica isolado da rede.
Dá pra testar timeout e retry do Jev sem esperar o orçamento de 30 segundos de verdade?
Dá sim. O SDK já vem com retry ligado por padrão: 2 tentativas extras após a inicial em erro de conexão, timeout e respostas 408, 429 e 5xx, com orçamento total de 30 segundos por chamada. Pra testar erro sem essa espera, use RetryPolicy(max_retries=0) e deixe o mock devolver o erro na primeira tentativa.
Como mockar o fetch no SDK JavaScript/TypeScript do Jev?
O cliente JavaScript expõe fetch como propriedade de configuração junto com baseURL, defaultHeaders, defaultModel e timeout. Basta passar uma função fetch falsa na criação do client.systemOne e ela substitui a chamada real pro endpoint. Isso evita subir qualquer servidor de teste só pra simular a resposta.
Quais casos de borda vale a pena cobrir além do que a doc de jaggedness já lista?
A própria página de jaggedness do jev-1.13 já dá o roteiro: leitura literal de negação e escopo, contagem numérica não confiável, datas tratadas como texto e não como quantidade ordenada, e state vulnerável a injeção porque é tratado como dado, não como hostil. Vale testar também a queda de acurácia quando o state cresce com conteúdo irrelevante e o conflito entre instructions e criteria pedindo coisas diferentes. Nenhum desses casos exige chamada real, todos cabem em fixture.
Testar código que chama o Jev usando a API de verdade sai caro?
O Jev cobra US$ 0,042 por milhão de tokens de entrada e a saída é gratuita, então o custo por chamada isolada é baixo. Ainda assim, rodar a suíte inteira contra a API real consome chave e depende de rede a cada push. Com mock e fixture você tira esse custo e essa dependência do caminho crítico do CI.
Existe alternativa pra rodar teste de integração sem depender da waitlist do Jev?
A TypeSafe AI mantém no GitHub o repositório typesafe-ai/system-one-adapter-python, na release v0.1.5 de 2026-09-18, que funciona como substituto drop-in do TypeSafeClient apoiado por APIs de LLM. Como o Jev ainda está em early access com acesso por waitlist desde 2026-09-15, esse adapter é uma rota pra exercitar o fluxo de integração sem esperar a chave chegar.
Formações
Formação Vibe Coding
Do Prompt ao Produto: Crie Software Real com IA
- 474 aulas
- 20 projetos
- 39h 27min
Blog | Mais populares

Como montar um workflow de automação combinando decisões do Jev em código
Aprenda a montar um workflow com Jev: decisões tipadas encadeadas em código, alta confiança agindo sozinha e casos incertos escalando para revisão.

Para quem o Jev serve (e para quem não serve)?
Jev serve pra roteamento, scoring e guardrails em IA, não pra texto ou código. Veja pra quem o Jev serve e quando evitar.

Jev decide, LLM escreve: como dividir os papéis dentro de um agente de IA
Jev é o modelo que decide, não escreve: entenda como dividir papéis entre Jev e LLM dentro de um agente de IA e quando usar cada um.
