ai_client 0.2.3 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|