ai_client 0.2.0 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 107e9d57f175adc72178f48365a833a4dab7fbe71088169774429f43624d7d9c
4
- data.tar.gz: d308008540c4fbf309235c31d60b1a98b14a608a0f69d7ee1bac6911f34dad47
3
+ metadata.gz: c938cc076640fa6952b1140252b0c234a44e7dfd6ee24d2485c344d3b2c98880
4
+ data.tar.gz: 1dc5cb21b49c2a731689e7ab7d4f6cce56392b06ecb83e2d54b535ec0d0b3d27
5
5
  SHA512:
6
- metadata.gz: b001c640758c846f2372354660d4a4f4c7ef4a6c4671bbd743fcc713d543faff1fe54ff2e341285ec57dbaf284db3e3a18a50cbd6b420fd01ac911112a93ae8a
7
- data.tar.gz: 0055623e374bb4cf111a234712e9ac1441d6ddb541f2f7ebadc604c255e27bdd6ad14d1b2a81d56cb6e2797e01f3d3398eb4b4b3e6c7d04802dadf620ecb0390
6
+ metadata.gz: d2d81aa7bc979a8c75856965261ce54470ef3df1586eb65e809c2e4d32f8a4771a22a85640e026260826ab691e54742666ce0b9d71f2ba992b60db3d5c689cc8
7
+ data.tar.gz: 7347641ccb974d62c0393a364cb09b214019c0e99a2514478632a9b0c4736bf530c0ba415bae316b0438230691418265b21aed46ef32ca122935d73e30085f41
data/CHANGELOG.md CHANGED
@@ -1,6 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
3
  ## Released
4
+ ### [0.2.1] - 2024-10-05
5
+ - Added support for YAML configuration files
6
+
4
7
  ### [0.2.0] - 2024-10-04
5
8
  - Configuration is more robust. Still room for improvement.
6
9
 
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
 
@@ -43,28 +47,71 @@ c2 = AiClient.new('gpt-4o-mini')
43
47
 
44
48
  ### Configuration
45
49
 
46
- There is an internal hard-coded configuration default. That default is duppled into a class-level configuration which can be over-ridden with a class-level config block like this ...
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
47
66
 
48
67
  ```ruby
49
- AiClient.configure do |config|
68
+ AiClient.configuration do |config|
50
69
  config.some_item = some_value
70
+ ...
51
71
  end
52
72
  ```
53
73
 
54
- Every instance of the AiClient inherents the class-level configuration; however, the instance configuration can also be over-ridden also with a block like this ...
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
55
94
 
56
95
  ```ruby
57
96
  client = AiClient.new('super-ai-overlord-model') do |config|
58
97
  config.some_item = some_value
98
+ ...
59
99
  end
60
100
  ```
61
101
 
62
- But wait, there's more. You can also load a YAML file as a configuration of an instance like this ...
102
+ 2. Suppliment from a YAML File
63
103
 
64
104
  ```ruby
65
105
  client = AiClient.new('baby-model', config: 'path/to/file.yml')
66
106
  ```
67
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
+
68
115
  ### What Now?
69
116
 
70
117
  TODO: Document the methods and their options.
@@ -77,7 +124,7 @@ AI.embed(...)
77
124
  AI.batch_embed(...)
