simple-openai-client 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/lib/openai-client.rb +163 -0
  3. data/openai-client.gemspec +24 -0
  4. metadata +164 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: efa6dcdade7db76ddf992abe323c53b86f773bc6e1955caf03b3fbe5411b8910
4
+ data.tar.gz: 1851ceaf75dae20d8a1eb6f4f08ecd71b3b0002faf1e921d32aa0f03110e9480
5
+ SHA512:
6
+ metadata.gz: b161ea843f3c55e500d303c18b51cd9a081c9d3250a5b70380fc8fdfac27ee506c640132094f834b2d64eced2fca2a10d25d7a2e1eb338fe7a8bbb5d70632951
7
+ data.tar.gz: e8e3f0e4b51c6c9f540dccc3eda4c91026bba5ee2ca53774296e756a71fd4b1ea34744379e60d78ff2171f638ac636c3e25bb7c92f244c1073d594354ba85cd8
@@ -0,0 +1,163 @@
1
+ # Reference:
2
+ # - https://platform.openai.com/docs/guides/function-calling/supported-models
3
+ #
4
+
5
+ require 'net/http'
6
+ require 'uri'
7
+ require 'json'
8
+
9
+ class OpenAIClient
10
+ attr_accessor :api_key, :model, :messages, :functions, :callbacks, :version
11
+
12
+ def initialize(api_key:, model:, messages: [], functions: [], callbacks: [], version: 'v2')
13
+ self.api_key = api_key
14
+ self.model = model
15
+ self.messages = messages
16
+ self.functions = functions
17
+ self.callbacks = callbacks
18
+ self.version = version
19
+ end
20
+
21
+ # Return an array with the available models
22
+ def models
23
+ uri = URI("https://api.openai.com/v1/models")
24
+ http = Net::HTTP.new(uri.host, uri.port)
25
+ http.use_ssl = true
26
+
27
+ # Create a GET request instead of POST
28
+ request = Net::HTTP::Get.new(uri.request_uri, {
29
+ "Authorization" => "Bearer #{self.api_key}",
30
+ "Content-Type" => "application/json"
31
+ # Removed "OpenAI-Beta" header as it's typically not required
32
+ })
33
+
34
+ response = http.request(request)
35
+
36
+ if response.is_a?(Net::HTTPSuccess)
37
+ parsed_response = JSON.parse(response.body)
38
+ # Extract and return the list of models
39
+ parsed_response['data'].map { |model| model['id'] }
40
+ else
41
+ raise "Error: #{response.code} #{response.message}"
42
+ #puts response.body
43
+ #[]
44
+ end
45
+ rescue JSON::ParserError => e
46
+ raise "Failed to parse JSON response: #{e.message}"
47
+ #[]
48
+ rescue StandardError => e
49
+ raise "An error occurred: #{e.message}"
50
+ #[]
51
+ end # models
52
+
53
+ # Ask something to GPT.
54
+ # Return the response.
55
+ def ask(s, context: [])
56
+ # Use v1 chat completions endpoint (with functions support)
57
+ uri = URI("https://api.openai.com/v1/chat/completions")
58
+
59
+ # add contenxt to the history of messages
60
+ self.messages += context
61
+
62
+ # add new question asked by the user to the history of messages
63
+ self.messages << { "role" => "user", "content" => s }
64
+
65
+ request_body = {
66
+ "model" => self.model, # A known model that supports function calling; update as needed
67
+ "messages" => self.messages
68
+ # To let the model decide if and when to call a function, omit "function_call"
69
+ # If you want the model to call a function explicitly, you can add: "function_call" => "auto"
70
+ }
71
+
72
+ request_body["functions"] = self.functions if self.functions.size > 0
73
+
74
+ http = Net::HTTP.new(uri.host, uri.port)
75
+ http.use_ssl = true
76
+
77
+ request = Net::HTTP::Post.new(uri.path, {
78
+ "Content-Type" => "application/json",
79
+ "Authorization" => "Bearer #{self.api_key}",
80
+ "OpenAI-Beta" => "assistants=#{version}"
81
+ })
82
+ request.body = JSON.dump(request_body)
83
+
84
+ response = http.request(request)
85
+
86
+ if response.is_a?(Net::HTTPSuccess)
87
+ response_json = JSON.parse(response.body)
88
+
89
+ # Check if the assistant decided to call a function
90
+ function_call = response_json.dig("choices", 0, "message", "function_call")
91
+
92
+ unless function_call
93
+ # add new response from AI to the history of messages
94
+ assistant_reply = response_json.dig("choices", 0, "message", "content")
95
+ self.messages << { "role" => "assistant", "content" => assistant_reply }
96
+ # return the resonse from AI
97
+ return assistant_reply
98
+ else
99
+ function_call_name = function_call["name"]
100
+ function_call_args = JSON.parse(function_call["arguments"]) rescue {}
101
+
102
+ # Handle the function call
103
+ result = self.callbacks[function_call_name.to_sym].call(function_call_args);
104
+
105
+ # Now we send the function result back to the assistant as another message:
106
+ follow_up_uri = URI("https://api.openai.com/v1/chat/completions")
107
+ follow_up_messages = messages.dup
108
+ follow_up_messages << {
109
+ "role" => "function",
110
+ "name" => function_call_name,
111
+ "content" => JSON.dump(result)
112
+ }
113
+
114
+ follow_up_request_body = {
115
+ "model" => self.model,
116
+ "messages" => follow_up_messages
117
+ }
118
+
119
+ follow_up_http = Net::HTTP.new(follow_up_uri.host, follow_up_uri.port)
120
+ follow_up_http.use_ssl = true
121
+
122
+ follow_up_request = Net::HTTP::Post.new(follow_up_uri.path, {
123
+ "Content-Type" => "application/json",
124
+ "Authorization" => "Bearer #{OPENAI_API_KEY}",
125
+ "OpenAI-Beta" => "assistants=#{version}"
126
+ })
127
+ follow_up_request.body = JSON.dump(follow_up_request_body)
128
+
129
+ follow_up_response = follow_up_http.request(follow_up_request)
130
+ if follow_up_response.is_a?(Net::HTTPSuccess)
131
+ follow_up_response_json = JSON.parse(follow_up_response.body)
132
+ final_reply = follow_up_response_json.dig("choices", 0, "message", "content")
133
+ # add new response from AI to the history of messages
134
+ self.messages << { "role" => "assistant", "content" => final_reply }
135
+ # return the response form the AI.
136
+ return final_reply
137
+ else
138
+ raise "Error after function call: #{follow_up_response.code} - #{follow_up_response.message} - #{follow_up_response.body}"
139
+ end
140
+ end
141
+ else
142
+ raise "Error: #{response.code} - #{response.message} - #{response.body}"
143
+ end
144
+ end # def ask
145
+
146
+ # manage copilot from terminal
147
+ def console
148
+ puts "Mass-Copilot Console".blue
149
+ puts "Type 'exit' to quit.".blue
150
+ while true
151
+ print "You: ".green
152
+ prompt = gets.chomp
153
+ break if prompt.downcase.strip == 'exit'
154
+ begin
155
+ puts "Mass-Copilot: #{ask(prompt)}".blue
156
+ rescue => e
157
+ puts "Error: #{e.to_console}".red
158
+ end
159
+ end
160
+ end # def console
161
+
162
+ end # class OpenAIClient
163
+
@@ -0,0 +1,24 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'simple-openai-client'
3
+ s.version = '1.0.1'
4
+ s.date = '2025-01-05'
5
+ s.summary = "Very simple Ruby library for operating OpenAI API for building Agents."
6
+ s.description = "Very simple Ruby library for operating OpenAI API for building Agents."
7
+ s.authors = ["Leandro Daniel Sardi"]
8
+ s.email = 'leandro@massprospecting.com'
9
+ s.files = [
10
+ 'lib/openai-client.rb',
11
+ 'openai-client.gemspec'
12
+ ]
13
+ s.homepage = 'https://github.com/leandrosardi/openai-client'
14
+ s.license = 'MIT'
15
+ s.add_runtime_dependency 'uri', '~> 0.11.2', '>= 0.11.2'
16
+ s.add_runtime_dependency 'net-http', '~> 0.2.0', '>= 0.2.0'
17
+ s.add_runtime_dependency 'json', '~> 2.6.3', '>= 2.6.3'
18
+ s.add_runtime_dependency 'blackstack-core', '~> 1.2.15', '>= 1.2.15'
19
+ #s.add_runtime_dependency 'selenium-webdriver', '~> 4.10.0', '>= 4.10.0'
20
+ #s.add_runtime_dependency 'watir', '~> 7.3.0', '>= 7.3.0'
21
+ #s.add_runtime_dependency 'sequel', '~> 5.75.0', '>= 5.75.0'
22
+ s.add_runtime_dependency 'colorize', '~> 0.8.1', '>= 0.8.1'
23
+ s.add_runtime_dependency 'simple_cloud_logging', '~> 1.2.2', '>= 1.2.2'
24
+ end
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simple-openai-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Leandro Daniel Sardi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-01-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: uri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.11.2
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.11.2
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: 0.11.2
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.11.2
33
+ - !ruby/object:Gem::Dependency
34
+ name: net-http
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 0.2.0
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 0.2.0
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: 0.2.0
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 0.2.0
53
+ - !ruby/object:Gem::Dependency
54
+ name: json
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: 2.6.3
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 2.6.3
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 2.6.3
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 2.6.3
73
+ - !ruby/object:Gem::Dependency
74
+ name: blackstack-core
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: 1.2.15
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 1.2.15
83
+ type: :runtime
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 1.2.15
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 1.2.15
93
+ - !ruby/object:Gem::Dependency
94
+ name: colorize
95
+ requirement: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - "~>"
98
+ - !ruby/object:Gem::Version
99
+ version: 0.8.1
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: 0.8.1
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: 0.8.1
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: 0.8.1
113
+ - !ruby/object:Gem::Dependency
114
+ name: simple_cloud_logging
115
+ requirement: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - "~>"
118
+ - !ruby/object:Gem::Version
119
+ version: 1.2.2
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: 1.2.2
123
+ type: :runtime
124
+ prerelease: false
125
+ version_requirements: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: 1.2.2
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: 1.2.2
133
+ description: Very simple Ruby library for operating OpenAI API for building Agents.
134
+ email: leandro@massprospecting.com
135
+ executables: []
136
+ extensions: []
137
+ extra_rdoc_files: []
138
+ files:
139
+ - lib/openai-client.rb
140
+ - openai-client.gemspec
141
+ homepage: https://github.com/leandrosardi/openai-client
142
+ licenses:
143
+ - MIT
144
+ metadata: {}
145
+ post_install_message:
146
+ rdoc_options: []
147
+ require_paths:
148
+ - lib
149
+ required_ruby_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ required_rubygems_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ requirements: []
160
+ rubygems_version: 3.3.7
161
+ signing_key:
162
+ specification_version: 4
163
+ summary: Very simple Ruby library for operating OpenAI API for building Agents.
164
+ test_files: []