FIX: improve function call parsing (#613)

- support " / ' wrapped values
- coerce integer to integer
- enforce enum at boundary
This commit is contained in:
Sam 2024-05-13 19:40:11 +10:00 committed by GitHub
parent 64f41454ad
commit 0069256efd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 129 additions and 1 deletions

View File

@ -208,6 +208,15 @@ module DiscourseAi
rescue JSON::ParserError
[value.to_s]
end
elsif param[:type] == "string" && value
value = strip_quotes(value).to_s
elsif param[:type] == "integer" && value
value = strip_quotes(value).to_i
end
if param[:enum] && value && !param[:enum].include?(value)
# invalid enum value
value = nil
end
arguments[name.to_sym] = value if value
@ -223,6 +232,20 @@ module DiscourseAi
)
end
def strip_quotes(value)
if value.is_a?(String)
if value.start_with?('"') && value.end_with?('"')
value = value[1..-2]
elsif value.start_with?("'") && value.end_with?("'")
value = value[1..-2]
else
value
end
else
value
end
end
def rag_fragments_prompt(conversation_context, llm:, user:)
upload_refs =
UploadReference.where(target_id: id, target_type: "AiPersona").pluck(:upload_id)

View File

@ -121,7 +121,8 @@ module DiscourseAi
i = 0
while decoded
parsed = JSON.parse(decoded.payload.string)
messages << Base64.decode64(parsed["bytes"])
# perhaps some control message we can just ignore
messages << Base64.decode64(parsed["bytes"]) if parsed && parsed["bytes"]
decoded, _done = @decoder.decode_chunk

View File

@ -70,6 +70,110 @@ RSpec.describe DiscourseAi::AiBot::Personas::Persona do
expect(tools.find { |t| t[:name] == "image" }).to be_nil
end
it "can parse string that are wrapped in quotes" do
SiteSetting.ai_stability_api_key = "123"
xml = <<~XML
<function_calls>
<invoke>
<tool_name>image</tool_name>
<tool_id>call_JtYQMful5QKqw97XFsHzPweB</tool_id>
<parameters>
<prompts>["cat oil painting", "big car"]</prompts>
<aspect_ratio>"16:9"</aspect_ratio>
</parameters>
</invoke>
<invoke>
<tool_name>image</tool_name>
<tool_id>call_JtYQMful5QKqw97XFsHzPweB</tool_id>
<parameters>
<prompts>["cat oil painting", "big car"]</prompts>
<aspect_ratio>'16:9'</aspect_ratio>
</parameters>
</invoke>
</function_calls>
XML
image1, image2 =
tools =
DiscourseAi::AiBot::Personas::Artist.new.find_tools(
xml,
bot_user: nil,
llm: nil,
context: nil,
)
expect(image1.parameters[:prompts]).to eq(["cat oil painting", "big car"])
expect(image1.parameters[:aspect_ratio]).to eq("16:9")
expect(image2.parameters[:aspect_ratio]).to eq("16:9")
expect(tools.length).to eq(2)
end
it "enforces enums" do
xml = <<~XML
<function_calls>
<invoke>
<tool_name>search</tool_name>
<tool_id>call_JtYQMful5QKqw97XFsHzPweB</tool_id>
<parameters>
<max_posts>"3.2"</max_posts>
<status>cow</status>
<foo>bar</foo>
</parameters>
</invoke>
<invoke>
<tool_name>search</tool_name>
<tool_id>call_JtYQMful5QKqw97XFsHzPweB</tool_id>
<parameters>
<max_posts>"3.2"</max_posts>
<status>open</status>
<foo>bar</foo>
</parameters>
</invoke>
</function_calls>
XML
search1, search2 =
tools =
DiscourseAi::AiBot::Personas::General.new.find_tools(
xml,
bot_user: nil,
llm: nil,
context: nil,
)
expect(search1.parameters.key?(:status)).to eq(false)
expect(search2.parameters[:status]).to eq("open")
end
it "can coerce integers" do
xml = <<~XML
<function_calls>
<invoke>
<tool_name>search</tool_name>
<tool_id>call_JtYQMful5QKqw97XFsHzPweB</tool_id>
<parameters>
<max_posts>"3.2"</max_posts>
<search_query>hello world</search_query>
<foo>bar</foo>
</parameters>
</invoke>
</function_calls>
XML
search, =
tools =
DiscourseAi::AiBot::Personas::General.new.find_tools(
xml,
bot_user: nil,
llm: nil,
context: nil,
)
expect(search.parameters[:max_posts]).to eq(3)
expect(search.parameters[:search_query]).to eq("hello world")
expect(search.parameters.key?(:foo)).to eq(false)
end
it "can correctly parse arrays in tools" do
SiteSetting.ai_openai_api_key = "123"