chatgpt-rb 0.1.6 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9c3a20f294a53407477460d0bcf1efc53524a4986cf1129c365f597a93766adc
4
- data.tar.gz: 75999a3494afb6b8726a1510c6e16ec29e8dc55015212175befef7f5fac3a766
3
+ metadata.gz: 1219489ab15f01f9161fc6c6ab35e5212c6851079559b7ae65303d7913e309af
4
+ data.tar.gz: 3c6951ea51ef8b71a25dc3c61ee5c6368f4c0f38bfc99e5f6d70782b43bf45e6
5
5
  SHA512:
6
- metadata.gz: 5a4aaa0890191fdd4c4b5221c49a3ce094e4d69f0ffd699207fa8987515fb4e12dd6ffcb349cd2015205e99ee65660b57c72f7c8c05e6a471703d557f50dbcaa
7
- data.tar.gz: 409b9eb640141bdd46ebbaf8b131c43b8f72d6e1e3b4da75c0f729ff9cf5bba1cb2b87a2536ddcf4fc906f6008f360a8694a5029ba57f43d55263f08783e10c6
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 = /^\\save (.+)/.match(message)[1]
81
- File.open(filename, "w") { |f| f.write(conversation.messages.to_json) }
82
- puts "saved to #{filename} ".colorize(:blue)
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
- metaschema = JSON::Validator.validator_for_name("draft4").metaschema
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[:functions] = functions.values.map(&:as_json) unless functions.empty?
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]
@@ -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
- name:,
20
- description:,
21
- parameters: {
22
- type: "object",
23
- properties: parameters.each_with_object({}) do |parameter, hash|
24
- hash[parameter.name] = parameter.as_json
25
- end,
26
- required: parameters.select(&:required?).map(&:name),
27
- },
28
- }.compact
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
@@ -1,3 +1,3 @@
1
1
  module ChatgptRb
2
- VERSION = "0.1.6"
2
+ VERSION = "0.1.7"
3
3
  end
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.6
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-05 00:00:00.000000000 Z
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.10
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: []