Discourse AI - 감정

:bookmark: 이 주제는 Discourse AI 플러그인의 감정(Sentiment) 기능 설정에 대해 다루고 있습니다.

:person_raising_hand: 필요한 사용자 권한: модератор(Moderator)

감정(Sentiment) 기능은 게시물을 분석하여 감정 점수와 정서 점수를 제공함으로써 특정 기간 동안 커뮤니티의 전반적인 분위기를 파악할 수 있도록 도와줍니다. 이러한 인사이트는 커뮤니티 내에서 게시물을 작성하고 상호작용하는 사용자의 유형을 파악하는 데 유용할 수 있습니다.

기능

  • 전반적인 감정: 긍정적 또는 부정적으로 분류된 게시물의 수를 비교합니다.
  • 긍정, 부정, 전반적인 점수의 수치를 토글하여 표시하는 막대 그래프
  • 감정(Emotion): 여러 감정으로 분류된 주제 및 게시물의 수를 시간 범위로 그룹화하여 표시
    • 오늘
    • 어제
    • 지난 7일
    • 지난 30일
  • 설정을 통해 접근할 수 있는 임의의 기간에 대한 보고서
    • 연도별
    • 분기별
    • 월별
    • 주별
    • 사용자 지정 범위
  • 모든 스태프 사용자(관리자 및 модератор)가 접근 가능

감정(Sentiment) 활성화

설정

감정(Sentiment)은 호스팅 고객에게 기본적으로 활성화되어 있습니다. 수동 단계는 아래를 참조하세요.

  1. Admin 설정 → Plugins로 이동하여 discourse-ai를 검색하거나 찾아 활성화되어 있는지 확인하세요.
  2. 감정 분석을 위해 ai_sentiment_enabled을 활성화하세요.
  3. /admin/dashboard/sentiment로 이동하여 해당 보고서를 확인하세요.

:information_source: 활성화되면, Sentiment는 5분마다 실행되는 예약 작업을 통해 모든 새 게시물과 지난 60일 간의 게시물을 자동으로 분류하고 백필(backfill)합니다. 60일 이전의 게시물을 백필하려면 ai_sentiment_backfill_post_max_age_days 사이트 설정을 증가시키세요.

:discourse2: 우리가 호스팅을 하고 있나요?

백필 설정 조정에 도움이 필요하면 team@discourse.org로 문의해 주세요.

:mechanic: 자체 호스팅을 하고 계신가요?

원하는 시간 범위를 포함하도록 사이트 설정에서 ai_sentiment_backfill_post_max_age_days를 증가시키세요. 예약된 백필 작업이 오래된 게시물을 자동으로 처리합니다. 필요한 모델 엔드포인트 설정에 대한 자세한 내용은 DiscourseAI를 위한 자체 호스팅 Sentiment 및 Emotion을 참조하세요.

기술적 FAQ

주제/게시물 데이터는 어떻게 처리되며, 점수는 어떻게 할당되나요?

  • Sentiment는 “게시물별” 정밀도를 가집니다. 각 게시물에 대해 감정을 파악한 후, 해당 데이터를 다양한 형태로(태그별 / 카테고리별 / 시간별 등…) 분할할 수 있습니다. 이는 긍정적 또는 부정적으로 분류된 게시물의 수를 비교합니다. 이러한 값은 긍정 또는 부정 점수가 고정 임계값인 0.6을 초과할 때 계산됩니다(현재는 설정할 수 없음).

다른 언어에 대한 지원 추가 계획이 있나요?

  • 네, 미래에 추가할 계획입니다! 전용 모델 대신 다국어 단순 기계 학습(ML) 모델을 추가하거나 다국어 대규모 언어 모델(LLMs)을 사용하여 데이터를 분류하는 방식으로 지원할 예정입니다.

Sentiment를 구동하는 데 어떤 모델이 사용되나요?

