simple-openai-client 1.0.1
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 +7 -0
- data/lib/openai-client.rb +163 -0
- data/openai-client.gemspec +24 -0
- 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: []
|