Usando IA para Taguear e Categorizar Postagens em Fóruns

Criei uma Persona para tentar categorizar e marcar automaticamente novos tópicos. Este é o prompt que estou usando para essa Categoria e Marcador.

Você é um assistente de IA para o meu fórum Discourse. Seu trabalho é classificar novos tópicos em uma das categorias predefinidas e aplicar até 5 tags relevantes.

Categorias:

  • Categoria 1
  • Categoria 2
  • Categoria 3
  • Categoria 4
  • Categoria 5

Tags:

Sua seleção deve vir desta lista predefinida (escolha até 5):
tag1, tag2, tag3, tag4, tag5

Tarefa:

  • Selecione exatamente uma categoria da lista acima que melhor se adapta ao tópico.
  • Selecione até 5 tags da lista acima com base na relevância.
  • Formate sua resposta como JSON exatamente assim:
{
  "category": "Nome da Categoria Escolhida",
  "tags": ["tag1", "tag2", "tag3"]
}

Quando seleciono essa persona para usar em Automações e criar a postagem, a IA identifica com precisão as categorias e tags com este resultado em uma postagem.

Listar categorias
{
“category”: “Categoria Identificada Corretamente”,
“tags”: ["Todas ", “válidas”, “tags”]
}

A pergunta é: já é possível realmente usar a IA e as Automações para mover novas postagens e categorizá-las, e para que as tags sejam atribuídas ao tópico. Se sim, tenho certeza de que o prompting pode ser ajustado para refinar o quão bem funciona.

Obrigado,

1 curtida

Chegando lá… é um pouco complicado, faltam 2 pequenas peças:

  1. Ferramentas personalizadas agora suportam muitas coisas, mas não suportam categorização e marcação, podemos adicionar isso facilmente.
  2. Preciso de um respondedor que funcione em modo “silencioso”, para que não responda realmente ao tópico.

Assim que ambos estiverem implementados, você dará acesso a 2 ferramentas personalizadas:

  1. Marcar tópico
  2. Categorizar tópico

(ou uma única ferramenta que faça ambos)

5 curtidas

Na verdade, contanto que você esteja de acordo com o whisper, você pode fazer algo agora.

A ideia é que você definiria uma ferramenta personalizada para categorizar um tópico (ou marcar um tópico) e, em seguida, faria com que a persona a chamasse.

Estou curioso para ver como essa abordagem funciona para você, você precisará testar a ferramenta, deve ser bastante simples a partir da interface da ferramenta.

É bastante complicado gerenciar tudo isso, mas também é muito :exploding_head: que isso possa ser feito. O whisper é na verdade razoavelmente útil porque lhe dá um pouco do “processo de pensamento” de como o respondedor chegou às tags/categorias.

3 curtidas

Obrigado Sam,

Mais fácil para alguns do que para outros. Eu sou da última categoria :joy:

Estou mexendo nisso e ainda não consegui fazer funcionar. Não tenho experiência com ferramentas, então estou realmente ansioso para fazer isso funcionar para pensar em outras maneiras de usar ferramentas.

Tentei usar o código do seu blog com uma chave de API, o que não funcionou. A IA sugeriu que eu usasse uma URL codificada, então tentei isso sem sucesso.

A chave de API não foi chamada e não há logs criados com erros.

Isso é usado para categorizar automaticamente os tópicos na criação

Classificarei o tópico “Insights dos primeiros anos e milagres de Jesus” na categoria Novo Testamento, pois se relaciona com a vida e os ensinamentos de Jesus.
Movendo o tópico agora…

Foi isso que eu tentei.

/**

  • Referência Rápida da API da Ferramenta
  • Funções de Entrada
  • invoke(parameters): Função principal. Recebe parâmetros (Objeto). Deve retornar um valor serializável em JSON.
  • Exemplo:
  • function invoke(parameters) { return “resultado”; }
  • details(): Opcional. Retorna uma string descrevendo a ferramenta.
  • Exemplo:
  • function details() { return “Descrição da ferramenta.”; }
  • Objetos Fornecidos
    1. http
  • http.get(url, options?): Realiza uma requisição HTTP GET.
  • Parâmetros:
  •  url (string): A URL da requisição.
    
  •  options (Object, opcional):
    
  •    headers (Object): Cabeçalhos da requisição.
    
  • Retorna:
  •  { status: number, body: string }
    
  • http.post(url, options?): Realiza uma requisição HTTP POST.
  • Parâmetros:
  •  url (string): A URL da requisição.
    
  •  options (Object, opcional):
    
  •    headers (Object): Cabeçalhos da requisição.
    
  •    body (string): Corpo da requisição.
    
  • Retorna:
  •  { status: number, body: string }
    
  • (também disponível: http.put, http.patch, http.delete)
  • Nota: Máximo de 20 requisições HTTP por execução.
    1. llm
  • llm.truncate(text, length): Trunca o texto para um comprimento de token especificado.
  • Parâmetros:
  •  text (string): Texto a ser truncado.
    
  •  length (number): Tokens máximos.
    
  • Retorna:
  •  String truncada.
    
    1. index
  • index.search(query, options?): Busca documentos indexados.
  • Parâmetros:
  •  query (string): Consulta de busca.
    
  •  options (Object, opcional):
    
  •    filenames (Array): Limita a busca a arquivos específicos.
    
  •    limit (number): Máximo de fragmentos (até 200).
    
  • Retorna:
  •  Array de { fragment: string, metadata: string }
    
    1. upload
  • upload.create(filename, base_64_content): Envia um arquivo.
  • Parâmetros:
  •  filename (string): Nome do arquivo.
    
  •  base_64_content (string): Conteúdo do arquivo codificado em Base64.
    
  • Retorna:
  •  { id: number, short_url: string }
    
    1. chain
  • chain.setCustomRaw(raw): Define o corpo da postagem e sai da cadeia.
  • Parâmetros:
  •  raw (string): Conteúdo bruto a ser adicionado à postagem.
    
  • Restrições
  • Tempo de Execução: ≤ 2000ms
  • Memória: ≤ 10MB
  • Requisições HTTP: ≤ 20 por execução
  • Exceder os limites resultará em erros ou encerramento.
  • Segurança
  • Ambiente Sandboxed: Sem acesso a objetos do sistema ou globais.
  • Sem Acesso ao Sistema de Arquivos: Não é possível ler ou escrever arquivos.
    */