주의 사항

  • 중립(긍정도 부정도 아님)으로 분류된 게시물은 표시되지 않습니다.
  • 개인 메시지(PM)는 계산에서 제외됩니다.
10개의 좋아요

A post was merged into an existing topic: Problems with Sentiment Backfill

A post was merged into an existing topic: Problems with Sentiment Backfill

The OP has been updated with a new video showcasing the updated features of Sentiment including a ton more emotions and understanding which topics/posts are associated with each emotion

I configured the sentiment model config with a model_name, endpoint, and api_key copied from the LLM settings, where it passes the test, but I get the error below in /logs.

(But maybe I don’t understand correctly, because why doesn’t sentiment use one of the configured LLMs?)

Using claude-3-5-sonnet.


{"type":"error","error":{"type":"invalid_request_error","message":"anthropic-version: header is required"}} (Net::HTTPBadResponse)
/var/www/discourse/plugins/discourse-ai/lib/inference/hugging_face_text_embeddings.rb:71:in `classify'
/var/www/discourse/plugins/discourse-ai/lib/sentiment/post_classification.rb:142:in `request_with'
/var/www/discourse/plugins/discourse-ai/lib/sentiment/post_classification.rb:78:in `block (4 levels) in bulk_classify!'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/promises.rb:1593:in `evaluate_to'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/promises.rb:1776:in `block in on_resolvable'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:359:in `run_task'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:350:in `block (3 levels) in create_worker'
<internal:kernel>:187:in `loop'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:341:in `block (2 levels) in create_worker'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:340:in `catch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:340:in `block in create_worker'
Status: 400

{"type":"error","error":{"type":"invalid_request_error","message":"anthropic-version: header is required"}} (Net::HTTPBadResponse)
/var/www/discourse/plugins/discourse-ai/lib/inference/hugging_face_text_embeddings.rb:71:in `classify'
/var/www/discourse/plugins/discourse-ai/lib/sentiment/post_classification.rb:142:in `request_wit...

The sentiment module doesn’t use general LLMs, but models specifically fine tuned to sentiment classification. If you want to run those models on your own that is documented at Self-Hosting Sentiment and Emotion for DiscourseAI

3개의 좋아요

@Falco 2025년 1월 이후로 감정 분석(sentiment)이 더 이상 실행되지 않는 것을 방금 확인했습니다. 위 링크에서 설명하듯, ai_sentiment_model이라는 새로운 설정이 있어 자체적인 전용 감정 분석 모델/이미지를 실행하는 데 사용되는 것 같습니다. Discourse를 업데이트한 후 ai_sentiment_model_configs가 모두 비어 있는 것을 확인했는데(이것이 비어 있어야 하나요?).

백필(backfill) rake를 실행하려고 하면 오류가 발생합니다:

