ai_client 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,60 +1,76 @@
1
1
  # lib/ai_client/open_router_extensions.rb
2
- # frozen_string_literal: true
3
2
 
4
- # These extensions to AiClient are only available with
5
- # a valid API Key for the open_router.ai web-service
3
+ # OpenRouter Extensions for AiClient
4
+ #
5
+ # This file adds several public instance and class methods to the AiClient class
6
+ # to provide information about AI models and providers.
7
+ #
8
+ # Instance Methods:
9
+ # - model_details: Retrieves details for the current model.
10
+ # - models: Retrieves model names for the current provider.
11
+ #
12
+ # Class Methods:
13
+ # - providers: Retrieves all available providers.
14
+ # - models: Retrieves model names, optionally filtered by provider.
15
+ # - model_details: Retrieves details for a specific model.
16
+ #
17
+ # These methods utilize the AiClient::LLM class and the models.yml file
18
+ # for model information.
6
19
 
7
20
  require 'open_router'
8
21
  require 'yaml'
9
22
 
10
23
  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
24
 
20
- # Retrieves the available providers.
25
+ # Retrieves details for the current model.
21
26
  #
22
- # @return [Array<String>] List of provider names.
23
- def providers
24
- self.class.providers
27
+ # @return [Hash, nil] Details of the current model or nil if not found.
28
+ def model_details
29
+ id = "#{@provider}/#{@model}"
30
+ LLM.find(id.downcase)
25
31
  end
26
32
 
27
- # Retrieves model names, optionally filtered by provider.
33
+ # Retrieves model names for the current provider.
28
34
  #
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
35
+ # @return [Array<String>] List of model names for the current provider.
36
+ def models = LLM.models(@provider)
34
37
 
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
38
 
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
39
+ class << self
40
+
41
+ # Retrieves all available providers.
42
+ #
43
+ # @return [Array<Symbol>] List of all provider names.
44
+ def providers = LLM.providers
50
45
 
51
46
 
52
- class << self
53
-
54
- # Adds OpenRouter extensions to AiClient.
47
+ # Retrieves model names, optionally filtered by provider.
48
+ #
49
+ # @param substring [String, nil] Optional substring to filter models by.
50
+ # @return [Array<String>] List of model names.
51
+ def models(substring = nil) = LLM.models(substring)
52
+
53
+ # Retrieves details for a specific model.
54
+ #
55
+ # @param model_id [String] The model ID to retrieve details for,
56
+ # in the pattern "provider/model".downcase
57
+ # @return [AiClient::LLM, nil] Details of the model or nil if not found.
58
+ def model_details(model_id) = LLM.find(model_id.downcase)
59
+
60
+
61
+ # Resets LLM data with the available ORC models.
55
62
  #
56
63
  # @return [void]
57
64
  #
65
+ def reset_llm_data = LLM.reset_llm_data
66
+
67
+
68
+ # Initializes OpenRouter extensions for AiClient.
69
+ #
70
+ # This sets up the access token and initializes the ORC client.
71
+ #
72
+ # @return [void]
73
+ #
58
74
  def add_open_router_extensions
59
75
  access_token = fetch_access_token
60
76
 
@@ -64,7 +80,9 @@ class AiClient
64
80
  initialize_orc_client
65
81
  end
66
82
 
67
- # Retrieves ORC client instance.
83
+
84
+
85
+ # Retrieves the ORC client instance.
68
86
  #
69
87
  # @return [OpenRouter::Client] Instance of the OpenRouter client.
70
88
  #
@@ -72,6 +90,10 @@ class AiClient
72
90
  @orc_client ||= add_open_router_extensions || raise("OpenRouter extensions are not available")
73
91
  end
74
92
 
93
+
94
+ private
95
+
96
+
75
97
  # Retrieves models from the ORC client.
76
98
  #
77
99
  # @return [Array<Hash>] List of models.
@@ -80,59 +102,7 @@ class AiClient
80
102
  @orc_models ||= orc_client.models
81
103
  end
82
104
 
