Discourse AI - 감정 분석

:bookmark: 이 가이드에서는 Discourse AI에서 감정 및 정서 분류를 설정하고, 생성된 보고서를 통해 커뮤니티 게시물을 탐색하는 방법을 설명합니다.

:person_raising_hand: 필수 사용자 레벨: 설정을 위한 관리자(Administrator); 보고서 열람을 위한 관리자 또는 중재자(Moderator).

감정 및 정서 분류를 통해 커뮤니티 논의의 패턴을 파악할 수 있습니다.

  • 감정(Sentiment): 게시물을 긍정, 중립, 부정으로 분류합니다.
  • 정서(Emotion): 감사, 호기심, 분노, 슬픔 등 28가지 레이블에 대해 점수를 할당합니다.

이 두 기능은 서로 독립적입니다. 둘 중 하나 또는 둘 다 활성화할 수 있으며, 각 기능마다 서로 다른 분류 백엔드를 선택할 수 있습니다. 보고서는 분류된 게시물의 수를 집계하며, 고유한 사용자나 성격 유형을 집계하지 않습니다. 읽어볼 가치가 있는 토론을 찾는 데 활용하세요. 맥락 이해를 대체하는 수단으로는 사용하지 마세요.

시작하기 전에

discourse_ai_enabled를 통해 Discourse AI가 활성화되어 있어야 하며, 분류 백엔드가 설정되어 있어야 합니다.

Discourse에서 호스팅 중이신가요? 사이트에는 이미 전용 분류 엔드포인트가 프로비저닝되어 있을 수 있습니다. 구성을 변경하기 전에 기존 구성을 확인하세요. 가용성, 엔드포인트 구성 또는 백필 제한과 관련하여 도움이 필요하면 team@discourse.org로 문의하세요.

자체 호스팅 중이신가요? 호환되는 전용 엔드포인트, 언어 모델을 사용하는 AI 에이전트 또는 의사 결정 모델을 설정하세요. 기능만 활성화한다고 해서 추론 서비스가 제공되지 않습니다.

자동 처리를 활성화하기 전에 서비스 제공업체의 개인정보 보호 정책, 데이터 보관 규정 및 비용을 검토하세요. 분류 시 게시물 텍스트가 선택된 백엔드로 전송됩니다. 아래 스크린샷은 테스트 사이트의 가상의 게시물과 의사 결정 모델을 사용한 것으로, 개인 커뮤니티 콘텐츠에 해당 제공업체를 추천하는 것이 아닙니다.

감정 및 정서 설정

  1. **관리자 → 플러그인 → AI → 기능(Admin → Plugins → AI → Features)**으로 이동합니다.
  2. “감정 및 정서(Sentiment and emotion)”를 엽니다.
  3. 긍정, 중립, 부정 분류가 필요하면 “감정(Sentiment)” 섹션에서 “활성화(Enabled)”를 선택하세요. 이는 ai_sentiment_enabled을 제어합니다.
  4. “백엔드(Backend)”를 선택하고 아래 설명에 따라 해당 엔드포인트, 에이전트 또는 모델을 설정하세요.
  5. 정서 분류가 필요하면 “정서(Emotion)” 섹션에서 “활성화(Enabled)”를 선택하세요. 이는 ai_sentiment_emotion_enabled을 제어합니다.
  6. 해당 백엔드를 선택하고 독립적으로 설정하세요.
  7. 다음 섹션에서 설명하는 백그라운드 처리 제한을 설정하세요.
  8. “제출(Submit)”을 클릭하고 선택 사항이 저장되었는지 확인하세요.

이 예시에서는 두 기능 모두 의사 결정 모델을 사용합니다. “시간당 게시물 수(Posts per hour)”는 테스트 중에 자동 스캔을 일시 중지하기 위해 의도적으로 0으로 설정되었습니다. 새 게시물과 과거 게시물을 자동으로 처리하려면 양의 값을 사용하세요.

전용 분류 엔드포인트 사용

전용 모델로 실행할 각 기능에 대해 “전용 분류 엔드포인트(Dedicated classification endpoint)”를 선택하세요. “전용 분류 엔드포인트 설정” 지침에 따라 ai_sentiment_model_configs 설정을 구성하세요.

참고용 전용 모델은 다음과 같습니다.

기능 모델
감정 cardiffnlp/twitter-roberta-base-sentiment-latest
정서 SamLowe/roberta-base-go_emotions

