discourse-ai/spec/lib/discourse_automation/llm_tool_triage_spec.rb
Sam e255c7a8f0
FEATURE: automation triage using personas (#1126)
## LLM Persona Triage
- Allows automated responses to posts using AI personas
- Configurable to respond as regular posts or whispers
- Adds context-aware formatting for topics and private messages
- Provides special handling for topic metadata (title, category, tags)

## LLM Tool Triage
- Enables custom AI tools to process and respond to posts
- Tools can analyze post content and invoke personas when needed
- Zero-parameter tools can be used for automated workflows
- Not enabled in production yet

## Implementation Details
- Added new scriptable registration in discourse_automation/ directory
- Created core implementation in lib/automation/ modules
- Enhanced PromptMessagesBuilder with topic-style formatting
- Added helper methods for persona and tool selection in UI
- Extended AI Bot functionality to support whisper responses
- Added rate limiting to prevent abuse

## Other Changes
- Added comprehensive test coverage for both automation types
- Enhanced tool runner with LLM integration capabilities
- Improved error handling and logging

This feature allows forum admins to configure AI personas to automatically respond to posts based on custom criteria and leverage AI tools for more complex triage workflows.

Tool Triage has been disabled in production while we finalize details of new scripting capabilities.
2025-03-06 09:41:09 +11:00

114 lines
3.6 KiB
Ruby

# frozen_string_literal: true
require "rails_helper"
RSpec.describe DiscourseAi::Automation::LlmToolTriage do
fab!(:solver) { Fabricate(:user) }
fab!(:new_user) { Fabricate(:user, trust_level: TrustLevel[0], created_at: 1.day.ago) }
fab!(:topic) { Fabricate(:topic, user: new_user) }
fab!(:post) { Fabricate(:post, topic: topic, user: new_user, raw: "How do I reset my password?") }
fab!(:llm_model)
fab!(:ai_persona) do
persona = Fabricate(:ai_persona, default_llm: llm_model)
persona.create_user
persona
end
fab!(:tool) do
tool_script = <<~JS
function invoke(params) {
const postId = context.post_id;
const post = discourse.getPost(postId);
const user = discourse.getUser(post.user_id);
if (user.trust_level > 0) {
return {
processed: false,
reason: "User is not new"
};
}
const helper = discourse.getPersona("#{ai_persona.name}");
const answer = helper.respondTo({ post_id: post.id });
return {
answer: answer,
processed: true,
reason: "answered question"
};
}
JS
AiTool.create!(
name: "New User Question Answerer",
tool_name: "new_user_question_answerer",
description: "Automatically answers questions from new users when possible",
parameters: [], # No parameters as required by llm_tool_triage
script: tool_script,
created_by_id: Discourse.system_user.id,
summary: "Answers new user questions",
enabled: true,
)
end
before do
SiteSetting.discourse_ai_enabled = true
SiteSetting.ai_bot_enabled = true
end
it "It is able to answer new user questions" do
result = nil
DiscourseAi::Completions::Llm.with_prepared_responses(
["this is how you reset your password"],
) { result = described_class.handle(post: post, tool_id: tool.id) }
expect(result["processed"]).to eq(true)
response = post.topic.reload.posts.order(:post_number).last
expect(response.raw).to eq("this is how you reset your password")
end
it "Is able to respond as a whisper if instructed" do
# Create a tool with a script that explicitly requests a whisper response
whisper_tool =
AiTool.create!(
name: "Whisper Triage Tool",
tool_name: "whisper_triage_tool",
description: "Responds with whispers to moderation issues",
parameters: [],
script: <<~JS,
function invoke(params) {
const postId = context.post_id;
const post = discourse.getPost(postId);
const helper = discourse.getPersona("#{ai_persona.name}");
// Pass instructions to make response a whisper
const answer = helper.respondTo({
post_id: post.id,
instructions: "Respond as a whisper for moderators only",
whisper: true
});
return {
answer: answer,
processed: true,
reason: "responded with whisper"
};
}
JS
created_by_id: Discourse.system_user.id,
summary: "Responds with whispers",
enabled: true,
)
result = nil
DiscourseAi::Completions::Llm.with_prepared_responses(
["This moderation note is only visible to staff"],
) { result = described_class.handle(post: post, tool_id: whisper_tool.id) }
expect(result["processed"]).to eq(true)
response = post.topic.reload.posts.order(:post_number).last
expect(response.raw).to eq("This moderation note is only visible to staff")
# Check that the response is indeed a whisper
expect(response.post_type).to eq(Post.types[:whisper])
end
end