diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index b70b072c..3c2104f4 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -120,6 +120,7 @@ en:
read: "Read topic"
setting_context: "Look up site setting context"
schema: "Look up database schema"
+ search_settings: "Searching site settings"
command_description:
read: "Reading: %{title}"
time: "Time in %{timezone} is %{time}"
@@ -139,6 +140,9 @@ en:
other: "Found %{count} results for '%{query}'"
setting_context: "Reading context for: %{setting_name}"
schema: "%{tables}"
+ search_settings:
+ one: "Found %{count} result for '%{query}'"
+ other: "Found %{count} results for '%{query}'"
summarization:
configuration_hint:
diff --git a/lib/modules/ai_bot/anthropic_bot.rb b/lib/modules/ai_bot/anthropic_bot.rb
index c71dd95c..364e9df8 100644
--- a/lib/modules/ai_bot/anthropic_bot.rb
+++ b/lib/modules/ai_bot/anthropic_bot.rb
@@ -28,6 +28,10 @@ module DiscourseAi
completion
end
+ def tokenizer
+ DiscourseAi::Tokenizer::AnthropicTokenizer
+ end
+
private
def build_message(poster_username, content, system: false, function: nil)
@@ -58,10 +62,6 @@ module DiscourseAi
&blk
)
end
-
- def tokenizer
- DiscourseAi::Tokenizer::AnthropicTokenizer
- end
end
end
end
diff --git a/lib/modules/ai_bot/commands/search_settings_command.rb b/lib/modules/ai_bot/commands/search_settings_command.rb
new file mode 100644
index 00000000..b1c86337
--- /dev/null
+++ b/lib/modules/ai_bot/commands/search_settings_command.rb
@@ -0,0 +1,85 @@
+#frozen_string_literal: true
+
+module DiscourseAi::AiBot::Commands
+ class SearchSettingsCommand < Command
+ class << self
+ def name
+ "search_settings"
+ end
+
+ def desc
+ "Will search through site settings and return top 20 results"
+ end
+
+ def parameters
+ [
+ Parameter.new(
+ name: "query",
+ description:
+ "comma delimited list of settings to search for (e.g. 'setting_1,setting_2')",
+ type: "string",
+ required: true,
+ ),
+ ]
+ end
+ end
+
+ def result_name
+ "results"
+ end
+
+ def description_args
+ { count: @last_num_results || 0, query: @last_query || "" }
+ end
+
+ INCLUDE_DESCRIPTIONS_MAX_LENGTH = 10
+ MAX_RESULTS = 200
+
+ def process(query:)
+ @last_query = query
+ @last_num_results = 0
+
+ terms = query.split(",").map(&:strip).map(&:downcase).reject(&:blank?)
+
+ found =
+ SiteSetting.all_settings.filter do |setting|
+ name = setting[:setting].to_s.downcase
+ description = setting[:description].to_s.downcase
+ plugin = setting[:plugin].to_s.downcase
+
+ search_string = "#{name} #{description} #{plugin}"
+
+ terms.any? { |term| search_string.include?(term) }
+ end
+
+ if found.blank?
+ {
+ args: {
+ query: query,
+ },
+ rows: [],
+ instruction: "no settings matched #{query}, expand your search",
+ }
+ else
+ include_descriptions = false
+
+ if found.length > MAX_RESULTS
+ found = found[0..MAX_RESULTS]
+ elsif found.length < INCLUDE_DESCRIPTIONS_MAX_LENGTH
+ include_descriptions = true
+ end
+
+ @last_num_results = found.length
+
+ format_results(found, args: { query: query }) do |setting|
+ result = { name: setting[:setting] }
+ if include_descriptions
+ result[:description] = setting[:description]
+ result[:plugin] = setting[:plugin]
+ end
+ result
+ end
+ end
+ end
+ end
+end
diff --git a/lib/modules/ai_bot/entry_point.rb b/lib/modules/ai_bot/entry_point.rb
index da31adb4..711efb27 100644
--- a/lib/modules/ai_bot/entry_point.rb
+++ b/lib/modules/ai_bot/entry_point.rb
@@ -39,6 +39,7 @@ module DiscourseAi
require_relative "commands/google_command"
require_relative "commands/read_command"
require_relative "commands/setting_context_command"
+ require_relative "commands/search_settings_command"
require_relative "commands/db_schema_command"
require_relative "personas/persona"
require_relative "personas/artist"
diff --git a/lib/modules/ai_bot/personas/settings_explorer.rb b/lib/modules/ai_bot/personas/settings_explorer.rb
index 5dff3b57..676dab3d 100644
--- a/lib/modules/ai_bot/personas/settings_explorer.rb
+++ b/lib/modules/ai_bot/personas/settings_explorer.rb
@@ -9,24 +9,23 @@ module DiscourseAi
end
def all_available_commands
- [DiscourseAi::AiBot::Commands::SettingContextCommand]
+ [
+ DiscourseAi::AiBot::Commands::SettingContextCommand,
+ DiscourseAi::AiBot::Commands::SearchSettingsCommand,
+ ]
end
def system_prompt
<<~PROMPT
You are Discourse Site settings bot.
- - You know the full list of all the site settings.
+ - You are able to find information about all the site settings.
- You are able to request context for a specific setting.
- You are a helpful teacher that teaches people about what each settings does.
+ - Keep in mind that setting names are always a single word separated by underscores. eg. 'site_description'
Current time is: {time}
- Full list of all the site settings:
- {{
- #{SiteSetting.all_settings.map { |setting| setting[:setting].to_s }.join("\n")}
- }}
-
{commands}
PROMPT
diff --git a/lib/shared/inference/anthropic_completions.rb b/lib/shared/inference/anthropic_completions.rb
index db9f6496..f6f2b920 100644
--- a/lib/shared/inference/anthropic_completions.rb
+++ b/lib/shared/inference/anthropic_completions.rb
@@ -14,6 +14,10 @@ module ::DiscourseAi
max_tokens: nil,
user_id: nil
)
+ log = nil
+ response_data = +""
+ response_raw = +""
+
url = URI("https://api.anthropic.com/v1/complete")
headers = {
"anthropic-version" => "2023-06-01",
@@ -68,12 +72,9 @@ module ::DiscourseAi
return parsed_response
end
- response_data = +""
-
begin
cancelled = false
cancel = lambda { cancelled = true }
- response_raw = +""
response.read_body do |chunk|
if cancelled
@@ -104,17 +105,22 @@ module ::DiscourseAi
end
rescue IOError
raise if !cancelled
- ensure
- log.update!(
- raw_response_payload: response_raw,
- request_tokens: DiscourseAi::Tokenizer::AnthropicTokenizer.size(prompt),
- response_tokens: DiscourseAi::Tokenizer::AnthropicTokenizer.size(response_data),
- )
end
end
return response_data
end
+ ensure
+ if block_given?
+ log.update!(
+ raw_response_payload: response_raw,
+ request_tokens: DiscourseAi::Tokenizer::AnthropicTokenizer.size(prompt),
+ response_tokens: DiscourseAi::Tokenizer::AnthropicTokenizer.size(response_data),
+ )
+ end
+ if Rails.env.development? && log
+ puts "AnthropicCompletions: request_tokens #{log.request_tokens} response_tokens #{log.response_tokens}"
+ end
end
def self.try_parse(data)
diff --git a/lib/shared/inference/openai_completions.rb b/lib/shared/inference/openai_completions.rb
index 5a445c30..2a0d5828 100644
--- a/lib/shared/inference/openai_completions.rb
+++ b/lib/shared/inference/openai_completions.rb
@@ -15,6 +15,10 @@ module ::DiscourseAi
functions: nil,
user_id: nil
)
+ log = nil
+ response_data = +""
+ response_raw = +""
+
url =
if model.include?("gpt-4")
if model.include?("32k")
@@ -84,12 +88,9 @@ module ::DiscourseAi
return parsed_response
end
- response_data = +""
-
begin
cancelled = false
cancel = lambda { cancelled = true }
- response_raw = +""
leftover = ""
@@ -125,19 +126,25 @@ module ::DiscourseAi
end
rescue IOError
raise if !cancelled
- ensure
- log.update!(
- raw_response_payload: response_raw,
- request_tokens:
- DiscourseAi::Tokenizer::OpenAiTokenizer.size(extract_prompt(messages)),
- response_tokens: DiscourseAi::Tokenizer::OpenAiTokenizer.size(response_data),
- )
end
end
return response_data
end
end
+ ensure
+ if log && block_given?
+ request_tokens = DiscourseAi::Tokenizer::OpenAiTokenizer.size(extract_prompt(messages))
+ response_tokens = DiscourseAi::Tokenizer::OpenAiTokenizer.size(response_data)
+ log.update!(
+ raw_response_payload: response_raw,
+ request_tokens: request_tokens,
+ response_tokens: response_tokens,
+ )
+ end
+ if log && Rails.env.development?
+ puts "OpenAiCompletions: request_tokens #{log.request_tokens} response_tokens #{log.response_tokens}"
+ end
end
def self.extract_prompt(messages)
diff --git a/spec/lib/modules/ai_bot/commands/search_settings_command_spec.rb b/spec/lib/modules/ai_bot/commands/search_settings_command_spec.rb
new file mode 100644
index 00000000..e9bb61bd
--- /dev/null
+++ b/spec/lib/modules/ai_bot/commands/search_settings_command_spec.rb
@@ -0,0 +1,29 @@
+#frozen_string_literal: true
+
+RSpec.describe DiscourseAi::AiBot::Commands::SearchSettingsCommand do
+ let(:search) { described_class.new(bot_user: nil, args: nil) }
+
+ describe "#process" do
+ it "can handle no results" do
+ results = search.process(query: "this will not exist frogs")
+ expect(results[:args]).to eq({ query: "this will not exist frogs" })
+ expect(results[:rows]).to eq([])
+ end
+
+ it "can return more many settings with no descriptions if there are lots of hits" do
+ results = search.process(query: "a")
+
+ expect(results[:rows].length).to be > 30
+ expect(results[:rows][0].length).to eq(1)
+ end
+
+ it "can return descriptions if there are few matches" do
+ results =
+ search.process(query: "this will not be found!@,default_locale,ai_bot_enabled_personas")
+
+ expect(results[:rows].length).to eq(2)
+
+ expect(results[:rows][0][1]).not_to eq(nil)
+ end
+ end
+end
diff --git a/spec/lib/modules/ai_bot/personas/settings_explorer_spec.rb b/spec/lib/modules/ai_bot/personas/settings_explorer_spec.rb
index 68a7beae..39ab3d12 100644
--- a/spec/lib/modules/ai_bot/personas/settings_explorer_spec.rb
+++ b/spec/lib/modules/ai_bot/personas/settings_explorer_spec.rb
@@ -7,11 +7,17 @@ RSpec.describe DiscourseAi::AiBot::Personas::SettingsExplorer do
it "renders schema" do
prompt = settings_explorer.render_system_prompt
- # check we render settings
- expect(prompt).to include("ai_bot_enabled_personas")
+
+ # check we do not render plugin settings
+ expect(prompt).not_to include("ai_bot_enabled_personas")
+
+ expect(prompt).to include("site_description")
expect(settings_explorer.available_commands).to eq(
- [DiscourseAi::AiBot::Commands::SettingContextCommand],
+ [
+ DiscourseAi::AiBot::Commands::SettingContextCommand,
+ DiscourseAi::AiBot::Commands::SearchSettingsCommand,
+ ],
)
end
end