ai_client 0.1.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 +4 -4
- data/.irbrc +3 -0
- data/CHANGELOG.md +8 -1
- data/README.md +82 -2
- data/examples/README.md +13 -0
- data/lib/ai_client/config.yml +33 -0
- data/lib/ai_client/configuration.rb +130 -66
- data/lib/ai_client/middleware.rb +37 -0
- data/lib/ai_client/version.rb +1 -1
- data/lib/ai_client.rb +43 -37
- metadata +34 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c938cc076640fa6952b1140252b0c234a44e7dfd6ee24d2485c344d3b2c98880
|
4
|
+
data.tar.gz: 1dc5cb21b49c2a731689e7ab7d4f6cce56392b06ecb83e2d54b535ec0d0b3d27
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2d81aa7bc979a8c75856965261ce54470ef3df1586eb65e809c2e4d32f8a4771a22a85640e026260826ab691e54742666ce0b9d71f2ba992b60db3d5c689cc8
|
7
|
+
data.tar.gz: 7347641ccb974d62c0393a364cb09b214019c0e99a2514478632a9b0c4736bf530c0ba415bae316b0438230691418265b21aed46ef32ca122935d73e30085f41
|
data/.irbrc
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
-
##
|
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**
|
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
|
-
|
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.
|
data/examples/README.md
ADDED
@@ -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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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(
|
108
|
+
def configure(&block)
|
109
|
+
yield(class_config)
|
34
110
|
end
|
35
111
|
|
36
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
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
|
data/lib/ai_client/version.rb
CHANGED
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
|
-
|
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,
|
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
|
-
|
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.
|
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
|
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-
|
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
|