ai_client 0.2.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: 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