2023-05-16 13:38:21 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2023-09-14 17:02:37 -04:00
|
|
|
class FakeBot < DiscourseAi::AiBot::Bot
|
|
|
|
class Tokenizer
|
|
|
|
def tokenize(text)
|
|
|
|
text.split(" ")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def tokenizer
|
|
|
|
Tokenizer.new
|
|
|
|
end
|
|
|
|
|
FEATURE: UI to update ai personas on admin page (#290)
Introduces a UI to manage customizable personas (admin only feature)
Part of the change was some extensive internal refactoring:
- AIBot now has a persona set in the constructor, once set it never changes
- Command now takes in bot as a constructor param, so it has the correct persona and is not generating AIBot objects on the fly
- Added a .prettierignore file, due to the way ALE is configured in nvim it is a pre-req for prettier to work
- Adds a bunch of validations on the AIPersona model, system personas (artist/creative etc...) are all seeded. We now ensure
- name uniqueness, and only allow certain properties to be touched for system personas.
- (JS note) the client side design takes advantage of nested routes, the parent route for personas gets all the personas via this.store.findAll("ai-persona") then child routes simply reach into this model to find a particular persona.
- (JS note) data is sideloaded into the ai-persona model the meta property supplied from the controller, resultSetMeta
- This removes ai_bot_enabled_personas and ai_bot_enabled_chat_commands, both should be controlled from the UI on a per persona basis
- Fixes a long standing bug in token accounting ... we were doing to_json.length instead of to_json.to_s.length
- Amended it so {commands} are always inserted at the end unconditionally, no need to add it to the template of the system message as it just confuses things
- Adds a concept of required_commands to stock personas, these are commands that must be configured for this stock persona to show up.
- Refactored tests so we stop requiring inference_stubs, it was very confusing to need it, added to plugin.rb for now which at least is clearer
- Migrates the persona selector to gjs
---------
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
Co-authored-by: Martin Brennan <martin@discourse.org>
2023-11-21 00:56:43 -05:00
|
|
|
def prompt_limit(allow_commands: false)
|
2023-09-14 17:02:37 -04:00
|
|
|
10_000
|
|
|
|
end
|
|
|
|
|
|
|
|
def build_message(poster_username, content, system: false, function: nil)
|
|
|
|
role = poster_username == bot_user.username ? "Assistant" : "Human"
|
|
|
|
|
|
|
|
"#{role}: #{content}"
|
|
|
|
end
|
|
|
|
|
2023-10-31 17:41:31 -04:00
|
|
|
def submit_prompt(prompt, post: nil, prefer_low_cost: false)
|
2023-09-14 17:02:37 -04:00
|
|
|
rows = @responses.shift
|
|
|
|
rows.each { |data| yield data, lambda {} }
|
|
|
|
end
|
|
|
|
|
|
|
|
def get_delta(partial, context)
|
|
|
|
partial
|
|
|
|
end
|
|
|
|
|
|
|
|
def add_response(response)
|
|
|
|
@responses ||= []
|
|
|
|
@responses << response
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe FakeBot do
|
2023-10-23 02:00:58 -04:00
|
|
|
before do
|
|
|
|
SiteSetting.ai_bot_enabled_chat_bots = "gpt-4"
|
|
|
|
SiteSetting.ai_bot_enabled = true
|
|
|
|
end
|
|
|
|
|
|
|
|
let(:bot_user) { User.find(DiscourseAi::AiBot::EntryPoint::GPT4_ID) }
|
2023-09-14 17:02:37 -04:00
|
|
|
fab!(:post) { Fabricate(:post, raw: "hello world") }
|
|
|
|
|
|
|
|
it "can handle command truncation for long messages" do
|
|
|
|
bot = FakeBot.new(bot_user)
|
|
|
|
|
2023-11-23 14:39:56 -05:00
|
|
|
tags_command = <<~TEXT
|
|
|
|
<function_calls>
|
|
|
|
<invoke>
|
|
|
|
<tool_name>tags</tool_name>
|
|
|
|
</invoke>
|
|
|
|
</function_calls>
|
|
|
|
TEXT
|
|
|
|
|
|
|
|
bot.add_response(["hello this is a big test I am testing 123\n", "#{tags_command}\nabc"])
|
2023-09-14 17:02:37 -04:00
|
|
|
bot.add_response(["this is the reply"])
|
|
|
|
|
|
|
|
bot.reply_to(post)
|
|
|
|
|
|
|
|
reply = post.topic.posts.order(:post_number).last
|
|
|
|
|
|
|
|
expect(reply.raw).not_to include("abc")
|
|
|
|
expect(reply.post_custom_prompt.custom_prompt.to_s).not_to include("abc")
|
|
|
|
expect(reply.post_custom_prompt.custom_prompt.length).to eq(3)
|
|
|
|
expect(reply.post_custom_prompt.custom_prompt[0][0]).to eq(
|
2023-11-23 14:39:56 -05:00
|
|
|
"hello this is a big test I am testing 123\n#{tags_command.strip}",
|
2023-09-14 17:02:37 -04:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "can handle command truncation for short bot messages" do
|
|
|
|
bot = FakeBot.new(bot_user)
|
|
|
|
|
2023-11-23 14:39:56 -05:00
|
|
|
tags_command = <<~TEXT
|
|
|
|
_calls>
|
|
|
|
<invoke>
|
|
|
|
<tool_name>tags</tool_name>
|
|
|
|
</invoke>
|
|
|
|
</function_calls>
|
|
|
|
TEXT
|
|
|
|
|
|
|
|
bot.add_response(["hello\n<function", "#{tags_command}\nabc"])
|
2023-09-14 17:02:37 -04:00
|
|
|
bot.add_response(["this is the reply"])
|
|
|
|
|
|
|
|
bot.reply_to(post)
|
|
|
|
|
|
|
|
reply = post.topic.posts.order(:post_number).last
|
|
|
|
|
|
|
|
expect(reply.raw).not_to include("abc")
|
|
|
|
expect(reply.post_custom_prompt.custom_prompt.to_s).not_to include("abc")
|
|
|
|
expect(reply.post_custom_prompt.custom_prompt.length).to eq(3)
|
2023-11-23 14:39:56 -05:00
|
|
|
expect(reply.post_custom_prompt.custom_prompt[0][0]).to eq(
|
|
|
|
"hello\n<function#{tags_command.strip}",
|
|
|
|
)
|
|
|
|
|
|
|
|
# we don't want function leftovers
|
|
|
|
expect(reply.raw).to start_with("hello\n\n<details>")
|
2023-09-14 17:02:37 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe DiscourseAi::AiBot::Bot do
|
2023-10-23 02:00:58 -04:00
|
|
|
before do
|
|
|
|
SiteSetting.ai_bot_enabled_chat_bots = "gpt-4"
|
|
|
|
SiteSetting.ai_bot_enabled = true
|
|
|
|
end
|
|
|
|
|
|
|
|
let(:bot_user) { User.find(DiscourseAi::AiBot::EntryPoint::GPT4_ID) }
|
|
|
|
let(:bot) { described_class.as(bot_user) }
|
2023-05-16 13:38:21 -04:00
|
|
|
|
2023-05-20 03:45:54 -04:00
|
|
|
fab!(:user) { Fabricate(:user) }
|
2023-10-23 02:00:58 -04:00
|
|
|
let!(:pm) do
|
2023-05-20 03:45:54 -04:00
|
|
|
Fabricate(
|
|
|
|
:private_message_topic,
|
|
|
|
title: "This is my special PM",
|
|
|
|
user: user,
|
|
|
|
topic_allowed_users: [
|
|
|
|
Fabricate.build(:topic_allowed_user, user: user),
|
|
|
|
Fabricate.build(:topic_allowed_user, user: bot_user),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
end
|
2023-10-23 02:00:58 -04:00
|
|
|
let!(:first_post) { Fabricate(:post, topic: pm, user: user, raw: "This is a reply by the user") }
|
|
|
|
let!(:second_post) do
|
2023-05-20 03:45:54 -04:00
|
|
|
Fabricate(:post, topic: pm, user: user, raw: "This is a second reply by the user")
|
|
|
|
end
|
2023-05-16 13:38:21 -04:00
|
|
|
|
2023-05-20 03:45:54 -04:00
|
|
|
describe "#system_prompt" do
|
|
|
|
it "includes relevant context in system prompt" do
|
|
|
|
bot.system_prompt_style!(:standard)
|
|
|
|
|
|
|
|
SiteSetting.title = "My Forum"
|
|
|
|
SiteSetting.site_description = "My Forum Description"
|
|
|
|
|
FEATURE: UI to update ai personas on admin page (#290)
Introduces a UI to manage customizable personas (admin only feature)
Part of the change was some extensive internal refactoring:
- AIBot now has a persona set in the constructor, once set it never changes
- Command now takes in bot as a constructor param, so it has the correct persona and is not generating AIBot objects on the fly
- Added a .prettierignore file, due to the way ALE is configured in nvim it is a pre-req for prettier to work
- Adds a bunch of validations on the AIPersona model, system personas (artist/creative etc...) are all seeded. We now ensure
- name uniqueness, and only allow certain properties to be touched for system personas.
- (JS note) the client side design takes advantage of nested routes, the parent route for personas gets all the personas via this.store.findAll("ai-persona") then child routes simply reach into this model to find a particular persona.
- (JS note) data is sideloaded into the ai-persona model the meta property supplied from the controller, resultSetMeta
- This removes ai_bot_enabled_personas and ai_bot_enabled_chat_commands, both should be controlled from the UI on a per persona basis
- Fixes a long standing bug in token accounting ... we were doing to_json.length instead of to_json.to_s.length
- Amended it so {commands} are always inserted at the end unconditionally, no need to add it to the template of the system message as it just confuses things
- Adds a concept of required_commands to stock personas, these are commands that must be configured for this stock persona to show up.
- Refactored tests so we stop requiring inference_stubs, it was very confusing to need it, added to plugin.rb for now which at least is clearer
- Migrates the persona selector to gjs
---------
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
Co-authored-by: Martin Brennan <martin@discourse.org>
2023-11-21 00:56:43 -05:00
|
|
|
system_prompt = bot.system_prompt(second_post, allow_commands: true)
|
2023-05-20 03:45:54 -04:00
|
|
|
|
|
|
|
expect(system_prompt).to include(SiteSetting.title)
|
|
|
|
expect(system_prompt).to include(SiteSetting.site_description)
|
|
|
|
|
|
|
|
expect(system_prompt).to include(user.username)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "#reply_to" do
|
2023-08-03 19:37:58 -04:00
|
|
|
it "can respond to a search command" do
|
2023-05-20 03:45:54 -04:00
|
|
|
bot.system_prompt_style!(:simple)
|
|
|
|
|
2023-06-19 18:45:31 -04:00
|
|
|
expected_response = {
|
|
|
|
function_call: {
|
|
|
|
name: "search",
|
|
|
|
arguments: { query: "test search" }.to_json,
|
|
|
|
},
|
|
|
|
}
|
2023-05-20 03:45:54 -04:00
|
|
|
|
FEATURE: UI to update ai personas on admin page (#290)
Introduces a UI to manage customizable personas (admin only feature)
Part of the change was some extensive internal refactoring:
- AIBot now has a persona set in the constructor, once set it never changes
- Command now takes in bot as a constructor param, so it has the correct persona and is not generating AIBot objects on the fly
- Added a .prettierignore file, due to the way ALE is configured in nvim it is a pre-req for prettier to work
- Adds a bunch of validations on the AIPersona model, system personas (artist/creative etc...) are all seeded. We now ensure
- name uniqueness, and only allow certain properties to be touched for system personas.
- (JS note) the client side design takes advantage of nested routes, the parent route for personas gets all the personas via this.store.findAll("ai-persona") then child routes simply reach into this model to find a particular persona.
- (JS note) data is sideloaded into the ai-persona model the meta property supplied from the controller, resultSetMeta
- This removes ai_bot_enabled_personas and ai_bot_enabled_chat_commands, both should be controlled from the UI on a per persona basis
- Fixes a long standing bug in token accounting ... we were doing to_json.length instead of to_json.to_s.length
- Amended it so {commands} are always inserted at the end unconditionally, no need to add it to the template of the system message as it just confuses things
- Adds a concept of required_commands to stock personas, these are commands that must be configured for this stock persona to show up.
- Refactored tests so we stop requiring inference_stubs, it was very confusing to need it, added to plugin.rb for now which at least is clearer
- Migrates the persona selector to gjs
---------
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
Co-authored-by: Martin Brennan <martin@discourse.org>
2023-11-21 00:56:43 -05:00
|
|
|
prompt = bot.bot_prompt_with_topic_context(second_post, allow_commands: true)
|
2023-05-20 03:45:54 -04:00
|
|
|
|
2023-06-19 18:45:31 -04:00
|
|
|
req_opts = bot.reply_params.merge({ functions: bot.available_functions, stream: true })
|
|
|
|
|
2023-05-20 03:45:54 -04:00
|
|
|
OpenAiCompletionsInferenceStubs.stub_streamed_response(
|
|
|
|
prompt,
|
2023-06-19 18:45:31 -04:00
|
|
|
[expected_response],
|
|
|
|
model: bot.model_for,
|
|
|
|
req_opts: req_opts,
|
2023-05-20 03:45:54 -04:00
|
|
|
)
|
|
|
|
|
2023-06-21 03:10:30 -04:00
|
|
|
result =
|
|
|
|
DiscourseAi::AiBot::Commands::SearchCommand
|
FEATURE: UI to update ai personas on admin page (#290)
Introduces a UI to manage customizable personas (admin only feature)
Part of the change was some extensive internal refactoring:
- AIBot now has a persona set in the constructor, once set it never changes
- Command now takes in bot as a constructor param, so it has the correct persona and is not generating AIBot objects on the fly
- Added a .prettierignore file, due to the way ALE is configured in nvim it is a pre-req for prettier to work
- Adds a bunch of validations on the AIPersona model, system personas (artist/creative etc...) are all seeded. We now ensure
- name uniqueness, and only allow certain properties to be touched for system personas.
- (JS note) the client side design takes advantage of nested routes, the parent route for personas gets all the personas via this.store.findAll("ai-persona") then child routes simply reach into this model to find a particular persona.
- (JS note) data is sideloaded into the ai-persona model the meta property supplied from the controller, resultSetMeta
- This removes ai_bot_enabled_personas and ai_bot_enabled_chat_commands, both should be controlled from the UI on a per persona basis
- Fixes a long standing bug in token accounting ... we were doing to_json.length instead of to_json.to_s.length
- Amended it so {commands} are always inserted at the end unconditionally, no need to add it to the template of the system message as it just confuses things
- Adds a concept of required_commands to stock personas, these are commands that must be configured for this stock persona to show up.
- Refactored tests so we stop requiring inference_stubs, it was very confusing to need it, added to plugin.rb for now which at least is clearer
- Migrates the persona selector to gjs
---------
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
Co-authored-by: Martin Brennan <martin@discourse.org>
2023-11-21 00:56:43 -05:00
|
|
|
.new(bot: nil, args: nil)
|
2023-08-03 19:37:58 -04:00
|
|
|
.process(query: "test search")
|
2023-06-21 03:10:30 -04:00
|
|
|
.to_json
|
|
|
|
|
|
|
|
prompt << { role: "function", content: result, name: "search" }
|
2023-05-20 03:45:54 -04:00
|
|
|
|
|
|
|
OpenAiCompletionsInferenceStubs.stub_streamed_response(
|
|
|
|
prompt,
|
2023-06-19 18:45:31 -04:00
|
|
|
[content: "I found nothing, sorry"],
|
|
|
|
model: bot.model_for,
|
|
|
|
req_opts: req_opts,
|
2023-05-20 03:45:54 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
bot.reply_to(second_post)
|
|
|
|
|
|
|
|
last = second_post.topic.posts.order("id desc").first
|
|
|
|
|
|
|
|
expect(last.raw).to include("<details>")
|
|
|
|
expect(last.raw).to include("<summary>Search</summary>")
|
|
|
|
expect(last.raw).not_to include("translation missing")
|
2023-06-19 18:45:31 -04:00
|
|
|
expect(last.raw).to include("I found nothing")
|
2023-05-23 09:08:17 -04:00
|
|
|
|
2023-06-20 01:44:03 -04:00
|
|
|
expect(last.post_custom_prompt.custom_prompt).to eq(
|
2023-06-21 03:10:30 -04:00
|
|
|
[[result, "search", "function"], ["I found nothing, sorry", bot_user.username]],
|
2023-06-20 01:44:03 -04:00
|
|
|
)
|
2023-10-31 17:41:31 -04:00
|
|
|
log = AiApiAuditLog.find_by(post_id: second_post.id)
|
|
|
|
expect(log).to be_present
|
2023-05-20 03:45:54 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "#update_pm_title" do
|
|
|
|
let(:expected_response) { "This is a suggested title" }
|
2023-05-16 13:38:21 -04:00
|
|
|
|
|
|
|
before { SiteSetting.min_personal_message_post_length = 5 }
|
|
|
|
|
|
|
|
it "updates the title using bot suggestions" do
|
|
|
|
OpenAiCompletionsInferenceStubs.stub_response(
|
2023-06-19 18:45:31 -04:00
|
|
|
bot.title_prompt(second_post),
|
2023-05-16 13:38:21 -04:00
|
|
|
expected_response,
|
2023-06-19 18:45:31 -04:00
|
|
|
model: bot.model_for,
|
2023-05-16 13:38:21 -04:00
|
|
|
req_opts: {
|
|
|
|
temperature: 0.7,
|
|
|
|
top_p: 0.9,
|
|
|
|
max_tokens: 40,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
2023-05-20 03:45:54 -04:00
|
|
|
bot.update_pm_title(second_post)
|
2023-05-16 13:38:21 -04:00
|
|
|
|
2023-05-20 03:45:54 -04:00
|
|
|
expect(pm.reload.title).to eq(expected_response)
|
2023-05-16 13:38:21 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|