rake ai:sentiment:backfill
rake aborted!
ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR:  syntax error at or near ")" (ActiveRecord::StatementInvalid)
LINE 1: ...e_upload_id", "posts"."outbound_message_id" FROM () as posts...
                                                             ^
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/rack-mini-profiler-3.3.1/lib/patches/db/pg.rb:69:in `exec_params'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/rack-mini-profiler-3.3.1/lib/patches/db/pg.rb:69:in `exec_params'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/postgresql_adapter.rb:894:in `block (2 levels) in exec_no_cache'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract_adapter.rb:1004:in `block in with_raw_connection'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activesupport-7.2.2.1/lib/active_support/concurrency/null_lock.rb:9:in `synchronize'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract_adapter.rb:976:in `with_raw_connection'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/postgresql_adapter.rb:893:in `block in exec_no_cache'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activesupport-7.2.2.1/lib/active_support/notifications/instrumenter.rb:58:in `instrument'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract_adapter.rb:1119:in `log'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/postgresql_adapter.rb:892:in `exec_no_cache'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/postgresql_adapter.rb:872:in `execute_and_clear'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/postgresql/database_statements.rb:66:in `internal_exec_query'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract/database_statements.rb:647:in `select'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract/database_statements.rb:73:in `select_all'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract/query_cache.rb:251:in `select_all'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/querying.rb:70:in `_query_by_sql'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:1431:in `block (2 levels) in exec_main_query'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:415:in `with_connection'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_handling.rb:296:in `with_connection'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:1430:in `block in exec_main_query'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract/query_cache.rb:143:in `disable_query_cache'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/query_cache.rb:30:in `uncached'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation/delegation.rb:78:in `block in uncached'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:1355:in `_scoping'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:541:in `scoping'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation/delegation.rb:78:in `uncached'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:1450:in `skip_query_cache_if_necessary'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:1414:in `exec_main_query'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:1392:in `block in exec_queries'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract/query_cache.rb:143:in `disable_query_cache'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/query_cache.rb:30:in `uncached'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation/delegation.rb:120:in `public_send'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation/delegation.rb:120:in `block in method_missing'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:1355:in `_scoping'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:541:in `scoping'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation/delegation.rb:120:in `method_missing'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:1450:in `skip_query_cache_if_necessary'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:1386:in `exec_queries'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:1167:in `load'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:336:in `records'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation/batches.rb:380:in `block in batch_on_unloaded_relation'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation/batches.rb:378:in `batch_on_unloaded_relation'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation/batches.rb:269:in `in_batches'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation/batches.rb:157:in `find_in_batches'
/var/www/discourse/plugins/discourse-ai/lib/tasks/modules/sentiment/backfill.rake:7:in `block in <main>'
/usr/local/bin/bundle:25:in `load'
/usr/local/bin/bundle:25:in `<main>'

Caused by:
PG::SyntaxError: ERROR:  syntax error at or near ")" (PG::SyntaxError)
LINE 1: ...e_upload_id", "posts"."outbound_message_id" FROM () as posts...
                                                             ^
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/rack-mini-profiler-3.3.1/lib/patches/db/pg.rb:69:in `exec_params'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/rack-mini-profiler-3.3.1/lib/patches/db/pg.rb:69:in `exec_params'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/postgresql_adapter.rb:894:in `block (2 levels) in exec_no_cache'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract_adapter.rb:1004:in `block in with_raw_connection'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activesupport-7.2.2.1/lib/active_support/concurrency/null_lock.rb:9:in `synchronize'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract_adapter.rb:976:in `with_raw_connection'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/postgresql_adapter.rb:893:in `block in exec_no_cache'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activesupport-7.2.2.1/lib/active_support/notifications/instrumenter.rb:58:in `instrument'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract_adapter.rb:1119:in `log'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/postgresql_adapter.rb:892:in `exec_no_cache'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/postgresql_adapter.rb:872:in `execute_and_clear'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/postgresql/database_statements.rb:66:in `internal_exec_query'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract/database_statements.rb:647:in `select'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract/database_statements.rb:73:in `select_all'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract/query_cache.rb:251:in `select_all'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/querying.rb:70:in `_query_by_sql'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:1431:in `block (2 levels) in exec_main_query'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:415:in `with_connection'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_handling.rb:296:in `with_connection'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:1430:in `block in exec_main_query'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract/query_cache.rb:143:in `disable_query_cache'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/query_cache.rb:30:in `uncached'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation/delegation.rb:78:in `block in uncached'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:1355:in `_scoping'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:541:in `scoping'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation/delegation.rb:78:in `uncached'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:1450:in `skip_query_cache_if_necessary'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:1414:in `exec_main_query'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:1392:in `block in exec_queries'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/connection_adapters/abstract/query_cache.rb:143:in `disable_query_cache'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/query_cache.rb:30:in `uncached'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation/delegation.rb:120:in `public_send'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation/delegation.rb:120:in `block in method_missing'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:1355:in `_scoping'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:541:in `scoping'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation/delegation.rb:120:in `method_missing'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:1450:in `skip_query_cache_if_necessary'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:1386:in `exec_queries'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:1167:in `load'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation.rb:336:in `records'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation/batches.rb:380:in `block in batch_on_unloaded_relation'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation/batches.rb:378:in `batch_on_unloaded_relation'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation/batches.rb:269:in `in_batches'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation/batches.rb:157:in `find_in_batches'
/var/www/discourse/plugins/discourse-ai/lib/tasks/modules/sentiment/backfill.rake:7:in `block in <main>'
/usr/local/bin/bundle:25:in `load'
/usr/local/bin/bundle:25:in `<main>'
Tasks: TOP => ai:sentiment:backfill
(See full trace by running task with --trace)

제 질문은, 이 기능이 2024년 11월/12월까지는 잘 작동했다는 것입니다. 이전에는 전용 감정 분석 모델/서버 없이 어떻게 작동했던 것일까요? 전용 서버를 실행하지 않고 일반적인 공개 서비스나 내장 서비스를 사용하여 다시 작동하게 만드는 방법이 있을까요? 저는 Discourse가 자체적으로 완결된(setup) 구조라 단순한 배포가 쉽다는 점을 정말 좋아합니다. 커스텀 배포를 해야 하면 복잡성과 유지보수 비용이 증가하여 소규모 배포 환경에서는 부담이 됩니다. 2025년 이전처럼 감정 분석이 기본적으로(out of the box) 작동하던 설정으로 돌아갈 수 있는 방법이 있을까요?

플러그인은 기본적으로 DigitalOcean에 있는 서버를 사용하도록 설정되어 있었습니다. 저는 테스트를 더 쉽게 하기 위해 해당 서버를 직접 구축했습니다.

현재 플러그인 기본값을 깨끗한 상태로 변경했으며, AI 분류를 사용하려는 사용자는 Meta의 문서에 따라 서버를 직접 실행해야 합니다.

맞습니다. 하지만 우리는 테스트 목적을 위해 그 비용을 지불하고 있었습니다. 모든 셀프 호스터에게 이러한 서비스를 제공하는 것은 지속 가능하지 않습니다.

참고로, 우리는 GPU 가속 서버를 통해 이 분류 서비스를 호스팅 서비스의 일부로 제공하고 있습니다.

2개의 좋아요

Discourse AI 감정 분석 문제: Hugging Face 모델 형식 및 Azure 엔드포인트 실패


안녕하세요, Discourse 커뮤니티 및 개발자 여러분,

저는 포럼의 Discourse AI 플러그인에서 감정 분석 기능을 설정하고 사용하려고 할 때 심각한 문제를 겪고 있습니다. 올바르게 작동하지 않도록 하는 두 가지 별개의 문제가 있는 것으로 보입니다.


문제 1: Hugging Face 모델 응답 형식 불일치

감정 분석을 위해 Hugging Face의 cardiffnlp/twitter-roberta-base-sentiment 모델을 구성했습니다. API 키는 유효하며 curl을 사용하여 Discourse 인스턴스에서 API를 성공적으로 호출할 수 있지만, Hugging Face 모델의 출력 형식이 변경된 것으로 인해 Discourse AI 플러그인이 응답을 올바르게 파싱하지 못하고 있는 것 같습니다.

curl 명령 (유효한 API 키 및 새 형식 확인):

Bashcurl -X POST https://api-inference.huggingface.co/models/cardiffnlp/twitter-roberta-base-sentiment \ -H "Authorization: Bearer hf_xxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d "{\"inputs\": \"I love Discourse!\"}"

curl 출력 (새로운 중첩된 배열 형식 표시):

[[{"label":"LABEL_2","score":0.9891520738601685},{"label":"LABEL_1","score":0.009014752693474293},{"label":"LABEL_0","score":0.0018332178005948663}]]

문제점: twitter-roberta-base-sentiment model과거에 단일 배열의 라벨-스코어 해시([{"label": "LABEL_2", "score": 0.98}, ...])를 반환했습니다. 그러나 이제는 중첩된 배열([[{"label": "LABEL_2", "score": 0.98}, ...]])을 반환합니다.

Discourse AI 플러그인의 하드코딩된 파싱 로직(특히, 백트레이스에 표시된 바와 같이 classification["label"][/\d+/].to_i)은 이 외부 배열 계층을 고려하지 않습니다. 이로 인해 Symbol에 Integer로 접근을 시도할 때 TypeError가 발생합니다.

에러 메시지 (Job Exception에서):

no implicit conversion of Symbol into Integer (TypeError)
/var/www/discourse/plugins/discourse-ai/lib/sentiment/post_classification.rb:163:in block in transform_result’
/var/www/discourse/plugins/discourse-ai/lib/sentiment/post_classification.rb:163:in each’
/var/www/discourse/plugin…`

