chatgpt-rb 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/chatgpt-rb +71 -4
- data/lib/chatgpt_rb/conversation.rb +30 -19
- data/lib/chatgpt_rb/function.rb +2 -2
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2dcefe52467b8bae3bc12c17e1f36a20b7a5a8a207f21c20d3db413b19f0a15e
|
4
|
+
data.tar.gz: 0cde2181779a22b431d8afd59dc89a80a1a5c58634fd02f7795cc31a9ee874be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5ba3dd8cf78ef49ca4df7ed0521c309b8cc44bc837a0fd14aed5a7f346cced3a5204c56581514b19761d0f081b82edae25298a871eba7dfc409a226819f3a1b5
|
7
|
+
data.tar.gz: 73f97ff7857d9947cfd60036eb8c0b455e603fe73b84d02624ff693f969cfcdef47c8e078c3e7a95cb78fde9ca70fccffc3d2b52c36c9465220adfa4e3e8aa95
|
data/bin/chatgpt-rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require "dotenv/load"
|
4
4
|
require "colorize"
|
5
5
|
require "reline"
|
6
|
+
require "optparse"
|
6
7
|
require_relative "./../lib/chatgpt_rb"
|
7
8
|
|
8
9
|
begin
|
@@ -10,17 +11,83 @@ begin
|
|
10
11
|
rescue
|
11
12
|
end
|
12
13
|
|
14
|
+
options = {
|
15
|
+
key: ENV["OPEN_AI_KEY"],
|
16
|
+
model: "gpt-3.5-turbo",
|
17
|
+
base_uri: "https://api.openai.com/v1",
|
18
|
+
functions_files: [],
|
19
|
+
}
|
20
|
+
|
21
|
+
OptionParser.new do |opts|
|
22
|
+
opts.banner = "Usage: chatgpt-rb [options]"
|
23
|
+
|
24
|
+
opts.on("-f", "--file FILE", "Load a previous conversation from FILE") do |file|
|
25
|
+
options[:file] = file
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on("-k", "--api-key KEY", "Use the provided API key for authentication") do |key|
|
29
|
+
options[:key] = key
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on("-m", "--model MODEL", "Use the provided MODEL (Default: #{options[:model]})") do |model|
|
33
|
+
options[:model] = model
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on("-b", "--base-uri URI", "Use the provided base URI (Default: #{options[:base_uri]})") do |uri|
|
37
|
+
options[:base_uri] = uri
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on("-u", "--functions-file FILE", "Add functions defined in FILE to your conversation") do |functions_file|
|
41
|
+
options[:functions_files] << functions_file
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on("-p", "--prompt PROMPT", "Declare the PROMPT for your conversation") do |prompt|
|
45
|
+
options[:prompt] = prompt
|
46
|
+
end
|
47
|
+
end.parse!
|
48
|
+
|
13
49
|
begin
|
14
|
-
|
50
|
+
puts "Type any message to talk with ChatGPT. Type '\\help' for a list of commands."
|
51
|
+
|
52
|
+
functions = options[:functions_files].map do |function_file|
|
53
|
+
puts "Loading functions from #{function_file}"
|
54
|
+
|
55
|
+
ChatgptRb::DSL::Conversation.new(ChatgptRb::Conversation.new).instance_eval(File.read(function_file))
|
56
|
+
end
|
57
|
+
|
58
|
+
messages = if options[:file]
|
59
|
+
JSON.parse(File.read(options[:file])).map { |hash| hash.transform_keys(&:to_sym) }
|
60
|
+
else
|
61
|
+
[]
|
62
|
+
end
|
63
|
+
|
64
|
+
if options[:prompt]
|
65
|
+
puts "prompt> ".colorize(:blue) + options[:prompt]
|
66
|
+
end
|
15
67
|
|
16
|
-
|
68
|
+
conversation = ChatgptRb::Conversation.new(api_key: options.fetch(:key), model: options.fetch(:model), base_uri: options.fetch(:base_uri), messages:, functions:, prompt: options[:prompt])
|
17
69
|
|
18
70
|
while message = Reline.readline("me> ".colorize(:red), true) do
|
19
71
|
case message.chomp
|
20
|
-
when "
|
72
|
+
when "\\help", "\\h"
|
73
|
+
puts <<~COMMANDS.colorize(:blue)
|
74
|
+
- `\\quit` Exit
|
75
|
+
- `\\save <filename>` Save this conversation to a JSON file that can be reloaded later with the `-f` argument
|
76
|
+
- `\\functions` Get a list of configured functions
|
77
|
+
COMMANDS
|
78
|
+
when "\\q", "\\quit", "\\exit"
|
21
79
|
exit
|
22
|
-
when "dump"
|
80
|
+
when "\\dump"
|
23
81
|
puts "dump> ".colorize(:blue) + conversation.messages.to_json
|
82
|
+
when "\\functions"
|
83
|
+
puts "available functions:".colorize(:blue)
|
84
|
+
functions.each do |function|
|
85
|
+
puts "- `#{function.name}` #{function.description}".colorize(:blue)
|
86
|
+
end
|
87
|
+
when /^\\save .+/
|
88
|
+
filename = /^\\save (.+)/.match(message)[1]
|
89
|
+
File.open(filename, "w") { |f| f.write(conversation.messages.to_json) }
|
90
|
+
puts "saved to #{filename} ".colorize(:blue)
|
24
91
|
else
|
25
92
|
print("ai> ".colorize(:yellow))
|
26
93
|
conversation.ask(message) { |fragment| print(fragment) }
|
@@ -1,14 +1,11 @@
|
|
1
1
|
require "httparty"
|
2
|
+
require "json-schema"
|
2
3
|
require_relative "./function"
|
3
4
|
require_relative "./dsl/conversation"
|
4
5
|
|
5
6
|
module ChatgptRb
|
6
7
|
class Conversation
|
7
|
-
|
8
|
-
|
9
|
-
base_uri "https://api.openai.com"
|
10
|
-
|
11
|
-
attr_accessor :api_key, :model, :functions, :temperature, :max_tokens, :top_p, :frequency_penalty, :presence_penalty, :prompt
|
8
|
+
attr_accessor :api_key, :model, :functions, :temperature, :max_tokens, :top_p, :frequency_penalty, :presence_penalty, :prompt, :base_uri
|
12
9
|
attr_reader :messages
|
13
10
|
|
14
11
|
# @param api_key [String]
|
@@ -21,7 +18,8 @@ module ChatgptRb
|
|
21
18
|
# @param presence_penalty [Float]
|
22
19
|
# @param messages [Array<Hash>]
|
23
20
|
# @param prompt [String, nil] instructions that the model can use to inform its responses, for example: "Act like a sullen teenager."
|
24
|
-
|
21
|
+
# @param base_uri [String]
|
22
|
+
def initialize(api_key: nil, model: "gpt-3.5-turbo", functions: [], temperature: 0.7, max_tokens: 1024, top_p: 1.0, frequency_penalty: 0.0, presence_penalty: 0.0, messages: [], prompt: nil, base_uri: "https://api.openai.com/v1", &configuration)
|
25
23
|
@api_key = api_key
|
26
24
|
@model = model
|
27
25
|
@functions = functions.each_with_object({}) do |function, hash|
|
@@ -33,8 +31,9 @@ module ChatgptRb
|
|
33
31
|
@top_p = top_p
|
34
32
|
@frequency_penalty = frequency_penalty
|
35
33
|
@presence_penalty = presence_penalty
|
36
|
-
@messages = messages
|
34
|
+
@messages = messages.map { |message| message.transform_keys(&:to_sym) }
|
37
35
|
@prompt = prompt
|
36
|
+
@base_uri = base_uri
|
38
37
|
ChatgptRb::DSL::Conversation.configure(self, &configuration) if block_given?
|
39
38
|
@messages << { role: "system", content: prompt } if prompt
|
40
39
|
end
|
@@ -64,7 +63,18 @@ module ChatgptRb
|
|
64
63
|
@messages << message
|
65
64
|
end
|
66
65
|
|
66
|
+
# Ensure that each function's argument declarations conform to the JSON Schema
|
67
|
+
# See https://github.com/voxpupuli/json-schema/
|
68
|
+
def validate_functions!
|
69
|
+
metaschema = JSON::Validator.validator_for_name("draft4").metaschema
|
70
|
+
functions.values.each do |function|
|
71
|
+
raise ArgumentError, "Invalid function declaration for #{function.name}: #{function.as_json}" unless JSON::Validator.validate(metaschema, function.as_json)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
67
75
|
def get_next_response(&block)
|
76
|
+
validate_functions!
|
77
|
+
|
68
78
|
streamed_content = ""
|
69
79
|
streamed_arguments = ""
|
70
80
|
streamed_role = ""
|
@@ -84,8 +94,8 @@ module ChatgptRb
|
|
84
94
|
hash[:functions] = functions.values.map(&:as_json) unless functions.empty?
|
85
95
|
end
|
86
96
|
|
87
|
-
response =
|
88
|
-
"/
|
97
|
+
response = HTTParty.post(
|
98
|
+
"#{base_uri}/chat/completions",
|
89
99
|
steam_body: block_given?,
|
90
100
|
headers: {
|
91
101
|
"Content-Type" => "application/json",
|
@@ -97,11 +107,12 @@ module ChatgptRb
|
|
97
107
|
) do |fragment|
|
98
108
|
if block_given?
|
99
109
|
fragment.each_line do |line|
|
100
|
-
next if line.nil?
|
101
|
-
next if line == "\n"
|
102
110
|
break if line == "data: [DONE]\n"
|
103
111
|
|
104
|
-
line_without_prefix = line.gsub(/^data: /, "")
|
112
|
+
line_without_prefix = line.gsub(/^data: /, "").rstrip
|
113
|
+
|
114
|
+
next if line_without_prefix.empty?
|
115
|
+
|
105
116
|
json = JSON.parse(line_without_prefix)
|
106
117
|
|
107
118
|
break if json.dig("choices", 0, "finish_reason")
|
@@ -131,17 +142,17 @@ module ChatgptRb
|
|
131
142
|
error_buffer.each { |e| $stderr.puts("Error: #{e}") }
|
132
143
|
|
133
144
|
@messages << if block_given? && streamed_content != ""
|
134
|
-
{
|
145
|
+
{ content: streamed_content, role: streamed_role }
|
135
146
|
elsif block_given? && streamed_arguments != ""
|
136
|
-
{
|
147
|
+
{ role: "assistant", content: nil, function_call: { "name" => streamed_function, "arguments" => streamed_arguments } }
|
137
148
|
else
|
138
|
-
response.dig("choices", 0, "message")
|
149
|
+
response.dig("choices", 0, "message").transform_keys(&:to_sym)
|
139
150
|
end
|
140
151
|
|
141
|
-
if @messages.last[
|
142
|
-
@messages.last[
|
143
|
-
elsif @messages.last[
|
144
|
-
function_args = @messages.last[
|
152
|
+
if @messages.last[:content]
|
153
|
+
@messages.last[:content]
|
154
|
+
elsif @messages.last[:function_call]
|
155
|
+
function_args = @messages.last[:function_call]
|
145
156
|
function_name = function_args.fetch("name")
|
146
157
|
arguments = JSON.parse(function_args.fetch("arguments"))
|
147
158
|
function = functions[function_name]
|
data/lib/chatgpt_rb/function.rb
CHANGED
@@ -2,11 +2,11 @@ module ChatgptRb
|
|
2
2
|
class Function
|
3
3
|
attr_accessor :name, :description, :parameters, :implementation
|
4
4
|
|
5
|
-
# @param name [String]
|
5
|
+
# @param name [String, nil]
|
6
6
|
# @param description [String, nil]
|
7
7
|
# @param parameters [Array<ChatgptRb::Parameter>]
|
8
8
|
# @param implementation [Lambda, nil]
|
9
|
-
def initialize(name
|
9
|
+
def initialize(name: nil, description: nil, parameters: [], implementation: nil)
|
10
10
|
@name = name
|
11
11
|
@description = description
|
12
12
|
@parameters = parameters
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chatgpt-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aaron Breckenridge
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-07-
|
11
|
+
date: 2023-07-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0.3'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: json-schema
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '4.0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '4.0'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: rspec
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|