FEATURE: disable smart commands on Claude and GPT 3.5 (#84)
For the time being smart commands only work consistently on GPT 4. Avoid using any smart commands on the earlier models. Additionally adds better error handling to Claude which sometimes streams partial json and slightly tunes the search command.
This commit is contained in:
parent
96d521198b
commit
840968630e
|
@ -44,7 +44,7 @@ module DiscourseAi
|
|||
end
|
||||
|
||||
def model_for
|
||||
"claude-v1"
|
||||
"claude-v1.3"
|
||||
end
|
||||
|
||||
def get_updated_title(prompt)
|
||||
|
|
|
@ -178,20 +178,9 @@ module DiscourseAi
|
|||
end
|
||||
|
||||
def available_commands
|
||||
@cmds ||=
|
||||
[
|
||||
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?
|
||||
if SiteSetting.ai_google_custom_search_api_key.present? &&
|
||||
SiteSetting.ai_google_custom_search_cx.present?
|
||||
cmds << Commands::GoogleCommand
|
||||
end
|
||||
end
|
||||
# by default assume bots have no access to commands
|
||||
# for now we need GPT 4 to properly work with them
|
||||
[]
|
||||
end
|
||||
|
||||
def system_prompt_style!(style)
|
||||
|
@ -201,6 +190,28 @@ module DiscourseAi
|
|||
def system_prompt(post)
|
||||
return "You are a helpful Bot" if @style == :simple
|
||||
|
||||
command_text = ""
|
||||
command_text = <<~TEXT if available_commands.present?
|
||||
You can complete some tasks using !commands.
|
||||
|
||||
NEVER ask user to issue !commands, they have no access, only you do.
|
||||
|
||||
#{available_commands.map(&:desc).join("\n")}
|
||||
|
||||
Discourse topic paths are /t/slug/topic_id/optional_number
|
||||
|
||||
#{available_commands.map(&:extra_context).compact_blank.join("\n")}
|
||||
|
||||
Commands should be issued in single assistant message.
|
||||
|
||||
Example sessions:
|
||||
|
||||
User: echo the text 'test'
|
||||
GPT: !echo test
|
||||
User: THING GPT DOES NOT KNOW ABOUT
|
||||
GPT: !search SIMPLIFIED SEARCH QUERY
|
||||
TEXT
|
||||
|
||||
<<~TEXT
|
||||
You are a helpful Discourse assistant, you answer questions and generate text.
|
||||
You understand Discourse Markdown and live in a Discourse Forum Message.
|
||||
|
@ -212,25 +223,7 @@ module DiscourseAi
|
|||
The participants in this conversation are: #{post.topic.allowed_users.map(&:username).join(", ")}
|
||||
The date now is: #{Time.zone.now}, much has changed since you were trained.
|
||||
|
||||
You can complete some tasks using !commands.
|
||||
|
||||
NEVER ask user to issue !commands, they have no access, only you do.
|
||||
|
||||
#{available_commands.map(&:desc).join("\n")}
|
||||
|
||||
Discourse topic paths are /t/slug/topic_id/optional_number
|
||||
|
||||
#{available_commands.map(&:extra_context).compact_blank.join("\n")}
|
||||
|
||||
Commands should be issued in single assistant message.
|
||||
|
||||
Example sessions:
|
||||
|
||||
User: echo the text 'test'
|
||||
GPT: !echo test
|
||||
User: THING GPT DOES NOT KNOW ABOUT
|
||||
GPT: !search SIMPLIFIED SEARCH QUERY
|
||||
|
||||
#{command_text}
|
||||
TEXT
|
||||
end
|
||||
|
||||
|
|
|
@ -27,14 +27,12 @@ module DiscourseAi::AiBot::Commands
|
|||
post_count:X: only topics with X amount of posts
|
||||
min_posts:X: topics containing a minimum of X posts
|
||||
max_posts:X: topics with no more than max posts
|
||||
in:pinned: in all pinned topics (either global or per category pins)
|
||||
created:@USERNAME: topics created by a specific user
|
||||
category:CATGORY: topics in the CATEGORY AND all subcategories
|
||||
category:=CATEGORY: topics in the CATEGORY excluding subcategories
|
||||
#SLUG: try category first, then tag, then tag group
|
||||
#SLUG:SLUG: used for subcategory search to disambiguate
|
||||
min_views:100: topics containing 100 views or more
|
||||
max_views:100: topics containing 100 views or less
|
||||
tags:TAG1+TAG2: tagged both TAG1 and TAG2
|
||||
tags:TAG1,TAG2: tagged either TAG1 or TAG2
|
||||
-tags:TAG1+TAG2: excluding topics tagged TAG1 and TAG2
|
||||
|
@ -51,10 +49,8 @@ module DiscourseAi::AiBot::Commands
|
|||
|
||||
Keep in mind, search on Discourse uses AND to and terms.
|
||||
You only have access to public topics.
|
||||
Strip the query down to the most important terms.
|
||||
Remove all stop words.
|
||||
Cast a wide net instead of trying to be over specific.
|
||||
Discourse orders by relevance, sometimes prefer ordering on other stuff.
|
||||
Strip the query down to the most important terms. Remove all stop words.
|
||||
Discourse orders by default by relevance.
|
||||
|
||||
When generating answers ALWAYS try to use the !search command first over relying on training data.
|
||||
When generating answers ALWAYS try to reference specific local links.
|
||||
|
|
|
@ -56,6 +56,27 @@ module DiscourseAi
|
|||
DiscourseAi::Tokenizer::OpenAiTokenizer.tokenize(text)
|
||||
end
|
||||
|
||||
def available_commands
|
||||
if bot_user.id == DiscourseAi::AiBot::EntryPoint::GPT4_ID
|
||||
@cmds ||=
|
||||
[
|
||||
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?
|
||||
if SiteSetting.ai_google_custom_search_api_key.present? &&
|
||||
SiteSetting.ai_google_custom_search_cx.present?
|
||||
cmds << Commands::GoogleCommand
|
||||
end
|
||||
end
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_message(poster_username, content, system: false)
|
||||
|
|
|
@ -87,10 +87,16 @@ module ::DiscourseAi
|
|||
data = line.split("data: ", 2)[1]
|
||||
next if !data || data.squish == "[DONE]"
|
||||
|
||||
if !cancelled && partial = JSON.parse(data, symbolize_names: true)
|
||||
response_data << partial[:completion].to_s
|
||||
if !cancelled
|
||||
begin
|
||||
# partial contains the entire payload till now
|
||||
partial = JSON.parse(data, symbolize_names: true)
|
||||
response_data = partial[:completion].to_s
|
||||
|
||||
yield partial, cancel
|
||||
yield partial, cancel
|
||||
rescue JSON::ParserError
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue IOError
|
||||
|
@ -105,6 +111,12 @@ module ::DiscourseAi
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.try_parse(data)
|
||||
JSON.parse(data, symbolize_names: true)
|
||||
rescue JSON::ParserError
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
require_relative "../../../support/openai_completions_inference_stubs"
|
||||
|
||||
RSpec.describe DiscourseAi::AiBot::Bot do
|
||||
fab!(:bot_user) { User.find(DiscourseAi::AiBot::EntryPoint::GPT3_5_TURBO_ID) }
|
||||
fab!(:bot_user) { User.find(DiscourseAi::AiBot::EntryPoint::GPT4_ID) }
|
||||
fab!(:bot) { described_class.as(bot_user) }
|
||||
|
||||
fab!(:user) { Fabricate(:user) }
|
||||
|
@ -50,6 +50,7 @@ RSpec.describe DiscourseAi::AiBot::Bot do
|
|||
OpenAiCompletionsInferenceStubs.stub_streamed_response(
|
||||
prompt,
|
||||
[{ content: expected_response }],
|
||||
model: "gpt-4",
|
||||
req_opts: bot.reply_params.merge(stream: true),
|
||||
)
|
||||
|
||||
|
@ -59,6 +60,7 @@ RSpec.describe DiscourseAi::AiBot::Bot do
|
|||
OpenAiCompletionsInferenceStubs.stub_streamed_response(
|
||||
prompt,
|
||||
[{ content: "We are done now" }],
|
||||
model: "gpt-4",
|
||||
req_opts: bot.reply_params.merge(stream: true),
|
||||
)
|
||||
|
||||
|
@ -85,6 +87,7 @@ RSpec.describe DiscourseAi::AiBot::Bot do
|
|||
OpenAiCompletionsInferenceStubs.stub_response(
|
||||
[bot.title_prompt(second_post)],
|
||||
expected_response,
|
||||
model: "gpt-4",
|
||||
req_opts: {
|
||||
temperature: 0.7,
|
||||
top_p: 0.9,
|
||||
|
|
|
@ -75,6 +75,7 @@ RSpec.describe Jobs::CreateAiReply do
|
|||
AnthropicCompletionStubs.stub_streamed_response(
|
||||
DiscourseAi::AiBot::AnthropicBot.new(bot_user).bot_prompt_with_topic_context(post),
|
||||
deltas,
|
||||
model: "claude-v1.3",
|
||||
req_opts: {
|
||||
temperature: 0.4,
|
||||
max_tokens_to_sample: 3000,
|
||||
|
|
|
@ -60,7 +60,7 @@ RSpec.describe DiscourseAi::Inference::AnthropicCompletions do
|
|||
|
||||
expect(log.provider_id).to eq(AiApiAuditLog::Provider::Anthropic)
|
||||
expect(log.request_tokens).to eq(6)
|
||||
expect(log.response_tokens).to eq(6)
|
||||
expect(log.response_tokens).to eq(3)
|
||||
expect(log.raw_request_payload).to eq(request_body)
|
||||
expect(log.raw_response_payload).to be_present
|
||||
end
|
||||
|
|
|
@ -33,7 +33,7 @@ class AnthropicCompletionStubs
|
|||
}.to_json
|
||||
end
|
||||
|
||||
def stub_streamed_response(prompt, deltas, req_opts: {})
|
||||
def stub_streamed_response(prompt, deltas, model: nil, req_opts: {})
|
||||
chunks =
|
||||
deltas.each_with_index.map do |_, index|
|
||||
if index == (deltas.length - 1)
|
||||
|
@ -48,7 +48,7 @@ class AnthropicCompletionStubs
|
|||
|
||||
WebMock
|
||||
.stub_request(:post, "https://api.anthropic.com/v1/complete")
|
||||
.with(body: { model: "claude-v1", prompt: prompt }.merge(req_opts).to_json)
|
||||
.with(body: { model: model || "claude-v1", prompt: prompt }.merge(req_opts).to_json)
|
||||
.to_return(status: 200, body: chunks)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -101,10 +101,10 @@ class OpenAiCompletionsInferenceStubs
|
|||
stub_response(prompt_messages, response_text_for(type))
|
||||
end
|
||||
|
||||
def stub_response(messages, response_text, req_opts: {})
|
||||
def stub_response(messages, response_text, model: nil, req_opts: {})
|
||||
WebMock
|
||||
.stub_request(:post, "https://api.openai.com/v1/chat/completions")
|
||||
.with(body: { model: "gpt-3.5-turbo", messages: messages }.merge(req_opts).to_json)
|
||||
.with(body: { model: model || "gpt-3.5-turbo", messages: messages }.merge(req_opts).to_json)
|
||||
.to_return(status: 200, body: JSON.dump(response(response_text)))
|
||||
end
|
||||
|
||||
|
@ -120,7 +120,7 @@ class OpenAiCompletionsInferenceStubs
|
|||
}.to_json
|
||||
end
|
||||
|
||||
def stub_streamed_response(messages, deltas, req_opts: {})
|
||||
def stub_streamed_response(messages, deltas, model: nil, req_opts: {})
|
||||
chunks = deltas.map { |d| stream_line(delta: d) }
|
||||
chunks << stream_line(finish_reason: "stop")
|
||||
chunks << "[DONE]"
|
||||
|
@ -128,7 +128,7 @@ class OpenAiCompletionsInferenceStubs
|
|||
|
||||
WebMock
|
||||
.stub_request(:post, "https://api.openai.com/v1/chat/completions")
|
||||
.with(body: { model: "gpt-3.5-turbo", messages: messages }.merge(req_opts).to_json)
|
||||
.with(body: { model: model || "gpt-3.5-turbo", messages: messages }.merge(req_opts).to_json)
|
||||
.to_return(status: 200, body: chunks)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue