AI가 LLM 토큰 한도를 예측할 수 없게 초과함

LLM의 출력 토큰을 최대 8000으로 설정했지만, Discourse AI가 정기적으로 이 임계값을 초과하는 요청을 보내고 있습니다.

시간이 지나며 시행착오를 거치고 로그를 확인하는 과정에서(대시보드에 아무런 표시가 없어서 이 자체가 문제입니다) 7800, 7500, 7200으로 줄이고 결국 며칠간의 시행착오 끝에 7000으로 설정했습니다.

몇 달간 정상적으로 작동하다가 갑자기 멈추었고, 디버깅을 통해 일부 엣지 케이스에서 LLM이 최대 7000토큰으로 설정되어 있음에도 불구하고 Discourse가 다시 8000토큰을 초과하는 출력을 요청하고 있음을 알게 되었습니다.

Message (3 copies reported)

DiscourseAi::Completions::Endpoints::OpenAi: status: 413 - body: {"error":{"message":"Request too large for model `openai/gpt-oss-120b` in organization `org_01kccx1be8f` service tier `on_demand` on tokens per minute (TPM): Limit 8000, Requested 8102, please reduce your message size and try again. Need more tokens? Upgrade to Dev Tier today at https://console.groq.com/settings/billing","type":"tokens","code":"rate_limit_exceeded"}}


Backtrace

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이 1M 토큰 길이의 컨텍스트 윈도우를 처리하는 요즘에는 그렇게 많이 사용하지 않는 수준입니다. 저는 데스크톱 PC에서 여러분이 사용하는 것보다 훨씬 더 나은 모델을 사용하여 256k 컨텍스트 윈도우를 실행할 수 있습니다.

컨텍스트 윈도우가 130k로 설정되어 있습니다

하지만 이 문제는 여전히 동일한 문제로 돌아갑니다. Groq의 모델 한도는 131,072이며, 저는 이미 130,000으로 설정해 두었습니다. Discourse가 얼마나 많은 데이터를 전송하는지 파악하기 위해 한도를 직접 실험해 보며 알아내야 할 이유는 없습니다. Discourse는 LLM 구성에서 제공된 한도 내에서 작동할 수 있어야 합니다.

아직 이해가 안 되는 부분은 최대 출력 토큰을 줄이면 문제가 해결되는 이유입니다. 컨텍스트 윈도우에는 변경 사항이 없었고, 단순히 최대 출력 토큰만 더 줄였을 뿐인데 작동하기 시작했고 중단된 지점부터 이어서 처리하기 시작했습니다.

참고로, 문제는 처음에 번역 서비스가 멈추고 토큰이 소진되면서 시작되었습니다:

DiscourseAi::Completions::Endpoints::OpenAi: status: 429 - body: {“error”:{“message”:“Rate limit reached for model openai/gpt-oss-120b in organization org_01kccx1be8fffaz5sbe17 service tier on_demand on tokens per day (TPD): Limit 200000, Used 193487, Requested 7464. Please try again in 6m50.832s. Need more tokens? Upgrade to Dev Tier today at https://console.groq.com/settings/billing",“type”:“tokens”,“code”:"rate_limit_exceeded”}}

그 후 일일 사용량 한도가 초기화되도록 서비스를 24시간 동안 일시 중지했습니다. 다시 시작한 후 다음과 같은 오류를 발견했습니다:

DiscourseAi::Completions::Endpoints::OpenAi: status: 413 - body: {“error”:{“message”:“Request too large for model openai/gpt-oss-120b in organization org_01kccx1be8fffaz5sbe17 service tier on_demand on tokens per minute (TPM): Limit 8000, Requested 8102, please reduce your message size and try again. Need more tokens? Upgrade to Dev Tier today at https://console.groq.com/settings/billing",“type”:“tokens”,“code”:"rate_limit_exceeded”}}

이후 LLM 설정에서 최대 출력 토큰을 7000에서 6800으로 줄였더니 다시 작동하기 시작했습니다.

제가 놓치고 있는 것이 있나요? 맥락 윈도우(context window)와 관련이 있고 최대 출력 토큰과는 무관하다는 것을 제안하시는 건가요? Groq/모델 한도와 Discourse LLM 설정의 수치를 어떻게 일치시켜야 하는지 파악하려고 합니다.