The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.

Classificação com LLM Local em Escala

Visão geral

Esta vignette cobre o fluxo de ac_qual_code() quando o modelo roda localmente (LM Studio, llama.cpp, vLLM, Ollama em modo OpenAI) em vez de um provedor em nuvem: o cenário mais comum quando os documentos não podem sair do ambiente do pesquisador (dados sensíveis, acordos de confidencialidade institucional) ou quando o volume de documentos tornaria o custo por token de um provedor pago proibitivo. A orientação aqui vem de uso em produção classificando atos normativos administrativos, com documentos de até 1,4 milhão de caracteres e rodadas de milhares de documentos.

Classificar localmente troca custo monetário e risco de vazamento de dados por gerência própria de infraestrutura: tamanho de contexto, throughput e estabilidade do servidor passam a ser problema do pesquisador, não do provedor. As seções abaixo cobrem exatamente os pontos onde isso dá errado na prática.

1. Conectando a um servidor local

[ac_chat_local()] cria um Chat do ellmer apontando para qualquer servidor compatível com a API da OpenAI, sem exigir uma chave de API real:

library(acR)

# LM Studio, servindo um modelo na porta padrao (1234)
chat_local <- ac_chat_local(model = "qwen2.5-14b-instruct")

# llama.cpp (llama-server), porta customizada
chat_llama <- ac_chat_local(
  model    = "modelo-local",
  base_url = "http://localhost:8080/v1"
)

Use chat = chat_local em [ac_qual_code()] exatamente como usaria qualquer outro Chat do ellmer:

coded <- ac_qual_code(corpus, codebook, chat = chat_local)

2. Dimensionando o contexto antes de gastar uma chamada

Servidores locais costumam ter janelas de contexto menores que provedores em nuvem (4k–32k tokens é comum, contra 128k+ de modelos de ponta). Antes de rodar um corpus inteiro, estime quantos tokens cada documento vai consumir:

tamanhos <- ac_estimate_tokens(corpus$text)
summary(tamanhos)

# Quantos documentos provavelmente excedem uma janela de 8k tokens?
sum(tamanhos > 8000)

ac_estimate_tokens() é uma heurística (caracteres por token): não faz nenhuma chamada de rede. Para uma estimativa calibrada contra o modelo e tokenizador real, rode ac_calibrate_tokens() numa amostra pequena antes da rodada completa:

chat_factory <- function(temperature, system_prompt) {
  ac_chat_local(model = "qwen2.5-14b-instruct",
               temperature = temperature, system_prompt = system_prompt)
}

razao <- ac_calibrate_tokens(chat_factory, sample(corpus$text, 10))
tamanhos_calibrados <- ac_estimate_tokens(corpus$text, chars_per_token = razao)

Documentos que excedem a janela de contexto do servidor não devem ser enviados como estão: considere dividir manualmente em partes menores antes de montar o ac_corpus(), ou reduzir reasoning_length para liberar espaço para a resposta.

3. Rodando em lotes com checkpoint

Uma rodada de milhares de documentos contra um servidor local pode levar horas, e qualquer interrupção (queda de energia, servidor reiniciado, notebook suspenso) sem checkpoint significa reclassificar tudo. Use checkpoint_dir:

coded <- ac_qual_code(
  corpus, codebook,
  chat           = chat_local,
  checkpoint_dir = "checkpoints/rodada_2026_10",
  max_retries    = 2L,
  live           = "progress"
)

Cada documento é salvo (gravação atômica) assim que classificado. Se a rodada for interrompida, rodar o mesmo comando de novo pula os documentos já concluídos e continua de onde parou. live = "progress" mostra uma barra enxuta (documento atual + status) em vez de ecoar categoria e raciocínio a cada uma das k_consistency rodadas: importante para não poluir o console numa rodada de milhares de documentos.

Se os parâmetros da rodada mudarem (codebook revisado, modelo diferente, k_consistency diferente), o checkpoint_dir é invalidado e recomeça do zero automaticamente (com aviso), para nunca misturar classificações de codebooks diferentes sem que o pesquisador perceba.

4. Lidando com falhas técnicas

Servidores locais sob carga sustentada falham de formas que provedores em nuvem bem estabelecidos raramente mostram: timeout por sobrecarga de memória, resposta truncada, conexão recusada depois de muitas horas rodando. A coluna status do resultado diferencia os tipos de falha:

table(coded$status)
# ok                 -- classificado normalmente
# resposta_invalida  -- o servidor respondeu, mas não em JSON valido
# estouro_contexto   -- documento excedeu a janela de contexto
# timeout            -- servidor não respondeu a tempo
# erro_servidor      -- outro erro de rede/servidor

max_retries tenta de novo (com espera exponencial) para timeout e erro_servidor, mas não para estouro_contexto: repetir a mesma chamada com o mesmo texto não vai resolver um documento que simplesmente não cabe na janela do modelo. Se mais da metade dos últimos documentos falhar, a rodada é interrompida automaticamente: sinal de que o servidor está instável e insistir só desperdiçaria tempo.

5. Validação humana por categoria (multilabel)

Quando o codebook é multilabel = TRUE, a métrica agregada de [ac_qual_reliability()] mistura decisões binárias independentes num único número: uma concordância “boa” pode esconder uma categoria específica indo mal. Use by_variable = TRUE:

rel <- ac_qual_reliability(
  llm    = coded,
  human  = humano_df,
  by_variable    = TRUE,
  exclude_status = c("estouro_contexto", "timeout", "erro_servidor")
)

attr(rel, "by_variable")

exclude_status remove da comparação documentos cuja rodada principal falhou: uma categoria vinda de uma resposta inválida ou um timeout não é um dado confiável sobre concordância, e misturá-la nas métricas por variável distorceria o diagnóstico.

Para montar a amostra de validação humana de forma representativa da população (não só de cada categoria igualmente, como strategy = "stratified" faz), use strategy = "proportional":

amostra <- ac_qual_sample(
  coded, n = 100,
  strategy = "proportional", strata = "especie_normativa"
)

A amostra ganha peso_amostral, útil se você for agregar a validação numa estimativa populacional ponderada.

6. Auditando a configuração da execução

Toda chamada de [ac_qual_code()] anexa um atributo run_info ao resultado, com a configuração efetiva da rodada:

info <- attr(coded, "run_info")
info$model
info$params_efetivos$temperature   # a temperatura que o PROVEDOR confirmou
info$temperatura_pedida            # a temperatura que você PEDIU
info$codebook_hash

Compare params_efetivos$temperature com temperatura_pedida antes de reportar confidence_score num artigo: se um Chat pré-configurado não confirmar a temperatura pedida, ac_qual_code() já emite um aviso explícito no momento da rodada, mas run_info permite auditar isso depois, inclusive em rodadas de terceiros.

Referências

Wang, X. et al. (2023). Self-Consistency Improves Chain of Thought Reasoning in Language Models. ICLR.

Gilardi, F.; Alizadeh, M.; Kubli, M. (2023). ChatGPT Outperforms Crowd Workers for Text-Annotation Tasks. PNAS, 120(30).

Gwet, K. L. (2014). Handbook of Inter-Rater Reliability (4th ed.). Advanced Analytics.

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.