JEV na prática: decisões tipadas em FastAPI pelo Vercel AI Gateway
English summary: A production-minded walkthrough of using TypeSafe AI's JEV through Vercel AI Gateway to triage support tickets in a FastAPI vertical slice. It covers typed Choice, Score, and Noul questions, graceful degradation, validation, evals, cost, security, rollback, and the gap between a working proof of concept and a system you can trust.
Publicado: setembro de 2026
Tempo de leitura: 18 min
Palavras-chave: JEV, TypeSafe AI, Vercel AI Gateway, FastAPI, decisões tipadas, vertical slice, Python, evals
Um ticket diz que o checkout parou, há clientes esperando e o assunto veio marcado como “dúvida”. Seu backend precisa decidir três coisas: isso é urgente, qual time assume e qual o nível de prioridade.
Pedir a um LLM para “analisar o ticket e responder em JSON” funciona na demo. Em produção, a pergunta importante vem depois: quem garante que o departamento existe, que a probabilidade está no intervalo certo e que uma indisponibilidade do modelo não impede o ticket de ser criado?
Foi esse problema que eu usei para testar o JEV, modelo da TypeSafe AI voltado a decisões estruturadas, por meio do Vercel AI Gateway. A implementação entrou em uma fatia vertical FastAPI real: router, service, repository, contrato de avaliação e testes de degradação. Foi uma POC funcional feita durante algumas horas de trabalho, não um benchmark de produção nem uma promessa de acurácia.
Minha conclusão é específica: JEV é interessante quando seu software precisa de um julgamento estreito e probabilístico, mas a aplicação deve continuar dona do contrato, da política e do fallback. Tipar a resposta reduz uma classe de erros. Não transforma uma inferência em verdade.
TL;DR
- JEV recebe um
statee perguntas tipadas; devolve respostas que o código consegue consumir sem extrair texto livre. - Os três primitivos documentados são
choice,scoreenoul.Noulrepresenta a probabilidade de uma resposta “sim”. - No meu fluxo, uma chamada decide urgência, departamento e nível de urgência para um ticket.
- O Vercel AI Gateway expõe JEV como
typesafe-ai/jevno endpoint/v1/evaluate. - A cota gratuita documentada em 19 de setembro de 2026 era de US$ 5 por mês para cada time no free tier. Ela começa na primeira requisição; comprar créditos move o time para o tier pago e remove o crédito mensal. Isso é uma condição comercial mutável, não “API grátis para sempre”.
- Timeout, erro HTTP, resposta inválida ou baixa confiança precisam levar a fallback ou revisão humana, não a uma ação silenciosa.
- O exemplo mínimo abaixo ensina a integração. A seção de produção mostra o que ainda falta antes de colocar decisões no caminho crítico.
O que JEV é, e o que ele não é
A TypeSafe chama JEV de seu primeiro modelo “System One”: um modelo para decisões rápidas e estruturadas. Em vez de pedir prosa e tentar recuperar estrutura depois, você envia o estado do problema e perguntas com tipos conhecidos.
Os primitivos são:
| Tipo | Pergunta que resolve | Resposta útil ao código |
|---|---|---|
choice | Qual opção definida se aplica? | opção escolhida, probabilidades por opção e confiança |
score | Em qual nível ordenado o caso cai? | score, probabilidades por nível e confiança |
noul | Qual a probabilidade de “sim”? | número entre 0 e 1 |
O nome incomum noul importa porque copiar um contrato como boolean quebraria a integração. No Gateway, a página do modelo descreve capacidades como Choice, Score e Boolean; na API de avaliação e na documentação TypeSafe, o tipo usado no payload é noul. O contrato da API vence o rótulo de marketing.
JEV não é:
- uma regra de negócio;
- um mecanismo de autorização;
- uma garantia de calibração no seu domínio;
- um substituto automático para validação semântica;
- a melhor escolha quando um
if, regex, enum ou consulta ao banco resolve o caso de forma determinística.
Use código comum para fatos. Considere um modelo de decisão quando o input é ambíguo e a saída possível já é conhecida.
Rode o núcleo em poucos minutos
Este é um exemplo didático, adaptado da minha implementação. Requer Python 3.11+, httpx e uma chave do Vercel AI Gateway.
python -m venv .venv
source .venv/bin/activate
pip install "httpx>=0.27,<1"
export AI_GATEWAY_API_KEY="sua-chave"
Crie jev_demo.py:
import os
from typing import Any
import httpx
GATEWAY_URL = "https://ai-gateway.vercel.sh"
MODEL = "typesafe-ai/jev"
QUESTIONS = {
"is_urgent": {
"type": "noul",
"instructions": "Does this ticket require immediate attention?",
},
"department": {
"type": "choice",
"instructions": "Which team should handle this ticket?",
"criteria": {
"billing": "Payments, invoices, refunds, or charges",
"technical": "Bugs, outages, integrations, or product errors",
"shipping": "Delivery, tracking, or logistics",
"general": "Anything outside the other departments",
},
},
"urgency": {
"type": "score",
"instructions": "How urgent is this ticket?",
"criteria": ["low", "medium", "high"],
},
}
def evaluate_ticket(subject: str, message: str) -> dict[str, Any]:
api_key = os.environ["AI_GATEWAY_API_KEY"]
payload = {
"model": MODEL,
"state": {"subject": subject, "message": message},
"questions": QUESTIONS,
}
with httpx.Client(timeout=10.0) as client:
response = client.post(
f"{GATEWAY_URL}/v1/evaluate",
headers={"Authorization": f"Bearer {api_key}"},
json=payload,
)
response.raise_for_status()
return response.json()["answers"]
if __name__ == "__main__":
answers = evaluate_ticket(
subject="Checkout fora do ar",
message="Pagamentos falham há 20 minutos; 42 pedidos estão bloqueados.",
)
print(answers)
Execute:
python jev_demo.py
O formato exato inclui metadados definidos pelo serviço, mas o consumo relevante segue este desenho:
{
"is_urgent": {"noul": 0.97},
"department": {
"choice": "technical",
"probabilities": {
"billing": 0.01,
"technical": 0.96,
"shipping": 0.0,
"general": 0.03
}
},
"urgency": {"score": 2.8}
}
Valores acima são ilustrativos, não output prometido. O ganho estrutural está em conhecer os campos possíveis antes da requisição. Seu código ainda deve validar a resposta real.
Intuição: modelo propõe, política dispõe
Pense no JEV como um sensor. Um sensor mede e carrega incerteza; ele não decide sozinho se uma esteira industrial deve parar. O controlador lê a medição, verifica limites, considera outras condições e escolhe a ação segura.
Aqui, o modelo produz julgamento:
ticket → {urgente: 0.97, departamento: technical, score: 2.8}
A aplicação produz política:
if result.is_urgent >= 0.90 and result.confidence >= 0.80:
page_on_call()
elif result.is_urgent >= 0.60:
enqueue_human_review()
else:
follow_normal_queue()
Os cortes pertencem ao seu domínio. Eles não devem ficar escondidos em prompt, resposta do modelo ou regra do provedor.
Minha fatia vertical em FastAPI
Na PR privada do POC FastAPI, implementei um CRUD de tickets no mesmo padrão vertical já usado pelo projeto. O link registra a origem do estudo, mas o conteúdo não é acessível sem permissão. Por isso, os trechos úteis e sanitizados estão reproduzidos aqui; nenhuma credencial ou configuração privada foi copiada.
flowchart LR
C[Cliente HTTP] --> R[Ticket Router]
R --> S[Ticket Service]
S --> J[JevClient]
J --> G[Vercel AI Gateway]
G --> M[typesafe-ai/jev]
S --> P[Ticket Repository]
P --> D[(MongoDB)]
J -. timeout ou erro .-> F[Fallback sem avaliação]
F --> P
O JevClient é um adapter fino. Ele conhece URL, autenticação, timeout e envelope HTTP. O service conhece o caso de uso e decide o que fazer quando o adapter falha.
from typing import Any
import httpx
class JevEvaluationError(RuntimeError):
pass
class JevClient:
def __init__(
self,
*,
api_key: str,
model: str = "typesafe-ai/jev",
base_url: str = "https://ai-gateway.vercel.sh",
timeout: float = 10.0,
) -> None:
self.api_key = api_key
self.model = model
self.base_url = base_url.rstrip("/")
self.timeout = timeout
def evaluate(self, state: Any, questions: dict[str, Any]) -> dict[str, Any]:
try:
with httpx.Client(timeout=self.timeout) as client:
response = client.post(
f"{self.base_url}/v1/evaluate",
headers={"Authorization": f"Bearer {self.api_key}"},
json={
"model": self.model,
"state": state,
"questions": questions,
},
)
response.raise_for_status()
body = response.json()
except (httpx.HTTPError, ValueError, KeyError) as error:
raise JevEvaluationError("evaluation unavailable") from error
answers = body.get("answers")
if not isinstance(answers, dict):
raise JevEvaluationError("invalid evaluation contract")
return answers
O fluxo do POST /tickets preserva a função principal mesmo sem IA:
sequenceDiagram
actor User as Cliente
participant API as FastAPI Router
participant Service as Ticket Service
participant Jev as JevClient
participant Gateway as Vercel AI Gateway
participant DB as Repository
User->>API: POST /tickets
API->>Service: create(subject, message)
Service->>Jev: evaluate(state, questions)
Jev->>Gateway: POST /v1/evaluate
alt resposta válida
Gateway-->>Jev: answers tipadas
Jev-->>Service: answers
Service->>DB: save(ticket + evaluation)
else timeout, HTTP error ou contrato inválido
Gateway--xJev: falha
Jev-->>Service: JevEvaluationError
Service->>DB: save(ticket + evaluation null)
end
DB-->>API: ticket persistido
API-->>User: 201 Created
Esse detalhe mudou o risco do experimento. JEV enriquece o ticket; não possui a criação. Em falha, o dado principal continua entrando no sistema e pode ser triado manualmente.
Contratos e ownership
Uma integração confiável precisa responder “quem é dono?” antes de responder “qual modelo?”.
| Responsabilidade | Dono | Evidência | Reversão |
|---|---|---|---|
| esquema das perguntas | time do domínio | contrato versionado e revisão de código | voltar à versão anterior |
| chamada ao Gateway | adapter de infraestrutura | métricas de latência/status e testes HTTP fake | desligar provider via flag |
| validação da resposta | boundary da aplicação | erros de schema e fixtures canônicas | encaminhar para fallback |
| cortes e ação | service/policy | decisão registrada com versão da policy | modo shadow ou advisory |
| segredo do Gateway | plataforma/secret manager | auditoria de acesso e rotação | revogar chave |
| qualidade do modelo | owner do produto | eval por segmento e revisão de erros | regra determinística/manual |
O modelo nunca deve receber permissão implícita para executar uma ação sensível. Ele fornece evidência para uma policy que o código controla.
Do brinquedo para produção
O script mínimo ensina endpoint e payload. Ele omite pontos que importam sob carga ou incidente:
- Validação estrutural e semântica. Verifique IDs esperados, tipo de cada resposta, opções permitidas, limites
[0, 1], soma das probabilidades e faixa do score. Pydantic ajuda, mas tipos válidos não provam decisão correta. - Cliente reutilizável. Não abra uma conexão por request em carga alta. Injete
httpx.AsyncClientcom lifecycle controlado pelo FastAPI. - Timeout e orçamento de retry. Retry cego pode duplicar custo e piorar congestionamento. Restrinja a erros transitórios, use backoff com jitter e mantenha deadline total.
- Idempotência. Persistência e avaliação podem terminar em ordens diferentes. Defina chave idempotente, status
pending/evaluated/failedou outbox se reprocessamento for necessário. - Observabilidade sem vazar dados. Registre request ID, versão do contrato, modelo, latência, status, rota tomada e custo. Não grave ticket completo, bearer token ou PII por conveniência.
- Concorrência e escala horizontal. Estado de retry, locks e fila não podem morar na memória de uma única instância. Workers devem conseguir reprocessar por ID, com claim atômico e deduplicação.
- Versionamento. Guarde versão da pergunta, policy e modelo junto à decisão.
typesafe-ai/jevé o ID do Gateway; a TypeSafe documentavajev-latestcomo alias dejev-1.13.0em 19 de setembro de 2026. Aliases facilitam upgrades, mas reduzem reprodutibilidade.
Seis falhas que aparecem depois da demo
1. O Gateway está indisponível
Timeouts, 429, 5xx e falhas de rede acontecem. Se triagem é enriquecimento, salve o ticket com evaluation: null, emita métrica e reprocese depois. Se a decisão protege uma ação irreversível, falhe fechado ou exija humano.
2. A resposta passa no JSON, mas viola o contrato
department="sales" não deve entrar se o enum só aceita quatro times. Uma probabilidade 1.2 também não. Valide antes de persistir ou agir. Resposta inválida é falha de integração, não “melhor esforço”.
3. Confiança alta, decisão errada
Confiança não substitui acurácia. Meça falsos negativos de urgência e erros por idioma, produto e tipo de cliente. Um corte global pode esconder desempenho ruim num segmento pequeno.
4. O input contém instrução maliciosa
Texto do ticket é dado não confiável. A superfície é menor do que em geração com tools, mas prompt injection ainda pode influenciar classificação. Não inclua segredos no estado; mantenha ações fora do modelo; teste entradas adversariais.
5. Custo muda sem mudança de código
Em 19 de setembro de 2026, a página de pricing do Gateway documentava US$ 5 mensais para times no free tier, enquanto superfícies do catálogo mostravam JEV como “Free” e o detalhe do modelo exibia US$ 0,04 por milhão. Essa divergência é motivo para tratar preço como snapshot, medir uso real e configurar alertas. Comprar créditos remove o crédito mensal gratuito daquele time, segundo a documentação vigente.
6. Um alias muda o comportamento
Atualização de modelo pode alterar distribuição das respostas sem quebrar schema. Rode eval antes de promover nova versão. Guarde versão efetiva quando o provedor a expuser e mantenha rollback para modelo ou policy anterior.
Adoção incremental: observe antes de automatizar
Automação não precisa nascer com permissão de agir.
| Modo | Comportamento | Critério para avançar |
|---|---|---|
offline | roda eval em dataset histórico | baseline e taxonomia aceitos |
shadow | avalia tráfego real, sem afetar rota | cobertura, latência e erros conhecidos |
advisory | mostra sugestão ao operador | concordância e override medidos |
enforce | policy executa ações limitadas | SLO, cortes e rollback aprovados |
Eu comecei com dependency injection. Nos testes FastAPI, get_jev_client é substituído por um fake determinístico e por outro que lança erro. Isso cobre caminho feliz e degradação sem consumir API ou depender de credencial real.
A mesma arquitetura apareceu em duas PRs públicas do meu hub de skills:
- PR #38 — decision-native routing: adicionou modos
heuristic,shadow,advisoryeenforce, além de um envelope tipado para decisões entre estágios. Merge em 19 de setembro de 2026. - PR #40 — typed decision engineering: tornou o padrão independente de provedor, com schemas, validação semântica, composição determinística, roteamento por incerteza e benchmark de custo/tokens. Merge em 19 de setembro de 2026.
O ponto não é colocar JEV em todo lugar. É separar julgamento, contrato e autoridade de execução para poder trocar o provider sem redesenhar o domínio.
O mesmo padrão dentro do meu harness
Depois da POC FastAPI, levei a ideia para o harness que uso no dia a dia. O caso está documentado em duas PRs abertas, não em um exemplo inventado:
- Lemon Code PR #25 — estratégia de roteamento JEV: adiciona JEV ao Auto Dispatch, chama
typesafe-ai/jevpelo Vercel AI Gateway e mantém uma réplica local determinística,Builtin One, para fallback. - Lemon Code App PR #13 — proveniência JEV no app macOS: transporta
route.jevde forma aditiva no RPC e mostra fonte, modo, provider/modelo, probabilidade, confiança e fallback na interface.
O PR do core inclui typecheck, 849 testes, 2.879 assertions, 50 testes focados e smoke live com cinco casos. O benchmark local mediu média de 0,070 ms antes e 72,177 ms depois; o smoke remoto, uma execução por caso, mediu média de 536,641 ms. São números da POC, datados de 20 de setembro de 2026, não SLO nem benchmark de produção. O smoke observou respostas tipadas e fallbacks; isso prova integração exercitada, não qualidade estatística suficiente para automatizar decisões críticas.
flowchart TD
Q[Pedido do usuário] --> R[Auto Dispatch]
R --> H{Modo JEV}
H -->|offline| B[Builtin One determinístico]
H -->|shadow| E[Avalia, não troca rota]
H -->|advisory| J[JEV via Vercel AI Gateway]
H -->|enforce| J
J --> G{Resposta válida + gates}
G -->|sim| D[Decisão candidata]
G -->|não| B
D --> P[Policy local]
P --> F[Provider/model/runtime final]
D --> T[RouteTrace + RPC]
T --> A[App macOS: proveniência visível]
Human-in-the-loop como contrato, não como botão
O paradigma muda quando a incerteza vira parte do protocolo. O modelo não recebe autoridade para “escolher o próximo agente”; ele propõe uma decisão entre candidatos que o código já conhece. A policy local aplica dois gates no modo padrão do PR: confidence >= 0.65 e probability >= 0.55. Resposta inválida, baixa confiança, timeout ou erro remoto caem para Builtin One.
type JevGate = {
mode: "shadow" | "advisory" | "enforce";
confidenceFloor: number;
probabilityFloor: number;
};
const defaultGate: JevGate = {
mode: "advisory",
confidenceFloor: 0.65,
probabilityFloor: 0.55,
};
shadow mede sem alterar rota. advisory mostra sugestão para inspeção. enforce permite aplicar policy somente depois de evals e rollback aprovados. Cancelamento do caller aborta a avaliação sem inventar fallback; o erro remoto chega ao restante do sistema como classificação estável, não como stack trace de provider.
Essa separação preserva controle humano em três pontos: o conjunto de candidatos, os limites de confiança e a ação final. O ciclo real é JEV → sugestão → operador vê proveniência → aceita ou faz override → decisão fica auditável. A PR do App implementa visibilidade de proveniência, não um workflow completo de aprovação humana. A UI macOS recebe route.jev opcional; payloads legados continuam decodificando quando o core ainda não envia esse campo. Credencial, prompt bruto e decisão de provider permanecem no core. A interface apenas torna a proveniência auditável; o readiness router continua soberano.
O ganho não é “tirar humano do loop”. É tirar julgamento implícito do loop: quando o sistema está confiante e a policy permite, ele avança; quando não está, entrega evidência suficiente para uma pessoa revisar.