ai_client 0.2.3 → 0.2.5
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 +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +1 -2
- data/examples/README.md +1 -0
- data/examples/tools.rb +90 -0
- data/lib/ai_client/config.yml +1 -1
- data/lib/ai_client/configuration.rb +31 -2
- data/lib/ai_client/llm.rb +25 -13
- data/lib/ai_client/logger_middleware.rb +13 -0
- data/lib/ai_client/middleware.rb +25 -2
- data/lib/ai_client/models.yml +4839 -0
- data/lib/ai_client/open_router_extensions.rb +98 -10
- data/lib/ai_client/retry_middleware.rb +15 -0
- data/lib/ai_client/tool.rb +18 -0
- data/lib/ai_client/version.rb +1 -1
- data/lib/ai_client.rb +68 -3
- metadata +7 -4
@@ -5,16 +5,56 @@
|
|
5
5
|
# a valid API Key for the open_router.ai web-service
|
6
6
|
|
7
7
|
require 'open_router'
|
8
|
+
require 'yaml'
|
8
9
|
|
9
10
|
class AiClient
|
11
|
+
|
12
|
+
# Retrieves the available models.
|
13
|
+
#
|
14
|
+
# @return [Array<String>] List of model IDs.
|
15
|
+
#
|
16
|
+
def models
|
17
|
+
self.class.models
|
18
|
+
end
|
19
|
+
|
20
|
+
# Retrieves the available providers.
|
21
|
+
#
|
22
|
+
# @return [Array<String>] List of provider names.
|
23
|
+
def providers
|
24
|
+
self.class.providers
|
25
|
+
end
|
26
|
+
|
27
|
+
# Retrieves model names, optionally filtered by provider.
|
28
|
+
#
|
29
|
+
# @param provider [String, nil] The provider to filter models by.
|
30
|
+
# @return [Array<String>] List of model names.
|
31
|
+
def model_names(provider = nil)
|
32
|
+
self.class.model_names(provider)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Retrieves details for a specific model.
|
36
|
+
#
|
37
|
+
# @param a_model [String] The model ID to retrieve details for.
|
38
|
+
# @return [Hash, nil] Details of the model or nil if not found.
|
39
|
+
def model_details(a_model)
|
40
|
+
self.class.model_details(a_model)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Finds models matching a given substring.
|
44
|
+
#
|
45
|
+
# @param a_model_substring [String] The substring to search for.
|
46
|
+
# @return [Array<String>] List of matching model names.
|
47
|
+
def find_model(a_model_substring)
|
48
|
+
self.class.find_model(a_model_substring)
|
49
|
+
end
|
10
50
|
|
11
|
-
def models = self.class.models
|
12
|
-
def providers = self.class.providers
|
13
|
-
def model_names(a_provider=nil) = self.class.model_names(a_provider)
|
14
|
-
def model_details(a_model) = self.class.model_details(a_model)
|
15
|
-
def find_model(a_model_substring) = self.class.find_model(a_model_substring)
|
16
51
|
|
17
52
|
class << self
|
53
|
+
|
54
|
+
# Adds OpenRouter extensions to AiClient.
|
55
|
+
#
|
56
|
+
# @return [void]
|
57
|
+
#
|
18
58
|
def add_open_router_extensions
|
19
59
|
access_token = fetch_access_token
|
20
60
|
|
@@ -24,17 +64,30 @@ class AiClient
|
|
24
64
|
initialize_orc_client
|
25
65
|
end
|
26
66
|
|
67
|
+
# Retrieves ORC client instance.
|
68
|
+
#
|
69
|
+
# @return [OpenRouter::Client] Instance of the OpenRouter client.
|
70
|
+
#
|
27
71
|
def orc_client
|
28
72
|
@orc_client ||= add_open_router_extensions || raise("OpenRouter extensions are not available")
|
29
73
|
end
|
30
74
|
|
31
|
-
|
32
|
-
|
75
|
+
# Retrieves models from the ORC client.
|
76
|
+
#
|
77
|
+
# @return [Array<Hash>] List of models.
|
78
|
+
#
|
79
|
+
def orc_models
|
80
|
+
@orc_models ||= orc_client.models
|
33
81
|
end
|
34
82
|
|
35
83
|
# TODO: Refactor these DB like methods to take
|
36
84
|
# advantage of AiClient::LLM
|
37
85
|
|
86
|
+
# Retrieves model names associated with a provider.
|
87
|
+
#
|
88
|
+
# @param provider [String, nil] The provider to filter models by.
|
89
|
+
# @return [Array<String>] List of model names.
|
90
|
+
#
|
38
91
|
def model_names(provider=nil)
|
39
92
|
model_ids = models.map { _1['id'] }
|
40
93
|
|
@@ -43,37 +96,72 @@ class AiClient
|
|
43
96
|
model_ids.filter_map { _1.split('/')[1] if _1.start_with?(provider.to_s.downcase) }
|
44
97
|
end
|
45
98
|
|
99
|
+
# Retrieves details of a specific model.
|
100
|
+
#
|
101
|
+
# @param model [String] The model ID to retrieve details for.
|
102
|
+
# @return [Hash, nil] Details of the model or nil if not found.
|
103
|
+
#
|
46
104
|
def model_details(model)
|
47
105
|
orc_models.find { _1['id'].include?(model) }
|
48
106
|
end
|
49
107
|
|
108
|
+
# Retrieves the available providers.
|
109
|
+
#
|
110
|
+
# @return [Array<String>] List of unique provider names.
|
111
|
+
#
|
50
112
|
def providers
|
51
113
|
@providers ||= models.map{ _1['id'].split('/')[0] }.sort.uniq
|
52
114
|
end
|
53
115
|
|
116
|
+
# Finds models matching a given substring.
|
117
|
+
#
|
118
|
+
# @param a_model_substring [String] The substring to search for.
|
119
|
+
# @return [Array<String>] List of matching model names.
|
120
|
+
#
|
54
121
|
def find_model(a_model_substring)
|
55
122
|
model_names.select{ _1.include?(a_model_substring) }
|
56
123
|
end
|
57
124
|
|
125
|
+
# Resets LLM data with the available ORC models.
|
126
|
+
#
|
127
|
+
# @return [void]
|
128
|
+
#
|
129
|
+
def reset_llm_data
|
130
|
+
LLM.data = orc_models
|
131
|
+
LLM::DATA_PATH.write(orc_models.to_yaml)
|
132
|
+
end
|
133
|
+
|
134
|
+
|
58
135
|
private
|
59
136
|
|
60
|
-
#
|
137
|
+
# Fetches the access token from environment variables.
|
138
|
+
#
|
139
|
+
# @return [String, nil] The access token or nil if not found.
|
140
|
+
#
|
61
141
|
def fetch_access_token
|
62
142
|
class_config.envar_api_key_names.open_router
|
63
143
|
.map { |key| ENV[key] }
|
64
144
|
.compact
|
65
145
|
.first
|
66
146
|
end
|
67
|
-
|
147
|
+
|
148
|
+
# Configures the OpenRouter client with the access token.
|
149
|
+
#
|
150
|
+
# @param access_token [String] The access token to configure.
|
151
|
+
# @return [void]
|
152
|
+
#
|
68
153
|
def configure_open_router(access_token)
|
69
154
|
OpenRouter.configure { |config| config.access_token = access_token }
|
70
155
|
end
|
71
156
|
|
157
|
+
# Initializes the ORC client.
|
158
|
+
#
|
159
|
+
# @return [void]
|
160
|
+
#
|
72
161
|
def initialize_orc_client
|
73
162
|
@orc_client ||= OpenRouter::Client.new
|
74
163
|
end
|
75
164
|
end
|
76
165
|
end
|
77
166
|
|
78
|
-
|
79
167
|
AiClient.add_open_router_extensions
|
@@ -11,12 +11,27 @@ class AiClient
|
|
11
11
|
# )
|
12
12
|
#
|
13
13
|
class RetryMiddleware
|
14
|
+
|
15
|
+
# Initializes a new instance of RetryMiddleware.
|
16
|
+
#
|
17
|
+
# @param max_retries [Integer] The maximum number of retries to attempt (default: 3).
|
18
|
+
# @param base_delay [Integer] The base delay in seconds before retrying (default: 2).
|
19
|
+
# @param max_delay [Integer] The maximum delay in seconds between retries (default: 16).
|
20
|
+
#
|
14
21
|
def initialize(max_retries: 3, base_delay: 2, max_delay: 16)
|
15
22
|
@max_retries = max_retries
|
16
23
|
@base_delay = base_delay
|
17
24
|
@max_delay = max_delay
|
18
25
|
end
|
19
26
|
|
27
|
+
# Calls the next middleware, retrying on specific errors.
|
28
|
+
#
|
29
|
+
# @param client [AiClient] The client instance that invokes the middleware.
|
30
|
+
# @param next_middleware [Proc] The next middleware in the chain to call.
|
31
|
+
# @param args [Array] Any additional arguments to pass to the next middleware.
|
32
|
+
#
|
33
|
+
# @raise [StandardError] Reraise the error if max retries are exceeded.
|
34
|
+
#
|
20
35
|
def call(client, next_middleware, *args)
|
21
36
|
retries = 0
|
22
37
|
begin
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# lib/ai_client/tool.rb
|
2
|
+
|
3
|
+
# TODO: Turn this into a Function class using the pattern
|
4
|
+
# in examples/tools.rb
|
5
|
+
# put the function names as symbols into a class Array
|
6
|
+
# In the AiClient class transform the tools: []
|
7
|
+
# parameter from an Array of Symbols into an Array
|
8
|
+
# of FUnction instances.
|
9
|
+
|
10
|
+
class AiClient::Tool < OmniAI::Tool
|
11
|
+
|
12
|
+
def xyzzy = self.class.xyzzy
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def xyzzy = puts "Magic"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
data/lib/ai_client/version.rb
CHANGED
data/lib/ai_client.rb
CHANGED
@@ -16,6 +16,8 @@ require 'omniai/openai'
|
|
16
16
|
|
17
17
|
require 'open_router'
|
18
18
|
|
19
|
+
require_relative 'ai_client/version'
|
20
|
+
|
19
21
|
require_relative 'ai_client/chat'
|
20
22
|
require_relative 'ai_client/embed'
|
21
23
|
require_relative 'ai_client/speak'
|
@@ -23,10 +25,10 @@ require_relative 'ai_client/transcribe'
|
|
23
25
|
|
24
26
|
require_relative 'ai_client/configuration'
|
25
27
|
require_relative 'ai_client/middleware'
|
26
|
-
require_relative 'ai_client/version'
|
27
28
|
|
28
29
|
require_relative 'ai_client/open_router_extensions'
|
29
30
|
require_relative 'ai_client/llm' # SMELL: must come after the open router stuff
|
31
|
+
require_relative 'ai_client/tool'
|
30
32
|
|
31
33
|
# Create a generic client instance using only model name
|
32
34
|
# client = AiClient.new('gpt-3.5-turbo')
|
@@ -76,6 +78,8 @@ class AiClient
|
|
76
78
|
:timeout,
|
77
79
|
:config # Instance configuration
|
78
80
|
|
81
|
+
# Initializes a new AiClient instance.
|
82
|
+
#
|
79
83
|
# You can over-ride the class config by providing a block like this
|
80
84
|
# c = AiClient.new(...) do |config|
|
81
85
|
# config.logger = nil
|
@@ -90,6 +94,14 @@ class AiClient
|
|
90
94
|
# The options object is basically those things that the
|
91
95
|
# OmniAI clients want to see.
|
92
96
|
#
|
97
|
+
# @param model [String] The model name to use for the client.
|
98
|
+
# @param options [Hash] Optional named parameters:
|
99
|
+
# - :provider [Symbol] Specify the provider.
|
100
|
+
# - :config [String] Path to a YAML configuration file.
|
101
|
+
# - :logger [Logger] Logger instance for the client.
|
102
|
+
# - :timeout [Integer] Timeout value for requests.
|
103
|
+
# @yield [config] An optional block to configure the instance.
|
104
|
+
#
|
93
105
|
def initialize(model, **options, &block)
|
94
106
|
# Assign the instance variable @config from the class variable @@config
|
95
107
|
@config = self.class.class_config.dup
|
@@ -121,13 +133,34 @@ class AiClient
|
|
121
133
|
@last_response = nil
|
122
134
|
end
|
123
135
|
|
136
|
+
# TODO: Review these raw-ish methods are they really needed?
|
137
|
+
# raw? should be a private method ??
|
138
|
+
|
139
|
+
# Returns the last response received from the client.
|
140
|
+
#
|
141
|
+
# @return [OmniAI::Response] The last response.
|
142
|
+
#
|
124
143
|
def response = last_response
|
144
|
+
|
145
|
+
# Checks if the client is set to return raw responses.
|
146
|
+
#
|
147
|
+
# @return [Boolean] True if raw responses are to be returned.
|
125
148
|
def raw? = config.return_raw
|
126
149
|
|
150
|
+
|
151
|
+
# Sets whether to return raw responses.
|
152
|
+
#
|
153
|
+
# @param value [Boolean] The value to set for raw responses return.
|
154
|
+
#
|
127
155
|
def raw=(value)
|
128
156
|
config.return_raw = value
|
129
157
|
end
|
130
158
|
|
159
|
+
# Extracts the content from the last response based on the provider.
|
160
|
+
#
|
161
|
+
# @return [String] The extracted content.
|
162
|
+
# @raise [NotImplementedError] If content extraction is not implemented for the provider.
|
163
|
+
#
|
131
164
|
def content
|
132
165
|
case @provider
|
133
166
|
when :localai, :mistral, :ollama, :open_router, :openai
|
@@ -142,6 +175,13 @@ class AiClient
|
|
142
175
|
end
|
143
176
|
alias_method :text, :content
|
144
177
|
|
178
|
+
# Handles calls to methods that are missing on the AiClient instance.
|
179
|
+
#
|
180
|
+
# @param method_name [Symbol] The name of the method called.
|
181
|
+
# @param args [Array] Arguments passed to the method.
|
182
|
+
# @param block [Proc] Optional block associated with the method call.
|
183
|
+
# @return [Object] The result from the underlying client or raises NoMethodError.
|
184
|
+
#
|
145
185
|
def method_missing(method_name, *args, &block)
|
146
186
|
if @client.respond_to?(method_name)
|
147
187
|
result = @client.send(method_name, *args, &block)
|
@@ -152,6 +192,12 @@ class AiClient
|
|
152
192
|
end
|
153
193
|
end
|
154
194
|
|
195
|
+
# Checks if the instance responds to the missing method.
|
196
|
+
#
|
197
|
+
# @param method_name [Symbol] The name of the method to check.
|
198
|
+
# @param include_private [Boolean] Whether to include private methods in the check.
|
199
|
+
# @return [Boolean] True if the method is supported by the client, false otherwise.
|
200
|
+
#
|
155
201
|
def respond_to_missing?(method_name, include_private = false)
|
156
202
|
@client.respond_to?(method_name) || super
|
157
203
|
end
|
@@ -160,6 +206,12 @@ class AiClient
|
|
160
206
|
##############################################
|
161
207
|
private
|
162
208
|
|
209
|
+
# Validates the specified provider.
|
210
|
+
#
|
211
|
+
# @param provider [Symbol] The provider to validate.
|
212
|
+
# @return [Symbol, nil] Returns the validated provider or nil.
|
213
|
+
# @raise [ArgumentError] If the provider is unsupported.
|
214
|
+
#
|
163
215
|
def validate_provider(provider)
|
164
216
|
return nil if provider.nil?
|
165
217
|
|
@@ -171,7 +223,11 @@ class AiClient
|
|
171
223
|
provider
|
172
224
|
end
|
173
225
|
|
174
|
-
|
226
|
+
# Creates an instance of the appropriate OmniAI client based on the provider.
|
227
|
+
#
|
228
|
+
# @return [OmniAI::Client] An instance of the configured OmniAI client.
|
229
|
+
# @raise [ArgumentError] If the provider is unsupported.
|
230
|
+
#
|
175
231
|
def create_client
|
176
232
|
client_options = {
|
177
233
|
api_key: fetch_api_key,
|
@@ -209,7 +265,10 @@ class AiClient
|
|
209
265
|
end
|
210
266
|
|
211
267
|
|
212
|
-
# Similar to
|
268
|
+
# Similar to fetch_access_token but for the instance config
|
269
|
+
#
|
270
|
+
# @return [String, nil] The retrieved API key or nil if not found.
|
271
|
+
#
|
213
272
|
def fetch_api_key
|
214
273
|
config.envar_api_key_names[@provider]
|
215
274
|
&.map { |key| ENV[key] }
|
@@ -217,6 +276,12 @@ class AiClient
|
|
217
276
|
&.first
|
218
277
|
end
|
219
278
|
|
279
|
+
# Determines the provider based on the provided model.
|
280
|
+
#
|
281
|
+
# @param model [String] The model name.
|
282
|
+
# @return [Symbol] The corresponding provider.
|
283
|
+
# @raise [ArgumentError] If the model is unsupported.
|
284
|
+
#
|
220
285
|
def determine_provider(model)
|
221
286
|
config.provider_patterns.find { |provider, pattern| model.match?(pattern) }&.first ||
|
222
287
|
raise(ArgumentError, "Unsupported model: #{model}")
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ai_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dewayne VanHoozer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-10-
|
11
|
+
date: 2024-10-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: active_hash
|
@@ -100,14 +100,14 @@ dependencies:
|
|
100
100
|
requirements:
|
101
101
|
- - ">="
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version:
|
103
|
+
version: 1.8.3
|
104
104
|
type: :runtime
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version:
|
110
|
+
version: 1.8.3
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: open_router
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -219,6 +219,7 @@ files:
|
|
219
219
|
- examples/embed.rb
|
220
220
|
- examples/speak.rb
|
221
221
|
- examples/text.rb
|
222
|
+
- examples/tools.rb
|
222
223
|
- examples/transcribe.rb
|
223
224
|
- lib/ai_client.rb
|
224
225
|
- lib/ai_client/chat.rb
|
@@ -228,9 +229,11 @@ files:
|
|
228
229
|
- lib/ai_client/llm.rb
|
229
230
|
- lib/ai_client/logger_middleware.rb
|
230
231
|
- lib/ai_client/middleware.rb
|
232
|
+
- lib/ai_client/models.yml
|
231
233
|
- lib/ai_client/open_router_extensions.rb
|
232
234
|
- lib/ai_client/retry_middleware.rb
|
233
235
|
- lib/ai_client/speak.rb
|
236
|
+
- lib/ai_client/tool.rb
|
234
237
|
- lib/ai_client/transcribe.rb
|
235
238
|
- lib/ai_client/version.rb
|
236
239
|
- sig/ai_client.rbs
|