각 구성 항목은 classification_type (sentiment 또는 emotion), model_name, endpoint 및 api_key를 지정합니다. 사용하는 각 전용 분류기별로 별도 항목을 제공하세요. 자격 증명(private credentials)은 비공개로 유지하며 지원 게시물이나 스크린샷에 포함하지 마세요.

추론 서버 배포에 관한 내용은 Discourse AI를 위한 감정 및 정서 자체 호스팅을 참조하세요. 위의 컨트롤을 사용하여 각 기능을 별도로 활성화하고 선택하세요.

전용 백엔드는 호환되는 분류 API를 기대합니다. Anthropic, OpenAI 챗 또는 Azure Language 엔드포인트를 이 설정에 복사해도 호환되지 않습니다. Discourse의 다른 곳에서 언어 모델의 연결 테스트가 성공했다고 해서 전용 감정 엔드포인트가 테스트되는 것은 아닙니다.

AI 에이전트 또는 의사 결정 모델 사용

“LLM 에이전트”의 경우, 설정된 언어 모델이 포함된 적절한 분류기 에이전트를 선택하세요. Discourse에는 감정 및 정서 분류기 에이전트가 포함되어 있습니다. 일반적인 대화형 에이전트는 예상된 레이블 점수를 반환하도록 구성된 에이전트를 대체할 수 없습니다.

“의사 결정 모델”의 경우, 먼저 호환되는 의사 결정 모델을 구성하고 테스트한 후 활성화된 각 기능에 대해 선택하세요. 이 분류를 위해 의사 결정 모델에는 별도의 대화형 언어 모델이 필요하지 않습니다.

에이전트 및 의사 결정 모델 요청은 제공업체 요금을 발생시키거나 크레딧을 소진할 수 있습니다. 자동 처리 기능에 할당하기 전에 선택된 모델의 데이터 처리 규정을 검토하세요.

처리 및 과거 데이터 커버리지 제어

스케줄링된 작업은 5분마다 분류가 누락된 게시물이 있는지 확인합니다. 새 게시물과 과거 게시물 모두 이 스캔을 공유하며, 결과물은 게시 후 즉시_available_되지 않을 수 있습니다. 데이터가 표시되기 전에 최소한의 게시물 수나 일수가 필요하지 않습니다. 성공적으로 분류된 게시물 1개만으로도 기여할 수 있습니다.

제어 사이트 설정 기본값 효과
시간당 게시물 수 ai_sentiment_backfill_maximum_posts_per_hour 240 시간당 처리되는 대략적인 게시물 수. 0은 새 게시물을 포함하여 스케줄링된 스캔을 중지합니다.
게시물 경과 시간 제한(일) ai_sentiment_backfill_post_max_age_days 60 이 경과 시간 윈도우 내에서 생성된 게시물을 스캔합니다. 0은 제한 없음이 아닌 '오늘’을 의미합니다.

나중에 게시물을 포함하려면 경과 시간 제한을 늘리세요. 스케줄링된 작업은 누락된 결과를 시간 경과에 따라 처리하지만, 보고서에서 더 오래된 날짜 범위를 선택하는 것 자체가 해당 과거 데이터를 분류하는 것은 아닙니다.

속도는 정확하며 요청 또는 지출 상한이 아닌 근사치입니다. 두 분류기 모두를 활성화하면 게시물당 두 번의 요청이 발생할 수 있습니다. 실패한 요청 또는 거부된 요청은 재시도될 수 있습니다. 편집된 게시물은 별도로 확인되며, 스케줄링된 스캔의 속도 또는 경과 시간 제한의 영향을 받지 않습니다.

개인 메시지, 삭제된 게시물, 삭제된 토픽 및 일반적이지 않은 게시물 유형은 스케줄링된 백필 스캔에서 제외됩니다. 스캐너와 보고서의 PM 제외 조치는 모든 분류 경로에 대한 보장이 아닙니다. 편집된 게시물 작업에는 동일한 PM 필터가 적용되지 않습니다. 외부 제공업체를 구성하기 전에 이 사항을 고려하세요.

카테고리 또는 태그별 감정 탐색

  1. **관리자 → 보고서 → 감정 분석(Admin → Reports → Sentiment analysis)**으로 이동하거나, 사이트의 /admin/reports/sentiment_analysis로访问하세요.
  2. 시작 및 종료 날짜를 선택합니다.
  3. 결과를 그룹화하기 위해 “카테고리(Category)” 또는 “태그(Tag)”를 선택합니다.
  4. 해당 카테고리 또는 태그 필터를 사용하여 결과를 좁힙니다. 카테고리 필터링은 하위 카테고리를 포함하는 옵션도 제공합니다.
  5. 각 그룹의 총합과 긍정, 부정, 중립 세부 사항을 읽습니다.
  6. 그룹 행을 클릭하여 차트와 원본 게시물을 확인합니다.