/**

  • Categorizador de Tópicos do Discourse
  • Esta ferramenta permite alterar a categoria de um tópico do Discourse
  • usando a API do Discourse.
    */

/**

  • Categorizador de Tópicos do Discourse
  • Esta ferramenta permite alterar a categoria de um tópico do Discourse
  • usando a API do Discourse.
    */

/**

  • Categorizador de Tópicos do Discourse
  • Esta ferramenta permite alterar a categoria de um tópico do Discourse
  • usando a API do Discourse.
    */

function invoke(params) {
// Validação de parâmetros obrigatórios
if (!params.topic_id) {
return { error: “Parâmetro obrigatório ausente: topic_id” };
}

if (!params.category_id) {
return { error: “Parâmetro obrigatório ausente: category_id” };
}

// URL base para sua instância do Discourse
const baseUrl = “https://community.mysite.com”;

// URL completa do endpoint da API para atualizar um tópico
const apiUrl = ${baseUrl}/t/${params.topic_id}.json;

// Prepara o corpo da requisição
const requestBody = {
category_id: params.category_id
};

// Parâmetro opcional: atualiza o título se fornecido
if (params.title) {
requestBody.title = params.title;
}

// Use sua chave de API fornecida
const apiKey = “Discourse-API-Key”;

try {
// Faz a requisição PUT para atualizar o tópico
const response = http.put(apiUrl, {
headers: {
“Content-Type”: “application/json”,
“Api-Key”: apiKey,
“Api-Username”: params.api_username || “system”
},
body: JSON.stringify(requestBody)
});

if (response.status >= 200 && response.status < 300) {
  return {
    success: true,
    topic_id: params.topic_id,
    category_id: params.category_id,
    response: JSON.parse(response.body)
  };
} else {
  return {
    error: `Falha ao atualizar a categoria do tópico. Status: ${response.status}`,
    details: response.body
  };
}

} catch (error) {
return {
error: “Ocorreu um erro ao atualizar a categoria do tópico”,
details: error.toString()
};
}
}

function details() {
return “Categoriza um tópico movendo-o para uma categoria especificada”;
}

2 curtidas

@BrianC você acabou descobrindo isso?

1 curtida

Discourse AI Tagger Categoria Guia Rápido.pdf|anexo (147,1 KB)

Discourse AI Tagger Categoria.pdf|anexo (506,0 KB)

@Sam,

Obrigado por verificar. Fiz uma tentativa, mas não consegui fazer funcionar, então pausei. Executei o teste de cenário pelo Gemini 2.5 e salvei a saída como PDFs.

Vou tentar novamente. Ferramentas ainda não são meu ponto forte, mas preciso me tornar mais habilidoso nisso. PDFs anexados—talvez haja algo útil neles.

3 curtidas

