ИИ случайным образом и непредсказуемо превышает пороги токенов LLM

Я настроил LLM на максимальный объем выходных токенов 8000, но Discourse AI регулярно отправляет запросы, превышающие этот порог.

Со временем методом проб и ошибок и путем анализа логов (что само по себе является проблемой, так как на панели управления ничего не отображается) я снизил значения до 7800, 7500, 7200 и, наконец, до 7000 после нескольких дней проб и ошибок.

Внезапно, после месяцев нормальной работы, система перестала функционировать. После отладки я обнаружил, что в некоторых пограничных случаях Discourse снова запрашивает > 8000 выходных токенов, даже несмотря на то, что LLM настроен на максимум 7000 токенов.

Сообщение (3 копии)

DiscourseAi::Completions::Endpoints::OpenAi: статус: 413 - тело: {"error":{"message":"Запрос слишком велик для модели `openai/gpt-oss-120b` в организации `org_01kccx1be8fffaz5sbe174je1r` тарифный план `on_demand` по токенам в минуту (TPM): Лимит 8000, Запрошено 8102, уменьшите размер вашего сообщения и повторите попытку. Нужно больше токенов? Перейдите на тариф Dev Tier сегодня по ссылке https://console.groq.com/settings/billing","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. Проблемы с конфигурацией LLM не отображаются на панели управления, а когда Discourse игнорирует настройки LLM, это приводит к непредсказуемым ошибкам и разочарованию.

Discourse необходимо найти способ ограничивать себя параметрами конфигурации LLM и отображать проблемы на административной панели.

Вы не перепутали ли токены запроса с токенами ответа?

Ошибка 413 означает, что ваш запрос был слишком большим, а не запрошенный вами ответ.

Чтобы решить эту проблему, нужно настроить параметр Context window в конфигурации LLM. Однако предупрежу, что 8 тыс. токенов сегодня — это слишком мало. Этого хватит для некоторых функций, но в наше время, когда LLM работают с контекстными окнами размером до 1 млн токенов, этого явно недостаточно. Я могу запустить контекстное окно размером 256 тыс. токенов на своём настольном ПК, используя модель, которая значительно лучше той, что вы используете.

Окно контекста установлено на 130 тыс.

Но это возвращает меня к той же проблеме. Лимит модели в 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 по токенам в день (TPD): Лимит 200000, Использовано 193487, Запрошено 7464. Пожалуйста, повторите попытку через 6 м 50.832 с. Нужно больше токенов? Перейдите на тариф Dev Tier сегодня по ссылке https://console.groq.com/settings/billing",“type”:“tokens”,“code”:"rate_limit_exceeded”}}

Затем я приостановил работу сервиса на 24 часа, чтобы сбросить суточные лимиты. После перезапуска я обнаружил эту ошибку:

DiscourseAi::Completions::Endpoints::OpenAi: status: 413 - body: {“error”:{“message”:“Запрос слишком велик для модели openai/gpt-oss-120b в организации org_01kccx1be8fffaz5sbe17 на уровне обслуживания on_demand по токенам в минуту (TPM): Лимит 8000, Запрошено 8102, пожалуйста, уменьшите размер сообщения и повторите попытку. Нужно больше токенов? Перейдите на тариф Dev Tier сегодня по ссылке https://console.groq.com/settings/billing",“type”:“tokens”,“code”:"rate_limit_exceeded”}}

После этого я уменьшил максимальное количество выходных токенов с 7000 до 6800 в конфигурации LLM, и всё снова заработало.

Что я упускаю? Вы предполагаете, что это связано с окном контекста и не имеет ничего общего с максимальным количеством выходных токенов? Просто пытаюсь понять, как сопоставить числовые значения конфигурации из Groq / лимитов модели с настройками LLM в Discourse.