이 예시에는 9개의 분류된 게시물이 포함되어 있습니다. 긍정 3개, 부정 3개, 중립 3개입니다. 이 보고서에는 중립 게시물도 포함됩니다.

세부 정보 보기에서는 다음과 같은 작업을 수행할 수 있습니다.

  • “전체(All)”, “긍정(Positive)”, “중립(Neutral)” 또는 “부정(Negative)”을 선택하여 로드된 게시물을 필터링합니다.
  • 게시물 토픽을 열어 주변 토론을 읽습니다.
  • 차트 옆의 링크 아이콘을 사용하여 해당 보고서 보기의 URL을 복사합니다. 받는 사람도 직원(staff) 접근 권한이 필요합니다.
  • “뒤로(Back)”를 클릭하여 그룹화된 결과로 돌아갑니다.

보고서 날짜는 분류가 실행된 시점이 아니라 게시물이 생성된 시점을 참조합니다. 토픽에 여러 태그가 있는 경우 게시물이 여러 태그 그룹에 기여할 수 있으므로, 태그 총합을 더하면 동일한 게시물이 두 번 이상 집계될 수 있습니다.

전체 감정 추세 읽기

관리자 → 보고서에서 “전체 감정(Overall sentiment)” 보고서를 열거나 /admin/reports/overall_sentiment로 이동하세요.

점수는 다음과 같습니다.

긍정 게시물 수 − 부정 게시물 수

게시물이 해당 그룹에 집계되려면 긍정 또는 부정 점수가 0.6보다 커야 합니다. 어느 쪽도 임계값을 초과하지 않으면 그룹화 된 보고서에서는 게시물을 중립으로 간주합니다. 이 임계값은 고정되어 있으며 변경할 수 있는 사이트 설정이 없습니다.

전체 감정은 평균 확률이나 행복도 백분율이 아닌 순수 게시물 수입니다. 긍정 게시물 3개와 부정 게시물 3개가 있으면 중립 게시물의 수와 관계없이 전체 점수는 0이 됩니다. 평평한 추세를 강한 의견의 부재로 해석하기 전에 그룹화 세부 정보와 원본 토론을 확인하세요.

정서 탐색

각 정서에는 관리자 → 보고서 아래에 보고서가 있습니다. 예를 들어 “분노(Anger)”에 대해 /admin/reports/emotion_anger를, “호기심(Curiosity)”에 대해 /admin/reports/emotion_curiosity를访问하세요. 추세를 탐색하기 위해 날짜 범위와 일별, 주별 또는 월별 그룹화를 선택하세요.

정서 보고서는 해당 레이블의 점수가 0.1보다 큰 게시물을 집계합니다. 이 임계값 또한 고정되어 있습니다. 하나의 게시물에 여러 정서가 해당될 수 있으므로, 정서 총합은 상호 배타적이지 않습니다.

정서를 기준으로 토픽을 필터링할 수도 있습니다. /filter?q=order:emotion_anger를访问하여 일치하는 토픽을 찾으세요. 이 토픽들은 평균 정서 점수가 아닌, 해당되는 분노 분류된 게시물의 수에 따라 순위가 매겨집니다. 순서를 반전시키려면 order:emotion_anger-asc를 사용하세요. activity-after: 필터가 없으면, 이 정렬은 지난 1년 동안의 해당되는 게시물을 집계합니다. 일반적인 토픽 목록 접근 권한이 여전히 적용됩니다.

사용 가능한 정서 레이블

감탄, 재미, 분노, 짜증, 승인, 배려, 혼란, 호기심, 욕망, 실망, 불승인, 혐오, 부끄러움, 흥분, 두려움, 감사, 슬픔, 기쁨, 사랑, 신경과민, 중립, 낙관, 자부심, 깨달음, 안도, 후회, 슬픔, 놀람.

