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

diagrama mostrando como testar código que chama o Jev usando mocks e fixtures
Resposta rápida

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 (ou uv 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ção
  • TYPESAFE_DEFAULT_MODEL: o modelo padrão. Sem isso, o cliente usa o alias jev-latest, que hoje resolve para jev-1.13.0
  • TYPESAFE_BASE_URL: a URL base, por padrão https://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
Formação Recomendada

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

  1. 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

  1. 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

  1. 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

  1. 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

  1. 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:

  1. Isola a chamada do SDK da regra de negócio
  2. Injeta transporte falso (http_client no Python, fetch no JS) e trava a base URL
  3. Mocka a rota POST /v1/systemone com RESPX, conferindo Authorization e Content-Type
  4. Escreve um fixture por primitiva, com probabilities e confidence coerentes entre si
  5. 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.




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 Vibe Coding

Formação Vibe Coding

Do Prompt ao Produto: Crie Software Real com IA

  • 474 aulas
  • 20 projetos
  • 39h 27min

Blog | Mais populares