RLM 2027 · Capítulo 6 de 24 · 15 min
Processar as partes e juntar as respostas sem perder a evidência
A mesma pergunta, cortada de dois jeitos, devolve listas diferentes; a diferença está no que cada subchamada consegue citar.
Este capítulo faz parte do curso gratuito RLM 2027. Para marcar como concluído e salvar o progresso, abra este capítulo na página do curso.
Com o acervo mapeado e duas divisões candidatas na mesa, falta o trabalho que consome quase todo o orçamento: mandar cada parte a uma subchamada e juntar o que volta. É nessa etapa que o RLM ganha ou perde a pergunta da Veredas, porque cada resposta parcial precisa chegar ao topo da árvore com a prova junto.
Errar aqui não faz barulho. Uma subchamada que responde “há laudo contraditório” sem dizer qual laudo entrega a Helena uma afirmação que ela não consegue conferir nem defender diante do órgão ambiental. Multiplique por 214 condicionantes e o relatório vira opinião bem formatada.
Ao terminar, você desenha o pedido de cada subchamada como um registro com campos fixos e junta os registros em código em vez de pedir um resumo. Também confere os pares suspeitos numa segunda passada e devolve uma resposta maior que a janela do modelo.
Por que o formato da resposta parcial decide a qualidade do todo
O padrão Partition + Map, do post de Zhang de outubro de 2025, divide e manda cada pedaço a uma chamada. O artigo de Zhang, Kraska e Khattab explica por que isso só funciona dentro do REPL. A terceira escolha de desenho que eles dizem faltar em outros andaimes é a recursão simbólica: o código chama o modelo sobre pedaços construídos por programa, dentro de laços, quantas vezes for preciso.
Os autores escrevem que o laço pode chamar llm_query Ω(|P|) ou Ω(|P|²) vezes. Traduzindo: um número de chamadas que cresce com o tamanho da entrada, ou com o quadrado dele, como comparar cada documento com todos os outros. Um agente em que a subchamada é uma ação escrita na conversa não consegue isso; o artigo chama esse desenho de falha nº 3, porque a ação verbalizada não entra num laço.
Para disparar várias subchamadas ao mesmo tempo existe llm_query_batched, que recebe uma lista de pedidos e devolve uma lista de respostas, como mandar dez cartas no mesmo malote. A biblioteca rlms oferece a função e limita o paralelismo por max_concurrent_subcalls. Nos experimentos do artigo, todas as chamadas foram bloqueantes e sequenciais, e o blog avisa que não havia cache de prefixo; a latência publicada é a do pior arranjo.
Os tetos de chamada também variam por implementação e entram no desenho. No dspy.RLM, max_llm_calls tem padrão de 50 chamadas por execução e max_iters, de 20 turnos do REPL; o nome antigo, max_iterations, mudou na versão 3.3.0, de agosto de 2026. Uma divisão que pede 400 subchamadas estoura o padrão na primeira execução. Melhor descobrir isso na conta do reconhecimento do que no meio da leitura.
Juntar as respostas pede outra decisão. O padrão Summarization do blog resume subconjuntos para o modelo de fora decidir, e o autor chama os RLMs de generalização natural das estratégias de resumo. A diferença está no que sobra depois. Um resumo descarta o detalhe que um par de documentos vai precisar mais adiante; o REPL guarda cada resposta parcial numa variável, e o raiz combina em código.
Resumir o histórico contra guardar as partes em variáveis, com GPT-5
| Método | CodeQA | BrowseComp+ (1K docs) | OOLONG | OOLONG-Pairs |
|---|---|---|---|---|
| Agente de compactação (GPT-5-nano resume) | 58,0 (US$ 1,31) | 70,5 (US$ 0,57) | 46,0 (US$ 0,13) | 0,1 (US$ 0,13) |
| RLM, profundidade 1 | 62,0 (US$ 0,11) | 91,3 (US$ 0,99) | 56,0 (US$ 0,43) | 58,0 (US$ 0,33) |
| RLM, profundidade 3 | 58,0 (US$ 0,15) | 92,0 (US$ 0,51) | 58,0 (US$ 0,51) | 76,0 (US$ 0,39) |
Acurácia em %, exceto OOLONG-Pairs (F1); custo médio de API por consulta entre parênteses. Subchamadas do RLM ao GPT-5-mini.
Fonte: Zhang, Kraska e Khattab, Recursive Language Models, v3 (maio de 2026), Tabela 1
O OOLONG-Pairs é o teste que mais se parece com a pergunta da Veredas: pede pares de itens que satisfazem uma condição, e o trabalho cresce com o quadrado da entrada. O agente de compactação marca 0,1 de F1, na prática zero, enquanto o RLM com profundidade 1 chega a 58,0 com custo médio menor. Os pares sumiram no resumo antes que alguém pudesse cruzá-los.
Guardar em variáveis resolve metade do problema; a outra metade é o formato do que volta. Se cada subchamada devolve texto livre, o raiz precisa de outro modelo para juntar textos livres, e o erro reaparece. Armadilha comum: pedir à subchamada “comente o bloco” e depois pedir a um segundo modelo que “consolide os comentários”. A consolidação inventa números que nenhuma subchamada escreveu.
O λ-RLM mediu o custo do código livre com modelos abertos de até 405 bilhões de parâmetros e contextos de até 128 mil tokens. Na ablação em que o modelo volta a escrever código livre no lugar dos combinadores fixos, o OOLONG cai de 48,3% para 24,1% e a latência sobe de 62,4 s para 241,6 s. O resultado não inclui modelos fechados de fronteira, mas aponta a mesma direção: forma fixa reduz a variância.
O que a subchamada devolve ao modelo raiz
Resposta parcial em texto livre
Coluna a evitar
- Parágrafo com a opinião do modelo sobre o bloco
- Documento citado quando o modelo lembra
- Juntar exige outro modelo, que resume de novo
- Número final sem origem conferível
Resposta parcial em registro com prova
Coluna recomendada
- Campos fixos: empreendimento, condicionante, situação
- Id do documento e trecho literal obrigatórios
- Código valida o registro e descarta o que vem sem prova
- Contagem feita em Python sobre registros aceitos
Resta devolver a resposta. No artigo, o raiz encerra com FINAL(resposta) para texto curto ou FINAL_VAR(nome) para devolver uma variável do REPL; no dspy.RLM, o equivalente é SUBMIT(...). Devolver a variável permite uma saída maior que a janela do modelo, que é o padrão long-input, long-output do blog. Os autores relatam um ponto frágil: o modelo às vezes entrega o plano como se fosse a resposta.
Grupo grande demais levanta outra escolha: partir em código ou delegar a um sub-RLM, que tem REPL próprio e decide sozinho como dividir. A Tabela 1 do artigo mostra que profundidade maior não melhora de forma consistente. Com GPT-5, a profundidade 3 é a melhor no OOLONG-Pairs e piora no CodeQA. Com Qwen3-Coder, o OOLONG cai de 48,0 para 26,0 na profundidade 2, e os autores culpam erros de sintaxe que descem para os sub-RLMs.
A reprodução independente de Daren Wang, de março de 2026, reforça a cautela. Com DeepSeek v3.2 no OOLONG, o acerto sobe de 0,0% para 42,1% na profundidade 1 e desce a 33,7% na profundidade 2, numa amostra de 20 casos e execução única. Por isso a Veredas manteve a profundidade 1 e deixou a partição dos grupos grandes para o código, onde o corte é previsível e fácil de conferir.
- Etapa 1 de 6: Dividir em código
grupos pela unidade da pergunta; grupo grande é dividido de novo
- Etapa 2 de 6: Mapear em lote
llm_query_batched com pedido de registro em campos fixos
- Etapa 3 de 6: Validar cada registro
o código confere se o id existe e se o trecho está no documento
- Etapa 4 de 6: Cruzar os pares suspeitos
segunda subchamada lê só os dois trechos em conflito
- Etapa 5 de 6: Agregar em Python
contagem e tabela montadas sobre registros aceitos
- Etapa 6 de 6: Devolver a variável
FINAL_VAR entrega a tabela inteira, maior que a janela
Como a Veredas comparou duas divisões para a mesma pergunta
O ensaio usou a mina e a barragem de rejeito, cerca de 900 mil tokens. Helena escreveu antes a lista de referência: nesses dois processos, 12 condicionantes de monitoramento hídrico estavam vencidas ou tinham laudos contraditórios. Caio rodou a pergunta de negócio duas vezes, uma com cada divisão, e guardou o rastro das duas execuções para conferir item por item.
Na divisão A, o acervo virou blocos fixos de 200 mil caracteres, 18 no total, e cada subchamada recebeu o pedido de listar condicionantes vencidas ou contraditórias no bloco, com evidência. Na divisão B, o código formou 31 grupos, um por condicionante; dois passavam de 500 mil caracteres e foram partidos ao meio, o que deu 33 leituras. Seis pares suspeitos ganharam uma subchamada de verificação: 39 no total.
# Divisão B: um grupo por condicionante, registro fixo e validação em código.
import json
PEDIDO = ("Leia a condicionante e os documentos abaixo. Responda só JSON: "
'{"situacao": "vencida|contraditoria|em_dia", "doc_id": "...", "trecho": "..."}. '
"O trecho deve ser copiado literalmente do documento citado.\n\n")
pedidos = [PEDIDO + g["texto"] for g in grupos] # 33 leituras
respostas = llm_query_batched(pedidos)
aceitos, descartados = [], []
for g, r in zip(grupos, respostas):
try:
reg = json.loads(r)
except json.JSONDecodeError:
descartados.append((g["id"], "formato")); continue
doc = documentos.get(reg.get("doc_id"))
if doc is None or reg.get("trecho", "") not in doc: # prova tem de existir
descartados.append((g["id"], "sem prova")); continue
aceitos.append({**reg, "condicionante": g["id"], "empreendimento": g["emp"]})
tabela = [r for r in aceitos if r["situacao"] != "em_dia"]
FINAL_VAR("tabela") # devolve a variável inteira, não um texto resumidoEnsaio da Veredas: a mesma pergunta na mina e na barragem
| Medida | A: blocos fixos | B: grupos por condicionante |
|---|---|---|
| Subchamadas | 18 | 39 (33 leituras e 6 verificações) |
| Tokens lidos pelas subchamadas | 900 mil | 658 mil |
| Itens listados | 10 | 12 |
| Corretos pela referência de Helena (12) | 7 | 11 |
| Falsos positivos | 3 | 1 |
| Itens da referência perdidos | 5 | 1 |
| Itens com id de documento e trecho literal | 6 de 10 | 12 de 12 |
| Custo da execução | US$ 0,38 | US$ 0,35 |
| Tempo de relógio, 8 subchamadas em paralelo | 1 min 40 s | 3 min 10 s |
Cenário ilustrativo. Custo a preços de referência: GPT-5-mini nas subchamadas (US$ 0,25 e US$ 2 por milhão de tokens de entrada e saída) e cerca de US$ 0,10 do modelo raiz.
A conta do custo fecha assim. Na divisão A, 900 mil tokens de entrada a US$ 0,25 por milhão dão US$ 0,225, e 27 mil tokens de saída a US$ 2 por milhão dão US$ 0,054; com US$ 0,10 do raiz, US$ 0,38. Na divisão B, as leituras somam 610 mil tokens e as verificações 48 mil: 658 mil a US$ 0,25 dão US$ 0,16, e 47 mil de saída dão US$ 0,09. Com o raiz, US$ 0,35.
O tempo de relógio também entrou na comparação. Com oito subchamadas em paralelo, A terminou em 1 min 40 s. B levou 3 min 10 s, porque as seis verificações só começam depois das leituras e o raiz gastou dois turnos a mais montando os grupos. Para uma pergunta que Helena responde ao cliente em dias, a diferença não pesou; numa consulta ao vivo durante uma reunião, pesaria.
Com os registros aceitos, a agregação é Python comum. Um Counter por empreendimento conta as condicionantes vencidas, um dicionário junta as contradições por ponto de coleta, e cada linha da tabela final carrega o id do documento e o trecho. Pedir ao modelo que conte reabriria a porta para número inventado. O código conta o que está na lista e nada mais.
Os cinco itens que A perdeu contam a história da divisão. Em três, a condicionante e o laudo que a contradizia caíram em blocos diferentes, e nenhuma subchamada viu os dois. Nos outros dois, uma planilha de laboratório foi cortada no meio da tabela, na fronteira entre blocos. Os três falsos positivos de A eram condicionantes vencidas no papel cuja prorrogação estava noutro bloco.
Na divisão B, os dois erros voltaram ao filtro. O falso positivo era uma condicionante cumprida por um ofício que não citava o número dela, e o filtro não o pôs no grupo. O item perdido era um laudo que citava a condicionante pelo número antigo, anterior à renovação da licença. Helena anotou os dois casos como regras para a próxima versão do filtro.
A segunda passada fez diferença mensurável. Das 33 leituras, seis apontaram contradição entre laudo e relatório. Cada verificação recebeu só os dois trechos em conflito, perto de 8 mil tokens, e a pergunta de se falavam do mesmo ponto de coleta, do mesmo parâmetro e da mesma unidade. Quatro contradições se confirmaram. Duas caíram: numa, um laudo media em mg/L e o outro em µg/L; na outra, os pontos de coleta eram diferentes.
Helena decidiu por B e fixou uma regra de conduta que vale para o resto do projeto: item sem id de documento e trecho literal não entra na tabela entregue ao cliente, por mais plausível que pareça. Na execução B, o código descartou dois registros por falta de prova antes da agregação, e nenhum dos dois estava na lista de referência.
A regra tem um limite que Caio documentou. A validação confere o trecho por igualdade de texto, e documentos digitalizados trazem diferenças de espaço, hífen e acento entre a leitura da subchamada e o arquivo guardado. O código compara as duas versões depois da mesma normalização usada no filtro. Sem isso, a regra de prova descartaria registros corretos e daria falsa impressão de rigor.
Para a divisão A havia um remédio conhecido, e Caio o testou fora da tabela: sobrepor os blocos em 20 mil caracteres para que nada caísse inteiro na fronteira. Duas das cinco perdas voltaram. Em troca, o mesmo laudo passou a aparecer em dois blocos e a ser contado duas vezes, o que exigiu um passo de remoção de duplicatas pelo id do documento. A sobreposição remenda o corte; não o põe no lugar certo.
No ensaio, a divisão por condicionante acertou 11 de 12, com prova em todos os itens, e custou três centavos a menos que os blocos fixos. Liste agora, para as 120 perguntas de referência da Helena, quais caberiam numa janela de 1 milhão de tokens e quais o RAG já acertava. Se mais da metade couber na janela, o contexto longo merece um teste justo antes de o RLM ir ao acervo inteiro.
Seu caderno neste capítulo
Abrir o caderno completoSelecione um trecho do capítulo para destacar ou anotar. No teclado, selecione com Shift e as setas e use Alt+Shift+D para destacar ou Alt+Shift+N para anotar.
Salvo neste navegador. Entre na sua conta para levar o caderno a outros aparelhos.
Entre na sua conta para compartilhar o que aprendeu e convidar alguém para estudar com você.
Voltar ao capítulo anterior: Explorar o acervo antes de decidir onde cortar
Todos os capítulos de RLM 2027
- 01Descobrir onde o modelo encontra cada informação que usa
- 02Medir o limite real do contexto e dividir o problema
- 03Guardar o acervo numa variável e deixar o modelo programar a leitura
- 04Desenhar a árvore de chamadas e decidir a profundidade
- 05Explorar o acervo antes de decidir onde cortar
- 06Processar as partes e juntar as respostas sem perder a evidência
- 07Comparar contexto longo, RAG e compactação antes de escolher o RLM
- 08Situar agentes, híbridos e RLM numa matriz de decisão
- 09Julgar um benchmark antes de acreditar no número dele
- 10Comparar métodos nas mesmas condições e ler os resultados contrários
- 11Preparar o acervo e rodar a biblioteca oficial rlm
- 12Montar o protótipo com dspy.RLM e auditar o rastro de cada resposta
- 13Decompor o custo de uma execução e pôr teto em cada parte
- 14Baixar o custo por pergunta e vigiar o que a média esconde
- 15Isolar o código gerado e tratar cada documento como dado
- 16Impedir que o erro de uma folha chegue à resposta final
- 17Treinar um modelo pequeno para decidir quando delegar
- 18Provar uma auto-melhoria antes de acreditar nela
- 19Julgar as apostas de recursão aprendida e execução previsível
- 20Mapear híbridos, imagem e trabalho prolongado com evidência e lacuna
- 21Ler 2027 por sinais observáveis em vez de apostar numa previsão
- 22Escolher onde o RLM entra primeiro e decidir o piloto
- 23Definir o problema e construir quatro alternativas comparáveis
- 24Avaliar as alternativas e dizer em quais condições o RLM compensa