DEV: simplify command framework (#125)

The command framework had some confusing dispatching where it would dispatch
JSON blobs, this meant there was lots of parsing required in every command

The refactor handles transforming the args prior to dispatch which makes
consuming far simpler

This is also general prep to supporting some basic command framework in other
llms.
This commit is contained in:
Sam 2023-08-04 09:37:58 +10:00 committed by GitHub
parent eb7fff3a55
commit 7edb57c005
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 32 additions and 37 deletions

View File

@ -24,7 +24,7 @@ module DiscourseAi::AiBot::Commands
{ count: @last_count || 0 } { count: @last_count || 0 }
end end
def process(_args) def process
columns = { columns = {
name: "Name", name: "Name",
slug: "Slug", slug: "Slug",

View File

@ -106,7 +106,9 @@ module DiscourseAi
post.post_custom_prompt ||= post.build_post_custom_prompt(custom_prompt: []) post.post_custom_prompt ||= post.build_post_custom_prompt(custom_prompt: [])
prompt = post.post_custom_prompt.custom_prompt || [] prompt = post.post_custom_prompt.custom_prompt || []
prompt << [process(args).to_json, self.class.name, "function"] parsed_args = JSON.parse(args).symbolize_keys
prompt << [process(**parsed_args).to_json, self.class.name, "function"]
post.post_custom_prompt.update!(custom_prompt: prompt) post.post_custom_prompt.update!(custom_prompt: prompt)
raw = +(<<~HTML) raw = +(<<~HTML)

View File

@ -39,13 +39,11 @@ module DiscourseAi::AiBot::Commands
} }
end end
def process(search_string) def process(query:)
search_string = JSON.parse(search_string)["query"] @last_query = query
@last_query = search_string
api_key = SiteSetting.ai_google_custom_search_api_key api_key = SiteSetting.ai_google_custom_search_api_key
cx = SiteSetting.ai_google_custom_search_cx cx = SiteSetting.ai_google_custom_search_cx
query = CGI.escape(search_string) query = CGI.escape(query)
uri = uri =
URI("https://www.googleapis.com/customsearch/v1?key=#{api_key}&cx=#{cx}&q=#{query}&num=10") URI("https://www.googleapis.com/customsearch/v1?key=#{api_key}&cx=#{cx}&q=#{query}&num=10")
body = Net::HTTP.get(uri) body = Net::HTTP.get(uri)

View File

@ -43,8 +43,8 @@ module DiscourseAi::AiBot::Commands
true true
end end
def process(prompt) def process(prompt:)
@last_prompt = prompt = JSON.parse(prompt)["prompt"] @last_prompt = prompt
results = DiscourseAi::Inference::StabilityGenerator.perform!(prompt) results = DiscourseAi::Inference::StabilityGenerator.perform!(prompt)
uploads = [] uploads = []

View File

@ -93,17 +93,15 @@ module DiscourseAi::AiBot::Commands
} }
end end
def process(search_args) def process(**search_args)
parsed = JSON.parse(search_args)
limit = nil limit = nil
search_string = search_string =
parsed search_args
.map do |key, value| .map do |key, value|
if key == "search_query" if key == :search_query
value value
elsif key == "limit" elsif key == :limit
limit = value.to_i limit = value.to_i
nil nil
else else

View File

@ -44,9 +44,7 @@ module DiscourseAi::AiBot::Commands
{ url: "#{Discourse.base_path}/t/-/#{@last_topic_id}", title: @last_topic_title || "" } { url: "#{Discourse.base_path}/t/-/#{@last_topic_id}", title: @last_topic_title || "" }
end end
def process(instructions) def process(topic_id:, guidance: nil)
topic_id, guidance = instructions.split(" ", 2)
@last_topic_id = topic_id @last_topic_id = topic_id
topic_id = topic_id.to_i topic_id = topic_id.to_i

View File

@ -24,7 +24,7 @@ module DiscourseAi::AiBot::Commands
{ count: @last_count || 0 } { count: @last_count || 0 }
end end
def process(_args) def process
column_names = { name: "Name", public_topic_count: "Topic Count" } column_names = { name: "Name", public_topic_count: "Topic Count" }
tags = tags =

View File

@ -31,9 +31,7 @@ module DiscourseAi::AiBot::Commands
{ timezone: @last_timezone, time: @last_time } { timezone: @last_timezone, time: @last_time }
end end
def process(args) def process(timezone:)
timezone = JSON.parse(args)["timezone"]
time = time =
begin begin
Time.now.in_time_zone(timezone) Time.now.in_time_zone(timezone)
@ -45,7 +43,7 @@ module DiscourseAi::AiBot::Commands
@last_timezone = timezone @last_timezone = timezone
@last_time = time.to_s @last_time = time.to_s
{ args: args, time: time.to_s } { args: { timezone: timezone }, time: time.to_s }
end end
end end
end end

View File

@ -40,7 +40,7 @@ RSpec.describe DiscourseAi::AiBot::Bot do
end end
describe "#reply_to" do describe "#reply_to" do
it "can respond to !search" do it "can respond to a search command" do
bot.system_prompt_style!(:simple) bot.system_prompt_style!(:simple)
bot.max_commands_per_reply = 2 bot.max_commands_per_reply = 2
@ -65,7 +65,7 @@ RSpec.describe DiscourseAi::AiBot::Bot do
result = result =
DiscourseAi::AiBot::Commands::SearchCommand DiscourseAi::AiBot::Commands::SearchCommand
.new(nil, nil) .new(nil, nil)
.process({ query: "test search" }.to_json) .process(query: "test search")
.to_json .to_json
prompt << { role: "function", content: result, name: "search" } prompt << { role: "function", content: result, name: "search" }

