FEATURE: add ai_bot_enabled_chat commands and tune search (#94)
* FEATURE: add ai_bot_enabled_chat commands and tune search This allows admins to disable/enable GPT command integrations. Also hones search results which were looping cause the result did not denote the failure properly (it lost context) * include more context for google command include more context for time command * type
This commit is contained in:
parent
d1ab79e82f
commit
a028309cbd
|
@ -61,6 +61,7 @@ en:
|
|||
ai_bot_enabled: "Enable the AI Bot module."
|
||||
ai_bot_allowed_groups: "When the GPT Bot has access to the PM, it will reply to members of these groups."
|
||||
ai_bot_enabled_chat_bots: "Available models to act as an AI Bot"
|
||||
ai_bot_enabled_chat_commands: "Available GPT integrations used to provide external functionality to the model. Only works with GPT-4 and GPT-3.5"
|
||||
ai_helper_add_ai_pm_to_header: "Display a button in the header to start a PM with a AI Bot"
|
||||
|
||||
ai_stability_api_key: "API key for the stability.ai API"
|
||||
|
|
|
@ -207,6 +207,18 @@ plugins:
|
|||
- gpt-3.5-turbo
|
||||
- gpt-4
|
||||
- claude-v1
|
||||
ai_bot_enabled_chat_commands:
|
||||
type: list
|
||||
default: "categories|google|image|search|tags|time"
|
||||
client: true
|
||||
choices:
|
||||
- categories
|
||||
- google
|
||||
- image
|
||||
- search
|
||||
- summarize
|
||||
- tags
|
||||
- time
|
||||
ai_helper_add_ai_pm_to_header:
|
||||
default: true
|
||||
client: true
|
||||
|
|
|
@ -133,7 +133,7 @@ module DiscourseAi
|
|||
[chain_next_response, post]
|
||||
end
|
||||
|
||||
def format_results(rows, column_names = nil)
|
||||
def format_results(rows, column_names = nil, args: nil)
|
||||
rows = rows.map { |row| yield row } if block_given?
|
||||
|
||||
if !column_names
|
||||
|
@ -154,7 +154,9 @@ module DiscourseAi
|
|||
|
||||
# this is not the most efficient format
|
||||
# however this is needed cause GPT 3.5 / 4 was steered using JSON
|
||||
{ column_names: column_names, rows: rows }
|
||||
result = { column_names: column_names, rows: rows }
|
||||
result[:args] = args if args
|
||||
result
|
||||
end
|
||||
|
||||
protected
|
||||
|
|
|
@ -59,7 +59,7 @@ module DiscourseAi::AiBot::Commands
|
|||
|
||||
@last_num_results = parsed.dig("searchInformation", "totalResults").to_i
|
||||
|
||||
format_results(results) do |result|
|
||||
format_results(results, args: json_data) do |result|
|
||||
{
|
||||
title: result["title"],
|
||||
link: result["link"],
|
||||
|
|
|
@ -15,7 +15,7 @@ module DiscourseAi::AiBot::Commands
|
|||
[
|
||||
Parameter.new(
|
||||
name: "search_query",
|
||||
description: "Search query to run against the discourse instance",
|
||||
description: "Search query (correct bad spelling, remove connector words!)",
|
||||
type: "string",
|
||||
),
|
||||
Parameter.new(
|
||||
|
@ -89,8 +89,8 @@ module DiscourseAi::AiBot::Commands
|
|||
}
|
||||
end
|
||||
|
||||
def process(search_string)
|
||||
parsed = JSON.parse(search_string)
|
||||
def process(search_args)
|
||||
parsed = JSON.parse(search_args)
|
||||
|
||||
limit = nil
|
||||
|
||||
|
@ -127,9 +127,9 @@ module DiscourseAi::AiBot::Commands
|
|||
@last_num_results = posts.length
|
||||
|
||||
if posts.blank?
|
||||
[]
|
||||
{ args: search_args, rows: [], instruction: "nothing was found, expand your search" }
|
||||
else
|
||||
format_results(posts) do |post|
|
||||
format_results(posts, args: search_args) do |post|
|
||||
{
|
||||
title: post.topic.title,
|
||||
url: Discourse.base_path + post.url,
|
||||
|
|
|
@ -8,14 +8,14 @@ module DiscourseAi::AiBot::Commands
|
|||
end
|
||||
|
||||
def desc
|
||||
"!time RUBY_COMPATIBLE_TIMEZONE - will generate the time in a timezone"
|
||||
"Will generate the time in a timezone"
|
||||
end
|
||||
|
||||
def parameters
|
||||
[
|
||||
Parameter.new(
|
||||
name: "timezone",
|
||||
description: "Ruby compatible timezone",
|
||||
description: "ALWAYS supply a Ruby compatible timezone",
|
||||
type: "string",
|
||||
required: true,
|
||||
),
|
||||
|
@ -45,7 +45,7 @@ module DiscourseAi::AiBot::Commands
|
|||
@last_timezone = timezone
|
||||
@last_time = time.to_s
|
||||
|
||||
time.to_s
|
||||
{ args: args, time: time.to_s }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -46,11 +46,12 @@ module DiscourseAi
|
|||
temperature: temperature,
|
||||
top_p: top_p,
|
||||
max_tokens: max_tokens,
|
||||
functions: available_functions,
|
||||
) { |key, old_value, new_value| new_value.nil? ? old_value : new_value }
|
||||
|
||||
model = model_for(low_cost: prefer_low_cost)
|
||||
|
||||
params[:functions] = available_functions if available_functions.present?
|
||||
|
||||
DiscourseAi::Inference::OpenAiCompletions.perform!(prompt, model, **params, &blk)
|
||||
end
|
||||
|
||||
|
@ -87,12 +88,14 @@ module DiscourseAi
|
|||
end
|
||||
|
||||
def available_commands
|
||||
# note: Summarize command is not ready yet, leave it out for now
|
||||
@cmds ||=
|
||||
return @cmds if @cmds
|
||||
|
||||
all_commands =
|
||||
[
|
||||
Commands::CategoriesCommand,
|
||||
Commands::TimeCommand,
|
||||
Commands::SearchCommand,
|
||||
Commands::SummarizeCommand,
|
||||
].tap do |cmds|
|
||||
cmds << Commands::TagsCommand if SiteSetting.tagging_enabled
|
||||
cmds << Commands::ImageCommand if SiteSetting.ai_stability_api_key.present?
|
||||
|
@ -101,6 +104,9 @@ module DiscourseAi
|
|||
cmds << Commands::GoogleCommand
|
||||
end
|
||||
end
|
||||
|
||||
allowed_commands = SiteSetting.ai_bot_enabled_chat_commands.split("|")
|
||||
@cmds = all_commands.filter { |klass| allowed_commands.include?(klass.name) }
|
||||
end
|
||||
|
||||
def model_for(low_cost: false)
|
||||
|
|
|
@ -62,7 +62,13 @@ RSpec.describe DiscourseAi::AiBot::Bot do
|
|||
req_opts: req_opts,
|
||||
)
|
||||
|
||||
prompt << { role: "function", content: "[]", name: "search" }
|
||||
result =
|
||||
DiscourseAi::AiBot::Commands::SearchCommand
|
||||
.new(nil, nil)
|
||||
.process({ query: "test search" }.to_json)
|
||||
.to_json
|
||||
|
||||
prompt << { role: "function", content: result, name: "search" }
|
||||
|
||||
OpenAiCompletionsInferenceStubs.stub_streamed_response(
|
||||
prompt,
|
||||
|
@ -81,7 +87,7 @@ RSpec.describe DiscourseAi::AiBot::Bot do
|
|||
expect(last.raw).to include("I found nothing")
|
||||
|
||||
expect(last.post_custom_prompt.custom_prompt).to eq(
|
||||
[["[]", "search", "function"], ["I found nothing, sorry", bot_user.username]],
|
||||
[[result, "search", "function"], ["I found nothing, sorry", bot_user.username]],
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,7 +14,8 @@ RSpec.describe DiscourseAi::AiBot::Commands::SearchCommand do
|
|||
search = described_class.new(bot_user, post1)
|
||||
|
||||
results = search.process({ query: "order:fake ABDDCDCEDGDG" }.to_json)
|
||||
expect(results).to eq([])
|
||||
expect(results[:args]).to eq("{\"query\":\"order:fake ABDDCDCEDGDG\"}")
|
||||
expect(results[:rows]).to eq([])
|
||||
end
|
||||
|
||||
it "supports subfolder properly" do
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
#frozen_string_literal: true
|
||||
|
||||
require_relative "../../../../support/openai_completions_inference_stubs"
|
||||
|
||||
RSpec.describe DiscourseAi::AiBot::Commands::TimeCommand do
|
||||
describe "#process" do
|
||||
it "can generate correct info" do
|
||||
freeze_time
|
||||
|
||||
args = { timezone: "America/Los_Angeles" }.to_json
|
||||
info = DiscourseAi::AiBot::Commands::TimeCommand.new(nil, nil).process(args)
|
||||
|
||||
expect(info).to eq({ args: args, time: Time.now.in_time_zone("America/Los_Angeles").to_s })
|
||||
expect(info.to_s).not_to include("not_here")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -14,6 +14,27 @@ RSpec.describe DiscourseAi::AiBot::OpenAiBot do
|
|||
|
||||
subject { described_class.new(bot_user) }
|
||||
|
||||
context "when changing available commands" do
|
||||
it "contains all commands by default" do
|
||||
# this will break as we add commands, but it is important as a sanity check
|
||||
SiteSetting.ai_stability_api_key = "test"
|
||||
SiteSetting.ai_google_custom_search_api_key = "test"
|
||||
SiteSetting.ai_google_custom_search_cx = "test"
|
||||
|
||||
expect(subject.available_commands.length).to eq(6)
|
||||
expect(subject.available_commands.length).to eq(
|
||||
SiteSetting.ai_bot_enabled_chat_commands.split("|").length,
|
||||
)
|
||||
end
|
||||
it "can properly filter out commands" do
|
||||
SiteSetting.ai_bot_enabled_chat_commands = "time|tags"
|
||||
expect(subject.available_commands.length).to eq(2)
|
||||
expect(subject.available_commands).to eq(
|
||||
[DiscourseAi::AiBot::Commands::TimeCommand, DiscourseAi::AiBot::Commands::TagsCommand],
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "when cleaning usernames" do
|
||||
it "can properly clean usernames so OpenAI allows it" do
|
||||
subject.clean_username("test test").should eq("test_test")
|
||||
|
|
Loading…
Reference in New Issue