2023-05-23 09:08:17 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2023-08-22 17:49:36 -04:00
|
|
|
module ::DiscourseAi
|
|
|
|
module AiBot
|
|
|
|
describe AnthropicBot do
|
|
|
|
def bot_user
|
|
|
|
User.find(EntryPoint::CLAUDE_V2_ID)
|
|
|
|
end
|
|
|
|
|
2023-10-23 02:00:58 -04:00
|
|
|
before do
|
|
|
|
SiteSetting.ai_bot_enabled_chat_bots = "claude-2"
|
|
|
|
SiteSetting.ai_bot_enabled = true
|
|
|
|
end
|
|
|
|
|
2023-08-22 17:49:36 -04:00
|
|
|
let(:bot) { described_class.new(bot_user) }
|
2023-11-23 14:39:56 -05:00
|
|
|
fab!(:post)
|
2023-08-22 17:49:36 -04:00
|
|
|
|
|
|
|
describe "system message" do
|
|
|
|
it "includes the full command framework" do
|
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.system_prompt(post, allow_commands: true)
|
2023-08-22 17:49:36 -04:00
|
|
|
|
|
|
|
expect(prompt).to include("read")
|
|
|
|
expect(prompt).to include("search_query")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-11-23 14:39:56 -05:00
|
|
|
it "does not include half parsed function calls in reply" do
|
|
|
|
completion1 = "<function"
|
|
|
|
completion2 = <<~REPLY
|
|
|
|
_calls>
|
|
|
|
<invoke>
|
|
|
|
<tool_name>search</tool_name>
|
|
|
|
<parameters>
|
|
|
|
<search_query>hello world</search_query>
|
|
|
|
</parameters>
|
|
|
|
</invoke>
|
|
|
|
</function_calls>
|
|
|
|
junk
|
|
|
|
REPLY
|
2023-09-04 20:37:58 -04:00
|
|
|
|
2023-11-23 14:39:56 -05:00
|
|
|
completion1 = { completion: completion1 }.to_json
|
|
|
|
completion2 = { completion: completion2 }.to_json
|
2023-09-04 20:37:58 -04:00
|
|
|
|
2023-11-23 14:39:56 -05:00
|
|
|
completion3 = { completion: "<func" }.to_json
|
2023-09-04 20:37:58 -04:00
|
|
|
|
2023-11-23 14:39:56 -05:00
|
|
|
request_number = 0
|
2023-09-04 20:37:58 -04:00
|
|
|
|
2023-11-23 14:39:56 -05:00
|
|
|
last_body = nil
|
2023-09-04 20:37:58 -04:00
|
|
|
|
2023-11-23 14:39:56 -05:00
|
|
|
stub_request(:post, "https://api.anthropic.com/v1/complete").with(
|
|
|
|
body:
|
|
|
|
lambda do |body|
|
|
|
|
last_body = body
|
|
|
|
request_number == 2
|
|
|
|
end,
|
|
|
|
).to_return(status: 200, body: lambda { |request| +"data: #{completion3}" })
|
2023-09-04 20:37:58 -04:00
|
|
|
|
2023-11-23 14:39:56 -05:00
|
|
|
stub_request(:post, "https://api.anthropic.com/v1/complete").with(
|
|
|
|
body:
|
|
|
|
lambda do |body|
|
|
|
|
request_number += 1
|
|
|
|
request_number == 1
|
|
|
|
end,
|
|
|
|
).to_return(
|
|
|
|
status: 200,
|
|
|
|
body: lambda { |request| +"data: #{completion1}\ndata: #{completion2}" },
|
|
|
|
)
|
2023-09-04 20:37:58 -04:00
|
|
|
|
2023-11-23 14:39:56 -05:00
|
|
|
bot.reply_to(post)
|
2023-09-04 20:37:58 -04:00
|
|
|
|
2023-11-23 14:39:56 -05:00
|
|
|
post.topic.reload
|
2023-09-04 20:37:58 -04:00
|
|
|
|
2023-11-23 14:39:56 -05:00
|
|
|
raw = post.topic.ordered_posts.last.raw
|
|
|
|
|
|
|
|
prompt = JSON.parse(last_body)["prompt"]
|
|
|
|
|
|
|
|
# function call is bundled into Assitant prompt
|
|
|
|
expect(prompt.split("Human:").length).to eq(2)
|
|
|
|
|
|
|
|
# this should be stripped
|
|
|
|
expect(prompt).not_to include("junk")
|
|
|
|
|
|
|
|
expect(raw).to end_with("<func")
|
2023-09-04 20:37:58 -04:00
|
|
|
|
2023-11-23 14:39:56 -05:00
|
|
|
# leading <function_call> should be stripped
|
|
|
|
expect(raw).to start_with("\n\n<details")
|
|
|
|
end
|
|
|
|
|
|
|
|
it "does not include Assistant: in front of the system prompt" do
|
|
|
|
prompt = nil
|
|
|
|
|
|
|
|
stub_request(:post, "https://api.anthropic.com/v1/complete").with(
|
|
|
|
body:
|
|
|
|
lambda do |body|
|
|
|
|
json = JSON.parse(body)
|
|
|
|
prompt = json["prompt"]
|
|
|
|
true
|
|
|
|
end,
|
|
|
|
).to_return(
|
|
|
|
status: 200,
|
|
|
|
body: lambda { |request| +"data: " << { completion: "Hello World" }.to_json },
|
|
|
|
)
|
|
|
|
|
|
|
|
bot.reply_to(post)
|
|
|
|
|
|
|
|
expect(prompt).not_to be_nil
|
|
|
|
expect(prompt).not_to start_with("Assistant:")
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "parsing a reply prompt" do
|
|
|
|
it "can correctly predict that a completion needs to be cancelled" do
|
2023-08-22 17:49:36 -04:00
|
|
|
functions = DiscourseAi::AiBot::Bot::FunctionCalls.new
|
2023-05-23 09:08:17 -04:00
|
|
|
|
2023-08-28 20:57:36 -04:00
|
|
|
# note anthropic API has a silly leading space, we need to make sure we can handle that
|
2023-11-23 14:39:56 -05:00
|
|
|
prompt = +<<~REPLY.strip
|
|
|
|
<function_calls>
|
|
|
|
<invoke>
|
|
|
|
<tool_name>search</tool_name>
|
|
|
|
<parameters>
|
|
|
|
<search_query>hello world</search_query>
|
|
|
|
<random_stuff>77</random_stuff>
|
|
|
|
</parameters>
|
|
|
|
</invoke>
|
|
|
|
</function_calls
|
2023-08-22 17:49:36 -04:00
|
|
|
REPLY
|
|
|
|
|
2023-11-23 14:39:56 -05:00
|
|
|
bot.populate_functions(
|
|
|
|
partial: nil,
|
|
|
|
reply: prompt,
|
|
|
|
functions: functions,
|
|
|
|
done: false,
|
|
|
|
current_delta: "",
|
|
|
|
)
|
|
|
|
|
|
|
|
expect(functions.found?).to eq(true)
|
|
|
|
expect(functions.cancel_completion?).to eq(false)
|
|
|
|
|
|
|
|
prompt << ">"
|
|
|
|
|
|
|
|
bot.populate_functions(
|
|
|
|
partial: nil,
|
|
|
|
reply: prompt,
|
|
|
|
functions: functions,
|
|
|
|
done: true,
|
|
|
|
current_delta: "",
|
|
|
|
)
|
2023-08-22 17:49:36 -04:00
|
|
|
|
|
|
|
expect(functions.found?).to eq(true)
|
|
|
|
|
2023-11-23 14:39:56 -05:00
|
|
|
expect(functions.to_a.length).to eq(1)
|
2023-08-22 17:49:36 -04:00
|
|
|
|
|
|
|
expect(functions.to_a).to eq(
|
2023-11-23 14:39:56 -05:00
|
|
|
[{ name: "search", arguments: "{\"search_query\":\"hello world\"}" }],
|
2023-08-22 17:49:36 -04:00
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
2023-05-23 09:08:17 -04:00
|
|
|
|
2023-08-22 17:49:36 -04:00
|
|
|
describe "#update_with_delta" do
|
|
|
|
describe "get_delta" do
|
2023-08-28 20:57:36 -04:00
|
|
|
it "can properly remove first leading space" do
|
|
|
|
context = {}
|
|
|
|
reply = +""
|
|
|
|
|
|
|
|
reply << bot.get_delta({ completion: " Hello" }, context)
|
|
|
|
reply << bot.get_delta({ completion: " World" }, context)
|
|
|
|
expect(reply).to eq("Hello World")
|
|
|
|
end
|
|
|
|
|
2023-08-22 17:49:36 -04:00
|
|
|
it "can properly remove Assistant prefix" do
|
|
|
|
context = {}
|
|
|
|
reply = +""
|
2023-05-23 09:08:17 -04:00
|
|
|
|
2023-08-22 17:49:36 -04:00
|
|
|
reply << bot.get_delta({ completion: "Hello " }, context)
|
|
|
|
expect(reply).to eq("Hello ")
|
2023-05-23 09:08:17 -04:00
|
|
|
|
2023-08-22 17:49:36 -04:00
|
|
|
reply << bot.get_delta({ completion: "world" }, context)
|
|
|
|
expect(reply).to eq("Hello world")
|
|
|
|
end
|
|
|
|
end
|
2023-05-23 09:08:17 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|