discourse/plugins/discourse-ai/db/fixtures/agents/603_ai_agents.rb
Sam 65024326dd
FEATURE: Add AI authoring to Discourse Workflows (#40504)
Previously, admins could only build Discourse Workflows by manually
adding and connecting every trigger, condition, and action node on the
canvas.

This change adds an AI authoring assistant, gated behind
`discourse_workflows_ai_authoring_enabled` and DiscourseAi, that turns a
natural-language request into a server-validated, reviewable workflow
patch the admin applies. It only ever proposes drafts and never
publishes.

---------

Co-authored-by: chapoi <101828855+chapoi@users.noreply.github.com>
Co-authored-by: Rafael Silva <xfalcox@gmail.com>
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
2026-06-18 18:32:30 +02:00

117 lines
4 KiB
Ruby
Vendored

# frozen_string_literal: true
# during transition period prior to rename of persona table
# it is a view, we can not write to it, post migration will take care
ai_agents_is_view =
DB.query_single(
"SELECT 1 FROM pg_class WHERE relname = 'ai_agents' AND relkind IN ('v')",
).present?
return if ai_agents_is_view
summarization_agents = [DiscourseAi::Agents::Summarizer, DiscourseAi::Agents::ShortSummarizer]
external_agent_ids =
DiscourseAi::Agents::Agent.system_agents.filter_map do |klass, id|
id unless DiscourseAi::Agents::Agent.send(:builtin_system_agents).key?(klass)
end
def from_setting(setting_name)
DB
.query_single(
"SELECT value FROM site_settings WHERE name = :setting_name",
setting_name: setting_name,
)
&.first
&.split("|")
end
DiscourseAi::Agents::Agent.system_agents.each do |agent_class, id|
agent = AiAgent.find_by(id: id)
if !agent
agent = AiAgent.new
agent.id = id
if agent_class == DiscourseAi::Agents::WebArtifactCreator
# this is somewhat sensitive, so we default it to staff
agent.allowed_group_ids = [Group::AUTO_GROUPS[:staff]]
elsif agent_class == DiscourseAi::Agents::AdminDashboardHighlights
agent.allowed_group_ids = [Group::AUTO_GROUPS[:admins]]
elsif external_agent_ids.include?(id)
agent.allowed_group_ids = [Group::AUTO_GROUPS[:admins]]
elsif summarization_agents.include?(agent_class)
# Copy group permissions from site settings.
default_groups = [Group::AUTO_GROUPS[:staff], Group::AUTO_GROUPS[:trust_level_1]]
setting_name = "ai_custom_summarization_allowed_groups"
if agent_class == DiscourseAi::Agents::ShortSummarizer
setting_name = "ai_summary_gists_allowed_groups"
default_groups = [Group::AUTO_GROUPS[:everyone]]
end
agent.allowed_group_ids = from_setting(setting_name) || default_groups
elsif agent_class == DiscourseAi::Agents::CustomPrompt
setting_name = "ai_helper_custom_prompts_allowed_groups"
default_groups = [Group::AUTO_GROUPS[:staff]]
agent.allowed_group_ids = from_setting(setting_name) || default_groups
elsif agent_class == DiscourseAi::Agents::ContentCreator
agent.allowed_group_ids = [Group::AUTO_GROUPS[:everyone]]
else
agent.allowed_group_ids = [Group::AUTO_GROUPS[:trust_level_0]]
end
agent.enabled = agent_class.default_enabled
agent.priority = true if agent_class == DiscourseAi::Agents::General
end
names = [
agent_class.name,
agent_class.name + " 1",
agent_class.name + " 2",
agent_class.name + SecureRandom.hex,
]
agent.name = DB.query_single(<<~SQL, names, id).first
SELECT guess_name
FROM (
SELECT unnest(Array[?]) AS guess_name
FROM (SELECT 1) as t
) x
LEFT JOIN ai_agents ON ai_agents.name = x.guess_name AND ai_agents.id <> ?
WHERE ai_agents.id IS NULL
ORDER BY x.guess_name ASC
LIMIT 1
SQL
agent.description = agent_class.description
agent.system = true
instance = agent_class.new
tools = {}
instance.tools.map { |tool| tool.to_s.split("::").last }.each { |name| tools[name] = nil }
existing_tools = agent.tools || []
existing_tools.each do |tool|
if tool.is_a?(Array)
name, value = tool
tools[name] = value if tools.key?(name)
end
end
forced_tool_names = instance.force_tool_use.map { |tool| tool.to_s.split("::").last }
agent.tools = tools.map { |name, value| [name, value, forced_tool_names.include?(name)] }
agent.forced_tool_count = instance.forced_tool_count
agent.execution_mode = agent_class.execution_mode
agent.max_turn_tokens = agent_class.max_turn_tokens
agent.compression_threshold =
if agent.execution_mode == "agentic"
agent_class.compression_threshold || 85
else
agent_class.compression_threshold
end
agent.response_format = instance.response_format
agent.examples = instance.examples
agent.system_prompt = instance.system_prompt
agent.top_p = instance.top_p
agent.temperature = instance.temperature
agent.save!(validate: false)
end