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.
@@ -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
- def models
32
- @models ||= orc_client.models
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
- # Similar to fetch_api_key but for the class_config
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
+
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class AiClient
4
- VERSION = "0.2.3"
4
+ VERSION = "0.2.5"
5
5
  end
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 fetch_access_tokne but for the instance config
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.3
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-10 00:00:00.000000000 Z
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: '0'
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: '0'
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