mirror of
https://github.com/discourse/discourse-ai.git
synced 2025-05-17 13:55:29 +00:00
The command framework had some confusing dispatching where it would dispatch JSON blobs, this meant there was lots of parsing required in every command The refactor handles transforming the args prior to dispatch which makes consuming far simpler This is also general prep to supporting some basic command framework in other llms.
171 lines
4.1 KiB
Ruby
171 lines
4.1 KiB
Ruby
#frozen_string_literal: true
|
|
|
|
module DiscourseAi
|
|
module AiBot
|
|
module Commands
|
|
class Parameter
|
|
attr_reader :name, :description, :type, :enum, :required
|
|
def initialize(name:, description:, type:, enum: nil, required: false)
|
|
@name = name
|
|
@description = description
|
|
@type = type
|
|
@enum = enum
|
|
@required = required
|
|
end
|
|
end
|
|
|
|
class Command
|
|
class << self
|
|
def name
|
|
raise NotImplemented
|
|
end
|
|
|
|
def invoked?(cmd_name)
|
|
cmd_name == name
|
|
end
|
|
|
|
def desc
|
|
raise NotImplemented
|
|
end
|
|
|
|
def custom_system_message
|
|
end
|
|
|
|
def parameters
|
|
raise NotImplemented
|
|
end
|
|
end
|
|
|
|
attr_reader :bot_user, :args
|
|
|
|
def initialize(bot_user, args)
|
|
@bot_user = bot_user
|
|
@args = args
|
|
end
|
|
|
|
def bot
|
|
@bot ||= DiscourseAi::AiBot::Bot.as(bot_user)
|
|
end
|
|
|
|
def standalone?
|
|
false
|
|
end
|
|
|
|
def low_cost?
|
|
false
|
|
end
|
|
|
|
def result_name
|
|
raise NotImplemented
|
|
end
|
|
|
|
def name
|
|
raise NotImplemented
|
|
end
|
|
|
|
def process(post)
|
|
raise NotImplemented
|
|
end
|
|
|
|
def description_args
|
|
{}
|
|
end
|
|
|
|
def custom_raw
|
|
end
|
|
|
|
def chain_next_response
|
|
true
|
|
end
|
|
|
|
def invoke_and_attach_result_to(post, parent_post)
|
|
placeholder = (<<~HTML).strip
|
|
<details>
|
|
<summary>#{I18n.t("discourse_ai.ai_bot.command_summary.#{self.class.name}")}</summary>
|
|
</details>
|
|
HTML
|
|
|
|
if !post
|
|
post =
|
|
PostCreator.create!(
|
|
bot_user,
|
|
raw: placeholder,
|
|
topic_id: parent_post.topic_id,
|
|
skip_validations: true,
|
|
skip_rate_limiter: true,
|
|
)
|
|
else
|
|
post.revise(
|
|
bot_user,
|
|
{ raw: post.raw + "\n\n" + placeholder + "\n\n" },
|
|
skip_validations: true,
|
|
skip_revision: true,
|
|
)
|
|
end
|
|
|
|
post.post_custom_prompt ||= post.build_post_custom_prompt(custom_prompt: [])
|
|
prompt = post.post_custom_prompt.custom_prompt || []
|
|
|
|
parsed_args = JSON.parse(args).symbolize_keys
|
|
|
|
prompt << [process(**parsed_args).to_json, self.class.name, "function"]
|
|
post.post_custom_prompt.update!(custom_prompt: prompt)
|
|
|
|
raw = +(<<~HTML)
|
|
<details>
|
|
<summary>#{I18n.t("discourse_ai.ai_bot.command_summary.#{self.class.name}")}</summary>
|
|
<p>
|
|
#{I18n.t("discourse_ai.ai_bot.command_description.#{self.class.name}", self.description_args)}
|
|
</p>
|
|
</details>
|
|
|
|
HTML
|
|
|
|
raw << custom_raw if custom_raw.present?
|
|
|
|
raw = post.raw.sub(placeholder, raw)
|
|
|
|
if chain_next_response
|
|
post.raw = raw
|
|
post.save!(validate: false)
|
|
else
|
|
post.revise(bot_user, { raw: raw }, skip_validations: true, skip_revision: true)
|
|
end
|
|
|
|
[chain_next_response, post]
|
|
end
|
|
|
|
def format_results(rows, column_names = nil, args: nil)
|
|
rows = rows.map { |row| yield row } if block_given?
|
|
|
|
if !column_names
|
|
index = -1
|
|
column_indexes = {}
|
|
|
|
rows =
|
|
rows.map do |data|
|
|
new_row = []
|
|
data.each do |key, value|
|
|
found_index = column_indexes[key.to_s] ||= (index += 1)
|
|
new_row[found_index] = value
|
|
end
|
|
new_row
|
|
end
|
|
column_names = column_indexes.keys
|
|
end
|
|
|
|
# this is not the most efficient format
|
|
# however this is needed cause GPT 3.5 / 4 was steered using JSON
|
|
result = { column_names: column_names, rows: rows }
|
|
result[:args] = args if args
|
|
result
|
|
end
|
|
|
|
protected
|
|
|
|
attr_reader :bot_user, :args
|
|
end
|
|
end
|
|
end
|
|
end
|