Construindo, com o Claude Code, um Índice de Tom das atas do Copom com múltiplos LLMs
Hoje vamos reconstruir, do zero e com o Claude Code, um projeto real de pesquisa aplicada: um Índice de Tom das atas do Copom calibrado em pontos da Selic, usando três LLMs em paralelo (Gemini, Claude e GPT).
É o passo seguinte ao Boletim Focus: saímos do “automatizar uma tarefa” para conduzir um pipeline de ciência de dados completo dirigindo um agente.
| Bloco | Tema | ~min |
|---|---|---|
| 0 | Abertura e o problema econômico | 10 |
| 1 | Arquitetura, CRISP-DM e o papel do agente | 10 |
| 2 | Setup: CLAUDE.md, ambiente e chaves |
15 |
| 3 | Coleta + pré-processamento (API BCB, seções A+B, cache) | 20 |
| 4 | Scoring com 3 LLMs (Pydantic, prompt, structured output) | 30 |
| 5 | Baseline léxico + calibração OLS + validação 3 camadas | 20 |
| 6 | Visualização, paper Quarto e versionamento | 10 |
| 7 | Resultados, regras de ouro e próximos passos | 5 |
Regra de ouro do projeto
Nunca inventar número. Score, β̂, R², RMSE — tudo sai do dado e do código que roda. Se um número não veio de uma célula executada, ele não entra no paper.
Nota
Tom de comunicação é uma dimensão informacional própria — separável da decisão de taxa já anunciada. É exatamente isso que vamos medir.
Os três modelos concordam sobre a direção do tom — qual ata é mais hawkish ou dovish — mas divergem sobre a intensidade com que isso vira variação da Selic.
| Modelo | \(R^2\) in-sample | \(\hat{\beta}\) (p.p.) | RMSE walk-forward |
|---|---|---|---|
| GPT-4.1-mini | 0,66 | \(+0{,}50\) | 0,357 (líder) |
| Claude Haiku 4.5 | 0,43 | \(+0{,}62\) | ~0,67 |
| Gemini Flash Lite | 0,35 | \(+0{,}36\) | ~0,67 |
| Baseline léxico | (ref.) | — | ~0,52 |
Dica
O número é o fim da aula. Vamos chegar nele rodando o pipeline — não decorando a tabela.
Nota
Cada etapa vira um ou mais prompts para o Claude Code. O agente escreve o código; nós conferimos o método.
| Fase | O que fazemos aqui |
|---|---|
| Entendimento do Negócio | Medir o tom das atas em escala comparável à Selic |
| Entendimento dos Dados | Atas (API BCB, da reunião 232) + meta Selic (SGS 432) |
| Preparação | Limpeza HTML, extração das seções A+B, alinhamento ata × Selic |
| Modelagem | Prompt único, saída Pydantic, 3 LLMs via LangChain, OLS por modelo |
| Avaliação | In-sample (β̂, IC 95%), holdout (6 atas), walk-forward (n=26) |
| Implantação | Pipeline Quarto, cache CSV por provedor, releases versionadas |
Dica
Pense no agente como um assistente de pesquisa sênior: rápido, incansável e literal. A direção científica é sua.
Estou começando um projeto de pesquisa em Python nesta pasta. O objetivo é
construir um Índice de Tom das atas do Copom (escala -3 a +3, hawkish/dovish)
calibrado na variação da Selic, usando três LLMs em paralelo (Gemini, Claude,
OpenAI) e um baseline léxico, entregue como um paper em Quarto.
Crie:
1. Um CLAUDE.md com o briefing: objetivo, fontes (API do Copom no bcb.gov.br e
série SGS 432 da Selic), metodologia CRISP-DM e a REGRA DE OURO: nunca
inventar número — todo valor citado vem de uma célula que roda.
2. Um .gitignore para Python/Quarto (.venv/, .quarto/, *_files/, .env, __pycache__/).
3. Um requirements.txt (deixe vazio por enquanto; vamos preencher no próximo passo).
Me mostre a árvore e o CLAUDE.md.Preencha o requirements.txt com o que o pipeline precisa:
- adaptadores LangChain dos três provedores: langchain, langchain-google-genai,
langchain-anthropic, langchain-openai
- pydantic (saída estruturada)
- requests e beautifulsoup4 (coleta e limpeza das atas do BCB)
- pandas, numpy
- statsmodels e scikit-learn (calibração OLS com inferência)
- plotnine (gráficos estilo ggplot)
- jupyter (para o Quarto renderizar Python)
Crie um .env.example com GOOGLE_API_KEY, ANTHROPIC_API_KEY e OPENAI_API_KEY.
Explique como criar o .venv e instalar.Importante
Nunca coloque chaves no código nem no git. Elas ficam no .env (ignorado) e são lidas por variável de ambiente. Peça isso explicitamente ao agente.
Nota
Três chaves: Google AI Studio, Anthropic e OpenAI. O custo de reprocessar a amostra inteira é < US$ 1 — os modelos são os “small & cheap” de cada provedor.
| Provedor | Onde criar a chave | Variável no .env |
|---|---|---|
| Google Gemini | aistudio.google.com/apikey → Create API key | GOOGLE_API_KEY |
| Anthropic Claude | console.anthropic.com → Settings → API Keys → Create Key | ANTHROPIC_API_KEY |
| OpenAI | platform.openai.com/api-keys → Create new secret key | OPENAI_API_KEY |
Importante
A chave aparece uma única vez — copie na hora. Se perder, é só apagar e gerar outra.
Cole as três no seu .env (e confirme que ele está no .gitignore):
Dica
Em Anthropic e OpenAI vale a pena pôr um limite de gasto (usage limit) na conta. Como o pipeline usa cache e modelos baratos, a amostra inteira custa < US$ 1.
Crie um módulo de coleta das atas do Copom usando a API pública do BCB:
- lista: https://www.bcb.gov.br/api/servico/sitebcb/copom/atas
- detalhes: https://www.bcb.gov.br/api/servico/sitebcb/copom/atas_detalhes
Para cada reunião a partir da 232, baixe o HTML da ata, limpe com BeautifulSoup
(remova tags <sup>, <script>, <style>, notas de rodapé e normalize espaços) e
guarde como um Document do LangChain com metadados nro_reuniao e data.
Use requests com Retry e backoff exponencial. Comente em português.Adicione uma função que extrai APENAS as seções A (atualização da conjuntura) e
B (cenários e riscos) de cada ata, parando antes da seção C (a decisão já
anunciada). Use regex para achar "A) Atualiza..." e o início de "C) Discuss/
Decis/Voto/Condu...". Limite a ~4500 caracteres. Explique no comentário por que
isso corta ~50% dos tokens sem perder sinal informacional.Dica
Aqui o agente é ótimo, mas você traz a hipótese econômica: o sinal está no diagnóstico e no balanço de riscos, não no anúncio da taxa.
Adicione a coleta da meta Selic pela série SGS 432 do BCB
(https://api.bcb.gov.br/dados/serie/bcdata.sgs.432/dados), alinhada por data de
reunião, e calcule a variação em p.p. a cada reunião.
Implemente cache incremental em três níveis no diretório do projeto:
- atas_cache.json → texto A+B por nro_reuniao (permanente: ata não muda)
- selic_cache.json → espelho da SGS 432 (fallback se a API cair)
- vamos ter, depois, scores_{gemini,claude,openai}_cache.csv
A regra: só chamar o BCB para reuniões inéditas. Para reprocessar do zero,
apago o cache correspondente.Nota
Por que recortar a amostra na reunião 232 (ago/2020)? Estrutura textual padronizada (A/B/C), regime monetário comparável e um ciclo completo (cortes → aperto → cortes → aperto). Isso é decisão de pesquisa — diga ao agente o porquê.
Vamos pontuar o tom de cada ata com LLMs. Crie um schema Pydantic chamado
TomAta com um único campo: score (float, de -3.0 a +3.0), com description
explicando que negativo = dovish, positivo = hawkish, zero = neutro.
Quero usar with_structured_output do LangChain para forçar o modelo a retornar
esse float — sem parsing de texto frágil. Mostre como ligar isso ao Gemini
(gemini-flash-lite-latest, temperature=0).Dica
Saída estruturada é o que torna 3 provedores comparáveis: todos devolvem o mesmo float, no mesmo schema. Sem isso, cada um responde em prosa diferente.
Escreva as INSTRUCOES_SISTEMA do scoring como uma constante reutilizável entre
os três provedores. Persona: economista especializado em política monetária do
BCB. Peça um score de -3.0 a +3.0 com ÂNCORAS EXPLÍCITAS por faixa:
-3.0 fortemente dovish ... -2.0 dovish ... -1.0 levemente dovish ...
0.0 neutro/data-dependent ...
+1.0 levemente hawkish ... +2.0 hawkish ... +3.0 fortemente hawkish
Instruções cruciais:
- avaliar APENAS o tom implícito nas seções de diagnóstico e balanço de riscos;
- NÃO se basear na decisão de taxa já anunciada;
- focar em inflação, ancoragem de expectativas, balanço de riscos e forward guidance;
- usar valores intermediários (ex: -1.5, +0.5) quando ambíguo;
- ser consistente: atas parecidas recebem scores próximos.temperature=0 + âncoras → mínima variância entre chamadas e entre atasNota
O mesmo prompt vai para os três provedores. Assim isolamos o efeito do modelo do efeito do prompt ou da amostra de texto.
Crie o loop de inferência do Gemini com cache incremental em
scores_gemini_cache.csv (colunas nro_reuniao, data, score):
- carrega o CSV se existir; para cada ata, se o nro_reuniao já está no cache,
reaproveita e NÃO chama a API;
- só chama o LLM para atas inéditas; faz clip do score em [-3, 3];
- usa with_retry (backoff exponencial com jitter, até 6 tentativas) para
resistir a ServerError;
- registra erros sem derrubar o render; só salva o CSV se houve score novo.
Imprima o progresso ata a ata.Dica
Dois caches somados: o CSV local evita a chamada quando o score já existe; o prompt caching (próximo slide) barateia as chamadas que de fato acontecem.
Replique o scoring para o Claude (claude-haiku-4-5, temperature=0), reusando o
MESMO schema TomAta e as MESMAS INSTRUCOES_SISTEMA. Diferença: envie o bloco de
instruções como SystemMessage com cache_control {type: "ephemeral"} para ativar
o prompt caching nativo da Anthropic — da segunda chamada em diante o cabeçalho
é lido do cache, reduzindo custo de input e latência. Cache em
scores_claude_cache.csv, alinhado por nro_reuniao com o Gemini.Agora o terceiro provedor: gpt-4.1-mini, temperature=0, mesmo schema e mesmo
prompt. A OpenAI usa tool calling nativo via with_structured_output (sem
caching explícito). Cache em scores_openai_cache.csv, alinhado por nro_reuniao
com os outros dois. Ao final, monte uma única tabela com uma coluna de score
por provedor + data + variação da Selic.Nota
Três provedores, um molde só. A única coisa que muda entre eles é o adaptador e o detalhe de caching — o schema e o prompt são idênticos.
Antes de calibrar, crie um baseline metodológico: um léxico hawkish/dovish em
português adaptado ao Copom (no espírito de Loughran-McDonald e Apel-Grimaldi).
Para cada ata, conte termos hawkish e dovish e calcule (n_h - n_d)/(n_h + n_d),
reescalando para [-3, +3] para ficar comparável aos LLMs. Mantenha o léxico
pequeno de propósito — ele é o PISO da literatura de dicionários, sem cache
(é cálculo instantâneo, sem custo de API).Dica
O baseline existe para dar régua: mostra quanto os LLMs ganham sobre contagem de palavras. Sem um piso, “R² de 0,66” não diz nada.
Calibre uma regressão OLS por modelo (3 LLMs + baseline) com statsmodels:
variação_Selic(t) = α + β · score(t) + ε(t)
Para cada modelo, reporte α̂, β̂, erro-padrão, t, p-valor, IC 95% de β, R² e
R²_ajustado. Monte uma tabela única com os quatro modelos. Quero poder afirmar
QUANDO a diferença entre dois modelos é estatisticamente robusta.\[ \Delta \text{Selic}_t = \alpha + \beta \cdot \text{Score}_t + \varepsilon_t \]
Nota
β̂ é a sensibilidade: quantos p.p. de Selic por unidade de score. É aqui que os modelos divergem (de +0,36 a +0,62).
Agora a robustez, em três exercícios complementares:
1. In-sample: a inferência OLS que já temos (β̂, IC 95%, R²).
2. Holdout: calibre α̂, β̂ só nas atas de treino e meça RMSE/MAE nas ÚLTIMAS
SEIS reuniões, fora da amostra.
3. Walk-forward (janela expansiva, treino mínimo 20 atas): para cada ata t a
partir daí, treine OLS em {1..t-1} e preveja t; varra toda a amostra e
reporte RMSE/MAE médios (n_pred ≈ 26).
Monte uma tabela para cada exercício. Explique no texto por que walk-forward
elimina o viés de "janela calma" que pode favorecer um modelo no holdout.Dica
É aqui que a tese se sustenta: o Claude tem o maior β̂ in-sample mas overfita (RMSE quase dobra fora da amostra); o GPT lidera na walk-forward. Mesmo sinal, calibrações diferentes.
Gere dois gráficos com plotnine (estilo ggplot), salvos em PNG por chunks
silenciosos:
1. O Índice de Tom calibrado em p.p. — uma linha por LLM ao longo do tempo
(o baseline fica fora dos gráficos).
2. O z-score do tom (surpresa de comunicação), por modelo.
Use a paleta da Análise Macro e rótulos em português.Monte tudo como um paper técnico em Quarto + LaTeX (PDF): titlepage com a logo
da Análise Macro, resumo, seções Introdução, Revisão da Literatura, Metodologia
e Dados, Implementação (com os chunks comentados), Resultados, Próximos Passos,
Conclusão e Referências (referencias.bib). As tabelas de regressão saem em
booktabs com threeparttable (nota embaixo, padrão de journal). Renderize com
`quarto render` e me mostre se algum chunk falhou.Nota
As três tabelas (in-sample, holdout, walk-forward) e os dois gráficos são gerados pelo código que roda — nenhum número é digitado à mão. É a regra de ouro virando engenharia.
Crie duas edições paralelas do paper que diferem em DUAS linhas:
- base (echo: true) — mostra todos os chunks, auditável
- público (echo: false) — esconde os chunks, edição do leitor
Mudanças de prosa atualizam ambos; mudanças de código vão só na base.
E um CHANGELOG.md no formato Keep a Changelog, com snapshot por release em
versions/vX.Y_AAAA-MM-DD/. Convenção: major = nova rodada metodológica,
minor = novos achados/próximos passos, patch = correções de prosa.Dica
Pesquisa séria é reprodutível e versionada. O agente é excelente em manter as duas edições em sincronia e em escrever um CHANGELOG honesto.
gpt-4.1, claude-sonnet-4-5, gemini-2.5-pro)O projeto completo
A cópia auditável vive em projetos/sentimento-copom/projeto/ — paper base e público, caches, scores e gráficos.
Obrigado! — Análise Macro