refactor: move main appart from config + storyteller
This commit is contained in:
parent
01efd5e597
commit
9de4a203d4
4 changed files with 225 additions and 199 deletions
|
@ -4,8 +4,7 @@ require "colorize"
|
||||||
|
|
||||||
module Builder
|
module Builder
|
||||||
class OpenAIInsert
|
class OpenAIInsert
|
||||||
alias Message = NamedTuple(role: String, content: String)
|
alias InsertBuffer = NamedTuple(before: String, after: String)
|
||||||
alias Chat = Array(Message)
|
|
||||||
|
|
||||||
getter verbose : Bool
|
getter verbose : Bool
|
||||||
def initialize(@verbose)
|
def initialize(@verbose)
|
||||||
|
@ -14,8 +13,8 @@ module Builder
|
||||||
|
|
||||||
# skip prelude_zone
|
# skip prelude_zone
|
||||||
# skip future_zone
|
# skip future_zone
|
||||||
def build(prompt : Prompt) : Chat
|
def build(prompt : Prompt) : InsertBuffer
|
||||||
chat = [] of Message
|
insert_data : InsertBuffer = { before: "", after: "" }
|
||||||
|
|
||||||
token_limit = 2_900
|
token_limit = 2_900
|
||||||
mandatory_token_count = (
|
mandatory_token_count = (
|
||||||
|
@ -25,7 +24,7 @@ module Builder
|
||||||
|
|
||||||
## Build mandatory system messages
|
## Build mandatory system messages
|
||||||
prompt.system_zone.each do |content|
|
prompt.system_zone.each do |content|
|
||||||
chat << { role: "system", content: content }
|
insert_data["before"] += content
|
||||||
end
|
end
|
||||||
|
|
||||||
## Build mandatory system messages
|
## Build mandatory system messages
|
||||||
|
|
|
@ -29,7 +29,7 @@ module Builder
|
||||||
end
|
end
|
||||||
|
|
||||||
if ! prompt.present_zone.content.empty?
|
if ! prompt.present_zone.content.empty?
|
||||||
str += "@@current".colorize(:yellow).to_s
|
# str += "@@current".colorize(:yellow).to_s
|
||||||
str += prompt.present_zone.content.join(SEPARATOR).colorize(:light_cyan).to_s
|
str += prompt.present_zone.content.join(SEPARATOR).colorize(:light_cyan).to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
195
src/main.cr
195
src/main.cr
|
@ -8,199 +8,10 @@ require "./parsers/prompt_string"
|
||||||
require "./builders/prompt_string"
|
require "./builders/prompt_string"
|
||||||
require "./builders/openai_chat"
|
require "./builders/openai_chat"
|
||||||
require "./builders/openai_insert"
|
require "./builders/openai_insert"
|
||||||
|
require "./storyteller"
|
||||||
|
|
||||||
class Storyteller
|
|
||||||
|
|
||||||
enum OpenAIMode
|
|
||||||
Chat
|
|
||||||
Complete
|
|
||||||
Insert
|
|
||||||
|
|
||||||
def self.from_s(str_mode)
|
|
||||||
return OpenAIMode.values.find do |openai_mode|
|
|
||||||
openai_mode.to_s.downcase == str_mode.downcase
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize()
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.start(argv)
|
|
||||||
input_file = STDIN
|
|
||||||
input_file_path = ""
|
|
||||||
output_file = STDOUT
|
|
||||||
output_file_path = ""
|
|
||||||
past_characters_limit = 1000
|
|
||||||
use_color = true
|
|
||||||
make_request = true
|
|
||||||
gpt_mode = OpenAIMode::Chat
|
|
||||||
verbose = false
|
|
||||||
gpt_temperature = 0.82
|
|
||||||
gpt_presence_penalty = 1
|
|
||||||
gpt_frequency_penalty = 1
|
|
||||||
gpt_max_tokens = 256
|
|
||||||
|
|
||||||
parser = OptionParser.new do |parser|
|
|
||||||
parser.banner = "Usage: storyteller [options]"
|
|
||||||
|
|
||||||
parser.separator("Options:")
|
|
||||||
|
|
||||||
|
|
||||||
parser.on("-i FILE", "--input=FILE", "Path to input file") do |file|
|
|
||||||
input_file_path = file
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.on("-h", "--help", "Show this help") do
|
|
||||||
puts parser
|
|
||||||
exit
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
parser.on("-n", "--no-color", "Disable color output") do
|
|
||||||
use_color = false
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.on("-o FILE", "--output=FILE", "Path to output file") do |file|
|
|
||||||
use_color = false
|
|
||||||
output_file_path = file
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.on("-v", "--verbose", "Be verbose (cumulative)") do
|
|
||||||
verbose = true
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.on("--dry-run", "Don't call the API") do
|
|
||||||
make_request = false
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
parser.separator("GPT options")
|
|
||||||
|
|
||||||
parser.on("--gpt-mode MODE", "GPT mode (chat,insert,complete) (default: chat)") do |chosen_mode|
|
|
||||||
result_mode = OpenAIMode.from_s(chosen_mode.downcase)
|
|
||||||
if result_mode.nil?
|
|
||||||
STDERR.puts "ERROR: unknown mode #{chosen_mode}"
|
|
||||||
exit 1
|
|
||||||
end
|
|
||||||
gpt_mode = result_mode unless result_mode.nil?
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.on("--gpt-temperature TEMPERATURE", "GPT Temperature") do |temperature|
|
|
||||||
gpt_temperature = temperature
|
|
||||||
end
|
|
||||||
parser.on("--gpt-presence-penalty PENALTY", "GPT Presence Penalty") do |presence_penalty|
|
|
||||||
gpt_presence_penalty = presence_penalty
|
|
||||||
end
|
|
||||||
parser.on("--gpt-frequency-penalty PENALTY", "GPT Frequency Penalty") do |frequency_penalty|
|
|
||||||
gpt_frequency_penalty = frequency_penalty
|
|
||||||
end
|
|
||||||
parser.on("--gpt-max-tokens TOKENS", "GPT Max Tokens") do |max_tokens|
|
|
||||||
gpt_max_tokens = max_tokens
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
parser.parse(ARGV)
|
|
||||||
|
|
||||||
# Create Storyteller instance
|
|
||||||
storyteller = Storyteller.new()
|
storyteller = Storyteller.new()
|
||||||
|
storyteller.prepare()
|
||||||
|
storyteller.run()
|
||||||
|
|
||||||
# Read file and initialize zones
|
|
||||||
if !input_file_path.empty?
|
|
||||||
# puts "d: Using input file #{input_file_path}"
|
|
||||||
input_file = File.open(input_file_path)
|
|
||||||
end
|
|
||||||
prompt = storyteller.read_file(input_file)
|
|
||||||
input_file.close
|
|
||||||
|
|
||||||
# Build GPT-3 request
|
|
||||||
prompt = storyteller.complete(prompt, make_request, verbose)
|
|
||||||
pp prompt if verbose
|
|
||||||
exit 0 if !make_request
|
|
||||||
|
|
||||||
if !output_file_path.empty?
|
|
||||||
# puts "d: Using output file #{input_file_path}"
|
|
||||||
output_file = File.open(output_file_path, "w")
|
|
||||||
end
|
|
||||||
storyteller.write_file(output_file, prompt, use_color)
|
|
||||||
output_file.close
|
|
||||||
rescue ex : OptionParser::InvalidOption
|
|
||||||
STDERR.puts parser
|
|
||||||
STDERR.puts "\nERROR: #{ex.message}"
|
|
||||||
exit 1
|
|
||||||
end
|
|
||||||
|
|
||||||
def complete(prompt : Prompt, make_request : Bool, verbose : Bool)
|
|
||||||
builder = Builder::OpenAIChat.new(verbose: verbose)
|
|
||||||
messages = builder.build(prompt)
|
|
||||||
|
|
||||||
STDERR.puts messages if verbose
|
|
||||||
|
|
||||||
return prompt if !make_request
|
|
||||||
|
|
||||||
channel_ready = Channel(Bool).new
|
|
||||||
channel_tick = Channel(Bool).new
|
|
||||||
# sp = Spin.new(0.5, Spinner::Charset[:progress])
|
|
||||||
|
|
||||||
spawn do
|
|
||||||
tick = 0
|
|
||||||
loop do
|
|
||||||
tick += 1
|
|
||||||
print "."
|
|
||||||
if tick > (3 * 60)
|
|
||||||
print "(timeout)"
|
|
||||||
exit 1
|
|
||||||
end
|
|
||||||
channel_tick.send(true)
|
|
||||||
sleep 1.seconds
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
spawn do
|
|
||||||
openai = OpenAI::Client.new(access_token: ENV.fetch("OPENAI_API_KEY"))
|
|
||||||
result = openai.chat(
|
|
||||||
"gpt-3.5-turbo",
|
|
||||||
messages,
|
|
||||||
{
|
|
||||||
"temperature" => 0.82,
|
|
||||||
"presence_penalty" => 1,
|
|
||||||
"frequency_penalty" => 1,
|
|
||||||
"max_tokens" => 256
|
|
||||||
}
|
|
||||||
)
|
|
||||||
prompt.past_zone.content << "\n" + result.choices.first["message"]["content"] + "\n"
|
|
||||||
channel_ready.send(true)
|
|
||||||
end
|
|
||||||
|
|
||||||
# sp.start
|
|
||||||
channel_ready.receive
|
|
||||||
channel_ready.close
|
|
||||||
# sp.stop
|
|
||||||
|
|
||||||
prompt
|
|
||||||
end
|
|
||||||
|
|
||||||
def read_file(input_file : IO::FileDescriptor)
|
|
||||||
content = input_file.gets_to_end
|
|
||||||
|
|
||||||
# puts "d: building parser"
|
|
||||||
parser = Parser::PromptString.new
|
|
||||||
# puts "d: parsing"
|
|
||||||
prompt = parser.parse(content)
|
|
||||||
# pp prompt
|
|
||||||
end
|
|
||||||
|
|
||||||
def write_file(output_file : IO::FileDescriptor, prompt : Prompt, use_color : Bool)
|
|
||||||
# STDERR.puts "d: building builder"
|
|
||||||
builder = Builder::PromptString.new(use_color)
|
|
||||||
# STDERR.puts "d: building"
|
|
||||||
text = builder.build(prompt)
|
|
||||||
output_file.write_string(text.to_slice)
|
|
||||||
end
|
|
||||||
|
|
||||||
def display_completion(completion : String)
|
|
||||||
# Code pour afficher la complétion
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Storyteller.start(ARGV)
|
|
||||||
|
|
216
src/storyteller.cr
Normal file
216
src/storyteller.cr
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
enum OpenAIMode
|
||||||
|
Chat
|
||||||
|
Complete
|
||||||
|
Insert
|
||||||
|
|
||||||
|
def self.from_s(str_mode)
|
||||||
|
return OpenAIMode.values.find do |openai_mode|
|
||||||
|
openai_mode.to_s.downcase == str_mode.downcase
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class GptConfig
|
||||||
|
# defaults
|
||||||
|
property gpt_temperature = 0.82
|
||||||
|
property gpt_presence_penalty = 1
|
||||||
|
property gpt_frequency_penalty = 1
|
||||||
|
property gpt_max_tokens = 256
|
||||||
|
property gpt_mode = OpenAIMode::Chat
|
||||||
|
end
|
||||||
|
|
||||||
|
class AppConfig
|
||||||
|
property input_file = STDIN
|
||||||
|
property input_file_path = ""
|
||||||
|
property output_file = STDOUT
|
||||||
|
property output_file_path = ""
|
||||||
|
property past_characters_limit = 1000
|
||||||
|
property use_color = true
|
||||||
|
property make_request = true
|
||||||
|
property verbose = false
|
||||||
|
property gpt_config = GptConfig.new
|
||||||
|
end
|
||||||
|
|
||||||
|
class Storyteller
|
||||||
|
property config = AppConfig.new
|
||||||
|
|
||||||
|
def initialize()
|
||||||
|
end
|
||||||
|
|
||||||
|
def prepare()
|
||||||
|
self._parse_config()
|
||||||
|
self._parse_command_line(ARGV)
|
||||||
|
end
|
||||||
|
|
||||||
|
private def _parse_config()
|
||||||
|
end
|
||||||
|
|
||||||
|
private def _parse_command_line(args)
|
||||||
|
parser = OptionParser.new do |parser|
|
||||||
|
parser.banner = "Usage: storyteller [options]"
|
||||||
|
|
||||||
|
parser.separator("Options:")
|
||||||
|
|
||||||
|
parser.on("-i FILE", "--input=FILE", "Path to input file") do |file|
|
||||||
|
@config.input_file_path = file
|
||||||
|
end
|
||||||
|
|
||||||
|
parser.on("-h", "--help", "Show this help") do
|
||||||
|
puts parser
|
||||||
|
exit
|
||||||
|
end
|
||||||
|
|
||||||
|
parser.on("-n", "--no-color", "Disable color output") do
|
||||||
|
@config.use_color = false
|
||||||
|
end
|
||||||
|
|
||||||
|
parser.on("-o FILE", "--output=FILE", "Path to output file") do |file|
|
||||||
|
@config.use_color = false
|
||||||
|
@config.output_file_path = file
|
||||||
|
end
|
||||||
|
|
||||||
|
parser.on("-v", "--verbose", "Be verbose (cumulative)") do
|
||||||
|
@config.verbose = true
|
||||||
|
end
|
||||||
|
|
||||||
|
parser.on("--dry-run", "Don't call the API") do
|
||||||
|
@config.make_request = false
|
||||||
|
end
|
||||||
|
|
||||||
|
parser.separator("GPT options")
|
||||||
|
|
||||||
|
parser.on("--gpt-mode MODE", "GPT mode (chat,insert,complete) (default: chat)") do |chosen_mode|
|
||||||
|
result_mode = OpenAIMode.from_s(chosen_mode.downcase)
|
||||||
|
if result_mode.nil?
|
||||||
|
STDERR.puts "ERROR: unknown mode #{chosen_mode}"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
gpt_mode = result_mode unless result_mode.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
parser.on("--gpt-temperature TEMPERATURE", "GPT Temperature") do |temperature|
|
||||||
|
gpt_temperature = temperature
|
||||||
|
end
|
||||||
|
parser.on("--gpt-presence-penalty PENALTY", "GPT Presence Penalty") do |presence_penalty|
|
||||||
|
gpt_presence_penalty = presence_penalty
|
||||||
|
end
|
||||||
|
parser.on("--gpt-frequency-penalty PENALTY", "GPT Frequency Penalty") do |frequency_penalty|
|
||||||
|
gpt_frequency_penalty = frequency_penalty
|
||||||
|
end
|
||||||
|
parser.on("--gpt-max-tokens TOKENS", "GPT Max Tokens") do |max_tokens|
|
||||||
|
gpt_max_tokens = max_tokens
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
parser.parse(args)
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_file()
|
||||||
|
# Read file and initialize zones
|
||||||
|
if !@config.input_file_path.empty?
|
||||||
|
# puts "d: Using input file #{input_file_path}"
|
||||||
|
@config.input_file = File.open(@config.input_file_path)
|
||||||
|
end
|
||||||
|
prompt = self.read_file(@config.input_file)
|
||||||
|
@config.input_file.close
|
||||||
|
prompt
|
||||||
|
end
|
||||||
|
|
||||||
|
def run()
|
||||||
|
original_prompt = self.load_file()
|
||||||
|
|
||||||
|
# Build GPT-3 request
|
||||||
|
prompt = self.complete(original_prompt, @config.make_request, @config.verbose)
|
||||||
|
pp prompt if @config.verbose
|
||||||
|
exit 0 if !@config.make_request
|
||||||
|
|
||||||
|
if !@config.output_file_path.empty?
|
||||||
|
# puts "d: Using output file #{input_file_path}"
|
||||||
|
@config.output_file = File.open(@config.output_file_path, "w")
|
||||||
|
end
|
||||||
|
self.write_file(@config.output_file, prompt, @config.use_color)
|
||||||
|
@config.output_file.close
|
||||||
|
|
||||||
|
# rescue ex : OptionParser::InvalidOption
|
||||||
|
# STDERR.puts @parser
|
||||||
|
# STDERR.puts "\nERROR: #{ex.message}"
|
||||||
|
# exit 1
|
||||||
|
|
||||||
|
# rescue ex: OpenAI::Client::ClientError
|
||||||
|
# STDERR.puts "ERROR: #{ex.message}"
|
||||||
|
# exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def complete(prompt : Prompt, make_request : Bool, verbose : Bool)
|
||||||
|
builder = Builder::OpenAIChat.new(verbose: verbose)
|
||||||
|
messages = builder.build(prompt)
|
||||||
|
|
||||||
|
STDERR.puts messages if verbose
|
||||||
|
|
||||||
|
return prompt if !make_request
|
||||||
|
|
||||||
|
channel_ready = Channel(Bool).new
|
||||||
|
channel_tick = Channel(Bool).new
|
||||||
|
# sp = Spin.new(0.5, Spinner::Charset[:progress])
|
||||||
|
|
||||||
|
spawn do
|
||||||
|
tick = 0
|
||||||
|
loop do
|
||||||
|
tick += 1
|
||||||
|
print "."
|
||||||
|
if tick > (3 * 60)
|
||||||
|
print "(timeout)"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
# channel_tick.send(true)
|
||||||
|
sleep 1.seconds
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
spawn do
|
||||||
|
openai = OpenAI::Client.new(access_token: ENV.fetch("OPENAI_API_KEY"))
|
||||||
|
result = openai.chat(
|
||||||
|
"gpt-3.5-turbo",
|
||||||
|
messages,
|
||||||
|
{
|
||||||
|
"temperature" => 0.82,
|
||||||
|
"presence_penalty" => 1,
|
||||||
|
"frequency_penalty" => 1,
|
||||||
|
"max_tokens" => 256
|
||||||
|
}
|
||||||
|
)
|
||||||
|
prompt.present_zone.content << result.choices.first["message"]["content"]
|
||||||
|
channel_ready.send(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
# sp.start
|
||||||
|
channel_ready.receive
|
||||||
|
channel_ready.close
|
||||||
|
# sp.stop
|
||||||
|
|
||||||
|
prompt
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_file(input_file : IO::FileDescriptor)
|
||||||
|
content = input_file.gets_to_end
|
||||||
|
|
||||||
|
# puts "d: building parser"
|
||||||
|
parser = Parser::PromptString.new
|
||||||
|
# puts "d: parsing"
|
||||||
|
prompt = parser.parse(content)
|
||||||
|
# pp prompt
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_file(output_file : IO::FileDescriptor, prompt : Prompt, use_color : Bool)
|
||||||
|
# STDERR.puts "d: building builder"
|
||||||
|
builder = Builder::PromptString.new(use_color)
|
||||||
|
# STDERR.puts "d: building"
|
||||||
|
text = builder.build(prompt)
|
||||||
|
output_file.write_string(text.to_slice)
|
||||||
|
end
|
||||||
|
|
||||||
|
def display_completion(completion : String)
|
||||||
|
# Code pour afficher la complétion
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
Loading…
Reference in a new issue