A API do Jev é simples o bastante para usar com curl, e a
primeira chamada mostra esse caminho. Em
aplicação de verdade, o SDK oficial paga o próprio peso: ele dá tipos para as
perguntas, acessores por primitiva, retentativa configurada e exceções que dizem
o que aconteceu.
Instalação e chave
A documentação indica Python 3.10 ou superior.
pip install typesafe-sdk
# ou
uv add typesafe-sdk
O cliente lê a chave da variável de ambiente TYPESAFE_API_KEY. Existem outras
três variáveis reconhecidas, e vale conhecer porque elas evitam código
desnecessário:
| Variável | Configura | Padrão |
|---|---|---|
TYPESAFE_API_KEY | A chave da API (obrigatória) | — |
TYPESAFE_BASE_URL | A raiz da API | https://api.typesafe.ai |
TYPESAFE_DEFAULT_MODEL | O modelo padrão | jev-latest |
TYPESAFE_LOG_LEVEL | O nível do logger typesafe_sdk | não definido |
A chamada síncrona
O método é system_one, e ele recebe o state e o mapa de perguntas. As perguntas
são objetos tipados: Choice, Score e Noul.
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient
estado = {
"mensagem": "A fatura veio com um valor que não reconheço e ninguém me explica.",
"politica": "Cobranças não reconhecidas são apuradas em até cinco dias úteis.",
}
perguntas = {
"cobranca": Noul(instructions="`mensagem` trata de cobrança?"),
"tom": Choice(
instructions="Qual é o tom de quem escreveu `mensagem`?",
criteria={"calmo": None, "frustrado": None, "irritado": None},
),
"urgencia": Score(
instructions="Qual a urgência de `mensagem`?",
criteria=["pode esperar", "esta semana", "hoje"],
),
}
with TypeSafeClient() as client:
resultado = client.system_one(estado, perguntas)
print(resultado.nouls["cobranca"].noul)
print(resultado.choices["tom"].choice)
print(resultado.scores["urgencia"].score)
Dois detalhes desse trecho. Primeiro, criteria={"calmo": None, ...}: a
documentação aceita descrição nula quando o nome da opção já diz tudo. Segundo, as
crases em `mensagem` apontam a parte do state que cada pergunta deve julgar
— prática recomendada quando o state é um objeto, explicada em
state.
A chamada assíncrona
Mesmo contrato, com async with e await:
import asyncio
from typesafe_sdk import AsyncTypeSafeClient, Choice, Noul
async def main() -> None:
async with AsyncTypeSafeClient() as client:
resultado = await client.system_one(
"Fui cobrado duas vezes. Resolvam hoje, por favor.",
{
"cobranca": Noul(instructions="É sobre cobrança?"),
"tom": Choice(
instructions="Qual é o tom?",
criteria={"calmo": None, "irritado": None},
),
},
)
print(resultado.nouls["cobranca"].noul, resultado.choices["tom"].choice)
asyncio.run(main())
Use o assíncrono quando a sua aplicação já é assíncrona, ou quando você vai disparar muitas chamadas em paralelo — lembrando que, dentro de uma chamada, as perguntas já correm em paralelo, então o assíncrono serve para vários states, não para várias perguntas do mesmo state.
Ler respostas tipadas
Você pode ler por answers, com o identificador da pergunta, ou pelos acessores
por tipo — nouls, choices e scores —, que dão o objeto certo para cada
primitiva. Um ScoreAnswer traz score, confidence, probabilities e
legend como campos tipados, e no SDK as chaves de probabilities e legend são
níveis inteiros, não strings como no JSON cru.
resposta = resultado.scores["urgencia"]
print(resposta.score) # posição na régua, pode cair entre níveis
print(resposta.confidence) # 0 a 1, derivado da distribuição
print(resposta.probabilities) # {0: ..., 1: ..., 2: ...}
print(resposta.legend[2]) # "hoje"
Para escolher o modelo, passe no construtor: TypeSafeClient(model="jev-1.13.0").
Fixar a versão é a recomendação da documentação quando você já calibrou limiares,
porque um alias se move quando sai versão nova. Para listar o que a sua conta
pode chamar, client.models.list().
Retentativa
A RetryPolicy já vem ligada com valores padrão. Conhecer esses padrões evita
duplicar lógica de retry por cima:
| Campo | Padrão | O que faz |
|---|---|---|
max_retries | 2 | Tentativas adicionais depois da primeira; 0 desliga |
backoff_initial | 0,5 s | Primeiro atraso, dobrando a cada tentativa |
backoff_max | 5,0 s | Teto do atraso |
backoff_jitter | 0,25 | Fração do atraso sorteada para baixo |
http_statuses | 408, 429 e 500 a 599 | Códigos que provocam nova tentativa |
respect_retry_after | verdadeiro | Honra Retry-After e retry-after-ms |
timeout | 30,0 s | Orçamento total por chamada, incluindo esperas |
Para ajustar, passe no cliente ou na chamada:
from typesafe_sdk import RetryPolicy, TypeSafeClient
client = TypeSafeClient(
retry=RetryPolicy(max_retries=3, timeout=10.0, http_statuses={429, 500, 502, 503, 504})
)
O orçamento total é a parte que mais gente esquece: o SDK para antes de uma
retentativa cuja espera alcançaria o limite, e repropaga o último erro. Em fluxo
interativo, um timeout curto vale mais que muitas tentativas.
Exceções
TypeSafeError é a base. TypeSafeAPIError representa resposta HTTP sem sucesso e
traz status, body, headers, endpoint e request_id — esse último vem do
cabeçalho x-typesafe-request-id e é o que você quer no log para abrir suporte.
from typesafe_sdk import TypeSafeAPIError, TypeSafeRateLimitError
try:
resultado = client.system_one(estado, perguntas)
except TypeSafeRateLimitError as erro:
esperar(erro.retry_after_ms)
except TypeSafeAPIError as erro:
registrar(erro.status, erro.request_id, erro.endpoint)
As especializações seguem os códigos: TypeSafeBadRequestError (400),
TypeSafeAuthenticationError (401), TypeSafePermissionDeniedError (403),
TypeSafeNotFoundError (404), TypeSafeUnprocessableEntityError (422),
TypeSafeRateLimitError (429, com retry_after_ms) e
TypeSafeInternalServerError (5xx). Falha sem resposta HTTP levanta
TypeSafeAPIConnectionError, e estouro de tempo levanta
TypeSafeAPITimeoutError. Quando a resposta chega bem-formada no HTTP mas sem os
dados obrigatórios, vem TypeSafeAPIResponseValidationError, com field_path
apontando o campo problemático.
Log: um aviso que importa no Brasil
O SDK registra no logger typesafe_sdk. Em info, uma linha de resumo por
requisição; em debug, também cabeçalhos e corpos. A documentação é explícita:
cabeçalhos secretos são mascarados, os corpos não são.
Se o seu state carrega dado pessoal — mensagem de cliente, currículo, fatura —,
debug em produção significa dado pessoal no arquivo de log. Isso é decisão de
governança, não de conveniência, e conversa direto com o motivo pelo qual vale
guardar probabilidade e confiança de cada decisão automatizada.
Compatibilidade para frente
Duas válvulas úteis quando a API anda mais rápido que o SDK: extra_body manda
campos que a sua versão do SDK ainda não modela, e uma pergunta pode ir como
dicionário puro em vez de objeto tipado. Respostas de tipo desconhecido são
ignoradas com aviso no log, e raw_http_response dá acesso ao JSON completo.
O próximo passo depende do que você vai construir: para desenhar as perguntas, vá para primitivas; para o índice da seção, volte para como usar o Jev.
Perguntas frequentes
Como instalo o SDK Python do Jev?
Com pip install typesafe-sdk ou uv add typesafe-sdk. A documentação indica Python 3.10 ou superior. O cliente lê a chave da variável de ambiente TYPESAFE_API_KEY.
Qual a diferença entre TypeSafeClient e AsyncTypeSafeClient?
São o mesmo contrato em dois modos: o síncrono usa with e chamadas diretas, o assíncrono usa async with e await. Escolha pelo resto da sua aplicação; a forma das perguntas e das respostas é idêntica.
Como leio a resposta no SDK?
Por answers, com o identificador que você escolheu, ou pelos acessores por tipo: nouls, choices e scores. No SDK, probabilities e legend de um Score vêm indexados por nível inteiro, não por string.
O SDK já repete quando dá erro?
Sim. A RetryPolicy padrão faz até 2 novas tentativas, com backoff inicial de 0,5 s dobrando até 5 s, jitter de 0,25, repetindo nos status 408, 429 e 500 a 599, respeitando Retry-After, com orçamento total de 30 s por chamada.
Quais exceções eu preciso tratar?
TypeSafeError é a base. TypeSafeAPIError traz status, body, headers e request_id; dela descendem os erros por código, como TypeSafeAuthenticationError (401), TypeSafeUnprocessableEntityError (422) e TypeSafeRateLimitError (429), que expõe retry_after_ms.
O log do SDK pode vazar conteúdo?
No nível debug, sim: a documentação avisa que cabeçalhos secretos são mascarados, mas os corpos de requisição e resposta não são. Se o state carrega dado pessoal, não deixe debug ligado em produção.