83
- # TODO: Refactor these DB like methods to take
84
- # advantage of AiClient::LLM
85
105
 
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
- #
91
- def model_names(provider=nil)
92
- model_ids = models.map { _1['id'] }
93
-
94
- return model_ids unless provider
95
-
96
- model_ids.filter_map { _1.split('/')[1] if _1.start_with?(provider.to_s.downcase) }
97
- end
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
- #
104
- def model_details(model)
105
- orc_models.find { _1['id'].include?(model) }
106
- end
107
-
108
- # Retrieves the available providers.
109
- #
110
- # @return [Array<String>] List of unique provider names.
111
- #
112
- def providers
113
- @providers ||= models.map{ _1['id'].split('/')[0] }.sort.uniq
114
- end
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
- #
121
- def find_model(a_model_substring)
122
- model_names.select{ _1.include?(a_model_substring) }
123
- end
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
-
135
- private
136
106
 
137
107
  # Fetches the access token from environment variables.
138
108
  #
@@ -154,10 +124,9 @@ class AiClient
154
124
  OpenRouter.configure { |config| config.access_token = access_token }
155
125
  end
156
126
 
157
- # Initializes the ORC client.
158
- #
159
- # @return [void]
127
+ # Initializes the ORC client instance.
160
128
  #
129
+ # @return [OpenRouter::Client] Instance of the OpenRouter client.
161
130
  def initialize_orc_client
162
131
  @orc_client ||= OpenRouter::Client.new
163
132
  end
@@ -1,14 +1,11 @@
1
1
  # lib/ai_client/tool.rb
2
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
3
  class AiClient::Tool < OmniAI::Tool
11
4
 
5
+ # TODO: Is there any additional functionality that
6
+ # needs to be added to the Rool class that would
7
+ # be helpful?
8
+
12
9
  def xyzzy = self.class.xyzzy
13
10
 
14
11
  class << self
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class AiClient
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
+
6
+ def version = VERSION
7
+ def self.version = VERSION
5
8
  end
data/lib/ai_client.rb CHANGED
@@ -1,6 +1,17 @@
1
1
  # ai_client.rb
2
- # WIP: a generic client to access LLM providers
3
- # kinda like the SaaS "open router"
2
+
3
+ # A generic client to access various LLM providers
4
+ # Inspired by the SaaS "open router" concept
5
+
6
+
7
+ # AiClient: A unified interface for interacting with various LLM providers
8
+ #
9
+ # Usage:
10
+ # client = AiClient.new('gpt-3.5-turbo')
11
+ #
12
+ # Add middlewares:
13
+ # AiClient.use(RetryMiddleware.new(max_retries: 5, base_delay: 2, max_delay: 30))
14
+ # AiClient.use(LoggingMiddleware.new(AiClient.configuration.logger))
4
15
  #
5
16
 
6
17
  unless defined?(DebugMe)
@@ -74,10 +85,12 @@ class AiClient
74
85
  attr_reader :client, # OmniAI's client instance
75
86
  :provider, # [Symbol]
76
87
  :model, # [String]
77
- :logger,
88
+ :logger,
89
+ :last_message,
78
90
  :last_response,
79
91
  :timeout,
80
- :config # Instance configuration
92
+ :config, # Instance configuration
93
+ :context # chat-bot context
81
94
 
82
95
  # Initializes a new AiClient instance.
83
96
  #
@@ -103,72 +116,54 @@ class AiClient
103
116
  # - :timeout [Integer] Timeout value for requests.
104
117
  # @yield [config] An optional block to configure the instance.
105
118
  #
106
- def initialize(model, **options, &block)
107
- # Assign the instance variable @config from the class variable @@config
108
- @config = self.class.class_config.dup
109
-
110
- # Yield the @config to a block if given
111
- yield(@config) if block_given?
112
-
113
- # Merge in an instance-specific YAML file
114
- if options.has_key?(:config)
115
- @config.merge! Config.load(options[:config])
116
- options.delete(:config) # Lconfig not supported by OmniAI
117
- end
118
-
119
- @model = model
120
- explicit_provider = options.fetch(:provider, config.provider)
121
-
122
- @provider = validate_provider(explicit_provider) || determine_provider(model)
123
-
124
- provider_config = @config.providers[@provider] || {}
125
-
126
- @logger = options[:logger] || @config.logger
127
- @timeout = options[:timeout] || @config.timeout
128
- @base_url = options[:base_url] || provider_config[:base_url]
129
- @options = options.merge(provider_config)
119
+ def initialize(model = nil, **options, &block)
120
+ @context = [] # An Array of String or response objects
121
+ @last_messages = nil
122
+ @last_response = nil
130
123
 
