From b0a0cbe3ca589b0b1fc2efd33e1f045d73755b13 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 9 Jan 2024 00:28:03 +1100 Subject: [PATCH] FIX: improve bot behavior (#408) * FIX: improve bot behavior - Provide more information to Gemini context post function execution - Use system prompts for Claude (fixes Dall E) - Ensure Assistant is properly separated - Teach Claude to return arrays in JSON vs XML Also refactors tests so we do not copy tool preamble everywhere * System msg is claude-2 only. fix typo --------- Co-authored-by: Roman Rizzi --- lib/completions/dialects/claude.rb | 8 ++- lib/completions/dialects/dialect.rb | 16 +++-- lib/completions/dialects/gemini.rb | 19 +++--- spec/lib/completions/dialects/claude_spec.rb | 27 +++----- spec/lib/completions/dialects/gemini_spec.rb | 63 ++++++++++++++++--- .../dialects/llama2_classic_spec.rb | 15 +---- spec/lib/completions/dialects/mixtral_spec.rb | 15 +---- .../completions/dialects/orca_style_spec.rb | 15 +---- 8 files changed, 98 insertions(+), 80 deletions(-) diff --git a/lib/completions/dialects/claude.rb b/lib/completions/dialects/claude.rb index e760ab70..e47b1498 100644 --- a/lib/completions/dialects/claude.rb +++ b/lib/completions/dialects/claude.rb @@ -15,7 +15,8 @@ module DiscourseAi end def translate - claude_prompt = +"Human: #{prompt[:insts]}\n" + claude_prompt = uses_system_message? ? +"" : +"Human: " + claude_prompt << prompt[:insts] << "\n" claude_prompt << build_tools_prompt if prompt[:tools] @@ -27,6 +28,7 @@ module DiscourseAi claude_prompt << "#{prompt[:post_insts]}\n" if prompt[:post_insts] + claude_prompt << "\n\n" claude_prompt << "Assistant:" claude_prompt << " #{prompt[:final_insts]}:" if prompt[:final_insts] claude_prompt << "\n" @@ -70,6 +72,10 @@ module DiscourseAi private + def uses_system_message? + model_name == "claude-2" + end + def build_examples(examples_arr) examples_arr.reduce("") do |memo, example| memo += "\nH: #{example[0]}\nA: #{example[1]}\n\n" diff --git a/lib/completions/dialects/dialect.rb b/lib/completions/dialects/dialect.rb index 37768348..1ca8721b 100644 --- a/lib/completions/dialects/dialect.rb +++ b/lib/completions/dialects/dialect.rb @@ -142,9 +142,7 @@ module DiscourseAi self.class.tokenizer.size(context[:content].to_s) end - def build_tools_prompt - return "" if prompt[:tools].blank? - + def self.tool_preamble <<~TEXT In this environment you have access to a set of tools you can use to answer the user's question. You may call them like this. Only invoke one function at a time and wait for the results before invoking another function: @@ -158,8 +156,18 @@ module DiscourseAi - Here are the tools available: + if a parameter type is an array, return a JSON array of values. For example: + [1,"two",3.0] + Here are the tools available: + TEXT + end + + def build_tools_prompt + return "" if prompt[:tools].blank? + + <<~TEXT + #{self.class.tool_preamble} #{tools} TEXT diff --git a/lib/completions/dialects/gemini.rb b/lib/completions/dialects/gemini.rb index a127d4e7..6fd98287 100644 --- a/lib/completions/dialects/gemini.rb +++ b/lib/completions/dialects/gemini.rb @@ -128,19 +128,20 @@ module DiscourseAi private def flatten_context(context) - context.map do |a_context| - if a_context[:type] == "multi_turn" - # Some multi-turn, like the ones that generate images, doesn't chain a next - # response. We don't have an assistant call for those, so we use the tool_call instead. - # We cannot use tool since it confuses the model, making it stop calling tools in next responses, - # and replying with a JSON. + flattened = [] + context.each do |c| + if c[:type] == "multi_turn" + # gemini quirk + if c[:content].first[:type] == "tool" + flattend << { type: "assistant", content: "ok." } + end - a_context[:content].find { |c| c[:type] == "assistant" } || - a_context[:content].find { |c| c[:type] == "tool_call" } + flattened.concat(c[:content]) else - a_context + flattened << c end end + flattened end end end diff --git a/spec/lib/completions/dialects/claude_spec.rb b/spec/lib/completions/dialects/claude_spec.rb index 1107814b..453db8f3 100644 --- a/spec/lib/completions/dialects/claude_spec.rb +++ b/spec/lib/completions/dialects/claude_spec.rb @@ -48,9 +48,11 @@ RSpec.describe DiscourseAi::Completions::Dialects::Claude do describe "#translate" do it "translates a prompt written in our generic format to Claude's format" do anthropic_version = <<~TEXT - Human: #{prompt[:insts]} + #{prompt[:insts]} #{prompt[:input]} #{prompt[:post_insts]} + + Assistant: TEXT @@ -67,13 +69,15 @@ RSpec.describe DiscourseAi::Completions::Dialects::Claude do ], ] anthropic_version = <<~TEXT - Human: #{prompt[:insts]} + #{prompt[:insts]} H: #{prompt[:examples][0][0]} A: #{prompt[:examples][0][1]} #{prompt[:input]} #{prompt[:post_insts]} + + Assistant: TEXT @@ -86,25 +90,14 @@ RSpec.describe DiscourseAi::Completions::Dialects::Claude do prompt[:tools] = [tool] anthropic_version = <<~TEXT - Human: #{prompt[:insts]} - In this environment you have access to a set of tools you can use to answer the user's question. - You may call them like this. Only invoke one function at a time and wait for the results before invoking another function: - - - $TOOL_NAME - - <$PARAMETER_NAME>$PARAMETER_VALUE - ... - - - - - Here are the tools available: - + #{prompt[:insts]} + #{DiscourseAi::Completions::Dialects::Claude.tool_preamble} #{dialect.tools} #{prompt[:input]} #{prompt[:post_insts]} + + Assistant: TEXT diff --git a/spec/lib/completions/dialects/gemini_spec.rb b/spec/lib/completions/dialects/gemini_spec.rb index 528b329a..ab9c8642 100644 --- a/spec/lib/completions/dialects/gemini_spec.rb +++ b/spec/lib/completions/dialects/gemini_spec.rb @@ -150,14 +150,38 @@ RSpec.describe DiscourseAi::Completions::Dialects::Gemini do translated_context = dialect.conversation_context - expect(translated_context.size).to eq(1) - expect(translated_context.last[:role]).to eq("model") - expect(translated_context.last.dig(:parts, :functionCall)).to be_present + expected = [ + { + role: "function", + parts: { + functionResponse: { + name: "get_weather", + response: { + content: "I'm a tool result", + }, + }, + }, + }, + { + role: "model", + parts: { + functionCall: { + name: "get_weather", + args: { + location: "Sydney", + unit: "c", + }, + }, + }, + }, + ] + + expect(translated_context).to eq(expected) end end context "when the multi-turn is from a chainable tool" do - it "uses the assistand context" do + it "uses the assistant context" do prompt[:conversation_context] = [ { type: "multi_turn", @@ -181,9 +205,34 @@ RSpec.describe DiscourseAi::Completions::Dialects::Gemini do translated_context = dialect.conversation_context - expect(translated_context.size).to eq(1) - expect(translated_context.last[:role]).to eq("model") - expect(translated_context.last.dig(:parts, :text)).to be_present + expected = [ + { role: "model", parts: { text: "I'm a bot reply!" } }, + { + role: "function", + parts: { + functionResponse: { + name: "get_weather", + response: { + content: "I'm a tool result", + }, + }, + }, + }, + { + role: "model", + parts: { + functionCall: { + name: "get_weather", + args: { + location: "Sydney", + unit: "c", + }, + }, + }, + }, + ] + + expect(translated_context).to eq(expected) end end end diff --git a/spec/lib/completions/dialects/llama2_classic_spec.rb b/spec/lib/completions/dialects/llama2_classic_spec.rb index 81d088e6..4c60e2ee 100644 --- a/spec/lib/completions/dialects/llama2_classic_spec.rb +++ b/spec/lib/completions/dialects/llama2_classic_spec.rb @@ -94,20 +94,7 @@ RSpec.describe DiscourseAi::Completions::Dialects::Llama2Classic do [INST] <> #{prompt[:insts]} - In this environment you have access to a set of tools you can use to answer the user's question. - You may call them like this. Only invoke one function at a time and wait for the results before invoking another function: - - - $TOOL_NAME - - <$PARAMETER_NAME>$PARAMETER_VALUE - ... - - - - - Here are the tools available: - + #{DiscourseAi::Completions::Dialects::Llama2Classic.tool_preamble} #{dialect.tools} #{prompt[:post_insts]} diff --git a/spec/lib/completions/dialects/mixtral_spec.rb b/spec/lib/completions/dialects/mixtral_spec.rb index 4f1a5247..bbcb95d3 100644 --- a/spec/lib/completions/dialects/mixtral_spec.rb +++ b/spec/lib/completions/dialects/mixtral_spec.rb @@ -89,20 +89,7 @@ RSpec.describe DiscourseAi::Completions::Dialects::Mixtral do orca_style_version = <<~TEXT [INST] #{prompt[:insts]} - In this environment you have access to a set of tools you can use to answer the user's question. - You may call them like this. Only invoke one function at a time and wait for the results before invoking another function: - - - $TOOL_NAME - - <$PARAMETER_NAME>$PARAMETER_VALUE - ... - - - - - Here are the tools available: - + #{DiscourseAi::Completions::Dialects::Mixtral.tool_preamble} #{dialect.tools} #{prompt[:post_insts]} diff --git a/spec/lib/completions/dialects/orca_style_spec.rb b/spec/lib/completions/dialects/orca_style_spec.rb index d27dc9d3..32e32f15 100644 --- a/spec/lib/completions/dialects/orca_style_spec.rb +++ b/spec/lib/completions/dialects/orca_style_spec.rb @@ -93,20 +93,7 @@ RSpec.describe DiscourseAi::Completions::Dialects::OrcaStyle do orca_style_version = <<~TEXT ### System: #{prompt[:insts]} - In this environment you have access to a set of tools you can use to answer the user's question. - You may call them like this. Only invoke one function at a time and wait for the results before invoking another function: - - - $TOOL_NAME - - <$PARAMETER_NAME>$PARAMETER_VALUE - ... - - - - - Here are the tools available: - + #{DiscourseAi::Completions::Dialects::OrcaStyle.tool_preamble} #{dialect.tools} #{prompt[:post_insts]}