78
125
  ```
79
126
 
80
- TODO: see the [examples] directory.
127
+ See the [examples directory](examples/README.md) for some ideas on how to use AiClient.
81
128
 
82
129
  ### System Environment Variables
83
130
 
@@ -89,6 +136,15 @@ TODO: list all providers supported and their envar
89
136
 
90
137
  TODO: document the options like `provider: :ollama`
91
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
+
92
148
  ## Contributing
93
149
 
94
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,27 +1,81 @@
1
1
  # ai_client/configuration.rb
2
2
  #
3
- # Design Objective:
4
- # AiClient.configure do |config|
5
- # # global config items that over-ride the defaults
6
- # end
3
+ # AiClient and AiClient::Config
7
4
  #
8
- # client = AiClient.new(...) do
9
- # # client specific config items that over-ride the global config
10
- # end
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')
68
+
11
69
 
12
70
  require 'hashie'
13
71
  require 'logger'
72
+ require 'yaml'
73
+ require 'pathname'
14
74
 
15
75
  class AiClient
16
- # TODO: Use system environment varibles
17
- # AI_CLIENT_CONFIG_FILE
18
- #
19
- # TODO: Config.load('path/to/some_file.yml')
20
- # @@default_config (on require from lib/config.yml)
21
- # @@config (if the envar exists ?? merge with default)
22
- # @config ... done
23
-
24
76
  class Config < Hashie::Mash
77
+ DEFAULT_CONFIG_FILEPATH = Pathname.new(__dir__) + 'config.yml'
78
+
25
79
  include Hashie::Extensions::Mash::PermissiveRespondTo
26
80
  include Hashie::Extensions::Mash::SymbolizeKeys
27
81
  include Hashie::Extensions::Mash::DefineAccessors
@@ -36,48 +90,59 @@ class AiClient
36
90
  # end
37
91
  # end
38
92
 
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
39
103
  end
40
104
 
41
-
42
- # Class variables to hold default and current config
43
- @@default_config = Config.new(
44
- logger: Logger.new(STDOUT),
45
- timeout: nil,
46
- return_raw: false,
47
- providers: {},
48
- provider_patterns: {
49
- anthropic: /^claude/i,
50
- openai: /^(gpt|davinci|curie|babbage|ada|whisper|tts|dall-e)/i,
51
- google: /^(gemini|palm)/i,
52
- mistral: /^(mistral|codestral)/i,
53
- localai: /^local-/i,
54
- ollama: /(llama|nomic)/i
55
- },
56
- model_types: {
57
- text_to_text: /^(nomic|gpt|davinci|curie|babbage|ada|claude|gemini|palm|command|generate|j2-|mistral|codestral)/i,
58
- speech_to_text: /^whisper/i,
59
- text_to_speech: /^tts/i,
60
- text_to_image: /^dall-e/i
61
- }
62
- )
63
-
64
- @@class_config = @@default_config.dup
65
-
66
105
  class << self
106
+ attr_accessor :class_config, :default_config
107
+
67
108
  def configure(&block)
68
109
  yield(class_config)
69
110
  end
70
111
 
71
- def class_config
72
- @@class_config
73
- end
74
-
75
- def class_config=(value)
76
- @@class_config = value
77
- end
112
+ private
78
113
 
79
- def default_config
80
- @@default_config
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
81
136
  end
82
137
  end
138
+
139
+ initialize_defaults
140
+ end
141
+
142
+
143
+ AiClient.default_config = AiClient::Config.load
144
+ AiClient.class_config = AiClient.default_config.dup
145
+
146
+ if config_file = ENV.fetch('AI_CLIENT_CONFIG_FILE', nil)
147
+ AiClient.class_config.merge!(AiClient::Config.load(config_file))
83
148
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class AiClient
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ai_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dewayne VanHoozer
@@ -122,6 +122,20 @@ dependencies:
122
122
  - - ">="
123
123
  - !ruby/object:Gem::Version
124
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'
125
139
  - !ruby/object:Gem::Dependency
126
140
  name: mocha
127
141
  requirement: !ruby/object:Gem::Requirement
@@ -161,12 +175,14 @@ files:
161
175
  - README.md
162
176
  - Rakefile
163
177
  - examples/Bethany Hamilton.m4a
178
+ - examples/README.md
164
179
  - examples/common.rb
165
180
  - examples/embed.rb
166
181
  - examples/speak.rb
167
182
  - examples/text.rb
168
183
  - examples/transcribe.rb
169
184
  - lib/ai_client.rb
185
+ - lib/ai_client/config.yml
170
186
  - lib/ai_client/configuration.rb
171
187
  - lib/ai_client/logger_middleware.rb
172
188
  - lib/ai_client/middleware.rb