ai_client 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b12bef21b23a46a47cc409042afd5eaae36d6cf1cdd2fae47f12fd0b958ed961
4
- data.tar.gz: 0c70c7f7e562da50876ee65c1f1de81f6ee7d37e97a2a7c114bb692ce5d6155d
3
+ metadata.gz: c938cc076640fa6952b1140252b0c234a44e7dfd6ee24d2485c344d3b2c98880
4
+ data.tar.gz: 1dc5cb21b49c2a731689e7ab7d4f6cce56392b06ecb83e2d54b535ec0d0b3d27
5
5
  SHA512:
6
- metadata.gz: 56d14adae8ab29719083dc7bb63edf33fa625237274edef043b3738f39757fa0c15983232c9aab0a2afbf8db33cbcafc89ed0cb39d0180d045e3c2f6f467a5fe
7
- data.tar.gz: 5c3d314a10a6aa6f10a51b6f8eaf574dea6d108feb5b5ec64f47fb0e494cc1ebe865a7b2fbc9401aada23c692cae2346a48dbcf062e84a202e080f25f0790d9d
6
+ metadata.gz: d2d81aa7bc979a8c75856965261ce54470ef3df1586eb65e809c2e4d32f8a4771a22a85640e026260826ab691e54742666ce0b9d71f2ba992b60db3d5c689cc8
7
+ data.tar.gz: 7347641ccb974d62c0393a364cb09b214019c0e99a2514478632a9b0c4736bf530c0ba415bae316b0438230691418265b21aed46ef32ca122935d73e30085f41
data/.irbrc ADDED
@@ -0,0 +1,3 @@
1
+ # .irbrc
2
+
3
+ require_relative 'lib/ai_client'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [0.1.0] - 2024-10-02
3
+ ## Released
4
+ ### [0.2.1] - 2024-10-05
5
+ - Added support for YAML configuration files
6
+
7
+ ### [0.2.0] - 2024-10-04
8
+ - Configuration is more robust. Still room for improvement.
9
+
10
+ ### [0.1.0] - 2024-10-02
4
11
 
5
12
  - Initial working release
data/README.md CHANGED
@@ -2,7 +2,11 @@
2
2
 
