Compare commits
No commits in common. "4e3e6aa8f8d651cd51e1182b66936cab0c5ef330" and "01efd5e597bf403bcb2e43427feb69881f4e1f07" have entirely different histories.
4e3e6aa8f8
...
01efd5e597
21 changed files with 218 additions and 501 deletions
|
@ -29,6 +29,7 @@ Eve se balade dans les jardins d'Eden. Le soleil est haut dans le ciel.
|
||||||
@@before
|
@@before
|
||||||
Eve a chaud. Elle se refraichirait bien dans la riviere.
|
Eve a chaud. Elle se refraichirait bien dans la riviere.
|
||||||
|
|
||||||
Elle descend dans @@after
|
Elle descend dans
|
||||||
|
@@after
|
||||||
|
|
||||||
This text will not be used in the chat
|
This text will not be used in the chat
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
|
|
||||||
@@systeme
|
|
||||||
## Contexte
|
|
||||||
|
|
||||||
Un cours d'excellent niveau et adapté pour le MIT, l'université de Standford, l'Ecole Polytechinque, l'ENS Ulm.
|
|
||||||
|
|
||||||
Le professeur est un expert SSL/TLS avec 20 ans d'expérience.
|
|
||||||
|
|
||||||
## Objectifs
|
|
||||||
|
|
||||||
* Comprendre la theorie qui permet le fonctionnement de SSL/TLS
|
|
||||||
* Savoir utiliser openssl et configurer SSL/TLS dans les logiciels les plus répandus (apache, nginx, etc.)
|
|
||||||
* Connaitre les différentes bibliotheques SSL/TLS disponibles et savoir les utiliser dans le code.
|
|
||||||
|
|
||||||
## Public
|
|
||||||
|
|
||||||
* Des développeurs applicatif.
|
|
||||||
|
|
||||||
@@before
|
|
||||||
## Programme de formation
|
|
||||||
|
|
||||||
1. Introduction
|
|
||||||
|
|
||||||
1.1 Qu'est-ce que SSL/TLS ?
|
|
||||||
- Définition de SSL/TLS
|
|
||||||
- Fonctionnement général de SSL/TLS
|
|
||||||
|
|
||||||
1.2 Pourquoi utiliser SSL/TLS ?
|
|
||||||
- Les avantages de l'utilisation d'un protocole sécurisé comme SSL/TLS
|
|
||||||
- Les risques associés à l'utilisation d'une connexion non-sécurisée
|
|
||||||
|
|
||||||
1.3 Histoire de SSL/TLS
|
|
||||||
- Origine et évolution du protocole TLS/SSL au fil du temps.
|
|
||||||
|
|
||||||
2. Cryptographie asymétrique et symétrique
|
|
||||||
2.1 Principes de base de la cryptographie asymétrique et symétrique
|
|
||||||
2.2 Fonctionnement des algorithmes de chiffrement symétriques (AES, DES, etc.)
|
|
||||||
2.3 Fonctionnement des algorithmes de chiffrement asymétriques (RSA, DH, etc.)
|
|
||||||
|
|
||||||
3. Les protocoles SSL/TLS
|
|
||||||
3.1 TLS 1.x vs TLS 2.x : différences fondamentales
|
|
||||||
3.2 Protocole Handshake : établissement d'une connexion sécurisée
|
|
||||||
3.3 Protocole Record : échange de données entre les parties en toute sécurité
|
|
||||||
|
|
||||||
4.Configuration d'un serveur web sécurisé avec Apache ou Nginx
|
|
||||||
4.A Configuration d'Apache avec mod_ssl
|
|
||||||
4.B Configurationde Nginx avec ssl_module
|
|
||||||
|
|
||||||
5.Utilisation du logiciel OpenSSL pour le test et le débuggage.
|
|
||||||
5.A Utilisation basique: creation CA/Server Certificat/Clefs
|
|
||||||
5.B Utilisation avancée : révocation de certificat, création de certificat auto-signé, utilisation d'extensions personnalisées.
|
|
||||||
|
|
||||||
6. Les bibliothèques SSL/TLS
|
|
||||||
6.A Présentation des différentes bibliothèques disponibles (OpenSSL, GnuTLS, NSS, etc.)
|
|
||||||
6.B Utilisation de la bibliothèque OpenSSL dans le code C/Python
|
|
||||||
|
|
||||||
7. Sécurité et bonnes pratiques en matière de SSL/TLS
|
|
||||||
7.A Comprendre les principales vulnérabilités associées à SSL/TLS (POODLE, Heartbleed, etc.)
|
|
||||||
7.B Bonnes pratiques pour renforcer la sécurité du protocole.
|
|
||||||
|
|
||||||
8. Conclusion
|
|
||||||
8.A Récapitulation des principaux points abordés dans ce cours.
|
|
||||||
8.B Perspectives d'avenir pour SSL/TLS et la sécurité en ligne.
|
|
||||||
|
|
||||||
### Request
|
|
||||||
|
|
||||||
Merci.
|
|
||||||
|
|
||||||
Suit la structure du PROGRAMME DE FORMATION et concentre toi sur le chapitre « 1. Introduction ».
|
|
||||||
Focalise toi plus spécifiquement sur les sections suivantes de ce chapitre:
|
|
||||||
«
|
|
||||||
1.1 Qu'est-ce que SSL/TLS ?
|
|
||||||
1.2 Pourquoi utiliser SSL/TLS ?
|
|
||||||
1.3 Histoire de SSL/TLS
|
|
||||||
»
|
|
||||||
|
|
||||||
Rédige la table des matière détaillée de ces différentes sections.
|
|
||||||
Ajoute également à la fin les différents travaux pratiques possibles pour chaque section.
|
|
||||||
|
|
||||||
### Answer
|
|
||||||
Voici la table des matières détaillée pour les sections 1.1, 1.2 et 1.3:
|
|
||||||
|
|
||||||
#### 1. Introduction
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### Travaux pratiques possibles :
|
|
||||||
- Installation et configuration d'un serveur Apache ou Nginx avec le module mod_ssl pour la prise en charge de HTTPS sur un site web simple.
|
|
||||||
- Générer une paire clé / certificat auto-signé avec OpenSSL ;
|
|
||||||
- Mettre en place Certbot (Let’s encrypt) pour obtenir gratuitement un certificat valide sur votre domaine et intégrer ce dernier dans votre configuration Apache/Nginx@@after
|
|
||||||
|
|
6
src/builders/generic.cr
Normal file
6
src/builders/generic.cr
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
module Builder
|
||||||
|
abstract class PromptGeneric
|
||||||
|
abstract def build(prompt : Prompt)
|
||||||
|
end
|
||||||
|
end
|
|
@ -14,12 +14,10 @@ module Builder
|
||||||
|
|
||||||
# skip prelude_zone
|
# skip prelude_zone
|
||||||
# skip future_zone
|
# skip future_zone
|
||||||
def build(prompt : Prompt, context_token_limit : UInt32) : Chat
|
def build(prompt : Prompt) : Chat
|
||||||
chat = [] of Message
|
chat = [] of Message
|
||||||
|
|
||||||
# token_limit = 10_000
|
token_limit = 2_700
|
||||||
token_limit = (0.65 * context_token_limit).to_i
|
|
||||||
STDERR.puts "messages_token_limit = #{token_limit}"
|
|
||||||
mandatory_token_count = (
|
mandatory_token_count = (
|
||||||
prompt.system_zone.token_count +
|
prompt.system_zone.token_count +
|
||||||
prompt.present_zone.token_count
|
prompt.present_zone.token_count
|
|
@ -4,7 +4,8 @@ require "colorize"
|
||||||
|
|
||||||
module Builder
|
module Builder
|
||||||
class OpenAIInsert
|
class OpenAIInsert
|
||||||
alias InsertBuffer = NamedTuple(before: String, after: String)
|
alias Message = NamedTuple(role: String, content: String)
|
||||||
|
alias Chat = Array(Message)
|
||||||
|
|
||||||
getter verbose : Bool
|
getter verbose : Bool
|
||||||
def initialize(@verbose)
|
def initialize(@verbose)
|
||||||
|
@ -13,8 +14,8 @@ module Builder
|
||||||
|
|
||||||
# skip prelude_zone
|
# skip prelude_zone
|
||||||
# skip future_zone
|
# skip future_zone
|
||||||
def build(prompt : Prompt) : InsertBuffer
|
def build(prompt : Prompt) : Chat
|
||||||
insert_data : InsertBuffer = { before: "", after: "" }
|
chat = [] of Message
|
||||||
|
|
||||||
token_limit = 2_900
|
token_limit = 2_900
|
||||||
mandatory_token_count = (
|
mandatory_token_count = (
|
||||||
|
@ -24,7 +25,7 @@ module Builder
|
||||||
|
|
||||||
## Build mandatory system messages
|
## Build mandatory system messages
|
||||||
prompt.system_zone.each do |content|
|
prompt.system_zone.each do |content|
|
||||||
insert_data["before"] += content
|
chat << { role: "system", content: content }
|
||||||
end
|
end
|
||||||
|
|
||||||
## Build mandatory system messages
|
## Build mandatory system messages
|
|
@ -1,10 +1,10 @@
|
||||||
|
|
||||||
require "colorize"
|
require "colorize"
|
||||||
|
|
||||||
require "./generic_builder"
|
require "./generic"
|
||||||
|
|
||||||
module ContextBuilder
|
module Builder
|
||||||
class PromptString < GenericContextBuilder
|
class PromptString < PromptGeneric
|
||||||
|
|
||||||
SEPARATOR = "\n\n"
|
SEPARATOR = "\n\n"
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ module ContextBuilder
|
||||||
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
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
|
|
||||||
require "./openai_config"
|
|
||||||
|
|
||||||
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 = AIUtils::GptConfig.new
|
|
||||||
end
|
|
|
@ -1,6 +0,0 @@
|
||||||
|
|
||||||
module ContextBuilder
|
|
||||||
abstract class GenericContextBuilder
|
|
||||||
abstract def build(prompt : Prompt)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,45 +0,0 @@
|
||||||
|
|
||||||
require "colorize"
|
|
||||||
|
|
||||||
require "../config"
|
|
||||||
require "./generic_builder"
|
|
||||||
|
|
||||||
module Builder
|
|
||||||
class PromptDir < PromptGeneric
|
|
||||||
|
|
||||||
SEPARATOR = "\n\n"
|
|
||||||
|
|
||||||
getter use_color : Bool
|
|
||||||
def initialize(@use_color)
|
|
||||||
Colorize.enabled = @use_color
|
|
||||||
end
|
|
||||||
|
|
||||||
def build(prompt : Prompt)
|
|
||||||
str = ""
|
|
||||||
|
|
||||||
str += prompt.prelude_zone.content.join(SEPARATOR)
|
|
||||||
|
|
||||||
if ! prompt.system_zone.content.empty?
|
|
||||||
str += "@@system".colorize(:yellow).to_s
|
|
||||||
str += prompt.system_zone.content.join(SEPARATOR)
|
|
||||||
end
|
|
||||||
|
|
||||||
if ! prompt.past_zone.content.empty?
|
|
||||||
str += "@@before".colorize(:yellow).to_s
|
|
||||||
str += prompt.past_zone.content.join(SEPARATOR)
|
|
||||||
end
|
|
||||||
|
|
||||||
if ! prompt.present_zone.content.empty?
|
|
||||||
# str += "@@current".colorize(:yellow).to_s
|
|
||||||
str += prompt.present_zone.content.join(SEPARATOR).colorize(:light_cyan).to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
if ! prompt.future_zone.content.empty?
|
|
||||||
str += "@@after".colorize(:yellow).to_s
|
|
||||||
str += prompt.future_zone.content.join("\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
str
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
207
src/main.cr
207
src/main.cr
|
@ -3,15 +3,204 @@ require "pretty_print"
|
||||||
require "openai"
|
require "openai"
|
||||||
require "spinner"
|
require "spinner"
|
||||||
|
|
||||||
require "./prompts/zone"
|
require "./zone"
|
||||||
require "./parsers/prompt_string"
|
require "./parsers/prompt_string"
|
||||||
require "./context_builders/prompt_string"
|
require "./builders/prompt_string"
|
||||||
# require "./context_builders/prompt_dir"
|
require "./builders/openai_chat"
|
||||||
require "./context_builders/openai_chat"
|
require "./builders/openai_insert"
|
||||||
require "./context_builders/openai_insert"
|
|
||||||
require "./storyteller"
|
|
||||||
|
|
||||||
storyteller = Storyteller.new()
|
class Storyteller
|
||||||
storyteller.prepare()
|
|
||||||
storyteller.run()
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
|
|
||||||
Des classes qui modifient un prompt d'apres des contraintes
|
|
||||||
|
|
||||||
truncate(max_tokens) # le prompt aux X derniers tokens
|
|
||||||
|
|
||||||
summarize() # réduis la longueur du prompt
|
|
||||||
|
|
||||||
worldinfo() # introduis les éléments du monde
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
module AIUtils
|
|
||||||
enum OpenAIModel
|
|
||||||
Gpt_4
|
|
||||||
Gpt_3_5_Turbo
|
|
||||||
Gpt_3_5_Turbo_16k
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
vals = [
|
|
||||||
"gpt-4",
|
|
||||||
"gpt-3.5-turbo",
|
|
||||||
"gpt-3.5-turbo-16k"
|
|
||||||
]
|
|
||||||
vals[self.value]
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.from_s?(str_model : String) : OpenAIModel?
|
|
||||||
return OpenAIModel.values.find do |openai_model|
|
|
||||||
openai_model.to_s.downcase == str_model.downcase
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
enum OpenAIMode
|
|
||||||
Chat
|
|
||||||
Complete
|
|
||||||
Insert
|
|
||||||
|
|
||||||
def self.from_s?(str_mode) : OpenAIMode?
|
|
||||||
return OpenAIMode.parse?(str_mode)
|
|
||||||
# return OpenAIMode.values.find do |openai_mode|
|
|
||||||
# openai_mode.to_s.downcase == str_mode.downcase
|
|
||||||
# end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.context_token_limit(model : OpenAIModel) : UInt32
|
|
||||||
hash = {
|
|
||||||
OpenAIModel::Gpt_3_5_Turbo => 1024_u32 * 4_u32,
|
|
||||||
OpenAIModel::Gpt_3_5_Turbo_16k => 1024_u32 * 16_u32,
|
|
||||||
OpenAIModel::Gpt_4 => 1024_u32 * 8_u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0_u32 unless hash[model]?
|
|
||||||
return hash[model]
|
|
||||||
end
|
|
||||||
|
|
||||||
class GptConfig
|
|
||||||
# defaults
|
|
||||||
# property temperature = 0.82
|
|
||||||
property model : OpenAIModel = OpenAIModel::Gpt_3_5_Turbo
|
|
||||||
# property model = "gpt-3.5-turbo-16k"
|
|
||||||
# property prompt_size = 10_000
|
|
||||||
property prompt_size = 2_700
|
|
||||||
property temperature = 0.8
|
|
||||||
property presence_penalty = 1.0
|
|
||||||
property frequency_penalty = 1.0
|
|
||||||
property max_tokens = 384
|
|
||||||
property mode = OpenAIMode::Chat
|
|
||||||
property openai_key : String = ENV.fetch("OPENAI_API_KEY")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
require "../prompts/generic_prompt"
|
require "../prompt"
|
||||||
|
|
||||||
module Parser
|
module Parser
|
||||||
class PromptString
|
class PromptString
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
|
|
||||||
require "./generic_request"
|
|
||||||
|
|
||||||
module Requests
|
|
||||||
class ChatRequest < GenericRequest
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,8 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
require "./generic_request"
|
|
||||||
|
|
||||||
module Requests
|
|
||||||
class CompleteRequest < GenericRequest
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,23 +0,0 @@
|
||||||
|
|
||||||
module Requests
|
|
||||||
class GenericRequest
|
|
||||||
getter context_size : Int32 = 0
|
|
||||||
|
|
||||||
def initialize(@config : AIUtils::GptConfig)
|
|
||||||
end
|
|
||||||
|
|
||||||
def perform(messages)
|
|
||||||
openai = OpenAI::Client.new(access_token: @config.openai_key)
|
|
||||||
result = openai.chat(
|
|
||||||
@config.model.to_s,
|
|
||||||
messages,
|
|
||||||
{
|
|
||||||
"temperature" => @config.temperature,
|
|
||||||
"presence_penalty" => @config.presence_penalty,
|
|
||||||
"frequency_penalty" => @config.frequency_penalty,
|
|
||||||
"max_tokens" => @config.max_tokens,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,8 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
require "./generic_request"
|
|
||||||
|
|
||||||
module Requests
|
|
||||||
class InsertRequest < GenericRequest
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,201 +0,0 @@
|
||||||
|
|
||||||
require "./config"
|
|
||||||
require "./requests/chat"
|
|
||||||
|
|
||||||
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-model MODEL", "GPT model (...) (default: gpt-3.5-turbo)") do |chosen_model|
|
|
||||||
result_model = AIUtils::OpenAIModel.from_s?(chosen_model.downcase)
|
|
||||||
if result_model.nil?
|
|
||||||
STDERR.puts "ERROR: unknown model #{chosen_model}"
|
|
||||||
exit 1
|
|
||||||
end
|
|
||||||
@config.gpt_config.model = result_model unless result_model.nil?
|
|
||||||
end
|
|
||||||
|
|
||||||
# parser.on("--gpt-mode MODE", "GPT mode (chat,insert,complete) (default: chat)") do |chosen_mode|
|
|
||||||
# result_mode = AIUtils::OpenAIMode.from_s(chosen_mode.downcase)
|
|
||||||
# if result_mode.nil?
|
|
||||||
# STDERR.puts "ERROR: unknown mode #{chosen_mode}"
|
|
||||||
# exit 1
|
|
||||||
# end
|
|
||||||
# @config.gpt_config.mode = result_mode unless result_mode.nil?
|
|
||||||
# end
|
|
||||||
|
|
||||||
parser.on("--gpt-temperature TEMPERATURE", "GPT Temperature (default #{@config.gpt_config.temperature})") do |temperature|
|
|
||||||
@config.gpt_config.temperature = temperature.to_f
|
|
||||||
end
|
|
||||||
parser.on("--gpt-presence-penalty PENALTY", "GPT Presence Penalty (default #{@config.gpt_config.presence_penalty})") do |presence_penalty|
|
|
||||||
@config.gpt_config.presence_penalty = presence_penalty.to_f
|
|
||||||
end
|
|
||||||
parser.on("--gpt-frequency-penalty PENALTY", "GPT Frequency Penalty (default #{@config.gpt_config.frequency_penalty})") do |frequency_penalty|
|
|
||||||
@config.gpt_config.frequency_penalty = frequency_penalty.to_f
|
|
||||||
end
|
|
||||||
parser.on("--gpt-max-tokens TOKENS", "GPT Max Tokens (default #{@config.gpt_config.max_tokens})") do |max_tokens|
|
|
||||||
@config.gpt_config.max_tokens = max_tokens.to_i
|
|
||||||
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
|
|
||||||
end
|
|
||||||
|
|
||||||
def complete(prompt : Prompt, make_request : Bool, verbose : Bool)
|
|
||||||
builder = Builder::OpenAIChat.new(verbose: verbose)
|
|
||||||
context_token_limit = AIUtils.context_token_limit(@config.gpt_config.model)
|
|
||||||
messages = builder.build(prompt, context_token_limit)
|
|
||||||
|
|
||||||
STDERR.puts "model = #{@config.gpt_config.model}"
|
|
||||||
STDERR.puts "context_token_limit = #{context_token_limit}"
|
|
||||||
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 > (2 * 60)
|
|
||||||
print "(timeout)"
|
|
||||||
exit 1
|
|
||||||
end
|
|
||||||
# channel_tick.send(true)
|
|
||||||
sleep 1.seconds
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# TODO:
|
|
||||||
# request = Request::Chat.new(config: @config.gpt_config)
|
|
||||||
# result = request.perform(message)
|
|
||||||
|
|
||||||
spawn do
|
|
||||||
request = Requests::ChatRequest.new(config: @config.gpt_config)
|
|
||||||
result = request.perform(messages)
|
|
||||||
|
|
||||||
pp result.choices if @config.verbose
|
|
||||||
prompt.present_zone.content << result.choices.first["message"]["content"]
|
|
||||||
channel_ready.send(true)
|
|
||||||
rescue ex: KeyError
|
|
||||||
puts "(openai error)"
|
|
||||||
STDERR.puts "ERROR: #{ex.message}"
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
rescue ex: IO::Error
|
|
||||||
puts "(network error)"
|
|
||||||
STDERR.puts "ERROR: #{ex.message}"
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
rescue ex: Socket::Addrinfo::Error
|
|
||||||
puts "(network error)"
|
|
||||||
STDERR.puts "ERROR: #{ex.message}"
|
|
||||||
exit 1
|
|
||||||
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 = ContextBuilder::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…
Add table
Reference in a new issue