RLM 2027 · Capítulo 11 de 24 · 12 min
Preparar o acervo e rodar a biblioteca oficial rlm
Documento sem texto vira resposta errada sem aviso; preparar o acervo com cabeçalhos e escolher o ambiente certo evita isso antes da primeira execução.
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 protocolo assinado por Helena, Caio precisava de algo que rodasse sobre o acervo de verdade. O acervo, porém, não era texto: eram PDFs, planilhas de laboratório e atas digitalizadas, parte delas escaneada nos primeiros anos. O RLM lê o que estiver na variável que recebe, e mais nada.
Pular a preparação produz o pior tipo de erro, o silencioso. Um laudo escaneado sem camada de texto vira uma string vazia, o modelo raiz conta zero laudos naquele poço e responde com segurança que a condicionante está vencida. A resposta sai bem escrita, com documento citado, e errada.
Ao terminar, você sabe transformar um acervo heterogêneo no formato que a biblioteca oficial espera, escolhe entre os sete ambientes de execução que ela oferece e lê o resultado de uma execução com tokens, tempo e custo na mão.
Por que a preparação do acervo decide metade do resultado
No RLM, o prompt do usuário vai para uma variável chamada context dentro do REPL, e o modelo raiz começa sem ver o conteúdo. Recebe só metadados: o tipo da variável, o total de caracteres e o tamanho de cada pedaço. Tudo o que ele sabe sobre o acervo, descobre escrevendo código. Por isso o formato do acervo guia a exploração tanto quanto o prompt guia uma resposta comum.
Alex Zhang, no blog de outubro de 2025, descreve os primeiros movimentos que o modelo raiz costuma fazer: espiar o começo do texto e filtrar linhas com palavra-chave ou expressão regular. Expressão regular: um padrão de busca escrito em código, como pedir “toda linha que tenha LO seguido de número”. Os dois movimentos só funcionam se o acervo tiver marcas estáveis para espiar e filtrar.
Caio deu a cada documento um cabeçalho de uma linha, sempre no mesmo formato, com identificador, empreendimento, tipo, data e origem do texto. Com isso, uma única expressão regular separa os laudos de um empreendimento em milissegundos, sem gastar nenhuma subchamada. O cabeçalho custa cerca de 120 caracteres por documento, perto de 768 mil no acervo inteiro (6.400 vezes 120), uma fração desprezível do total.
O cabeçalho de cada documento e o que ele permite ao modelo raiz
| Campo | Exemplo | O que o modelo raiz consegue fazer com ele |
|---|---|---|
| id | VRD-2019-0412 | Citar a evidência de modo que Helena ache o original |
| empreendimento | Serra Azul | Recortar o acervo antes de qualquer subchamada |
| tipo | laudo_agua | Separar condicionante, laudo, parecer e ata |
| data | 2019-03-14 | Comparar prazo de condicionante com data de laudo em Python |
| ocr | sim, não ou falhou | Desconfiar de contagem zero em documento sem texto legível |
Fonte: Veredas Ambiental, cenário composto deste curso
Tamanho pede outra conta, e ela define a arquitetura. Na aproximação de 4 caracteres por token, que Caio conferiu numa amostra do acervo, os 52 milhões de tokens viram cerca de 208 milhões de caracteres. A função llm_query aceita cerca de 500 mil caracteres por chamada, segundo o prompt da própria biblioteca. Ler o acervo inteiro numa pergunta exigiria umas 416 subchamadas (208 milhões divididos por 500 mil).
Em dinheiro, a mesma leitura completa custaria cerca de US$ 13 só de entrada no GPT-5-mini (52 milhões de tokens a US$ 0,25 por milhão), antes de qualquer saída. Esse número fixa a regra de ouro do laboratório: o modelo raiz precisa recortar com código antes de delegar leitura, e o cabeçalho existe para tornar esse recorte barato.
Aqui aparece a armadilha comum de quem monta o primeiro protótipo: descartar em silêncio o documento que não tem texto. Na Veredas, 1.150 dos 6.400 documentos eram PDFs escaneados sem camada de texto, quase 18% do acervo (1.150 divididos por 6.400), a maioria laudos de laboratório anteriores a 2016. Caio rodou OCR, o reconhecimento de caracteres que transforma imagem de página em texto, e recuperou 1.112. Os 38 restantes entraram com um aviso no lugar do conteúdo.
O acervo da Veredas antes da primeira execução
6.400
documentos de 38 empreendimentos
14 anos de processos, cerca de 52 milhões de tokens
1.150
escaneados sem texto
OCR recuperou 1.112; 38 entraram com aviso de texto ilegível
~208 mi
caracteres no total
52 milhões de tokens vezes 4 caracteres, aproximação conferida em amostra
~416
subchamadas para ler tudo
208 milhões divididos por 500 mil caracteres por llm_query
Fonte: Veredas Ambiental, cenário composto; capacidade de llm_query segundo o prompt do pacote rlms 0.1.3
# Preparar o acervo da Veredas: um texto por documento, com cabeçalho estável
import json
from pathlib import Path
PASTA = Path("acervo_texto") # saída da extração de PDF e planilha, um .txt por documento
indice = json.loads(Path("indice_documentos.json").read_text(encoding="utf-8"))
acervo = {}
sem_texto = []
for doc in indice: # 6.400 registros com id, empreendimento, tipo, data e ocr
texto = (PASTA / f"{doc['id']}.txt").read_text(encoding="utf-8").strip()
if len(texto) < 200:
# nunca descartar calado: o modelo raiz precisa saber que o documento existe
texto = "[SEM TEXTO LEGÍVEL: consultar o original]"
sem_texto.append(doc["id"])
cabecalho = (
f"[DOC {doc['id']}] empreendimento={doc['empreendimento']} "
f"tipo={doc['tipo']} data={doc['data']} ocr={doc['ocr']}"
)
acervo[doc["id"]] = cabecalho + "\n" + texto
print(len(acervo), "documentos;", len(sem_texto), "sem texto legível")
print(sum(len(t) for t in acervo.values()), "caracteres no total")Um dicionário é aceito como prompt pela biblioteca. Quando o prompt é texto puro, ela grava um arquivo .txt e o lê para a variável; quando é dicionário ou lista, grava em JSON e carrega a estrutura. Caio preferiu o dicionário porque a chave é o próprio identificador do documento, e citar evidência vira ler a chave.
A alternativa seria juntar os 6.400 documentos numa única string e deixar o modelo raiz separá-los pelo cabeçalho. Funciona, mas obriga o modelo a escrever e acertar essa separação a cada pergunta, e um erro de expressão regular ali contamina tudo o que vem depois. Com o dicionário, o recorte por empreendimento é uma compreensão de lista de uma linha, e o custo fica na preparação, feita uma vez só.
Como Caio rodou a biblioteca oficial sobre o acervo da Veredas
A biblioteca do artigo fica no repositório alexzhang13/rlm, mantido pelo laboratório MIT OASYS, com licença MIT e 5.641 estrelas em 23 de setembro de 2026. O pacote no PyPI se chama rlms, pede Python 3.11 ou superior e está na versão 0.1.3, de 26 de junho de 2026. O GitHub ainda exibe uma tag v1.0.0 de janeiro, anterior às versões 0.x; a numeração do PyPI é a que vale para instalar.
Clientes de modelo cobrem OpenAI, Anthropic, OpenRouter e Portkey, e modelos locais entram pelo vLLM através do cliente da OpenAI. A escolha que mais pesa, porém, é o ambiente: onde o código escrito pelo modelo vai rodar. O README separa três ambientes não isolados de quatro isolados, que rodam em sandbox na nuvem, e avisa que o ambiente local não deve ser usado em produção.
Os sete ambientes da biblioteca rlms e quando cada um cabe
| Ambiente | Onde o código roda | Classificação no README | Uso na Veredas |
|---|---|---|---|
| local (padrão) | exec no mesmo processo Python | Não isolado; fora de produção | Só com documentos públicos de teste |
| ipython | Kernel IPython na máquina | Não isolado | Não usado |
| docker | Contêiner na máquina, imagem Python 3.11 | Não isolado | Desenvolvimento com cópia do acervo |
| modal | Sandbox da Modal na nuvem | Isolado | Candidato para produção |
| prime | Sandbox da Prime Intellect | Isolado, em beta e com lentidão reconhecida | Não usado |
| daytona | Sandbox da Daytona | Isolado | Candidato para produção |
| e2b | Máquina virtual Linux sob demanda | Isolado | Candidato para produção |
A escolha para produção depende da análise de isolamento e de dados do cliente, tratada na trilha de confiabilidade e segurança.
Fonte: Repositório alexzhang13/rlm, README consultado em setembro de 2026
Caio escolheu o Docker para o desenvolvimento. O contêiner roda na máquina dele, com uma cópia do acervo que nunca sai da rede da Veredas, e o código gerado não toca o sistema de arquivos do computador. O README coloca o Docker entre os não isolados, e Caio registrou essa ressalva no protocolo: nenhum documento de cliente iria para um ambiente de nuvem antes da revisão de segurança.
O ambiente local ficou restrito a documentos públicos de teste por um motivo concreto. Ele executa o código gerado com exec dentro do próprio processo, e o README avisa do risco quando prompts ou chamadas de ferramenta podem ser tocados por usuários mal-intencionados. No acervo da Veredas, boa parte do texto veio de terceiros: laboratórios, órgãos ambientais, empreiteiras. Qualquer um deles poderia ter escrito num PDF uma frase que o modelo leria como instrução.
Onde o código gerado roda, antes e depois da decisão de Caio
Ambiente local com acervo de cliente
Coluna a evitar
- Código gerado roda com exec no processo de Caio
- Arquivos, credenciais e rede da máquina ao alcance
- Documento de terceiro pode carregar instrução escondida
- README desaconselha o uso em produção
Docker agora, sandbox isolado depois
Coluna recomendada
- Contêiner com imagem Python 3.11 e cópia do acervo
- Local só com documentos públicos de teste
- Modal, Daytona ou E2B avaliados antes da produção
- Revisão de segurança antes de qualquer dado de cliente na nuvem
# Rodar a biblioteca oficial (pip install rlms; versão 0.1.3, Python 3.11 ou superior)
from rlm import RLM
from rlm.logger import RLMLogger
logger = RLMLogger(log_dir="./logs") # um arquivo JSONL por execução
rlm = RLM(
backend="openai",
backend_kwargs={"model_name": "gpt-5"}, # modelo raiz
other_backends=["openai"],
other_backend_kwargs=[{"model_name": "gpt-5-mini"}], # subchamadas de profundidade 1
environment="docker",
environment_kwargs={"image": "python:3.11-slim"},
max_depth=1, # subchamadas são modelos comuns, como no artigo
max_iterations=30, # padrão da biblioteca
max_timeout=1200, # 20 minutos, o limiar do protocolo
logger=logger,
)
pergunta = (
"Quais condicionantes de monitoramento hídrico do empreendimento Serra Azul "
"estão vencidas? Vencida: prazo passou sem laudo protocolado. "
"Cite o id de cada documento usado."
)
resultado = rlm.completion(prompt=acervo, root_prompt=pergunta)
print(resultado.response) # o que o modelo gravou em answer["content"]
print(round(resultado.execution_time), "segundos")
print(resultado.usage_summary.to_dict()) # chamadas e tokens por modeloTrês parâmetros merecem leitura. O max_depth=1 reproduz o padrão do artigo: o modelo raiz chama modelos comuns, sem REPL próprio; com esse valor, rlm_query recai em llm_query. O other_backends manda as subchamadas para o GPT-5-mini, e o root_prompt deixa o modelo raiz ver a pergunta sem ver o acervo. O max_timeout interrompe a execução e devolve a melhor resposta parcial disponível.
Dentro do REPL, o modelo raiz encontra a variável context, as funções llm_query e llm_query_batched, as recursivas rlm_query e rlm_query_batched, a função SHOW_VARS, que lista as variáveis criadas, e o dicionário answer. O prompt da biblioteca avisa que saídas acima de cerca de 20 mil caracteres são truncadas. Por isso o modelo imprime amostras e contagens, e manda o texto longo para as subchamadas.
Existe também um max_budget, em dólares, mas a documentação do código avisa que ele exige um cliente que informe custo, como o OpenRouter. Com o cliente da OpenAI, Caio calculou o custo por conta própria, multiplicando os tokens do usage_summary pelo preço congelado no protocolo.
Dois recursos ficaram desligados no primeiro teste. O parâmetro persistent reaproveita o ambiente entre chamadas, útil para conversa com várias perguntas encadeadas, e compaction resume o histórico do modelo raiz quando ele chega a 85% da janela. Caio preferiu medir primeiro a execução mais simples, uma pergunta por ambiente novo, para ter uma linha de base limpa antes de ligar qualquer otimização. O paralelismo das subchamadas recursivas segue o padrão de max_concurrent_subcalls, quatro linhas de execução.
Encerrar a execução mudou de nome entre o artigo e a biblioteca. O artigo descreve FINAL(resposta) e FINAL_VAR(variável). Na versão 0.1.3 do pacote, o prompt de sistema entrega ao modelo um dicionário answer: ele grava a resposta em answer["content"] e marca answer["ready"] como verdadeiro, e a execução termina ali. Quem escreve um prompt de sistema próprio copiando o artigo, com FINAL, arrisca uma execução que a biblioteca não reconhece como encerrada e que gasta as 30 iterações.
- Etapa 1 de 6: 1. Espiar
Imprime 5 chaves e os primeiros 2.000 caracteres de um documento
- Etapa 2 de 6: 2 e 3. Filtrar
Regex no cabeçalho: 212 documentos de Serra Azul, 57 sobre água
- Etapa 3 de 6: 4 a 6. Delegar
llm_query_batched em 14 lotes de cerca de 4 documentos cada
- Etapa 4 de 6: 7. Comparar
Python cruza prazo de condicionante com data de laudo
- Etapa 5 de 6: 8. Conferir
Imprime a lista candidata com os ids de documento
- Etapa 6 de 6: 9. Entregar
Grava answer["content"] e marca answer["ready"]
O primeiro teste foi uma pergunta de agregação sobre um empreendimento só. O modelo raiz seguiu os padrões que o blog descreve. Espiou, filtrou os 212 documentos de Serra Azul pelo cabeçalho e estreitou para 57 com termos como poço, piezômetro e qualidade da água. Só então delegou leitura, em 14 subchamadas agrupadas. As datas foram comparadas em Python, sem modelo nenhum, e a resposta listou três condicionantes vencidas com cinco documentos citados.
Custo e tempo saíram do usage_summary e do execution_time. O modelo raiz fez 9 turnos com cerca de 12 mil tokens de entrada cada, 108 mil no total, e 1.500 de saída por turno. As 14 subchamadas leram cerca de 45 mil tokens cada e escreveram mil. A conta, com os preços do protocolo, fecha em US$ 0,46 por essa pergunta, em 2 minutos e 50 segundos.
O usage_summary separa as contas por modelo, e essa separação foi a primeira surpresa de Caio. Ele esperava que a leitura dos documentos, feita pelo GPT-5-mini, dominasse o custo. Nessa pergunta, o modelo raiz custou US$ 0,27 e as subchamadas US$ 0,19: o modelo caro, lendo pouco, pesou mais que o barato lendo muito. A conta só aparece quando o custo sai separado por modelo.
Uma execução de agregação no cenário da Veredas
9
iterações do modelo raiz
Do espiar ao answer["ready"]; teto configurado em 30
14
subchamadas ao GPT-5-mini
Agrupadas com llm_query_batched sobre 57 documentos
2 min 50 s
tempo total
Abaixo do limiar de 20 minutos do protocolo
US$ 0,46
custo da pergunta
Raiz: 0,135 de entrada e 0,135 de saída; subchamadas: 0,158 e 0,028
Fonte: Veredas Ambiental, cenário composto; preços do GPT-5 e do GPT-5-mini conferidos em 23 de setembro de 2026
Helena abriu os cinco documentos citados e confirmou as três condicionantes. Mais útil que o acerto foi o arquivo de log: pela primeira vez ela via o caminho da resposta, com cada filtro e cada lote. O mesmo arquivo mostrou que 2 dos 57 documentos filtrados tinham o aviso de texto ilegível e que o modelo raiz os listou à parte, em vez de contá-los como laudo ausente.
A pergunta sobre Serra Azul também mostrou o limite do teste. Um empreendimento só, com 212 documentos, fica longe do caso difícil do protocolo, que cruza laudos de empreendimentos diferentes ao longo de 14 anos. Uma execução boa diz que a montagem funciona; ela não diz nada ainda sobre as 120 perguntas, e Caio escreveu isso no relatório do dia para ninguém confundir teste de fumaça com resultado.
Caio saiu do teste com o acervo preparado, o ambiente escolhido e uma execução de ponta a ponta com custo calculado. Rode agora as dez primeiras perguntas do conjunto na mesma configuração e anote tempo, custo e número de subchamadas de cada uma. O critério é que as dez terminem com answer["ready"] antes de 20 minutos; o passo seguinte é dar à resposta um formato tipado e um rastro que Helena consiga auditar sozinha.
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: Comparar métodos nas mesmas condições e ler os resultados contrários
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