mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-06-19 05:35:40 +08:00
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>
117 lines
4 KiB
Ruby
Vendored
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
|