mirror of
https://github.com/discourse/discourse-ai.git
synced 2025-02-06 11:38:13 +00:00
Native tools do not work well on Opus. Chain of Thought prompting means it consumes enormous amounts of tokens and has poor latency. This commit introduce and XML stripper to remove various chain of thought XML islands from anthropic prompts when tools are involved. This mean Opus native tools is now functions (albeit slowly) From local testing XML just works better now. Also fixes enum support in Anthropic native tools
130 lines
3.9 KiB
Ruby
130 lines
3.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module DiscourseAi
|
|
module Completions
|
|
module Dialects
|
|
class XmlTools
|
|
def initialize(tools)
|
|
@raw_tools = tools
|
|
end
|
|
|
|
def translated_tools
|
|
raw_tools.reduce(+"") do |tools, function|
|
|
parameters = +""
|
|
if function[:parameters].present?
|
|
function[:parameters].each do |parameter|
|
|
parameters << <<~PARAMETER
|
|
<parameter>
|
|
<name>#{parameter[:name]}</name>
|
|
<type>#{parameter[:type]}</type>
|
|
<description>#{parameter[:description]}</description>
|
|
<required>#{parameter[:required]}</required>
|
|
PARAMETER
|
|
if parameter[:enum]
|
|
parameters << "<options>#{parameter[:enum].join(",")}</options>\n"
|
|
end
|
|
parameters << "</parameter>\n"
|
|
end
|
|
end
|
|
|
|
tools << <<~TOOLS
|
|
<tool_description>
|
|
<tool_name>#{function[:name]}</tool_name>
|
|
<description>#{function[:description]}</description>
|
|
<parameters>
|
|
#{parameters}</parameters>
|
|
</tool_description>
|
|
TOOLS
|
|
end
|
|
end
|
|
|
|
def instructions
|
|
return "" if raw_tools.blank?
|
|
|
|
@instructions ||=
|
|
begin
|
|
has_arrays =
|
|
raw_tools.any? { |tool| tool[:parameters]&.any? { |p| p[:type] == "array" } }
|
|
|
|
(<<~TEXT).strip
|
|
#{tool_preamble(include_array_tip: has_arrays)}
|
|
<tools>
|
|
#{translated_tools}</tools>
|
|
TEXT
|
|
end
|
|
end
|
|
|
|
def from_raw_tool(raw_message)
|
|
(<<~TEXT).strip
|
|
<function_results>
|
|
<result>
|
|
<tool_name>#{raw_message[:name] || raw_message[:id]}</tool_name>
|
|
<json>
|
|
#{raw_message[:content]}
|
|
</json>
|
|
</result>
|
|
</function_results>
|
|
TEXT
|
|
end
|
|
|
|
def from_raw_tool_call(raw_message)
|
|
parsed = JSON.parse(raw_message[:content], symbolize_names: true)
|
|
parameters = +""
|
|
|
|
if parsed[:arguments]
|
|
parameters << "<parameters>\n"
|
|
parsed[:arguments].each { |k, v| parameters << "<#{k}>#{v}</#{k}>\n" }
|
|
parameters << "</parameters>\n"
|
|
end
|
|
|
|
(<<~TEXT).strip
|
|
<function_calls>
|
|
<invoke>
|
|
<tool_name>#{raw_message[:name] || parsed[:name]}</tool_name>
|
|
#{parameters}</invoke>
|
|
</function_calls>
|
|
TEXT
|
|
end
|
|
|
|
private
|
|
|
|
attr_reader :raw_tools
|
|
|
|
def tool_preamble(include_array_tip: true)
|
|
array_tip =
|
|
if include_array_tip
|
|
<<~TEXT
|
|
If a parameter type is an array, return an array of values. For example:
|
|
<$PARAMETER_NAME>["one","two","three"]</$PARAMETER_NAME>
|
|
TEXT
|
|
else
|
|
""
|
|
end
|
|
|
|
<<~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.
|
|
|
|
<function_calls>
|
|
<invoke>
|
|
<tool_name>$TOOL_NAME</tool_name>
|
|
<parameters>
|
|
<$PARAMETER_NAME>$PARAMETER_VALUE</$PARAMETER_NAME>
|
|
...
|
|
</parameters>
|
|
</invoke>
|
|
</function_calls>
|
|
#{array_tip}
|
|
If you wish to call multiple function in one reply, wrap multiple <invoke>
|
|
block in a single <function_calls> block.
|
|
|
|
Always prefer to lead with tool calls, if you need to execute any.
|
|
Avoid all niceties prior to tool calls, Eg: "Let me look this up for you.." etc.
|
|
Here are the complete list of tools available:
|
|
TEXT
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|