Visão geral
O módulo qualitativo do acR implementa um pipeline completo de análise de conteúdo assistida por modelos de linguagem (LLMs), seguindo as diretrizes metodológicas de Krippendorff (2018) e as recomendações empíricas de Gilardi, Alizadeh e Kubli (2023) sobre uso de LLMs para anotação de textos políticos.
O pipeline tem quatro etapas:
ac_fetch_camara() / ac_fetch_senado() ← coleta
↓
ac_corpus() ← estruturação
↓
ac_qual_codebook() ← codebook
↓
ac_qual_code() ← classificação com LLM
A função ac_qual_code() aceita o argumento
chat =, que recebe qualquer objeto Chat do
pacote ellmer (Wickham et al., 2025). Isso permite usar
qualquer provedor — Groq, OpenAI, Anthropic, Google Gemini, Ollama,
Mistral, DeepSeek, OpenRouter — sem alterar a lógica de análise.
Instalação e configuração
# Instalar acR
remotes::install_github("andersonheri/acR")
# Instalar ellmer
install.packages("ellmer")As chaves de API devem ser armazenadas no .Renviron,
nunca no código-fonte. Edite com:
usethis::edit_r_environ()Adicione as linhas correspondentes ao(s) provedor(es) que vai usar:
GROQ_API_KEY=sua_chave
OPENAI_API_KEY=sua_chave
ANTHROPIC_API_KEY=sua_chave
GOOGLE_API_KEY=sua_chave
MISTRAL_API_KEY=sua_chave
DEEPSEEK_API_KEY=sua_chave
OPENROUTER_API_KEY=sua_chave
Reinicie o R após salvar. Verifique com
Sys.getenv("GROQ_API_KEY").
Exemplo completo: discursos parlamentares
Etapa 1 — Coletar corpus via API da Câmara
library(acR)
library(ellmer)
library(dplyr)
# Coletar discursos plenários — março de 2024
corpus_raw <- ac_fetch_camara(
data_inicio = "2024-03-11",
data_fim = "2024-03-15",
tipo_discurso = "plenario",
n_max = 30L
)
# Estrutura do resultado
glimpse(corpus_raw)A função retorna um data.frame com as colunas
id_discurso, nome_deputado,
partido, uf, data,
tipo_discurso, sumario e texto
(transcrição integral quando disponível).
Etapa 3 — Definir codebook
O codebook estrutura as categorias analíticas, suas definições e instruções de classificação. Para discursos parlamentares temáticos, um codebook de cinco categorias cobre a maior parte do conteúdo do plenário:
codebook <- ac_qual_codebook(
name = "temas_plenario",
instructions = "Classifique o tema principal do discurso parlamentar.",
categories = list(
seguranca_publica = list(
definition = "Discursos sobre violência, polícia, crime e segurança pública."
),
economia_fiscal = list(
definition = "Discursos sobre impostos, orçamento, gastos públicos e política fiscal."
),
politica_social = list(
definition = "Discursos sobre saúde, educação, assistência social e combate à pobreza."
),
orientacao_votacao = list(
definition = "Orientação de bancada para votação de projetos de lei."
),
outros = list(
definition = "Discursos que não se encaixam nas categorias anteriores."
)
),
mode = "manual"
)
print(codebook)Etapa 4 — Classificar com LLM
O argumento chat = recebe qualquer objeto
Chat do ellmer. Abaixo usamos o Groq com
llama-3.3-70b-versatile, que oferece plano gratuito e
latência baixa:
# Instanciar provedor — chave lida do .Renviron automaticamente
chat_obj <- chat_groq(
model = "llama-3.3-70b-versatile",
echo = "none"
)
# Classificar corpus completo
resultado <- ac_qual_code(
corpus = corpus,
codebook = codebook,
chat = chat_obj,
confidence = "total", # self-consistency em k rodadas
k_consistency = 3L,
reasoning = TRUE, # inclui justificativa por documento
reasoning_length = "short"
)O argumento confidence = "total" ativa o cálculo de
certeza via self-consistency (Wang et al., 2023): o modelo
classifica cada documento k_consistency vezes com
temperatura > 0 e a confiança é a proporção de concordância entre as
rodadas. Valores ≥ 0.80 indicam alta consistência (Landis & Koch,
1977).
Resultados
Os resultados obtidos com 30 discursos do plenário de março/2024:
# Distribuição de categorias
resultado |>
count(categoria, sort = TRUE) |>
mutate(pct = round(n / sum(n) * 100, 1))## # A tibble: 5 × 3
## categoria n pct
## <chr> <int> <dbl>
## 1 orientacao_votacao 15 50
## 2 politica_social 6 20
## 3 economia_fiscal 4 13.3
## 4 outros 4 13.3
## 5 seguranca_publica 1 3.3
# Confiança média e distribuição
mean(resultado$confidence_score, na.rm = TRUE)
resultado |> count(confidence_level, sort = TRUE)## [1] 0.9111111
##
## # A tibble: 2 × 2
## confidence_level n
## <chr> <int>
## 1 alta 22
## 2 media 8
Confiança média de 0.91 com 22/30 documentos na
faixa “alta” (≥ 0.80) indica classificação estável e reproduzível. A
dominância de orientacao_votacao (50%) é consistente com o
padrão de votações intensas no plenário no período.
# Amostra de classificações com raciocínio
resultado |>
select(nome_deputado, partido, categoria, confidence_score, raciocinio) |>
slice_head(n = 5)## # A tibble: 5 × 5
## nome_deputado partido categoria confidence_score raciocinio
## <chr> <chr> <chr> <dbl> <chr>
## 1 Acácio Favacho MDB seguranca_publica 1 O discurso aborda os índices de
## 2 Adriana Ventura NOVO orientacao_votacao 1 O texto expressa a orientação
## 3 Adriana Ventura NOVO economia_fiscal 0.667 O texto foi classificado nesta
## 4 Adriana Ventura NOVO orientacao_votacao 1 O texto é uma orientação de voto
## 5 Adriana Ventura NOVO orientacao_votacao 1 O texto apresenta uma orientação
Provedores disponíveis
Qualquer provedor suportado pelo ellmer funciona via
chat =. A escolha depende de custo, privacidade e qualidade
para português:
# Groq — gratuito, rápido, bom para prototipagem
chat_obj <- chat_groq(model = "llama-3.3-70b-versatile", echo = "none")
# Google Gemini — tier gratuito generoso
chat_obj <- chat_google_gemini(model = "gemini-2.5-flash", echo = "none")
# Ollama — local, sem envio de dados (ideal para dados sensíveis)
chat_obj <- chat_ollama(model = "llama3.2", echo = "none")
# OpenAI
chat_obj <- chat_openai(model = "gpt-4.1", echo = "none")
# Anthropic Claude
chat_obj <- chat_anthropic(model = "claude-sonnet-4-20250514", echo = "none")
# Mistral
chat_obj <- chat_mistral(model = "mistral-large-latest", echo = "none")
# DeepSeek
chat_obj <- chat_deepseek(model = "deepseek-chat", echo = "none")
# OpenRouter (acesso a centenas de modelos com uma chave)
chat_obj <- chat_openrouter(model = "google/gemini-2.5-flash", echo = "none")| Provedor | Função | Variável de ambiente | Tier gratuito |
|---|---|---|---|
| Groq | chat_groq() |
GROQ_API_KEY |
Sim |
| Google Gemini | chat_google_gemini() |
GOOGLE_API_KEY |
Sim |
| Ollama | chat_ollama() |
não necessária | Gratuito |
| OpenAI | chat_openai() |
OPENAI_API_KEY |
Não |
| Anthropic | chat_anthropic() |
ANTHROPIC_API_KEY |
Não |
| Mistral | chat_mistral() |
MISTRAL_API_KEY |
Não |
| DeepSeek | chat_deepseek() |
DEEPSEEK_API_KEY |
Limitado |
| OpenRouter | chat_openrouter() |
OPENROUTER_API_KEY |
Por uso |
Busca de literatura via OpenAlex
ac_qual_search_literature() busca referências reais na
API do OpenAlex (Priem et al., 2022)
e usa a LLM para sintetizar os abstracts em português. Isso evita
alucinações bibliográficas comuns quando a LLM opera sem fonte
externa.
A arquitetura é: OpenAlex recupera registros verificados (autor, ano, DOI, abstract, revista, número de citações); a LLM sintetiza o abstract e extrai o trecho mais relevante.
lit <- ac_qual_search_literature(
concept = "democratic backsliding",
n_refs = 3,
journals = "default", # lista curada de periodicos de CP/CS
lang = "pt", # definicoes sintetizadas em portugues
min_citations = 50, # apenas trabalhos consolidados
chat = chat_obj
)
print(lit[, c("autor", "ano", "revista", "n_citacoes", "definicao_pt")])## # A tibble: 3 × 5
## autor ano revista n_citacoes
## <chr> <int> <chr> <int>
## 1 Nancy Bermeo 2016 Journal of democracy 2015
## 2 Dean T. Jamison et al. 2013 The Lancet 1209
## 3 David Waldner; Ellen Lust 2018 Annual Review of Political Science 782
## definicao_pt
## <chr>
## 1 O recuo democrático se refere à debilitação ou eliminação de instituições
## políticas que sustentam uma democracia existente, liderada pelo Estado.
## Esse conceito tem mudado significativamente desde a Guerra Fria, com formas
## contemporâneas se tornando mais sutis e complexas.
## 2 O texto fornecido não apresenta informações relevantes sobre o conceito de
## recuo democrático, pois trata de saúde pública e políticas fiscais.
## 3 O recuo democrático refere-se às mudanças que ocorrem dentro de um regime
## político, levando à deterioração da democracia. O estudo desse fenômeno
## carece de fundamentos conceituais e teóricos sólidos.
O resultado é um tibble com 9 colunas: conceito,
autor, ano, revista,
n_citacoes, trecho_original,
definicao_pt, abstract_original e
link.
O argumento journals = "default" prioriza periódicos de
referência em Ciência Política, Administração Pública e Ciências Sociais
(APSR, AJPS, DADOS, RBCS, Opinião Pública, entre outros). Use
journals = "all" para busca sem restrição, ou passe um
vetor customizado como
journals = c("default", "Latin American Politics and Society").
Validação: confiabilidade intercodificadores
Após a classificação automática, recomenda-se validar uma amostra com codificadores humanos. O fluxo é: exportar amostra → preencher manualmente → importar → calcular concordância.
# 1. Amostrar e exportar para revisao humana
amostra <- ac_qual_sample(
resultado,
n = 15,
strategy = "uncertainty" # prioriza documentos com menor confidence_score
)
ac_qual_export_for_review(
sample = amostra,
path = "revisao_humana.xlsx",
corpus = corpus # inclui texto original para facilitar a revisao
)O arquivo .xlsx exportado contém uma coluna
categoria_humano vazia para preenchimento manual. Após o
preenchimento, importe e calcule a concordância:
# 2. Importar revisao humana preenchida
humano <- ac_qual_import_human(
path = "revisao_humana.xlsx",
cat_col = "categoria_humano",
id_col = "doc_id"
)
# 3. Calcular concordancia intercodificadores
concordancia <- ac_qual_irr(
gold = humano, # classificacao humana (referencia)
predicted = resultado, # classificacao do modelo
method = "all",
id_col = "doc_id",
cat_col = "categoria"
)
print(concordancia)Os resultados obtidos na validação com 15 documentos (duas rodadas independentes do mesmo modelo, simulando dois codificadores):
## ── Confiabilidade inter-anotador (acR) ──
## • Documentos comparados: 15
## • Categorias: economia_fiscal, orientacao_votacao, outros,
## politica_social, seguranca_publica
##
## Metrica Estimativa IC 95% Interpretacao
## ──────────────────────────────────────────────────────────────────────────────
## Percent Agreement 0.800 [, ] Muito bom
## Cohen's Kappa (unweighted) 0.702 [0.403, 1.001] Substancial
## Fleiss' Kappa 0.699 [0.389, 1.009] Substancial
## Krippendorff's Alpha (nominal) 0.709 [, ] Substancial
##
## Matriz de confusao:
## Predicted
## Gold economia_fiscal orientacao_votacao outros politica_social
## orientacao_votacao 0 7 0 0
## outros 1 0 2 0
## politica_social 0 1 1 2
## seguranca_publica 0 0 0 0
Kappa de Cohen de 0.70 indica concordância substancial (Landis & Koch, 1977), resultado comparável aos benchmarks de anotação humana relatados por Gilardi, Alizadeh e Kubli (2023) para tarefas de classificação política.
ac_qual_irr() calcula quatro métricas: percentual de
concordância, kappa de Cohen, kappa de Fleiss e alpha de Krippendorff.
Interpretação: ≥ 0.80 = quase perfeita; 0.61–0.79 = substancial;
0.41–0.60 = moderada; < 0.41 = fraca.
Referências
GILARDI, F.; ALIZADEH, M.; KUBLI, M. ChatGPT outperforms crowd workers for text-annotation tasks. PNAS, v. 120, n. 30, 2023.
KRIPPENDORFF, K. Content Analysis: An Introduction to Its Methodology. 4. ed. Thousand Oaks: SAGE, 2018.
LANDIS, J. R.; KOCH, G. G. The measurement of observer agreement for categorical data. Biometrics, v. 33, n. 1, p. 159–174, 1977.
PRIEM, J. et al. OpenAlex: A fully-open index of the global research system. arXiv, 2205.01833, 2022.
WANG, X. et al. Self-consistency improves chain of thought reasoning in language models. EMNLP, 2023.
WICKHAM, H. et al. ellmer: Chat with Large Language Models. Posit, 2025. Disponível em: https://ellmer.tidyverse.org.
