chatgpt-rb 0.1.1 → 0.1.3

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: 3f4e1f6a8aea8a8fa06d4ebe9c35da19c8e7813c945a1ce534eeadd0e5088282
4
+ data.tar.gz: 611a070396c1c5006bd3df9b961dc2b2ed1a0fd3259626b4b38edbc2c08337c1
5
5
  SHA512:
6
- metadata.gz: a42400a64dee74e93b7b27c8d04fc1f8c750bd0b90abc1ba37c33bdd58cbc9a494906c544b2bb0e02585807fffffbaae9ff3488d328b9bdb3051cc4fbfb59c0b
7
- data.tar.gz: 4f187489399450e128b1f5f1634004be8de0a5f4b71516bff12ec234e3cf5cf3498102d6e44081e0e2bd00dc8ad14c1f75ca68770425eac0a7b1e32d284ee6e4
6
+ metadata.gz: b2d583053bfb3700bda25d4416796997ce24c8acfd053deb34a639119b48aea90e555acb0edf4fd8c002e72716eb195629ee1b913ab6db06511d65457431c618
7
+ data.tar.gz: 3dc0bcfb0bb3eabc87972722ff617431349634c501406a02eada63bf61d03df5c9b05c515759db76e6ea119d85c7325781a8887f51ec802bde108eae15cb1e59
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,85 @@ 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].flat_map do |function_file|
53
+ puts "Loading functions from #{function_file}"
54
+
55
+ ChatgptRb::Conversation.new.tap do |conversation|
56
+ ChatgptRb::DSL::Conversation.new(conversation).instance_eval(File.read(function_file))
57
+ end.functions.values
58
+ end
59
+
60
+ messages = if options[:file]
61
+ JSON.parse(File.read(options[:file])).map { |hash| hash.transform_keys(&:to_sym) }
62
+ else
63
+ []
64
+ end
65
+
66
+ if options[:prompt]
67
+ puts "prompt> ".colorize(:blue) + options[:prompt]
68
+ end
15
69
 
16
- puts "Type any message to talk with ChatGPT. Type 'exit' to quit. Type 'dump' to dump this conversation to JSON."
70
+ 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
71
 
18
72
  while message = Reline.readline("me> ".colorize(:red), true) do
19
73
  case message.chomp
20
- when "exit", "quit", "q", "\\q"
74
+ when "\\help", "\\h"
75
+ puts <<~COMMANDS.colorize(:blue)
76
+ - `\\quit` Exit
77
+ - `\\save <filename>` Save this conversation to a JSON file that can be reloaded later with the `-f` argument
78
+ - `\\functions` Get a list of configured functions
79
+ COMMANDS
80
+ when "\\q", "\\quit", "\\exit"
21
81
  exit
22
- when "dump"
82
+ when "\\dump"
23
83
  puts "dump> ".colorize(:blue) + conversation.messages.to_json
84
+ when "\\functions"
85
+ puts "available functions:".colorize(:blue)
86
+ functions.each do |function|
87
+ puts "- `#{function.name}` #{function.description}".colorize(:blue)
88
+ end
89
+ when /^\\save .+/
90
+ filename = /^\\save (.+)/.match(message)[1]
91
+ File.open(filename, "w") { |f| f.write(conversation.messages.to_json) }
92
+ puts "saved to #{filename} ".colorize(:blue)
24
93
  else
25
94
  print("ai> ".colorize(:yellow))
26
95
  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,10 +31,11 @@ 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
- @messages << { role: "system", content: prompt } if prompt
38
+ @messages.unshift(role: "system", content: prompt) if prompt
40
39
  end
41
40
 
42
41
  # @param content [String]
@@ -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
@@ -0,0 +1,3 @@
1
+ module ChatgptRb
2
+ VERSION = "0.1.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.1
4
+ version: 0.1.3
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-08-07 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
@@ -109,7 +123,7 @@ dependencies:
109
123
  - !ruby/object:Gem::Version
110
124
  version: '0'
111
125
  description: Provides libraries for interacting with the ChatGPT API and a CLI program
112
- `chatgpt-rb` for live conversations.
126
+ `chatgpt-rb` for live conversations. Supports writing functions in Ruby.
113
127
  email:
114
128
  - aaron@breckridge.dev
115
129
  executables:
@@ -127,6 +141,7 @@ files:
127
141
  - lib/chatgpt_rb/dsl/parameter.rb
128
142
  - lib/chatgpt_rb/function.rb
129
143
  - lib/chatgpt_rb/parameter.rb
144
+ - lib/chatgpt_rb/version.rb
130
145
  homepage: https://github.com/breckenedge/chatgpt-rb
131
146
  licenses:
132
147
  - MIT