131
- # @client is an instance of an OmniAI::* class
132
- @client = create_client
124
+ setup_config(options, &block)
125
+ set_provider_and_model(model, options[:provider])
126
+ setup_instance_variables(options)
133
127
 
134
- @last_response = nil
128
+ @client = create_client
135
129
  end
136
130
 
137
- # TODO: Review these raw-ish methods are they really needed?
138
- # raw? should be a private method ??
139
-
140
- # Returns the last response received from the client.
141
- #
142
- # @return [OmniAI::Response] The last response.
143
- #
144
- def response = last_response
145
131
 
146
132
  # Checks if the client is set to return raw responses.
147
133
  #
148
134
  # @return [Boolean] True if raw responses are to be returned.
149
- def raw? = config.return_raw
150
-
135
+ def raw?
136
+ config.return_raw
137
+ end
138
+
151
139
 
152
140
  # Sets whether to return raw responses.
153
141
  #
154
142
  # @param value [Boolean] The value to set for raw responses return.
155
- #
156
143
  def raw=(value)
157
144
  config.return_raw = value
158
145
  end
159
146
 
147
+
148
+ # Returns the last response received from the client.
149
+ #
150
+ # @return [OmniAI::Response] The last response.
151
+ #
152
+ def response = last_response
153
+
154
+
160
155
  # Extracts the content from the last response based on the provider.
161
156
  #
162
157
  # @return [String] The extracted content.
163
158
  # @raise [NotImplementedError] If content extraction is not implemented for the provider.
164
159
  #
165
- def content
160
+ def content(response=last_response)
166
161
  case @provider
167
162
  when :localai, :mistral, :ollama, :open_router, :openai
168
- last_response.data.tunnel 'content'
163
+ response.data.tunnel 'content'
169
164
 
170
165
  when :anthropic, :google
171
- last_response.data.tunnel 'text'
166
+ response.data.tunnel 'text'
172
167
 
173
168
  else
174
169
  raise NotImplementedError, "Content extraction not implemented for provider: #{@provider}"
@@ -207,6 +202,47 @@ class AiClient
207
202
  ##############################################
208
203
  private
209
204
 
205
+ def setup_config(options, &block)
206
+ @config = self.class.class_config.dup
207
+
208
+ yield(@config) if block_given?
209
+
210
+ if options.key?(:config)
211
+ @config.merge!(Config.load(options[:config]))
212
+ options.delete(:config) # config not supported by OmniAI
213
+ end
214
+ end
215
+
216
+
217
+ def set_provider_and_model(my_model, my_provider)
218
+ if my_model.nil?
219
+ if my_provider.nil?
220
+ @provider = @config.default_provider.to_sym
221
+ else
222
+ @provider = validate_provider(my_provider)
223
+ end
224
+ @model = @config.default_model[@provider]
225
+ else
226
+ @model = my_model
227
+ if my_provider.nil?
228
+ @provider = determine_provider(my_model)
229
+ else
230
+ @provider = validate_provider(my_provider)
231
+ end
232
+ end
233
+ end
234
+
235
+
236
+ def setup_instance_variables(options)
237
+ provider_config = @config.providers[@provider] || {}
238
+
239
+ @logger = options[:logger] || @config.logger
240
+ @timeout = options[:timeout] || @config.timeout
241
+ @base_url = options[:base_url] || provider_config[:base_url]
242
+ @options = options.merge(provider_config)
243
+ end
244
+
245
+
210
246
  # Validates the specified provider.
211
247
  #
212
248
  # @param provider [Symbol] The provider to validate.
@@ -284,9 +320,9 @@ class AiClient
284
320
  # @raise [ArgumentError] If the model is unsupported.
285
321
  #
286
322
  def determine_provider(model)
323
+ return nil if model.nil? || model.empty?
324
+
287
325
  config.provider_patterns.find { |provider, pattern| model.match?(pattern) }&.first ||
288
326
  raise(ArgumentError, "Unsupported model: #{model}")
289
327
  end
290
328
  end
291
-
292
-
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.3.0
4
+ version: 0.4.0
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-14 00:00:00.000000000 Z
11
+ date: 2024-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: active_hash
@@ -262,7 +262,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
262
262
  - !ruby/object:Gem::Version
263
263
  version: '0'
264
264
  requirements: []
265
- rubygems_version: 3.5.21
265
+ rubygems_version: 3.5.22
266
266
  signing_key:
267
267
  specification_version: 4
268
268
  summary: A generic AI Client for many providers