Adicionamos uma automação de marcação por IA que pode lhe interessar experimentar: FEATURE: create AI tagging automation (#34587) · discourse/discourse@e470f3d · GitHub

Prompt que tenho usado para testá-lo
 Você é um classificador de conteúdo especialista e assistente de marcação para este fórum.

 Sua tarefa é analisar postagens e sugerir marcações apropriadas com base no conteúdo, imagens e na
 lista de marcações disponíveis fornecida.

 Diretrizes:
 - Sugira apenas marcações da lista de marcações disponíveis fornecida
 - Seja conservador - marque apenas aquilo sobre o qual você tem confiança
 - Considere o tópico do conteúdo e a intenção da postagem

 Você deve sempre responder com JSON válido neste formato exato:
 {"tags": ["tag1", "tag2"], "confidence": 85}

 - tags: array de nomes de marcações da lista disponível
 - confidence: inteiro de 0 a 100 representando seu nível de confiança

 Se nenhuma marcação for apropriada, use: {"tags": [], "confidence": 0}

Formato de resposta JSON da Persona:

{
  "tags": "[string]",
  "confidence": "[integer]"
}

Ferramentas habilitadas da Persona: tags (opcional, isso é se você quiser que ele use as marcações existentes no site)

A automação tem dois modos:

  • Usar marcações existentes do site
  • Usar a lista de marcações fornecida

A automação também tem um nível de confiança configurável, você pode ativar/desativar marcações restritas e ajustar o número de postagens que ela usa em um tópico para contexto

6 curtidas

Obrigado por este ótimo recurso, só não consigo fazê-lo funcionar, o erro que recebi é:

llm_tagger: falha ao processar post 30550 /t/gecici-baslik-1756753838814/17361/1 : NoMethodError : método privado select’ chamado para uma instância de String`

Mensagem

llm_tagger: falha ao processar post 30550 /t/gecici-baslik-1756753838814/17361/1 : NoMethodError : método privado `select' chamado para uma instância de String

Backtrace

/var/www/discourse/plugins/discourse-ai/lib/automation/llm_tagger.rb:140:in `handle'
/var/www/discourse/plugins/discourse-ai/discourse_automation/llm_tagger.rb:110:in `block (2 levels) in <main>'
/var/www/discourse/plugins/automation/app/models/discourse_automation/automation.rb:158:in `block in trigger!'
/var/www/discourse/plugins/automation/app/models/discourse_automation/stat.rb:11:in `log'
/var/www/discourse/plugins/automation/app/models/discourse_automation/automation.rb:156:in `trigger!'
/var/www/discourse/plugins/automation/app/jobs/regular/discourse_automation/trigger.rb:29:in `execute'
/var/www/discourse/app/jobs/base.rb:318:in `block (2 levels) in perform'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/rails_multisite-7.0.0/lib/rails_multisite/connection_management/null_instance.rb:49:in `with_connection'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/rails_multisite-7.0.0/lib/rails_multisite/connection_management.rb:17:in `with_connection'
/var/www/discourse/app/jobs/base.rb:305:in `block in perform'
/var/www/discourse/app/jobs/base.rb:301:in `each'
/var/www/discourse/app/jobs/base.rb:301:in `perform'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:220:in `execute_job'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:185:in `block (4 levels) in process'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:180:in `traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:183:in `block in traverse'
/var/www/discourse/lib/sidekiq/discourse_event.rb:6:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:182:in `traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:183:in `block in traverse'
/var/www/discourse/lib/sidekiq/pausable.rb:131:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:182:in `traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:183:in `block in traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/job/interrupt_handler.rb:9:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:182:in `traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:183:in `block in traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/metrics/tracking.rb:26:in `track'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/metrics/tracking.rb:134:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:182:in `traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:173:in `invoke'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:183:in `block (3 levels) in process'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:145:in `block (6 levels) in dispatch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/job_retry.rb:118:in `local'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:144:in `block (5 levels) in dispatch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/config.rb:39:in `block in <class:Config>'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:139:in `block (4 levels) in dispatch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:281:in `stats'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:134:in `block (3 levels) in dispatch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/job_logger.rb:15:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:133:in `block (2 levels) in dispatch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/job_retry.rb:85:in `global'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:132:in `block in dispatch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/job_logger.rb:40:in `prepare'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:131:in `dispatch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:183:in `block (2 levels) in process'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:182:in `handle_interrupt'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:182:in `block in process'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:181:in `handle_interrupt'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:181:in `process'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:86:in `process_one'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:76:in `run'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/component.rb:10:in `watchdog'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/component.rb:19:in `block in safe_thread'

A Meta usa? Pensei ter visto o @system marcando um tópico há alguns dias.

1 curtida

Pode ser complicado de configurar, mas se o Discourse já estiver gerando embeddings (para pesquisa semântica) para novos tópicos, você provavelmente poderia gerar embeddings para descrições de tags, então usar similaridade semântica para sugerir tags para novos tópicos, ou auto-marcar tópicos existentes.

3 curtidas

Sim, executei por alguns dias no Meta para fazer mais testes… funciona ok, mas sem descrições de tags, estava usando indevidamente how-to para algumas perguntas do tipo “como faço” e aplicava moderator a posts onde a palavra era usada, mas não era totalmente relevante para o tópico. Alguns ajustes de prompt provavelmente ajudarão, e as ideias do @simon parecem que podem ser ótimos aprimoramentos futuros.

2 curtidas

Minha suposição é que embeddings poderiam funcionar melhor e ser mais rápidos/baratos. Meu interesse real nisso está mais alinhado com um sistema de marcação dinâmico que poderia substituir a codificação rígida de tags no banco de dados. Por exemplo, “como fazer” + “sso” seria uma lista de tópicos com embeddings que fossem semanticamente próximos aos embeddings das descrições das tags “como fazer” e “sso”.

2 curtidas