2025-05-14 12:36:16 +10:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2025-05-29 15:40:46 +10:00
|
|
|
RSpec.describe DiscourseAi::Agents::Tools::Researcher do
|
2025-05-14 12:36:16 +10:00
|
|
|
before { SearchIndexer.enable }
|
|
|
|
after { SearchIndexer.disable }
|
|
|
|
|
|
|
|
fab!(:llm_model)
|
|
|
|
let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) }
|
|
|
|
let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") }
|
|
|
|
let(:progress_blk) { Proc.new {} }
|
|
|
|
|
|
|
|
fab!(:admin)
|
|
|
|
fab!(:user)
|
|
|
|
fab!(:category) { Fabricate(:category, name: "research-category") }
|
|
|
|
fab!(:tag_research) { Fabricate(:tag, name: "research") }
|
|
|
|
fab!(:tag_data) { Fabricate(:tag, name: "data") }
|
|
|
|
|
|
|
|
fab!(:topic_with_tags) { Fabricate(:topic, category: category, tags: [tag_research, tag_data]) }
|
|
|
|
fab!(:post) { Fabricate(:post, topic: topic_with_tags) }
|
2025-05-26 16:00:44 +10:00
|
|
|
fab!(:another_post) { Fabricate(:post) }
|
2025-05-14 12:36:16 +10:00
|
|
|
|
|
|
|
before { SiteSetting.ai_bot_enabled = true }
|
|
|
|
|
|
|
|
describe "#invoke" do
|
2025-05-26 16:00:44 +10:00
|
|
|
it "can correctly filter to a topic id" do
|
|
|
|
researcher =
|
|
|
|
described_class.new(
|
|
|
|
{ dry_run: true, filter: "topic:#{topic_with_tags.id}", goals: "analyze topic content" },
|
|
|
|
bot_user: bot_user,
|
|
|
|
llm: llm,
|
2025-05-29 15:40:46 +10:00
|
|
|
context: DiscourseAi::Agents::BotContext.new(user: user, post: post),
|
2025-05-26 16:00:44 +10:00
|
|
|
)
|
|
|
|
results = researcher.invoke(&progress_blk)
|
|
|
|
expect(results[:number_of_posts]).to eq(1)
|
|
|
|
end
|
|
|
|
|
2025-05-14 12:36:16 +10:00
|
|
|
it "returns filter information and result count" do
|
|
|
|
researcher =
|
|
|
|
described_class.new(
|
|
|
|
{ filter: "tag:research after:2023", goals: "analyze post patterns", dry_run: true },
|
|
|
|
bot_user: bot_user,
|
|
|
|
llm: llm,
|
2025-05-29 15:40:46 +10:00
|
|
|
context: DiscourseAi::Agents::BotContext.new(user: user, post: post),
|
2025-05-14 12:36:16 +10:00
|
|
|
)
|
|
|
|
|
|
|
|
results = researcher.invoke(&progress_blk)
|
|
|
|
|
|
|
|
expect(results[:filter]).to eq("tag:research after:2023")
|
|
|
|
expect(results[:goals]).to eq("analyze post patterns")
|
|
|
|
expect(results[:dry_run]).to eq(true)
|
2025-05-15 17:48:21 +10:00
|
|
|
expect(results[:number_of_posts]).to be > 0
|
2025-05-14 12:36:16 +10:00
|
|
|
expect(researcher.filter).to eq("tag:research after:2023")
|
|
|
|
expect(researcher.result_count).to be > 0
|
|
|
|
end
|
|
|
|
|
|
|
|
it "handles empty filters" do
|
|
|
|
researcher =
|
|
|
|
described_class.new({ goals: "analyze all content" }, bot_user: bot_user, llm: llm)
|
|
|
|
|
|
|
|
results = researcher.invoke(&progress_blk)
|
|
|
|
|
|
|
|
expect(results[:error]).to eq("No filter provided")
|
|
|
|
end
|
|
|
|
|
|
|
|
it "accepts max_results option" do
|
|
|
|
researcher =
|
|
|
|
described_class.new(
|
|
|
|
{ filter: "category:research-category" },
|
2025-05-29 15:40:46 +10:00
|
|
|
agent_options: {
|
2025-05-14 12:36:16 +10:00
|
|
|
"max_results" => "50",
|
|
|
|
},
|
|
|
|
bot_user: bot_user,
|
|
|
|
llm: llm,
|
|
|
|
)
|
|
|
|
|
|
|
|
expect(researcher.options[:max_results]).to eq(50)
|
|
|
|
end
|
|
|
|
|
2025-05-26 16:00:44 +10:00
|
|
|
it "returns error for invalid filter fragments" do
|
|
|
|
researcher =
|
|
|
|
described_class.new(
|
|
|
|
{ filter: "invalidfilter tag:research", goals: "analyze content" },
|
|
|
|
bot_user: bot_user,
|
|
|
|
llm: llm,
|
2025-05-29 15:40:46 +10:00
|
|
|
context: DiscourseAi::Agents::BotContext.new(user: user, post: post),
|
2025-05-26 16:00:44 +10:00
|
|
|
)
|
|
|
|
|
|
|
|
results = researcher.invoke(&progress_blk)
|
|
|
|
|
|
|
|
expect(results[:error]).to include("Invalid filter fragment")
|
|
|
|
end
|
|
|
|
|
2025-05-14 12:36:16 +10:00
|
|
|
it "returns correct results for non-dry-run with filtered posts" do
|
|
|
|
# Stage 2 topics, each with 2 posts
|
|
|
|
topics = Array.new(2) { Fabricate(:topic, category: category, tags: [tag_research]) }
|
|
|
|
topics.flat_map do |topic|
|
|
|
|
[
|
|
|
|
Fabricate(:post, topic: topic, raw: "Relevant content 1", user: user),
|
|
|
|
Fabricate(:post, topic: topic, raw: "Relevant content 2", user: admin),
|
|
|
|
]
|
|
|
|
end
|
|
|
|
|
|
|
|
# Filter to posts by user in research-category
|
|
|
|
researcher =
|
|
|
|
described_class.new(
|
|
|
|
{
|
|
|
|
filter: "category:research-category @#{user.username}",
|
|
|
|
goals: "find relevant content",
|
|
|
|
dry_run: false,
|
|
|
|
},
|
|
|
|
bot_user: bot_user,
|
|
|
|
llm: llm,
|
2025-05-29 15:40:46 +10:00
|
|
|
context: DiscourseAi::Agents::BotContext.new(user: user, post: post),
|
2025-05-14 12:36:16 +10:00
|
|
|
)
|
|
|
|
|
|
|
|
responses = 10.times.map { |i| ["Found: Relevant content #{i + 1}"] }
|
|
|
|
results = nil
|
|
|
|
|
|
|
|
last_progress = nil
|
|
|
|
progress_blk = Proc.new { |response| last_progress = response }
|
|
|
|
|
|
|
|
DiscourseAi::Completions::Llm.with_prepared_responses(responses) do
|
|
|
|
researcher.llm = llm_model.to_llm
|
|
|
|
results = researcher.invoke(&progress_blk)
|
|
|
|
end
|
|
|
|
|
|
|
|
expect(last_progress).to include("find relevant content")
|
|
|
|
expect(last_progress).to include("category:research-category")
|
|
|
|
|
|
|
|
expect(results[:dry_run]).to eq(false)
|
|
|
|
expect(results[:goals]).to eq("find relevant content")
|
|
|
|
expect(results[:filter]).to eq("category:research-category @#{user.username}")
|
|
|
|
expect(results[:results].first).to include("Found: Relevant content 1")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|