Um memorando chega por e-mail em texto puro. As linhas foram quebradas no meio das frases, os títulos perderam a marcação, as duas listas perderam os marcadores e um aviso importante ficou indistinguível de um parágrafo comum. Você precisa daquilo como markdown, para publicar na base de conhecimento.
O conteúdo está todo lá. O que se perdeu foi a estrutura.
Por que a solução ingênua falha
Pedir a um modelo generativo que “reescreva isto em markdown” resolve na aparência e cria um risco que só aparece depois: a reescrita pode mudar palavras. Em memorando interno isso é irritante; em norma, contrato ou bula, é inaceitável — e a alteração é difícil de detectar justamente porque o texto sai bom.
A segunda tentativa é heurística pura: linha curta é título, linha que começa com verbo no infinitivo é item de lista. Funciona no documento que você usou para escrever as regras e quebra no próximo.
E existe uma terceira armadilha, mais sutil, que o cookbook documenta: perguntar a coisa errada ao modelo. Voltamos a ela adiante, porque é a lição mais transferível desta receita.
O desenho das perguntas
Duas requisições, e uma divisão clara de trabalho: o código lê a evidência direta, o modelo julga o que sobra, o código renderiza.
O que fica no código. Linhas em branco e marcadores explícitos (-, 1.,
#) são fatos do texto: o código os lê e nunca os manda ao modelo para
reconsiderar. No memorando do experimento, as linhas em branco sobreviveram e
todos os marcadores se perderam — então o modelo recebe só o que o código não
consegue decidir.
Requisição 1, costurar. Uma pergunta Noul por par de linhas vizinhas, todas juntas:
from typesafe_sdk import Noul, NoulCriteria
def pergunta_de_juncao(i: int) -> Noul:
return Noul(
instructions=(
f"A linha L{i:03d} começa no meio de uma frase, continuando uma frase "
f"deixada incompleta no fim da linha L{i - 1:03d}?"
),
criteria=NoulCriteria(
true="A linha começa no meio de uma frase iniciada na linha anterior: a quebra rasgou a frase",
false="A linha começa uma frase, um item, um título ou um pensamento próprio",
),
)
O limiar de junção depende de um fato que o código sabe ler: como a linha anterior termina. Depois de uma linha sem pontuação final, 0,2 já basta para juntar; depois de ponto, dois-pontos ou ponto e vírgula, o corte sobe para 0,5. Um corte único não serve para os dois casos — e essa é uma decisão que o código toma olhando a pontuação, não o modelo.
Requisição 2, classificar. Um Choice por bloco costurado, entre título, parágrafo, item de lista, citação, código e destaque. Junto vão perguntas acompanhantes — nível do título, se a ordem dos itens importa, que tipo de destaque é — lidas apenas quando o tipo do bloco as torna relevantes. Elas vão na mesma requisição porque os tipos ainda não são conhecidos, e esperar por eles custaria uma terceira ida e volta; a pergunta extra custa pouco, já que o state é a maior parte dos tokens e vai uma vez só.
O que o experimento oficial mediu
O documento é um memorando sobre migração de build, em inglês, no estado descrito
acima. Números publicados pela TypeSafe, com o jev-1.12:
| Etapa | Perguntas | Tempo |
|---|---|---|
| Costura (passo 1) | 16 | 0,32 s |
| Classificação (passo 2) | 62 sobre 17 blocos | 0,51 s |
| Total | — | 0,8 s, 10.211 tokens |
As 28 linhas viraram 17 blocos, com 11 quebras de linha costuradas. A classificação acertou a estrutura: o comando de shell solto virou bloco de código, as três linhas de time viraram lista com marcador (perguntas de ordem entre 0,12 e 0,16), as três tarefas antes de segunda viraram lista numerada (ordem entre 0,86 e 0,90) e o aviso sem marcação nenhuma foi classificado como destaque do tipo “warning”.
Sobre o custo em dólar, o cookbook se contradiz: o código imprime US$ 0,0003 para essa execução e o texto afirma US$ 0,0015 em dois lugares. Registramos as duas coisas e citamos o número que o código imprime.
E o bloco mais incerto é instrutivo: a frase que apresenta a lista ficou com confiança 0,43, dividida entre parágrafo 0,53, item de lista 0,24 e destaque 0,19. É ambiguidade real — a frase nomeia o que vem depois (cara de título), é uma frase completa (cara de parágrafo) e está onde um destaque costuma estar. O cookbook sugere o uso óbvio: sublinhar para revisão qualquer bloco cuja confiança de tipo fique abaixo de 0,55.
A lição que vale para toda receita
A parte mais transferível deste cookbook não é a estrutura: é a experiência sobre redação da pergunta.
A primeira versão perguntava o que parecia natural: “estas duas linhas são do mesmo parágrafo?”. O resultado foi ruim de um jeito específico. Um punhado de linhas curtas sob um título — uma lista digitada sem marcadores — é um parágrafo no sentido frouxo: as linhas estão juntas e falam do mesmo assunto. Com essa redação, todos os itens de lista pontuaram acima de 0,75 e as listas colapsaram em blocos corridos: 12 blocos em vez de 17.
Mesmo documento, mesma forma de requisição, só a redação mudou:
| Linha | “continua a frase” | “mesmo parágrafo” |
|---|---|---|
| “The platform team” | 0,22 | 0,77 |
| “The web client team” | 0,11 | 0,81 |
| “Delete the old build cache directory” | 0,08 | 0,88 |
| “Run the doctor script and fix anything it flags” | 0,05 | 0,91 |
A regra que sai daí: quando um julgamento alimenta um limiar, pergunte o fato mais estreito que decide aquele limiar. “Mesmo parágrafo” pede ao modelo que julgue se o assunto continua, e entre itens de lista ele continua. “Continua a frase” pergunta sobre o texto em si. É a diferença entre 17 blocos e 12 — e é a mesma disciplina descrita em critérios que separam.
O que muda em português
A mecânica não depende de idioma, e a costura tem uma vantagem em português: nossas frases são mais longas, então a quebra no meio da frase é mais frequente e a pergunta tem mais evidência para trabalhar.
Dois ajustes locais. A detecção de pontuação final precisa incluir o travessão de diálogo e as reticências, comuns em texto brasileiro. E documento oficial em PDF costuma trazer hifenização de fim de linha (“estabele-” / “cimento”): isso é remendo de código, feito antes da costura, não pergunta.
Como a TypeSafe declara que o inglês é a língua principal de treino, teste os dois limiares de junção nos seus documentos antes de rodar em lote.
Onde isso quebra
- Tabela. Texto que perdeu a formatação de tabela não volta com esta receita: as colunas viraram espaços e reconstruí-las é outro problema.
- Documento longo. O documento inteiro entra no state das duas requisições, e documento grande derruba acurácia, conforme os limites do jev-1.13. Fatie por seção.
- Contagem e numeração. “Renumere os itens de 1 a 7” é conta, e conta é código. A receita decide se a lista é ordenada; o código escreve os números.
- Geração. Se o objetivo é reescrever ou resumir, esta não é a ferramenta: o
jev-1.13não é treinado para gerar texto.
O índice das receitas está em receitas.
Perguntas frequentes
Por que não pedir a um LLM que reescreva em markdown?
Porque reescrever pode mudar as palavras. Nesta receita o modelo nunca gera texto: ele responde perguntas estreitas sobre o documento e o código faz a renderização, então cada caractere da saída vem da entrada.
Quais são as duas requisições?
A primeira faz uma pergunta Noul por par de linhas vizinhas: esta linha continua uma frase interrompida na anterior? A segunda, depois de costurar os blocos, faz uma pergunta Choice por bloco: que tipo de conteúdo é este?
Por que duas e não uma?
Porque os blocos só existem depois que a primeira respondeu. É um dos casos em que a documentação considera legítimo fazer uma segunda requisição: o código não consegue montar a segunda pergunta antes de ter a primeira resposta.
A redação da pergunta muda o resultado?
Muito. Com a pergunta 'as duas linhas são do mesmo parágrafo', todos os itens de lista sem marcador passaram de 0,75 e as listas colapsaram, resultando em 12 blocos. Com 'esta linha continua a frase', os mesmos itens ficaram entre 0,05 e 0,22 e a estrutura saiu certa, com 17 blocos.
Quanto custou o experimento?
10.211 tokens e 0,8 segundo em duas idas e voltas. Sobre o custo em dólar o cookbook se contradiz: o código imprime US$ 0,0003 e o texto afirma US$ 0,0015 em dois lugares. Citamos o valor que o código imprime.