AI がランダムかつ予測不可能に LLM のトークン閾値を超える

LLM の出力トークン数を最大 8000 に設定しましたが、Discourse AI は定期的にその閾値を超えるリクエストを送信しています。

試行錯誤を繰り返し、ログを確認しながら(これ自体が問題で、ダッシュボードには何も表示されません)、数値を 7800、7500、7200、そして最終的に数日間の試行錯誤を経て 7000 にまで減らしました。

しかし、数ヶ月間正常に動作していたものが突然停止し、デバッグを行った結果、いくつかの極端なケースにおいて、LLM を最大 7000 トークンに設定しているにもかかわらず、Discourse が再び 8000 を超える出力トークンを要求していることに気づきました。

メッセージ(3 回報告)

DiscourseAi::Completions::Endpoints::OpenAi: ステータス: 413 - 本文: {"error":{"message":"モデル `openai/gpt-oss-120b`(組織 `org_01kccx1be8fffaz5sbe174je1r`、サービスティア `on_demand`)のトークン/分(TPM)制限を超えています:制限 8000、要求 8102。メッセージサイズを減らして再試行してください。より多くのトークンが必要ですか?https://console.groq.com/settings/billing で今日 Dev ターにアップグレードしてください","type":"tokens","code":"rate_limit_exceeded"}}


バックトレース

