Um contrato diz “vigência a partir de 1º de janeiro de 2025, com término em 31 de dezembro de 2027”. Um e-mail diz “devolve o formulário assinado até 14 de agosto”. Outro diz “vamos marcar a revisão para quinta que vem”. Você precisa dos três como data de verdade, no banco, para ordenar, comparar e disparar lembrete.
É aqui que muita integração quebra, e por um motivo declarado.
Por que a solução ingênua falha
Pedir a data ao modelo e usar o que voltar é o caminho natural — e é exatamente o
que a documentação oficial diz para não fazer. Comparação de data e hora está na
lista dos nove modos de falha do jev-1.13: o modelo lê datas como texto, não
como quantidade ordenada. Dizer qual vem primeiro, quanto tempo separa duas ou se
uma cai dentro de uma janela é pouco confiável, e piora com formatos misturados,
referências relativas e fronteiras de trimestre ou competência.
A segunda tentativa é uma expressão regular. Ela resolve “14/08/2026” e falha em “quinta que vem”, “no dia 14 do mês que vem”, “até o fim do próximo trimestre” — que é como as pessoas escrevem.
A terceira é pedir a data a um LLM generativo, e aí você troca o problema de leitura por um problema de confiança: o modelo pode devolver uma data plausível que o documento não diz.
O desenho das perguntas
A divisão da receita é a lição inteira: extração é julgamento, aritmética não é. O modelo lê as partes que o texto nomeia; o código monta a data.
São sete perguntas Choice numa chamada. A primeira descobre a forma da data; as outras seis leem as peças:
from typesafe_sdk import Choice
MESES = ["janeiro", "fevereiro", "março", "abril", "maio", "junho",
"julho", "agosto", "setembro", "outubro", "novembro", "dezembro"]
DIAS_DA_SEMANA = ["segunda", "terça", "quarta", "quinta", "sexta", "sábado", "domingo"]
ausente = "O documento não informa isso, ou não é uma data deste tipo."
def perguntas_de_data(papel: str) -> dict:
return {
"tipo": Choice(
instructions=(
f"Como {papel} está escrita? 'absoluta' = data de calendário que nomeia o mês; "
"'relativa' = dada em relação a hoje (hoje, amanhã, depois de amanhã ou um dia "
"da semana nomeado); 'nenhuma' = o documento não informa esta data."
),
criteria={"absoluta": None, "relativa": None, "nenhuma": None},
),
"mes": Choice(
instructions=f"Se {papel} é uma data absoluta, de que mês é?",
criteria={m: None for m in MESES} | {"nao_informado": ausente},
),
"dia": Choice(
instructions=f"Se {papel} é uma data absoluta, que dia do mês (1 a 31)?",
criteria={str(d): None for d in range(1, 32)} | {"nao_informado": ausente},
),
"ano": Choice(
instructions=(
f"Se {papel} é uma data absoluta, de que ano? Escolha 'nao_informado' se o "
"documento não diz o ano, ou 'fora_da_lista' se diz um ano fora da lista."
),
criteria={str(a): None for a in range(1900, 2051)}
| {"fora_da_lista": "Um ano é informado, mas está fora da lista.",
"nao_informado": "Nenhum ano é informado para esta data."},
),
"ancora": Choice(
instructions=f"Se {papel} é relativa a hoje, qual é o dia?",
criteria={"hoje": None, "amanha": None, "depois_de_amanha": None,
"dia_da_semana": None, "nao_informado": ausente},
),
"dia_da_semana": Choice(
instructions=f"Se {papel} nomeia um dia da semana, qual?",
criteria={d: None for d in DIAS_DA_SEMANA} | {"nao_informado": ausente},
),
"semana": Choice(
instructions=(
f"Se {papel} nomeia um dia da semana, de que semana? 'proxima' para 'quinta que "
"vem'; 'atual' para 'esta quinta'; 'nenhuma' para um dia solto, sem qualificador."
),
criteria={"atual": None, "proxima": None, "nenhuma": None},
),
}
Duas coisas merecem destaque. Cada parte de uma data é um conjunto fechado —
doze meses, trinta e um dias possíveis, uma faixa de anos —, e é isso que
transforma extração em escolha entre opções enumeradas em vez de texto livre. E
cada pergunta tem escape: nao_informado e fora_da_lista existem para o modelo
poder dizer que o texto não diz, em vez de chutar. Como escrever esses critérios
está em critérios que separam.
O código faz o resto: preenche o ano quando o texto não informa, resolve qual quinta-feira é “quinta que vem” pela convenção que você escolheu, e reporta a menor confiança entre as partes usadas.
O que o experimento oficial mediu
O cookbook roda a função sobre quatro documentos curtos, com “hoje” fixado em 30
de julho de 2026 (uma quinta-feira) para que as datas relativas sejam
reproduzíveis, e limiar de revisão em 0,60. Os números publicados pela TypeSafe,
com o jev-1.12:
| O que foi pedido | Resultado | Confiança | Ação |
|---|---|---|---|
| Data de início do contrato | 2025-01-01 | 0,97 | automática |
| Data de término do contrato | 2027-12-31 | 0,91 | automática |
| Prazo para devolver o formulário (sem ano no texto) | 2026-08-14 | 0,95 | automática |
| Data de uma reunião que o formulário nunca menciona | nenhuma | 0,46 | revisão |
| Data em que a pesquisa fecha (“hoje”) | 2026-07-30 | 0,94 | automática |
| Data da revisão de design (“quinta que vem”) | 2026-08-06 | 0,92 | automática |
Cinco aceitas automaticamente, uma para revisão. As duas linhas mais interessantes são a do prazo sem ano — o modelo respondeu “não informado” e o código preencheu 2026 — e a da data inexistente: o tipo voltou “absoluta” sem mês para acompanhar, o código não conseguiu montar data nenhuma, e a confiança de 0,46 sinalizou o caso para uma pessoa. Nenhuma data inventada.
O que muda em português
Aqui a adaptação é real e vale mais que uma tradução. Três pontos:
Formato. “14/08” é dia/mês no Brasil e mês/dia nos Estados Unidos. Como as opções são enumeradas, você evita a ambiguidade perguntando mês e dia separadamente em vez de pedir a data formatada — o que já é o desenho da receita.
Vocabulário nosso. “Semana que vem”, “quinze dias” (que costuma significar duas semanas), “no dia 10 do mês que vem”, “até o fim do expediente”. Vale enumerar as âncoras que a sua base realmente usa, com descrição explícita.
Dias úteis. Prazo brasileiro conta dia útil com frequência (“cinco dias úteis para estorno”), e feriado varia por município. Isso é calendário, ou seja, código — e mais um motivo para o modelo não fazer a conta.
Como a TypeSafe declara que o inglês é a língua principal de treino, teste as sete perguntas com os seus documentos antes de baixar o limiar de revisão.
Onde isso quebra
- Aritmética de data continua fora. A receita extrai; ordenar, medir distância e checar janela é código. O modo de falha está descrito em limites do jev-1.13.
- Lista longa de opções. O ano como conjunto de 1900 a 2050 é grande. O próprio cookbook sugere, se isso incomodar, extrair os números com cara de ano antes e oferecer só esses ao modelo — o que é a receita de extrair valores.
- Data em documento grande. Contrato inteiro no state infla a entrada e derruba acurácia; mande a cláusula, não o contrato.
- Duas datas do mesmo tipo. “A partir de” e “até” no mesmo parágrafo exigem uma rodada de perguntas por papel, com o papel nomeado na instrução — é assim que o experimento pega início e fim do contrato.
O índice das receitas está em receitas.
Perguntas frequentes
Por que não pedir a data direto ao modelo?
Porque a documentação lista comparação de datas entre os modos de falha do jev-1.13: o modelo lê datas como texto, não como quantidade ordenada. Pedir as partes é leitura, que ele faz bem; montar e comparar é aritmética, que pertence ao código.
Quantas perguntas a receita usa?
Sete perguntas Choice em uma única chamada: o tipo da data (absoluta, relativa ou inexistente), mês, dia, ano, âncora relativa, dia da semana e qual semana. O código lê só as partes que o tipo exige.
Como a receita lida com data que o documento não menciona?
Com escapes explícitos. O tipo tem a opção 'nenhuma' e as partes têm a opção 'não informado', então o modelo pode dizer que o texto não diz, em vez de adivinhar. No experimento, a data inexistente voltou vazia com confiança 0,46 e foi para revisão.
E se o ano não estiver escrito?
O modelo responde 'não informado' e o código preenche: usa o ano corrente e avança para o próximo quando a data já passou há mais de um mês. Foi assim que o prazo sem ano resolveu para 2026-08-14 no experimento.
Qual confiança a data recebe?
A menor entre as partes que entraram na montagem. Assim, uma leitura fraca em qualquer parte derruba a confiança da data inteira, e o limiar de revisão do cookbook (0,60) pega o conjunto.