View File

@ -7,7 +7,7 @@ RSpec.describe DiscourseAi::AiBot::Commands::CategoriesCommand do
it "can generate correct info" do it "can generate correct info" do
Fabricate(:category, name: "america", posts_year: 999) Fabricate(:category, name: "america", posts_year: 999)
info = DiscourseAi::AiBot::Commands::CategoriesCommand.new(nil, nil).process(nil) info = DiscourseAi::AiBot::Commands::CategoriesCommand.new(nil, nil).process
expect(info.to_s).to include("america") expect(info.to_s).to include("america")
expect(info.to_s).to include("999") expect(info.to_s).to include("999")
end end

View File

@ -33,7 +33,7 @@ RSpec.describe DiscourseAi::AiBot::Commands::GoogleCommand do
).to_return(status: 200, body: json_text, headers: {}) ).to_return(status: 200, body: json_text, headers: {})
google = described_class.new(bot_user, post) google = described_class.new(bot_user, post)
info = google.process({ query: "some search term" }.to_json).to_json info = google.process(query: "some search term").to_json
expect(google.description_args[:count]).to eq(1) expect(google.description_args[:count]).to eq(1)
expect(info).to include("title1") expect(info).to include("title1")

View File

@ -13,8 +13,9 @@ RSpec.describe DiscourseAi::AiBot::Commands::SearchCommand do
post1 = Fabricate(:post) post1 = Fabricate(:post)
search = described_class.new(bot_user, post1) search = described_class.new(bot_user, post1)
results = search.process({ query: "order:fake ABDDCDCEDGDG" }.to_json) results = search.process(query: "order:fake ABDDCDCEDGDG")
expect(results[:args]).to eq("{\"query\":\"order:fake ABDDCDCEDGDG\"}")
expect(results[:args]).to eq({ query: "order:fake ABDDCDCEDGDG" })
expect(results[:rows]).to eq([]) expect(results[:rows]).to eq([])
end end
@ -25,7 +26,7 @@ RSpec.describe DiscourseAi::AiBot::Commands::SearchCommand do
search = described_class.new(bot_user, post1) search = described_class.new(bot_user, post1)
results = search.process({ limit: 1, user: post1.user.username }.to_json) results = search.process(limit: 1, user: post1.user.username)
expect(results[:rows].to_s).to include("/subfolder" + post1.url) expect(results[:rows].to_s).to include("/subfolder" + post1.url)
end end
@ -37,13 +38,13 @@ RSpec.describe DiscourseAi::AiBot::Commands::SearchCommand do
# search has no built in support for limit: so handle it from the outside # search has no built in support for limit: so handle it from the outside
search = described_class.new(bot_user, post1) search = described_class.new(bot_user, post1)
results = search.process({ limit: 2, user: post1.user.username }.to_json) results = search.process(limit: 2, user: post1.user.username)
expect(results[:column_names].length).to eq(4) expect(results[:column_names].length).to eq(4)
expect(results[:rows].length).to eq(2) expect(results[:rows].length).to eq(2)
# just searching for everything # just searching for everything
results = search.process({ order: "latest_topic" }.to_json) results = search.process(order: "latest_topic")
expect(results[:rows].length).to be > 1 expect(results[:rows].length).to be > 1
end end
end end

View File

@ -15,7 +15,7 @@ RSpec.describe DiscourseAi::AiBot::Commands::SummarizeCommand do
) )
summarizer = described_class.new(bot_user, post) summarizer = described_class.new(bot_user, post)
info = summarizer.process("#{post.topic_id} why did it happen?") info = summarizer.process(topic_id: post.topic_id, guidance: "why did it happen?")
expect(info).to include("Topic summarized") expect(info).to include("Topic summarized")
expect(summarizer.custom_raw).to include("summary stuff") expect(summarizer.custom_raw).to include("summary stuff")
@ -31,7 +31,7 @@ RSpec.describe DiscourseAi::AiBot::Commands::SummarizeCommand do
post = Fabricate(:post, topic: topic) post = Fabricate(:post, topic: topic)
summarizer = described_class.new(bot_user, post) summarizer = described_class.new(bot_user, post)
info = summarizer.process("#{post.topic_id} why did it happen?") info = summarizer.process(topic_id: post.topic_id, guidance: "why did it happen?")
expect(info).not_to include(post.raw) expect(info).not_to include(post.raw)

View File

@ -10,7 +10,7 @@ RSpec.describe DiscourseAi::AiBot::Commands::TagsCommand do
Fabricate(:tag, name: "america", public_topic_count: 100) Fabricate(:tag, name: "america", public_topic_count: 100)
Fabricate(:tag, name: "not_here", public_topic_count: 0) Fabricate(:tag, name: "not_here", public_topic_count: 0)
info = DiscourseAi::AiBot::Commands::TagsCommand.new(nil, nil).process(nil) info = DiscourseAi::AiBot::Commands::TagsCommand.new(nil, nil).process
expect(info.to_s).to include("america") expect(info.to_s).to include("america")
expect(info.to_s).not_to include("not_here") expect(info.to_s).not_to include("not_here")

View File

@ -7,8 +7,8 @@ RSpec.describe DiscourseAi::AiBot::Commands::TimeCommand do
it "can generate correct info" do it "can generate correct info" do
freeze_time freeze_time
args = { timezone: "America/Los_Angeles" }.to_json args = { timezone: "America/Los_Angeles" }
info = DiscourseAi::AiBot::Commands::TimeCommand.new(nil, nil).process(args) 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 eq({ args: args, time: Time.now.in_time_zone("America/Los_Angeles").to_s })
expect(info.to_s).not_to include("not_here") expect(info.to_s).not_to include("not_here")