Configuração de análise de tonalidade e emoções dos posts no Discourse AI via self-hosted HuggingFace Text Embeddings Inference (TEI).
O que é isso
Sentimento no Discourse AI não é um modelo LLM de chat/completions. Por baixo dos panos, há duas pequenas modelos classificadores RoBERTa (~125M de parâmetros cada), executados via HuggingFace TEI. Os nomes dos modelos estão hardcoded nos SQL queries dos dashboards do Discourse — não podem ser alterados.
Fonte: Self-Hosting Sentiment and Emotion for DiscourseAI (Falco, equipe do Discourse).
| Modelo | model_name (exatamente como no código) | Finalidade |
|---|---|---|
| Sentimento | cardiffnlp/twitter-roberta-base-sentiment-latest |
positivo / negativo / neutro |
| Emoção | SamLowe/roberta-base-go_emotions |
28 emoções (alegria, raiva, surpresa…) |
Formato da API: POST {\"inputs\": \"text\", \"truncate\": true} → array [{\"label\": \"...\", \"score\": 0.95}, ...]
Diferença: modelo cardiffnlp não possui tokenizer.json
O TEI exige tokenizer.json, mas o modelo cardiffnlp/twitter-roberta-base-sentiment-latest não tem (formato antigo: vocab.json + merges.txt). Solução: baixar os arquivos do modelo localmente e adicionar tokenizer.json do modelo SamLowe/roberta-base-go_emotions (o mesmo tokenizador RoBERTa-base).
Preparação (único)
sudo mkdir -p /opt/tei-sentiment-cache/model
cd /opt/tei-sentiment-cache/model
for f in config.json vocab.json merges.txt special_tokens_map.json pytorch_model.bin; do
sudo curl -sL -o "$f" "https://huggingface.co/cardiffnlp/twitter-roberta-base-sentiment-latest/resolve/main/$f"
done
sudo curl -sL -o tokenizer.json "https://huggingface.co/SamLowe/roberta-base-go_emotions/resolve/main/tokenizer.json"
sudo curl -sL -o tokenizer_config.json "https://huggingface.co/SamLowe/roberta-base-go_emotions/resolve/main/tokenizer_config.json"
Opção A: GPU
Imagem: ghcr.io/huggingface/text-embeddings-inference:cuda-1.9.3
O tag padrão
:latest(e:1.9) foi compilado para compute cap 80 (Ampere) e não funciona na Blackwell (RTX 50x0, compute cap 120). Use exatamentecuda-1.9.3
docker pull ghcr.io/huggingface/text-embeddings-inference:cuda-1.9.3
sudo mkdir -p /opt/tei-emotion-cache
docker run -d --name tei-sentiment --gpus all --shm-size 1g -p 8081:80 -v /opt/tei-sentiment-cache/model:/data/model --restart unless-stopped ghcr.io/huggingface/text-embeddings-inference:cuda-1.9.3 --model-id /data/model
docker run -d --name tei-emotion --gpus all --shm-size 1g -p 8082:80 -v /opt/tei-emotion-cache:/data --restart unless-stopped ghcr.io/huggingface/text-embeddings-inference:cuda-1.9.3 --model-id SamLowe/roberta-base-go_emotions
Primeira execução na Blackwell: a JIT-compilação dos kernels CUDA leva ~5 minutos por container. Isso é uma única vez.
Desempenho GPU (RTX 5060 Ti)
| Métrica | Valor |
|---|---|
| Inferência de Sentimento | ~14ms |
| Inferência de Emoção | ~60ms |
| VRAM por container | ~428 MB |
| VRAM total | ~856 MB |
Opção B: CPU (fallback)
Imagem: ghcr.io/huggingface/text-embeddings-inference:cpu-1.9
Adequado se GPU não estiver disponível ou se a VRAM for insuficiente. Não exige drivers NVIDIA.
docker pull ghcr.io/huggingface/text-embeddings-inference:cpu-1.9
sudo mkdir -p /opt/tei-emotion-cache
docker run -d --name tei-sentiment --shm-size 1g -p 8081:80 -v /opt/tei-sentiment-cache/model:/data/model --restart unless-stopped ghcr.io/huggingface/text-embeddings-inference:cpu-1.9 --model-id /data/model --dtype float32
docker run -d --name tei-emotion --shm-size 1g -p 8082:80 -v /opt/tei-emotion-cache:/data --restart unless-stopped ghcr.io/huggingface/text-embeddings-inference:cpu-1.9 --model-id SamLowe/roberta-base-go_emotions --dtype float32
```Ao encontrar alto LA, é possível limitar o CPU:
```bash
docker update --cpus=0.1 tei-sentiment tei-emotion
Desempenho do CPU
| Métrica | Valor |
|---|---|
| Inferência de Sentimento | ~270ms |
| Inferência de Emoção | ~205ms |
| RAM no container | ~500 MB |
| Com --cpus=0.1 | ~2-3s por post |
Alternância GPU ↔ CPU
docker stop tei-sentiment tei-emotion
docker rm tei-sentiment tei-emotion
Em seguida, inicie os containers conforme o modo desejado. Não é necessário alterar as configurações do Discourse — os endpoints permanecem os mesmos.
Verificação
curl -s http://localhost:8081/ -X POST -H 'Content-Type: application/json' -d '{"inputs": "I am happy"}'
curl -s http://localhost:8082/ -X POST -H 'Content-Type: application/json' -d '{"inputs": "I am happy"}'
Resposta esperada para sentiment: [{\"label\":\"positive\",\"score\":0.96},...]
Configuração do Discourse
Na /admin/plugins/discourse-ai/settings?filter=sentiment:
- discourse_ai_enabled = true
- ai_sentiment_enabled = true
- ai_sentiment_model_configs — dois objetos:
| Campo | Modelo 1 | Modelo 2 |
|---|---|---|
| model_name | cardiffnlp/twitter-roberta-base-sentiment-latest |
SamLowe/roberta-base-go_emotions |
| endpoint | http://<seu-host>:8081 |
http://<seu-host>:8082 |
| api_key | (vazio) | (vazio) |
Painéis
/admin/reports/overall_sentiment— tom geral (positivo - negativo)/admin/reports/emotion_joy(e outras 27 emoções)- Backfill: ~2500 posts/hora, posts não mais antigos que 60 dias
Condições e Riscos
- Os modelos foram treinados em inglês. Para texto em russo, o resultado é aproximado, mas o básico de sentiment analysis funciona.
- O endpoint está aberto sem api_key — para produção, feche por meio de um reverse proxy.
- Monitoramento de VRAM:
nvidia-smi --query-compute-apps=pid,name,used_memory --format=csv,noheader