149 lines
4.3 KiB
Ruby
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
|