0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-07 13:19:19 +08:00
discourse/plugins/discourse-ai/lib/summarization.rb
Rafael dos Santos Silva 0b73d7ac1a
PERF: Cache LlmModel lookups in backfill jobs (#36359)
Backfill jobs were fetching the same LlmModel on every loop iteration,
causing N+1 queries. For a batch of 100 items, this meant 100+ identical
SQL queries to populate the same LlmModel object.

## Changes

- Cache LlmModel once per job execution in summaries and translation
backfills
- Pass cached model through to summarization and translation methods via
optional parameters
- Add tests verifying models are queried once per batch instead of once
per item

## Impact

**Before:** For 100 items, 100+ `LlmModel.find_by()` queries  
**After:** For 100 items, 1-2 queries total

All changes are backward compatible - new parameters are optional with
nil defaults, so existing callers continue to work unchanged.
2025-12-01 17:04:25 -03:00

84 lines
2.7 KiB
Ruby
Vendored

# frozen_string_literal: true
module DiscourseAi
module Summarization
class << self
def topic_summary(topic, llm_model: nil)
return nil if !SiteSetting.ai_summarization_enabled
if (
ai_persona = AiPersona.find_by_id_from_cache(SiteSetting.ai_summarization_persona)
).blank?
return nil
end
persona_klass = ai_persona.class_instance
llm_model ||= find_summarization_model(persona_klass)
return nil if llm_model.blank?
DiscourseAi::Summarization::FoldContent.new(
build_bot(persona_klass, llm_model),
DiscourseAi::Summarization::Strategies::TopicSummary.new(topic),
)
end
def topic_gist(topic, llm_model: nil)
return nil if !SiteSetting.ai_summarization_enabled
if (
ai_persona = AiPersona.find_by_id_from_cache(SiteSetting.ai_summary_gists_persona)
).blank?
return nil
end
persona_klass = ai_persona.class_instance
llm_model ||= find_summarization_model(persona_klass)
return nil if llm_model.blank?
DiscourseAi::Summarization::FoldContent.new(
build_bot(persona_klass, llm_model),
DiscourseAi::Summarization::Strategies::HotTopicGists.new(topic),
)
end
def chat_channel_summary(channel, time_window_in_hours, llm_model: nil)
return nil if !SiteSetting.ai_summarization_enabled
if (
ai_persona = AiPersona.find_by_id_from_cache(SiteSetting.ai_summarization_persona)
).blank?
return nil
end
persona_klass = ai_persona.class_instance
llm_model ||= find_summarization_model(persona_klass)
return nil if llm_model.blank?
DiscourseAi::Summarization::FoldContent.new(
build_bot(persona_klass, llm_model),
DiscourseAi::Summarization::Strategies::ChatMessages.new(channel, time_window_in_hours),
persist_summaries: false,
)
end
# Priorities are:
# 1. Persona's default LLM
# 2. SiteSetting.ai_default_llm_model (or newest LLM if not set)
def find_summarization_model(persona_klass)
model_id = persona_klass.default_llm_id || SiteSetting.ai_default_llm_model
if model_id.present?
LlmModel.find_by(id: model_id)
else
LlmModel.last
end
end
### Private
def build_bot(persona_klass, llm_model)
persona = persona_klass.new
user = User.find_by(id: persona_klass.user_id) || Discourse.system_user
DiscourseAi::Personas::Bot.as(user, persona: persona, model: llm_model)
end
end
end
end