activesupport-8.0.5/lib/active_support/broadcast_logger.rb:218:in 'block in ActiveSupport::BroadcastLogger#dispatch'
activesupport-8.0.5/lib/active_support/broadcast_logger.rb:217:in 'Array#map'
activesupport-8.0.5/lib/active_support/broadcast_logger.rb:217:in 'ActiveSupport::BroadcastLogger#dispatch'
activesupport-8.0.5/lib/active_support/broadcast_logger.rb:129:in 'ActiveSupport::BroadcastLogger#error'
/var/www/discourse/plugins/discourse-ai/lib/completions/endpoints/base.rb:202:in 'block (2 levels) in DiscourseAi::Completions::Endpoints::Base#perform_completion!'
net-http-0.9.1/lib/net/http.rb:2461:in 'block in Net::HTTP#transport_request'
net-http-0.9.1/lib/net/http/response.rb:321:in 'Net::HTTPResponse#reading_body'
net-http-0.9.1/lib/net/http.rb:2458:in 'Net::HTTP#transport_request'
net-http-0.9.1/lib/net/http.rb:2410:in 'Net::HTTP#request'
rack-mini-profiler-4.0.1/lib/patches/net_patches.rb:19:in 'block in Net::HTTP#request_with_mini_profiler'
rack-mini-profiler-4.0.1/lib/mini_profiler/profiling_methods.rb:51:in 'Rack::MiniProfiler::ProfilingMethods#step'
rack-mini-profiler-4.0.1/lib/patches/net_patches.rb:18:in 'Net::HTTP#request_with_mini_profiler'
/var/www/discourse/plugins/discourse-ai/lib/completions/endpoints/base.rb:198:in 'block in DiscourseAi::Completions::Endpoints::Base#perform_completion!'
net-http-0.9.1/lib/net/http.rb:1630:in 'Net::HTTP#start'
net-http-0.9.1/lib/net/http.rb:1064:in 'Net::HTTP.start'
/var/www/discourse/plugins/discourse-ai/lib/completions/endpoints/base.rb:146:in 'DiscourseAi::Completions::Endpoints::Base#perform_completion!'
/var/www/discourse/plugins/discourse-ai/lib/completions/endpoints/open_ai_shared.rb:28:in 'DiscourseAi::Completions::Endpoints::OpenAiShared#perform_completion!'
/var/www/discourse/plugins/discourse-ai/lib/completions/llm.rb:214:in 'DiscourseAi::Completions::Llm#generate'
/var/www/discourse/plugins/discourse-ai/lib/agents/bot.rb:144:in 'DiscourseAi::Agents::Bot#reply'
/var/www/discourse/plugins/discourse-ai/lib/translation/base_translator.rb:55:in 'DiscourseAi::Translation::BaseTranslator#get_translation'
/var/www/discourse/plugins/discourse-ai/lib/translation/base_translator.rb:31:in 'block in DiscourseAi::Translation::BaseTranslator#translate'
/var/www/discourse/plugins/discourse-ai/lib/translation/base_translator.rb:31:in 'Array#map'
/var/www/discourse/plugins/discourse-ai/lib/translation/base_translator.rb:31:in 'DiscourseAi::Translation::BaseTranslator#translate'
/var/www/discourse/plugins/discourse-ai/lib/translation/post_localizer.rb:17:in 'DiscourseAi::Translation::PostLocalizer.localize'
/var/www/discourse/plugins/discourse-ai/app/jobs/regular/localize_posts.rb:39:in 'block in Jobs::LocalizePosts#execute'
/var/www/discourse/plugins/discourse-ai/app/jobs/regular/localize_posts.rb:29:in 'Array#each'
/var/www/discourse/plugins/discourse-ai/app/jobs/regular/localize_posts.rb:29:in 'Jobs::LocalizePosts#execute'
/var/www/discourse/app/jobs/base.rb:318:in 'block (2 levels) in Jobs::Base#perform'
rails_multisite-7.0.0/lib/rails_multisite/connection_management/null_instance.rb:49:in 'RailsMultisite::ConnectionManagement::NullInstance#with_connection'
rails_multisite-7.0.0/lib/rails_multisite/connection_management.rb:17:in 'RailsMultisite::ConnectionManagement.with_connection'
/var/www/discourse/app/jobs/base.rb:305:in 'block in Jobs::Base#perform'
/var/www/discourse/app/jobs/base.rb:301:in 'Array#each'
/var/www/discourse/app/jobs/base.rb:301:in 'Jobs::Base#perform'
sidekiq-7.3.10/lib/sidekiq/processor.rb:220:in 'Sidekiq::Processor#execute_job'
sidekiq-7.3.10/lib/sidekiq/processor.rb:185:in 'block (4 levels) in Sidekiq::Processor#process'
sidekiq-7.3.10/lib/sidekiq/middleware/chain.rb:180:in 'Sidekiq::Middleware::Chain#traverse'
sidekiq-7.3.10/lib/sidekiq/middleware/chain.rb:183:in 'block in Sidekiq::Middleware::Chain#traverse'
/var/www/discourse/lib/sidekiq/suppress_user_email_errors.rb:6:in 'Sidekiq::SuppressUserEmailErrors#call'
sidekiq-7.3.10/lib/sidekiq/middleware/chain.rb:182:in 'Sidekiq::Middleware::Chain#traverse'
sidekiq-7.3.10/lib/sidekiq/middleware/chain.rb:183:in 'block in Sidekiq::Middleware::Chain#traverse'
/var/www/discourse/lib/sidekiq/discourse_event.rb:6:in 'Sidekiq::DiscourseEvent#call'
sidekiq-7.3.10/lib/sidekiq/middleware/chain.rb:182:in 'Sidekiq::Middleware::Chain#traverse'
sidekiq-7.3.10/lib/sidekiq/middleware/chain.rb:183:in 'block in Sidekiq::Middleware::Chain#traverse'
/var/www/discourse/lib/sidekiq/pausable.rb:131:in 'Sidekiq::Pausable#call'
sidekiq-7.3.10/lib/sidekiq/middleware/chain.rb:182:in 'Sidekiq::Middleware::Chain#traverse'
sidekiq-7.3.10/lib/sidekiq/middleware/chain.rb:183:in 'block in Sidekiq::Middleware::Chain#traverse'
sidekiq-7.3.10/lib/sidekiq/job/interrupt_handler.rb:9:in 'Sidekiq::Job::InterruptHandler#call'
sidekiq-7.3.10/lib/sidekiq/middleware/chain.rb:182:in 'Sidekiq::Middleware::Chain#traverse'
sidekiq-7.3.10/lib/sidekiq/middleware/chain.rb:183:in 'block in Sidekiq::Middleware::Chain#traverse'
sidekiq-7.3.10/lib/sidekiq/metrics/tracking.rb:26:in 'Sidekiq::Metrics::ExecutionTracker#track'
sidekiq-7.3.10/lib/sidekiq/metrics/tracking.rb:134:in 'Sidekiq::Metrics::Middleware#call'
sidekiq-7.3.10/lib/sidekiq/middleware/chain.rb:182:in 'Sidekiq::Middleware::Chain#traverse'
sidekiq-7.3.10/lib/sidekiq/middleware/chain.rb:173:in 'Sidekiq::Middleware::Chain#invoke'
sidekiq-7.3.10/lib/sidekiq/processor.rb:184:in 'block (3 levels) in Sidekiq::Processor#process'
sidekiq-7.3.10/lib/sidekiq/processor.rb:145:in 'block (6 levels) in Sidekiq::Processor#dispatch'
sidekiq-7.3.10/lib/sidekiq/job_retry.rb:118:in 'Sidekiq::JobRetry#local'
sidekiq-7.3.10/lib/sidekiq/processor.rb:144:in 'block (5 levels) in Sidekiq::Processor#dispatch'
sidekiq-7.3.10/lib/sidekiq/config.rb:39:in 'block in <class:Config>'
sidekiq-7.3.10/lib/sidekiq/processor.rb:139:in 'block (4 levels) in Sidekiq::Processor#dispatch'
sidekiq-7.3.10/lib/sidekiq/processor.rb:281:in 'Sidekiq::Processor#stats'
sidekiq-7.3.10/lib/sidekiq/processor.rb:134:in 'block (3 levels) in Sidekiq::Processor#dispatch'
sidekiq-7.3.10/lib/sidekiq/job_logger.rb:15:in 'Sidekiq::JobLogger#call'
sidekiq-7.3.10/lib/sidekiq/processor.rb:133:in 'block (2 levels) in Sidekiq::Processor#dispatch'
sidekiq-7.3.10/lib/sidekiq/job_retry.rb:85:in 'Sidekiq::JobRetry#global'
sidekiq-7.3.10/lib/sidekiq/processor.rb:132:in 'block in Sidekiq::Processor#dispatch'
sidekiq-7.3.10/lib/sidekiq/job_logger.rb:40:in 'Sidekiq::JobLogger#prepare'
sidekiq-7.3.10/lib/sidekiq/processor.rb:131:in 'Sidekiq::Processor#dispatch'
sidekiq-7.3.10/lib/sidekiq/processor.rb:183:in 'block (2 levels) in Sidekiq::Processor#process'
sidekiq-7.3.10/lib/sidekiq/processor.rb:182:in 'Thread.handle_interrupt'
sidekiq-7.3.10/lib/sidekiq/processor.rb:182:in 'block in Sidekiq::Processor#process'
sidekiq-7.3.10/lib/sidekiq/processor.rb:181:in 'Thread.handle_interrupt'
sidekiq-7.3.10/lib/sidekiq/processor.rb:181:in 'Sidekiq::Processor#process'
sidekiq-7.3.10/lib/sidekiq/processor.rb:86:in 'Sidekiq::Processor#process_one'
sidekiq-7.3.10/lib/sidekiq/processor.rb:76:in 'Sidekiq::Processor#run'
sidekiq-7.3.10/lib/sidekiq/component.rb:10:in 'Sidekiq::Component#watchdog'
sidekiq-7.3.10/lib/sidekiq/component.rb:19:in 'block in Sidekiq::Component#safe_thread'