제한 사항 이해

  • 분류는 모델 의존적입니다. 냉소, 인용 자료, 전문 용어 및 다국어 토론은 오해될 수 있습니다. 언어 범위와 정확도는 선택한 모델에 따라 달라집니다. 서로 다른 백엔드의 점수는 반드시 동일한 기준으로 보정되는 것은 아닙니다.
  • 보고서는 저장된 결과만 다룹니다. 백엔드가 작동하지 않거나, 처리가 일시 중지되었거나, 과거 데이터 커버리지가 부족하면 활성화된 기능에서도 비어 있거나 불완전한 보고서가 생성될 수 있습니다.
  • 기존 분류는 재사용됩니다. 분류된 게시물을 편집해도 결과가 자동으로 대체되지 않습니다. 동일한 백엔드 내에서 선택된 에이전트나 의사 결정 모델을 변경해도 기존 결과가 재분류되지 않습니다.
  • 백엔드를 변경하면 보고서의 결과 원본이 변경됩니다. 이전 결과는 선택된 백엔드로 변환되지 않습니다. 누락된 분류가 처리되는 동안 보고서가 비어 있을 수 있습니다.
  • 보고서에서 개인 메시지는 제외됩니다. 이 보고서 제외 기능은 위에 설명된 데이터 처리 제한과 별개입니다.

누락되거나 예상치 못한 결과 문제 해결

문제 확인할 사항
활성화 후 결과가 없음 관련 기능이 활성화되었는지, 백엔드가 설정되었는지, “시간당 게시물 수”가 양수인지, 백그라운드 작업이 실행 중인지 확인하세요. 5분 스캔 및 처리 큐에 시간을 허용하세요.
감정은 작동하지만 정서가 비어 있음 “정서”를 별도로 활성화하고 설정하세요. 감정 전용의 전용 엔드포인트는 정서 분류를 제공하지 않습니다.
오래된 날짜에 결과가 없음 게시물 경과 시간 제한을 늘리고, 처리 시간을 허용하며, 보고서 범위에 게시물 생성 날짜가 포함되는지 확인하세요.
전용 엔드포인트에서 오류 반환 URL, 자격 증명, 모델 및 응답 형식을 확인하세요. 어댑터는 평면형 레이블 및 점수 배열을 기대하며, positive, neutral, negative와 같은 감정 레이블을 사용합니다. 임의의 챗 응답이나 LABEL_0 형식의 레이블은 기대하지 않습니다.
개인 네트워크 엔드포인트에 연결할 수 없음 연결성을 확인하고, 필요시 신뢰할 수 있는 추론 엔드포인트의 호스트 이름이나 IP를 allowed_internal_hosts에 추가하세요. 서버 측 요청 위조(SSRF) 보호 기능을 광범위하게 비활성화하지 마세요.
편집 또는 모델 선택 후 결과가 변경되지 않음 기존 결과가 재사용됩니다. 콘텐츠를 편집하거나 다른 모델을 저장한다고 해서 전체 재분류가 스케줄링된다고 가정하지 마세요.
필터에 집계 수보다 적은 게시물이 표시됨 집계는 전체 그룹을 설명하는 반면, 감정 탭은 로드된 게시물을 필터링합니다. 더 많은 결과를 로드하는지 확인하고 불일치를 조사할 때 원본 토픽을 열어보세요.

관리자는 **관리자 → 플러그인 → AI → 로그(Admin → Plugins → AI → Logs)**에서 에이전트 및 의사 결정 모델 요청 기록을 검토할 수 있습니다. 전용 엔드포인트 장애를 포함하여 백그라운드 분류 오류에 대해 /logs를 확인하세요. 로그 내용에는 게시물 텍스트와 모델 응답이 포함될 수 있으므로 민감하게 다루세요. 진단 정보를 공유하기 전에 자격 증명과 개인 콘텐츠를 제거하세요.

추가 리소스

10개의 좋아요

게시물이 기존 주제에 병합되었습니다: Problems with Sentiment Backfill

게시글이 기존 주제에 병합되었습니다: Problems with Sentiment Backfill

OP가 업데이트되어, Sentiment의 업데이트된 기능을 보여주는 새 영상이 추가되었습니다. 이 영상에는 훨씬 더 많은 감정과 각 감정에 연관된 주제/게시물을 이해하는 기능이 포함되어 있습니다.

감정 모델 설정(sentiment model config)에 model_name, endpoint, api_key를 LLM 설정에서 복사해서 구성했는데, 테스트는 통과하는 반면 /logs에서 아래 오류가 발생합니다.

(아마도 제가 잘못 이해하고 있는 것일 수도 있는데, 왜 감정이용(sentiment)이 구성된 LLM 중 하나를 사용하지 않을까요?)

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...

감정 분석 모듈은 일반 LLM이 아니라, 감정 분류에 특화되어 파인튜닝된 모델을 사용합니다. 이러한 모델을 직접 실행하려면 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개의 좋아요