3
3
  First and foremost a big **THANK YOU** to [Kevin Sylvestre](https://ksylvest.com/) for his gem [OmniAI](https://github.com/ksylvest/omniai) upon which this effort depends.
4
4
 
5
- **This is a work in progress** Its implemented as a class rather than the typical module for most gems. The `AiClient::Configuration` class is a little first draft-ish. I'm looking to bulk it up a lot. At this point I think some of the current tests are failing; but, over all `AiClien` is working. I've used early versions of it in several projects.
5
+ **This is a work in progress** I could use your help extending its capability.
6
+
7
+ AiClien` is working. I've used early versions of it in several projects.
8
+
9
+ See the [change log](CHANGELOG.md) for recent modifications.
6
10
 
7
11
  ## Summary
8
12
 
@@ -41,6 +45,73 @@ c1 = AiClient.new('nomic-embeddings-text')
41
45
  c2 = AiClient.new('gpt-4o-mini')
42
46
  ```
43
47
 
48
+ ### Configuration
49
+
50
+ There are three levels of configuration, each inherenting from the level above. The following sections
51
+ describe those configuration levels.
52
+
53
+ #### Default Configuration
54
+
55
+ The file [lib/ai_client/configuration.rb] hard codes the default configuration. This is used to
56
+ update the [lib/ai_client/config.yml] file during development. If you have
57
+ some changes for this configuration please send me a pull request so we
58
+ can all benefit from your efforts.
59
+
60
+ #### Class Configuration
61
+
62
+ The class configuration is derived initially from the default configuration. It
63
+ can be changed in three ways.
64
+
65
+ 1. Class Configuration Block
66
+
67
+ ```ruby
68
+ AiClient.configuration do |config|
69
+ config.some_item = some_value
70
+ ...
71
+ end
72
+ ```
73
+
74
+ 2. Set by a Config File
75
+
76
+ ```ruby
77
+ AiClient.class_config = AiClient::Config.load('path/to/file.yml')
78
+ ```
79
+
80
+ 3. Supplemented by a Config File
81
+
82
+ ```ruby
83
+ AiClient.class_config.merge! AiClient::Config.load('path/to/file.yml')
84
+ ```
85
+
86
+ #### Instance Configuration
87
+
88
+ All instances have a configuration. Initially that configuration is the same
89
+ as the class configuration; however, each instance can have its own separate
90
+ configuration. For an instance the class configuration can either be supplemented
91
+ or complete over-ridden.
92
+
93
+ 1. Supplement from a Constructor Block
94
+
95
+ ```ruby
96
+ client = AiClient.new('super-ai-overlord-model') do |config|
97
+ config.some_item = some_value
98
+ ...
99
+ end
100
+ ```
101
+
102
+ 2. Suppliment from a YAML File
103
+
104
+ ```ruby
105
+ client = AiClient.new('baby-model', config: 'path/to/file.yml')
106
+ ```
107
+
108
+ 3. Load Complete Configuration from a YAML File
109
+
110
+ ```ruby
111
+ client = AiClient.new('your-model')
112
+ client.config = AiClient::Config.load('path/to/file.yml')
113
+ ```
114
+
44
115
  ### What Now?
45
116
 
46
117
  TODO: Document the methods and their options.
@@ -53,7 +124,7 @@ AI.embed(...)
53
124
  AI.batch_embed(...)
54
125
  ```
55
126
 
56
- TODO: see the [examples] directory.
127
+ See the [examples directory](examples/README.md) for some ideas on how to use AiClient.
57
128
 
58
129
  ### System Environment Variables
59
130
 
@@ -65,6 +136,15 @@ TODO: list all providers supported and their envar
65
136
 
66
137
  TODO: document the options like `provider: :ollama`
67
138
 
139
+ ## Extensions for OmniAI
140
+
141
+ The AiClient makes use of extensions to the OmniAI gem that define
142
+ additional providers and protocols.
143
+
144
+ 1. **OmniAI::Ollama^** which wraps the OmniAI::OpenAI class
145
+ 2. **OmniAI::LocalAI** which also wraps the OmniAI::OpenAI class
146
+ 3. **OmniAI::OpenRouter** TODO: Still under development
147
+
68
148
  ## Contributing
69
149
 
70
150
  I can sure use your help. This industry is moving faster than I can keep up with. If you have a bug fix or new feature idea then have at it. Send me a pull request so we all can benefit from your efforts.
@@ -0,0 +1,13 @@
1
+ # Examples
2
+
3
+ | File | Content |
4
+ | --- | --- |
5
+ | Bethany Hamilton.m4a | Audio file used to demonstrate transcribe|
6
+ | common.rb | Stuff used by each example |
7
+ | embed.rb | Demonstrates using Ollama locally to vectorize text for embeddings|
8
+ | speak.rb | Demonstrates using OpenAI's text to speech models |
9
+ | text.rb | Demonstrates text-to-text transformers "chat" |
10
+ | transcribe.rb | Uses OpenAI's audio-to-text model |
11
+
12
+ Many of these example programs show both the raw response object as well as just the
13
+ content from the response.
@@ -0,0 +1,33 @@
1
+ ---
2
+ :logger: !ruby/object:Logger
3
+ level: 0
4
+ progname:
5
+ default_formatter: !ruby/object:Logger::Formatter
6
+ datetime_format:
7
+ formatter:
8
+ logdev: !ruby/object:Logger::LogDevice
9
+ shift_period_suffix:
10
+ shift_size:
11
+ shift_age:
12
+ filename:
13
+ dev: !ruby/object:IO {}
14
+ binmode: false
15
+ reraise_write_errors: []
16
+ mon_data: !ruby/object:Monitor {}
17
+ mon_data_owner_object_id: 920
18
+ level_override: {}
19
+ :timeout:
20
+ :return_raw: false
21
+ :providers: {}
22
+ :provider_patterns:
23
+ :anthropic: !ruby/regexp /^claude/i
24
+ :openai: !ruby/regexp /^(gpt|davinci|curie|babbage|ada|whisper|tts|dall-e)/i
25
+ :google: !ruby/regexp /^(gemini|palm)/i
26
+ :mistral: !ruby/regexp /^(mistral|codestral)/i
27
+ :localai: !ruby/regexp /^local-/i
28
+ :ollama: !ruby/regexp /(llama|nomic)/i
29
+ :model_types:
30
+ :text_to_text: !ruby/regexp /^(nomic|gpt|davinci|curie|babbage|ada|claude|gemini|palm|command|generate|j2-|mistral|codestral)/i
31
+ :speech_to_text: !ruby/regexp /^whisper/i
32
+ :text_to_speech: !ruby/regexp /^tts/i
33
+ :text_to_image: !ruby/regexp /^dall-e/i
@@ -1,84 +1,148 @@
1
1
  # ai_client/configuration.rb
2
+ #
3
+ # AiClient and AiClient::Config
4
+ #
5
+ # The AiClient class provides a configurable client for interacting with various AI service providers.
6
+ # It allows users to set global configurations and provider-specific settings via a block.
7
+ #
8
+ # There are three levels of configuration:
9
+ # * default_config .. the starting point
10
+ # * class_config .... for all instances
11
+ # * config .......... for an instance
12
+ #
13
+ # Class Configuration
14
+ # starts with the default configuration but can
15
+ # be changed in three different ways.
16
+ # 1. Use the configuration block
17
+ # AiClient.configuration do |config|
18
+ # some_item = some_value
19
+ # ...
20
+ # end
21
+ #
22
+ # 2. Automatic YAML configuration file
23
+ # Set the system environment variable AI_CLIENT_CONFIG_FILE
24
+ # to an existing configuration file. The contents of that
25
+ # file will be automatically merged on top of the
26
+ # default configuration.
27
+ #
28
+ # 3. Manual YAML configuration file
29
+ # You can completely replace the class configuration
30
+ # AiClient.class_config = AiClient::Config.load('path/to/file.yml')
31
+ # You can supplement the existing class config
32
+ # AiClient.class_config.merge!(AiClient::Config.load('path/to/file.yml'))
33
+ #
34
+ # Instance Configuration
35
+ # AiClient is setup so that you can have multiple instance
36
+ # of clients each using a different model / provider and having
37
+ # a different configuration. There are several ways you
38
+ # can manipulate an instance's configuration.
39
+ #
40
+ # 1. The default instance configuration inherents from the
41
+ # the class configuration.
42
+ # client = AiClient.new('your_model')
43
+ # You can access the instance configuration using
44
+ # client.config.some_item
45
+ # client.config[:some_item]
46
+ # client.config['some_item']
47
+ # All three ways returns the value for that configuration item.
48
+ # To change the value of an item its also that simple.
49
+ # client.config.some_item = some_value
50
+ # client.config[:some_item] = some_value
51
+ # client.config['some_item'] = some_value
52
+ # 2. Instance constructor block
53
+ # client = AiClient.new('your_model') do |config|
54
+ # config.some_item = some_value
55
+ # ...
56
+ # end
57
+ #
58
+ # 3. Like the class configuration you can can replace or
59
+ # supplement an instance's configuration from a YAML file.
60
+ # client = AiClient.new('your_model', config: 'path/to/file.yml')
61
+ # client.config.merge!(AiClient::Config.load('path/to/file.yml'))
62
+ # Both of those example suppliment / over0ride items in
63
+ # the class configuration to become the instance's
64
+ # configuration. To completely replace the instance's
65
+ # configuration you can do this.
66
+ # client = AiClient.new('your_model')
67
+ # client.config = AiClient::Config.load('path/to/file.yml')
2
68
 
69
+
70
+ require 'hashie'
3
71
  require 'logger'
72
+ require 'yaml'
73
+ require 'pathname'
4
74
 
5
75
  class AiClient
6
- # TODO: Need a centralized service where
7
- # metadata about LLMs are available
8
- # via and API call. Would hope that
9
- # the providers would add a "list"
10
- # endpoint to their API which would
11
- # return the metadata for all of their
12
- # models.
76
+ class Config < Hashie::Mash
77
+ DEFAULT_CONFIG_FILEPATH = Pathname.new(__dir__) + 'config.yml'
78
+
79
+ include Hashie::Extensions::Mash::PermissiveRespondTo
80
+ include Hashie::Extensions::Mash::SymbolizeKeys
81
+ include Hashie::Extensions::Mash::DefineAccessors
82
+
13
83
 
14
- PROVIDER_PATTERNS = {
15
- anthropic: /^claude/i,
16
- openai: /^(gpt|davinci|curie|babbage|ada|whisper|tts|dall-e)/i,
17
- google: /^(gemini|palm)/i,
18
- mistral: /^(mistral|codestral)/i,
19
- localai: /^local-/i,
20
- ollama: /(llama-|nomic)/i
21
- }
84
+ # I'm not sure about this ...
85
+ # def provider(name, &block)
86
+ # if block_given?
87
+ # providers[name] = block.call
88
+ # else
89
+ # providers[name] || {}
90
+ # end
91
+ # end
22
92
 
23
- MODEL_TYPES = {
24
- text_to_text: /^(nomic|gpt|davinci|curie|babbage|ada|claude|gemini|palm|command|generate|j2-|mistral|codestral)/i,
25
- speech_to_text: /^whisper/i,
26
- text_to_speech: /^tts/i,
27
- text_to_image: /^dall-e/i
28
- }
93
+ class << self
94
+ def load(filepath=DEFAULT_CONFIG_FILEPATH)
95
+ filepath = Pathname.new(filepath) unless Pathname == filepath.class
96
+ if filepath.exist?
97
+ new(YAML.parse(filepath.read).to_ruby)
98
+ else
99
+ raise ArgumentError, "#{filepath} does not exist"
100
+ end
101
+ end
102
+ end
103
+ end
29
104
 
30
105
  class << self
106
+ attr_accessor :class_config, :default_config
31
107
 
32
- def configure
33
- yield(configuration)
108
+ def configure(&block)
109
+ yield(class_config)
34
110
  end
35
111
 
36
- def configuration
37
- @configuration ||= Configuration.new
38
- end
112
+ private
39
113
 
114
+ def initialize_defaults
115
+ @default_config = Config.new(
116
+ logger: Logger.new(STDOUT),
117
+ timeout: nil,
118
+ return_raw: false,
119
+ providers: {},
120
+ provider_patterns: {
121
+ anthropic: /^claude/i,
122
+ openai: /^(gpt|davinci|curie|babbage|ada|whisper|tts|dall-e)/i,
123
+ google: /^(gemini|palm)/i,
124
+ mistral: /^(mistral|codestral)/i,
125
+ localai: /^local-/i,
126
+ ollama: /(llama|nomic)/i
127
+ },
128
+ model_types: {
129
+ text_to_text: /^(nomic|gpt|davinci|curie|babbage|ada|claude|gemini|palm|command|generate|j2-|mistral|codestral)/i,
130
+ speech_to_text: /^whisper/i,
131
+ text_to_speech: /^tts/i,
132
+ text_to_image: /^dall-e/i
133
+ }
134
+ )
135
+ @class_config = @default_config.dup
136
+ end
40
137
  end
41
138
 
139
+ initialize_defaults
140
+ end
42
141
 
43
142
 
143
+ AiClient.default_config = AiClient::Config.load
144
+ AiClient.class_config = AiClient.default_config.dup
44
145
 
45
- # Usage example:
46
- # Configure general settings
47
- # AiClient.configure do |config|
48
- # config.logger = Logger.new('ai_client.log')
49
- # config.return_raw = true
50
- # end
51
- #
52
- # Configure provider-specific settings
53
- # AiClient.configure do |config|
54
- # config.configure_provider(:openai) do
55
- # {
56
- # organization: 'org-123',
57
- # api_version: 'v1'
58
- # }
59
- # end
60
- # end
61
- #
62
-
63
- class Configuration
64
- attr_accessor :logger, :timeout, :return_raw
65
- attr_reader :providers, :provider_patterns, :model_types
66
-
67
- def initialize
68
- @logger = Logger.new(STDOUT)
69
- @timeout = nil
70
- @return_raw = false
71
- @providers = {}
72
- @provider_patterns = AiClient::PROVIDER_PATTERNS.dup
73
- @model_types = AiClient::MODEL_TYPES.dup
74
- end
75
-
76
- def provider(name, &block)
77
- if block_given?
78
- @providers[name] = block
79
- else
80
- @providers[name]&.call || {}
81
- end
82
- end
83
- end
84
- end
146
+ if config_file = ENV.fetch('AI_CLIENT_CONFIG_FILE', nil)
147
+ AiClient.class_config.merge!(AiClient::Config.load(config_file))
148
+ end
@@ -0,0 +1,37 @@
1
+ # lib/ai_client/middleware.rb
2
+
3
+ # TODO: As concurrently designed the middleware must
4
+ # be set before an instance of AiClient is created.
5
+ # Any `use` commands for middleware made after
6
+ # the instance is created will not be available
7
+ # to that instance.
8
+ # Change this so that middleware can be added
9
+ # and removed from an existing client.
10
+
11
+
12
+ class AiClient
13
+
14
+ def call_with_middlewares(method, *args, **kwargs, &block)
15
+ stack = self.class.middlewares.reverse.reduce(-> { send(method, *args, **kwargs, &block) }) do |next_middleware, middleware|
16
+ -> { middleware.call(self, next_middleware, *args, **kwargs) }
17
+ end
18
+ stack.call
19
+ end
20
+
21
+
22
+ class << self
23
+
24
+ def middlewares
25
+ @middlewares ||= []
26
+ end
27
+
28
+ def use(middleware)
29
+ middlewares << middleware
30
+ end
31
+
32
+ def clear_middlewares
33
+ @middlewares = []
34
+ end
35
+ end
36
+
37
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class AiClient
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.1"
5
5
  end
data/lib/ai_client.rb CHANGED
@@ -17,6 +17,7 @@ require_relative 'extensions/omniai-ollama'
17
17
  require_relative 'extensions/omniai-localai'
18
18
 
19
19
  require_relative 'ai_client/configuration'
20
+ require_relative 'ai_client/middleware'
20
21
  require_relative 'ai_client/version'
21
22
 
22
23
  # Create a generic client instance using only model name
@@ -26,25 +27,53 @@ require_relative 'ai_client/version'
26
27
  # AiClient.use(RetryMiddleware.new(max_retries: 5, base_delay: 2, max_delay: 30))
27
28
  # AiClient.use(LoggingMiddleware.new(AiClient.configuration.logger))
28
29
  #
29
- # TODO: As concurrently designed the middleware must
30
- # be set before an instance of AiClient is created.
31
- # Any `use` commands for middleware made after
32
- # the instance is created will not be available
33
- # to that instance.
34
- # Change this so that middleware can be added
35
- # and removed from an existing client.
30
+
36
31
 
37
32
  class AiClient
38
33
 
39
- attr_reader :client, :provider, :model, :model_type, :logger, :last_response, :config
34
+ attr_reader :client, # OmniAI's client instance
35
+ :provider, # [Symbol]
36
+ :model, # [String]
37
+ :model_type, # [Symbol]
38
+ :logger,
39
+ :last_response,
40
+ :timeout,
41
+ :config # Instance configuration
42
+
43
+ # You can over-ride the class config by providing a block like this
44
+ # c = AiClient.new(...) do |config|
45
+ # config.logger = nil
46
+ # end
47
+ #
48
+ # You can also load an instance's config from a YAML file.
49
+ # c = AiClient.new('model_name'. cpmfog: 'path/to/file.yml', ...)
50
+ #
51
+ # ... and you can do both = load from a file and
52
+ # over-ride with a config block
53
+ #
54
+ # The options object is basically those things that the
55
+ # OmniAI clients want to see.
56
+ #
57
+ def initialize(model, **options, &block)
58
+ # Assign the instance variable @config from the class variable @@config
59
+ @config = self.class.class_config.dup
60
+
61
+ # Yield the @config to a block if given
62
+ yield(@config) if block_given?
63
+
64
+ # Merge in an instance-specific YAML file
65
+ if options.has_key?(:config)
66
+ @config.merge! Config.load(options[:config])
67
+ options.delete(:config) # Lconfig not supported by OmniAI
68
+ end
69
+
70
+ @model = model
71
+ explicit_provider = options.fetch(:provider, config.provider)
40
72
 
41
- def initialize(model, config: Configuration.new, **options)
42
- @model = model
43
- @config = config
44
- @provider = validate_provider(options[:provider]) || determine_provider(model)
73
+ @provider = validate_provider(explicit_provider) || determine_provider(model)
45
74
  @model_type = determine_model_type(model)
46
75
 
47
- provider_config = @config.provider(@provider)
76
+ provider_config = @config.providers[@provider] || {}
48
77
 
49
78
  @logger = options[:logger] || @config.logger
50
79
  @timeout = options[:timeout] || @config.timeout
@@ -58,6 +87,7 @@ class AiClient
58
87
  end
59
88
 
60
89
 
90
+
61
91
  def response = last_response
62
92
  def raw? = config.return_raw
63
93
 
@@ -71,7 +101,6 @@ class AiClient
71
101
  def chat(messages, **params)
72
102
  result = call_with_middlewares(:chat_without_middlewares, messages, **params)
73
103
  @last_response = result
74
- # debug_me print " (raw: #{raw?}) "
75
104
  raw? ? result : content
76
105
  end
77
106
 
@@ -114,14 +143,6 @@ class AiClient
114
143
  ######################################
115
144
  ## Utilities
116
145
 
117
- def call_with_middlewares(method, *args, **kwargs, &block)
118
- stack = self.class.middlewares.reverse.reduce(-> { send(method, *args, **kwargs, &block) }) do |next_middleware, middleware|
119
- -> { middleware.call(self, next_middleware, *args, **kwargs) }
120
- end
121
- stack.call
122
- end
123
-
124
-
125
146
  def content
126
147
  case @provider
127
148
  when :openai, :localai, :ollama
@@ -141,21 +162,6 @@ class AiClient
141
162
  ##############################################
142
163
  ## Public Class Methods
143
164
 
144
- class << self
145
-
146
- def middlewares
147
- @middlewares ||= []
148
- end
149
-
150
- def use(middleware)
151
- middlewares << middleware
152
- end
153
-
154
- def clear_middlewares
155
- @middlewares = []
156
- end
157
- end
158
-
159
165
  def method_missing(method_name, *args, &block)
160
166
  if @client.respond_to?(method_name)
161
167
  result = @client.send(method_name, *args, &block)
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ai_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
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-03 00:00:00.000000000 Z
11
+ date: 2024-10-05 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: hashie
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: omniai
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +122,20 @@ dependencies:
108
122
  - - ">="
109
123
  - !ruby/object:Gem::Version
110
124
  version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: hashdiff
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
111
139
  - !ruby/object:Gem::Dependency
112
140
  name: mocha
113
141
  requirement: !ruby/object:Gem::Requirement
@@ -141,19 +169,23 @@ extensions: []
141
169
  extra_rdoc_files: []
142
170
  files:
143
171
  - ".envrc"
172
+ - ".irbrc"
144
173
  - CHANGELOG.md
145
174
  - LICENSE
146
175
  - README.md
147
176
  - Rakefile
148
177
  - examples/Bethany Hamilton.m4a
178
+ - examples/README.md
149
179
  - examples/common.rb
150
180
  - examples/embed.rb
151
181
  - examples/speak.rb
152
182
  - examples/text.rb
153
183
  - examples/transcribe.rb
154
184
  - lib/ai_client.rb
185
+ - lib/ai_client/config.yml
155
186
  - lib/ai_client/configuration.rb
156
187
  - lib/ai_client/logger_middleware.rb
188
+ - lib/ai_client/middleware.rb
157
189
  - lib/ai_client/retry_middleware.rb
158
190
  - lib/ai_client/version.rb
159
191
  - lib/extensions/omniai-localai.rb