Hugging Face 문제의 전체 백트레이스:

/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/promises.rb:1268:in `raise'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/promises.rb:1268:in `wait_until_resolved!'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/promises.rb:998:in `value!'
/var/www/discourse/plugins/discourse-ai/lib/sentiment/post_classification.rb:93:in `bulk_classify!'
/var/www/discourse/plugins/discourse-ai/app/jobs/scheduled/sentiment_backfill.rb:27:in `execute'
/var/www/discourse/app/jobs/base.rb:316:in `block (2 levels) in perform'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/rails_multisite-6.1.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-6.1.0/lib/rails_multisite/connection_management.rb:21:in `with_connection'
/var/www/discourse/app/jobs/base.rb:303:in `block in perform'
/var/www/discourse/app/jobs/base.rb:299:in `each'
/var/www/discourse/app/jobs/base.rb:299:in `perform'
/var/www/discourse/app/jobs/base.rb:379:in `perform'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/mini_scheduler-0.18.0/lib/mini_scheduler/manager.rb:137:in `process_queue'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/mini_scheduler-0.18.0/lib/mini_scheduler/manager.rb:77:in `worker_loop'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/mini_scheduler-0.18.0/lib/mini_scheduler/manager.rb:63:in `block (2 levels) in ensure_worker_threads'
<internal:kernel>:187:in `loop'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:341:in `block (2 levels) in create_worker'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:340:in `catch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:340:in `block in create_worker'
no implicit conversion of Symbol into Integer (TypeError)
/var/www/discourse/plugins/discourse-ai/lib/sentiment/post_classification.rb:163:in `block in transform_result'
/var/www/discourse/plugins/discourse-ai/lib/sentiment/post_classification.rb:163:in `each'
/var/www/discourse/plugin...