これは非常に問題があり、Discourse での AI 利用体験を著しく損なうものです。LLM 設定に関する問題はダッシュボードに表示されず、Discourse が LLM 設定を無視すると予測不能な問題や不満を引き起こします。

Discourse は、LLM 設定パラメータに自らを制限し、管理ダッシュボードに問題を明示する仕組みを見つける必要があります。

リクエストトークンとレスポンストークンを混同していませんか?

413 は、リクエストされたレスポンスではなく、リクエスト自体が大きすぎることを意味します。

これを処理するには、Context window の LLM 設定を調整する必要がありますが、現在では 8k トークンは非常に小さすぎると警告しておきます。一部の機能では機能するかもしれませんが、LLM が 100 万トークンのコンテキストウィンドウを処理している現在、私たちが頻繁に利用しているものではありません。私のデスクトップ PC では、あなたが使用しているモデルよりもはるかに優れたモデルを使って 256k のコンテキストウィンドウを実行できます。

コンテキストウィンドウは 130k に設定されています。

しかし、これでは同じ問題に戻ってしまいます。Groq のモデル制限は 131,072 で、私はすでにそれを 130,000 に設定しています。Discourse が送信している量を把握するために、制限を実験的に試す必要はありません。Discourse は、LLM 設定で提供される制限内で動作できるはずです。

理解できないのは、なぜ最大出力トークンを減らすと問題が解決する点です。コンテキストウィンドウに変更を加えたわけではなく、最大出力トークンをさらに減らしただけなのに、動作し始め、中断した場所から再開するようになりました。

参考までに、問題の発端は翻訳サービスがフリーズし、トークンが枯渇したことでした:

DiscourseAi::Completions::Endpoints::OpenAi: status: 429 - body: {“error”:{“message”:“モデル openai/gpt-oss-120b の組織 org_01kccx1be8fffaz5sbe17 におけるサービス階層 on_demand で、1 日あたりのトークン数(TPD)のレート制限に達しました:制限 200000、使用 193487、要求 7464。6 分 50.832 秒後に再度お試しください。トークンが不足していますか?今日、Billing - Manage で Dev Tier にアップグレードしてください”,“type”:“tokens”,“code”:“rate_limit_exceeded”}}

その後、1 日あたりのレート制限がリセットされるよう、サービスを 24 時間一時停止しました。再起動すると、以下のエラーが発生していることに気づきました:

DiscourseAi::Completions::Endpoints::OpenAi: status: 413 - body: {“error”:{“message”:“モデル openai/gpt-oss-120b の組織 org_01kccx1be8fffaz5sbe17 におけるサービス階層 on_demand で、1 分あたりのトークン数(TPM)の制限を超えたリクエストです:制限 8000、要求 8102。メッセージサイズを小さくして再度お試しください。トークンが不足していますか?今日、Billing - Manage で Dev Tier にアップグレードしてください”,“type”:“tokens”,“code”:“rate_limit_exceeded”}}

その後、LLM 設定で最大出力トークンを 7000 から 6800 に減らしたところ、再び動作するようになりました。

何が不足しているのでしょうか?コンテキストウィンドウに関連しており、最大出力トークンとは無関係だとおっしゃるのでしょうか?Groq/モデルの制限値を Discourse の LLM 設定とどう対応させるか、確認しているところです。