chatgpt-rb 0.1.6 → 0.1.7
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 +17 -11
- data/lib/chatgpt_rb/conversation.rb +37 -12
- data/lib/chatgpt_rb/dsl/conversation.rb +1 -1
- data/lib/chatgpt_rb/function.rb +20 -10
- data/lib/chatgpt_rb/version.rb +1 -1
- metadata +8 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1219489ab15f01f9161fc6c6ab35e5212c6851079559b7ae65303d7913e309af
|
4
|
+
data.tar.gz: 3c6951ea51ef8b71a25dc3c61ee5c6368f4c0f38bfc99e5f6d70782b43bf45e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 29b1ce40f4cf7c5e88f710fed4eb7c47468fd92f5f053e2321bfa207de58c370ad67aaeac41f79b3264b50596cac48eb00d350fb81600756ff7562704b46374c
|
7
|
+
data.tar.gz: fef058d0addadd83568529d5b96544a4ead59dfac7878d200cdf506ac65f3c89f0e6ca32d70f0fe9d2a4eaff264131ffe30d7af18b17a293f3f2f10cf7e90f31
|
data/bin/chatgpt-rb
CHANGED
@@ -14,6 +14,8 @@ options = {
|
|
14
14
|
temperature: 0.7,
|
15
15
|
}
|
16
16
|
|
17
|
+
SAVE_COMMAND_PATTERN = /sa?v?e? (.+)/.freeze
|
18
|
+
|
17
19
|
OptionParser.new do |opts|
|
18
20
|
opts.banner = "Usage: chatgpt-rb [options]"
|
19
21
|
|
@@ -74,32 +76,36 @@ begin
|
|
74
76
|
|
75
77
|
commands = [
|
76
78
|
{
|
77
|
-
names: ["s", "save"],
|
79
|
+
names: ["s", "save", SAVE_COMMAND_PATTERN],
|
78
80
|
description: "Save this conversation to a JSON file that can be reloaded later with the `-f` argument",
|
79
|
-
implementation: ->() {
|
80
|
-
filename =
|
81
|
-
|
82
|
-
|
81
|
+
implementation: ->(message) {
|
82
|
+
filename = if message =~ SAVE_COMMAND_PATTERN
|
83
|
+
SAVE_COMMAND_PATTERN.match(message)[1]
|
84
|
+
else
|
85
|
+
"conversation_#{Time.now.iso8601}.json"
|
86
|
+
end
|
87
|
+
File.open(filename, "w") { |f| f.write(conversation.to_json) }
|
88
|
+
puts "saved to #{filename}".colorize(:blue)
|
83
89
|
}
|
84
90
|
},
|
85
91
|
{
|
86
92
|
names: ["q", "quit", "exit"],
|
87
93
|
description: "Exit the program",
|
88
|
-
implementation: ->() {
|
94
|
+
implementation: ->(_) {
|
89
95
|
exit
|
90
96
|
}
|
91
97
|
},
|
92
98
|
{
|
93
99
|
names: ["d", "dump"],
|
94
100
|
description: "Print out all messages in this converastion.",
|
95
|
-
implementation: ->() {
|
101
|
+
implementation: ->(_) {
|
96
102
|
puts "dump> ".colorize(:blue) + conversation.messages.to_json
|
97
103
|
}
|
98
104
|
},
|
99
105
|
{
|
100
106
|
names: ["f", "functions"],
|
101
107
|
description: "List all available functions.",
|
102
|
-
implementation: ->() {
|
108
|
+
implementation: ->(_) {
|
103
109
|
puts "available functions:".colorize(:blue)
|
104
110
|
functions.each do |function|
|
105
111
|
puts "- `#{function.name}` #{function.description}".colorize(:blue)
|
@@ -109,7 +115,7 @@ begin
|
|
109
115
|
{
|
110
116
|
names: ["h", "help"],
|
111
117
|
description: "List all commands and their description",
|
112
|
-
implementation: ->() {
|
118
|
+
implementation: ->(_) {
|
113
119
|
puts commands.map { |command| " - #{command[:names].map { |str| "`\\#{str}`".colorize(:yellow) }.join(", ")}: #{command[:description].colorize(:blue)}" }.join("\n")
|
114
120
|
}
|
115
121
|
}
|
@@ -117,8 +123,8 @@ begin
|
|
117
123
|
|
118
124
|
while message = Reline.readline("me> ".colorize(:red), true) do
|
119
125
|
input = message.chomp
|
120
|
-
if (command = commands.find { |command| command[:names].any? { |name| "\\#{name}" == input } })
|
121
|
-
command[:implementation].call
|
126
|
+
if (command = commands.find { |command| command[:names].any? { |name| name.is_a?(Regexp) ? input =~ name : "\\#{name}" == input } })
|
127
|
+
command[:implementation].call(message)
|
122
128
|
else
|
123
129
|
print("ai> ".colorize(:yellow))
|
124
130
|
conversation.ask(message) { |fragment| print(fragment) }
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require "json"
|
1
2
|
require "httparty"
|
2
3
|
require "json-schema"
|
3
4
|
require_relative "./function"
|
@@ -5,7 +6,7 @@ require_relative "./dsl/conversation"
|
|
5
6
|
|
6
7
|
module ChatgptRb
|
7
8
|
class Conversation
|
8
|
-
attr_accessor :api_key, :model, :functions, :temperature, :max_tokens, :top_p, :frequency_penalty, :presence_penalty, :prompt, :base_uri
|
9
|
+
attr_accessor :api_key, :model, :functions, :temperature, :max_tokens, :top_p, :frequency_penalty, :presence_penalty, :prompt, :base_uri, :seed, :json
|
9
10
|
attr_reader :messages
|
10
11
|
|
11
12
|
# @param api_key [String]
|
@@ -18,8 +19,10 @@ module ChatgptRb
|
|
18
19
|
# @param presence_penalty [Float]
|
19
20
|
# @param messages [Array<Hash>]
|
20
21
|
# @param prompt [String, nil] instructions that the model can use to inform its responses, for example: "Act like a sullen teenager."
|
22
|
+
# @param json [true, false] whether or not ChatGPT should respond using only JSON objects
|
23
|
+
# @param seed [Integer, nil] deterministic best effort
|
21
24
|
# @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
|
+
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", json: false, seed: nil, &configuration)
|
23
26
|
@api_key = api_key
|
24
27
|
@model = model
|
25
28
|
@functions = functions.each_with_object({}) do |function, hash|
|
@@ -42,10 +45,16 @@ module ChatgptRb
|
|
42
45
|
@messages = messages.map { |message| message.transform_keys(&:to_sym) }
|
43
46
|
@prompt = prompt
|
44
47
|
@base_uri = base_uri
|
48
|
+
@json = json
|
49
|
+
@seed = seed
|
45
50
|
ChatgptRb::DSL::Conversation.configure(self, &configuration) if block_given?
|
46
51
|
@messages.unshift(role: "system", content: prompt) if prompt
|
47
52
|
end
|
48
53
|
|
54
|
+
def to_json
|
55
|
+
messages.to_json
|
56
|
+
end
|
57
|
+
|
49
58
|
# @param content [String]
|
50
59
|
# @yieldparam [String] the response, but streamed
|
51
60
|
# @return [String] the response
|
@@ -67,17 +76,10 @@ module ChatgptRb
|
|
67
76
|
|
68
77
|
private
|
69
78
|
|
70
|
-
def <<(message)
|
71
|
-
@messages << message
|
72
|
-
end
|
73
|
-
|
74
79
|
# Ensure that each function's argument declarations conform to the JSON Schema
|
75
80
|
# See https://github.com/voxpupuli/json-schema/
|
76
81
|
def validate_functions!
|
77
|
-
|
78
|
-
functions.values.each do |function|
|
79
|
-
raise ArgumentError, "Invalid function declaration for #{function.name}: #{function.as_json}" unless JSON::Validator.validate(metaschema, function.as_json)
|
80
|
-
end
|
82
|
+
functions.values.each(&:validate!)
|
81
83
|
end
|
82
84
|
|
83
85
|
def get_next_response(&block)
|
@@ -87,6 +89,7 @@ module ChatgptRb
|
|
87
89
|
streamed_arguments = ""
|
88
90
|
streamed_role = ""
|
89
91
|
streamed_function = ""
|
92
|
+
streamed_tool_calls = ""
|
90
93
|
error_buffer = []
|
91
94
|
|
92
95
|
body = {
|
@@ -99,7 +102,9 @@ module ChatgptRb
|
|
99
102
|
presence_penalty:,
|
100
103
|
stream: block_given?,
|
101
104
|
}.tap do |hash|
|
102
|
-
hash[:
|
105
|
+
hash[:tools] = functions.values.map(&:as_json) unless functions.empty?
|
106
|
+
hash[:response_format] = { type: :json_object } if json
|
107
|
+
hash[:seed] = seed unless seed.nil?
|
103
108
|
end
|
104
109
|
|
105
110
|
response = HTTParty.post(
|
@@ -140,6 +145,8 @@ module ChatgptRb
|
|
140
145
|
streamed_content << content
|
141
146
|
elsif arguments = json.dig("choices", 0, "delta", "function_call", "arguments")
|
142
147
|
streamed_arguments << arguments
|
148
|
+
elsif tool_calls = json.dig("choices", 0, "delta", "tool_calls")
|
149
|
+
streamed_tool_calls << tool_calls
|
143
150
|
end
|
144
151
|
rescue => e
|
145
152
|
error_buffer << "Error: #{e}"
|
@@ -155,12 +162,30 @@ module ChatgptRb
|
|
155
162
|
{ content: streamed_content, role: streamed_role }
|
156
163
|
elsif block_given? && streamed_arguments != ""
|
157
164
|
{ role: "assistant", content: nil, function_call: { "name" => streamed_function, "arguments" => streamed_arguments } }
|
165
|
+
elsif block_given? && streamed_tool_calls != ""
|
166
|
+
{ role: "assistant", content: nil, tool_calls: streamed_tool_calls }
|
158
167
|
else
|
159
168
|
response.dig("choices", 0, "message").transform_keys(&:to_sym)
|
160
169
|
end
|
161
170
|
|
162
171
|
if @messages.last[:content]
|
163
|
-
@messages.last[:content]
|
172
|
+
json ? JSON.parse(@messages.last[:content]) : @messages.last[:content]
|
173
|
+
elsif @messages.last[:tool_calls]
|
174
|
+
@messages.last[:tool_calls].each do |tool_call|
|
175
|
+
next unless tool_call.fetch("type") == "function"
|
176
|
+
function_name = tool_call.dig("function", "name")
|
177
|
+
function_args = JSON.parse(tool_call.dig("function", "arguments"))
|
178
|
+
function = functions.fetch(function_name)
|
179
|
+
content = function.implementation.call(**function_args.transform_keys(&:to_sym))
|
180
|
+
@messages << {
|
181
|
+
role: "tool",
|
182
|
+
tool_call_id: tool_call.fetch("id"),
|
183
|
+
name: function_name,
|
184
|
+
content: content.to_json,
|
185
|
+
}
|
186
|
+
end
|
187
|
+
|
188
|
+
get_next_response(&block)
|
164
189
|
elsif @messages.last[:function_call]
|
165
190
|
function_args = @messages.last[:function_call]
|
166
191
|
function_name = function_args.fetch("name")
|
@@ -5,7 +5,7 @@ require_relative "../function"
|
|
5
5
|
module ChatgptRb
|
6
6
|
module DSL
|
7
7
|
class Conversation < Base
|
8
|
-
supported_fields %i[api_key model functions temperature max_tokens top_p frequency_penalty presence_penalty prompt]
|
8
|
+
supported_fields %i[api_key model functions temperature max_tokens top_p frequency_penalty presence_penalty prompt json seed]
|
9
9
|
|
10
10
|
# @param name [String] the name of the function
|
11
11
|
# @param configuration [Block]
|
data/lib/chatgpt_rb/function.rb
CHANGED
@@ -13,19 +13,29 @@ module ChatgptRb
|
|
13
13
|
@implementation = implementation
|
14
14
|
end
|
15
15
|
|
16
|
+
def validate!
|
17
|
+
metaschema = JSON::Validator.validator_for_name("draft4").metaschema
|
18
|
+
return if JSON::Validator.validate(metaschema, as_json[:function])
|
19
|
+
|
20
|
+
raise ArgumentError, "Invalid function declaration for #{name}: #{as_json[:function]}"
|
21
|
+
end
|
22
|
+
|
16
23
|
# @return [Hash]
|
17
24
|
def as_json
|
18
25
|
{
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
type: "function",
|
27
|
+
function: {
|
28
|
+
name:,
|
29
|
+
description:,
|
30
|
+
parameters: {
|
31
|
+
type: "object",
|
32
|
+
properties: parameters.each_with_object({}) do |parameter, hash|
|
33
|
+
hash[parameter.name] = parameter.as_json
|
34
|
+
end,
|
35
|
+
required: parameters.select(&:required?).map(&:name),
|
36
|
+
},
|
37
|
+
}.compact
|
38
|
+
}
|
29
39
|
end
|
30
40
|
end
|
31
41
|
end
|
data/lib/chatgpt_rb/version.rb
CHANGED
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.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aaron Breckenridge
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-09
|
11
|
+
date: 2023-11-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -123,7 +123,8 @@ dependencies:
|
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
125
|
description: Provides libraries for interacting with the ChatGPT API and a CLI program
|
126
|
-
`chatgpt-rb` for live conversations. Supports writing functions in Ruby
|
126
|
+
`chatgpt-rb` for live conversations. Supports writing tools (functions) in Ruby
|
127
|
+
and streaming responses.
|
127
128
|
email:
|
128
129
|
- aaron@breckridge.dev
|
129
130
|
executables:
|
@@ -146,7 +147,7 @@ homepage: https://github.com/breckenedge/chatgpt-rb
|
|
146
147
|
licenses:
|
147
148
|
- MIT
|
148
149
|
metadata: {}
|
149
|
-
post_install_message:
|
150
|
+
post_install_message:
|
150
151
|
rdoc_options: []
|
151
152
|
require_paths:
|
152
153
|
- lib
|
@@ -161,8 +162,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
161
162
|
- !ruby/object:Gem::Version
|
162
163
|
version: '0'
|
163
164
|
requirements: []
|
164
|
-
rubygems_version: 3.4.
|
165
|
-
signing_key:
|
165
|
+
rubygems_version: 3.4.21
|
166
|
+
signing_key:
|
166
167
|
specification_version: 4
|
167
168
|
summary: A gem for interacting with the ChatGPT API
|
168
169
|
test_files: []
|