chatgpt-rb 0.1.1 → 0.1.2

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: 50e30e865ade9a6e20cad5680617972114a224bda8bf25d9969b9b57e6236763
4
- data.tar.gz: e7d1714512f1696b9401018b11292a46ed220c650639521def9b4fa0290303a0
3
+ metadata.gz: 2dcefe52467b8bae3bc12c17e1f36a20b7a5a8a207f21c20d3db413b19f0a15e
4
+ data.tar.gz: 0cde2181779a22b431d8afd59dc89a80a1a5c58634fd02f7795cc31a9ee874be
5
5
  SHA512:
6
- metadata.gz: a42400a64dee74e93b7b27c8d04fc1f8c750bd0b90abc1ba37c33bdd58cbc9a494906c544b2bb0e02585807fffffbaae9ff3488d328b9bdb3051cc4fbfb59c0b
7
- data.tar.gz: 4f187489399450e128b1f5f1634004be8de0a5f4b71516bff12ec234e3cf5cf3498102d6e44081e0e2bd00dc8ad14c1f75ca68770425eac0a7b1e32d284ee6e4
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
- conversation = ChatgptRb::Conversation.new(api_key: ENV.fetch("OPEN_AI_KEY"), model: ENV.fetch("OPEN_AI_MODEL", "gpt-3.5-turbo"))
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
- puts "Type any message to talk with ChatGPT. Type 'exit' to quit. Type 'dump' to dump this conversation to JSON."
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 "exit", "quit", "q", "\\q"
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
- include HTTParty
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
- 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, &configuration)
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 = self.class.post(
88
- "/v1/chat/completions",
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
- { "content" => streamed_content, "role" => streamed_role }
145
+ { content: streamed_content, role: streamed_role }
135
146
  elsif block_given? && streamed_arguments != ""
136
- { "role" => "assistant", "content" => nil, "function_call" => { "name" => streamed_function, "arguments" => streamed_arguments } }
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["content"]
142
- @messages.last["content"]
143
- elsif @messages.last["function_call"]
144
- function_args = @messages.last["function_call"]
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]
@@ -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:, description: nil, parameters: [], implementation: nil)
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.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-08 00:00:00.000000000 Z
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