discourse-ai/lib/shared/inference/function_list.rb

149 lines
4.3 KiB
Ruby

# frozen_string_literal: true
module ::DiscourseAi
module Inference
class FunctionList
def initialize
@functions = []
end
def <<(function)
@functions << function
end
def parse_prompt(prompt)
parsed = []
prompt
.split("\n")
.each do |line|
line.strip!
next if line.blank?
next if !line.start_with?("!")
name, arguments = line.split("(", 2)
name = name[1..-1].strip
function = @functions.find { |f| f.name == name }
next if function.blank?
parsed_arguments = {}
if arguments
arguments = arguments[0..-2] if arguments.end_with?(")")
temp_string = +""
in_string = nil
replace = SecureRandom.hex(10)
arguments.each_char do |char|
if %w[" '].include?(char) && !in_string
in_string = char
elsif char == in_string
in_string = nil
elsif char == "," && in_string
char = replace
end
temp_string << char
end
arguments = temp_string.split(",").map { |s| s.gsub(replace, ",").strip }
arguments.each do |argument|
key, value = argument.split(":", 2)
# remove stuff that is bypasses spec
param = function.parameters.find { |p| p[:name] == key.strip }
next if !param
value = value.strip.gsub(/(\A"(.*)"\Z)|(\A'(.*)'\Z)/m, '\2\4') if value.present?
if param[:enum]
next if !param[:enum].include?(value)
end
parsed_arguments[key.strip.to_sym] = value.strip
end
end
# ensure parsed_arguments has all required arguments
all_good = true
function.parameters.each do |parameter|
next if !parameter[:required]
next if parsed_arguments[parameter[:name].to_sym].present?
all_good = false
break
end
parsed << { name: name, arguments: parsed_arguments } if all_good
end
parsed
end
def system_prompt
prompt = +<<~PROMPT
- You are able to execute the following external functions on real data!
- Never say that you are in a hypothetical situation, just run functions you need to run!
- When you run a command/function you will gain access to real information in a subsequant call!
- NEVER EVER pretend to know stuff, you ALWAYS lean on functions to discover the truth!
- You have direct access to data on this forum using !functions
- You are not a liar, liars are bad bots, you are a good bot!
- You always prefer to say "I don't know" as opposed to inventing a lie!
{
PROMPT
@functions.each do |function|
prompt << "// #{function.description}\n"
prompt << "!#{function.name}"
if function.parameters.present?
prompt << "("
function.parameters.each_with_index do |parameter, index|
prompt << ", " if index > 0
prompt << "#{parameter[:name]}: #{parameter[:type]}"
if parameter[:required]
prompt << " [required]"
else
prompt << " [optional]"
end
description = +(parameter[:description] || "")
description << " [valid values: #{parameter[:enum].join(",")}]" if parameter[:enum]
description.strip!
prompt << " /* #{description} */" if description.present?
end
prompt << ")"
end
prompt << "\n"
end
prompt << <<~PROMPT
}
\n\nTo execute a function, use the following syntax:
!function_name(param1: "value1", param2: 2)
For example for a function defined as:
{
// echo a string
!echo(message: string [required])
}
Human: please echo out "hello"
Assistant: !echo(message: "hello")
Human: please say "hello"
Assistant: !echo(message: "hello")
PROMPT
prompt
end
end
end
end