discourse-ai/lib/inferred_concepts/applier.rb

182 lines
6.0 KiB
Ruby

# frozen_string_literal: true
module DiscourseAi
module InferredConcepts
class Applier
# Associates the provided concepts with a topic
# topic: a Topic instance
# concepts: an array of InferredConcept instances
def self.apply_to_topic(topic, concepts)
return if topic.blank? || concepts.blank?
concepts.each do |concept|
# Use the join table to associate the concept with the topic
# Avoid duplicates by using find_or_create_by
ActiveRecord::Base.connection.execute(<<~SQL)
INSERT INTO topics_inferred_concepts (topic_id, inferred_concept_id, created_at, updated_at)
VALUES (#{topic.id}, #{concept.id}, NOW(), NOW())
ON CONFLICT (topic_id, inferred_concept_id) DO NOTHING
SQL
end
end
# Associates the provided concepts with a post
# post: a Post instance
# concepts: an array of InferredConcept instances
def self.apply_to_post(post, concepts)
return if post.blank? || concepts.blank?
concepts.each do |concept|
# Use the join table to associate the concept with the post
# Avoid duplicates by using find_or_create_by
ActiveRecord::Base.connection.execute(<<~SQL)
INSERT INTO posts_inferred_concepts (post_id, inferred_concept_id, created_at, updated_at)
VALUES (#{post.id}, #{concept.id}, NOW(), NOW())
ON CONFLICT (post_id, inferred_concept_id) DO NOTHING
SQL
end
end
# Extracts content from a topic for concept analysis
# Returns a string with the topic title and first few posts
def self.topic_content_for_analysis(topic)
return "" if topic.blank?
# Combine title and first few posts for analysis
posts = Post.where(topic_id: topic.id).order(:post_number).limit(10)
content = "Title: #{topic.title}\n\n"
content += posts.map { |p| "#{p.post_number}) #{p.user.username}: #{p.raw}" }.join("\n\n")
content
end
# Extracts content from a post for concept analysis
# Returns a string with the post content
def self.post_content_for_analysis(post)
return "" if post.blank?
# Get the topic title for context
topic_title = post.topic&.title || ""
content = "Topic: #{topic_title}\n\n"
content += "Post by #{post.user.username}:\n#{post.raw}"
content
end
# Comprehensive method to analyze a topic and apply concepts
def self.analyze_and_apply(topic)
return if topic.blank?
# Get content to analyze
content = topic_content_for_analysis(topic)
# Identify concepts
concept_names = Finder.identify_concepts(content)
# Create or find concepts in the database
concepts = Finder.create_or_find_concepts(concept_names)
# Apply concepts to the topic
apply_to_topic(topic, concepts)
concepts
end
# Comprehensive method to analyze a post and apply concepts
def self.analyze_and_apply_post(post)
return if post.blank?
# Get content to analyze
content = post_content_for_analysis(post)
# Identify concepts
concept_names = Finder.identify_concepts(content)
# Create or find concepts in the database
concepts = Finder.create_or_find_concepts(concept_names)
# Apply concepts to the post
apply_to_post(post, concepts)
concepts
end
# Match a topic with existing concepts
def self.match_existing_concepts(topic)
return [] if topic.blank?
# Get content to analyze
content = topic_content_for_analysis(topic)
# Get all existing concepts
existing_concepts = DiscourseAi::InferredConcepts::Manager.list_concepts
return [] if existing_concepts.empty?
# Use the ConceptMatcher persona to match concepts
matched_concept_names = match_concepts_to_content(content, existing_concepts)
# Find concepts in the database
matched_concepts = InferredConcept.where(name: matched_concept_names)
# Apply concepts to the topic
apply_to_topic(topic, matched_concepts)
matched_concepts
end
# Match a post with existing concepts
def self.match_existing_concepts_for_post(post)
return [] if post.blank?
# Get content to analyze
content = post_content_for_analysis(post)
# Get all existing concepts
existing_concepts = DiscourseAi::InferredConcepts::Manager.list_concepts
return [] if existing_concepts.empty?
# Use the ConceptMatcher persona to match concepts
matched_concept_names = match_concepts_to_content(content, existing_concepts)
# Find concepts in the database
matched_concepts = InferredConcept.where(name: matched_concept_names)
# Apply concepts to the post
apply_to_post(post, matched_concepts)
matched_concepts
end
# Use ConceptMatcher persona to match content against provided concepts
def self.match_concepts_to_content(content, concept_list)
return [] if content.blank? || concept_list.blank?
# Prepare user message with only the content
user_message = <<~MESSAGE
Content to analyze:
#{content}
MESSAGE
# Use the ConceptMatcher persona to match concepts
llm = DiscourseAi::Completions::Llm.default_llm
persona = DiscourseAi::Personas::ConceptMatcher.new(concept_list: concept_list)
context =
DiscourseAi::Personas::BotContext.new(
messages: [{ type: :user, content: user_message }],
user: Discourse.system_user,
)
prompt = persona.craft_prompt(context)
response = llm.completion(prompt, extract_json: true)
return [] unless response.success?
matching_concepts = response.parsed_output["matching_concepts"]
matching_concepts || []
end
end
end
end