문제 2: Microsoft Azure 모델 구성 시 Hugging Face 오류 발생

Discourse AI 설정에서 Microsoft Text Analytics 모델로 전환을 시도했을 때 404 Resource not found 오류가 발생했으며, 놀랍게도 백트레이스는 여전히 hugging_face_text_embeddings.rb를 가리키고 있습니다.

에러 메시지 (Job Exception에서):

Job exception: 416 errors
{"error":{"code":"404","message": "Resource not found"}} (Net::HTTPBadResponse)

관련 백트레이스 조각 (Microsoft 모델이 선택되었음에도 Hugging Face를 가리킴):

/var/www/discourse/plugins/discourse-ai/lib/inference/hugging_face_text_embeddings.rb:76:in `do_request!'
/var/www/discourse/plugins/discourse-ai/lib/inference/hugging_face_text_embeddings.rb:51:in `classify_by_sentiment!'
/var/www/discourse/plugins/discourse-ai/lib/sentiment/post_classification.rb:156:in `request_with'

관찰 사항: 이는 Microsoft 모델의 엔드포인트와 API 키를 선택하고 구성하더라도, Discourse AI 플러그인이 감정 분석 요청을 Hugging Face 전용 로직 또는 엔드포인트를 통해 하드코딩되거나 잘못된 경로로 라우팅하고 있음을 시사합니다. 이로 인해 Microsoft 모델을 전혀 사용할 수 없습니다.


설정 스크린샷:

설정을 보여주기 위해 제 Discourse AI 설정 스크린샷을 첨부했습니다:

  • AI 감정 모델의 상세 설정 (Hugging Face 및 Microsoft 모델 모두 표시) - Hugging Face만 또는 Microsoft 모델 구성만 사용하여 테스트했으며 결과는 동일했습니다

이러한 문제로 인해 감정 분석 기능은 사실상 사용할 수 없는 상태입니다. 플러그인은 새로운 Hugging Face 응답 형식을 처리하고, 다른 감정 제공자가 구성된 경우 요청을 올바르게 라우팅하도록 업데이트가 필요한 것으로 보입니다.

이러한 문제에 대한 도움이나 지침을 주시면 감사하겠습니다.

감사합니다!

2개의 좋아요

감정 분석 리포팅 기능이 다른 사용자들에게도 정상적으로 작동하는지, 아니면 제가 무언가를 잘못 설정한 건지 궁금합니다. 동일한 문제가 계속 발생하고 있으므로, 감정 분석 리포팅을 활성화하기 위해 추가로 확인하거나 설정해야 할 사항이 무엇인지 알려주시면 감사하겠습니다.

1개의 좋아요

자체 호스팅된 Discourse 인스턴스에서 이 기능을 Azure AI Language와 함께 사용하려고 하고 있습니다. 이미 Azure 구독을 사용하여 GPT-4.5를 Discourse에 통합(요약 및 챗봇 기능)하고 있기 때문입니다:

하지만 감정 분석 대시보드에 데이터가 표시되지 않으며, 로그에 다음과 같은 오류가 나타납니다:

Discourse AI: Errors during bulk classification: Failed to classify 208 posts (example ids: 2256, 909, 2270, 2260, 2797) : JSON::ParserError : An empty string is not a valid JSON string.

백트레이스를 보면 Discourse가 HuggingFace를 사용하려는 것처럼 보입니다. 현재 지원되는 모델은 이것들뿐인가요?

감사합니다,

N

1개의 좋아요

그럼 이 기능을 사용하려면 자체 인스턴스를 설정해야 하는 것인가요? (리소스가 많이 필요한 GPU 인스턴스가 필요하며 비용이 꽤 들겠죠?) 이 기능은 매우 유용해 보이지만, 실제 Discourse 호스팅 비용보다 설정 비용이 더 들 것 같습니다.

네, 지원되는 모델은 OP(첫 번째 게시글)에 나열된 것들입니다.

비용이 문제가 되지 않는 사용자를 위해 나중에 LLM을 활용한 분류 기능도 지원할 예정입니다.

이 기능 전체가 ML 모델을 사용하여 게시물을 분류하는 것을 중심으로 구축되어 있으므로, 네, 모델을 실행할 장소가 필요합니다.

Discourse는 가장 저렴한 VPS에서도 실행될 수 있기 때문에, ML 모델을 실행하는 것이 실제로 더 비쌉니다. 가장 저렴한 방식으로 이 기능을 사용하려면, 모델 로딩에 충분한 RAM이 있는 한 CPU 코어가 몇 개뿐인 서버에서도 실행할 수 있습니다.

1개의 좋아요

이전에 이미 질문이 올라온 것이라면 죄송합니다. 하지만 임계 점수를 정확히 어디서 설정할 수 있는지에 대한 참고 자료를 찾을 수 없었거든요 :sweat_smile:

1개의 좋아요

안타깝게도 임계 점수는 사용자나 관리자가 설정할 수 있는 항목이 아닙니다. 코드베이스에 고정된 특정 값입니다.

1개의 좋아요