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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +278 -9
- data/lib/ai_client/chat.rb +64 -7
- data/lib/ai_client/config.yml +11 -17
- data/lib/ai_client/configuration.rb +12 -1
- data/lib/ai_client/llm.rb +13 -2
- data/lib/ai_client/middleware.rb +2 -2
- data/lib/ai_client/models.yml +526 -416
- data/lib/ai_client/open_router_extensions.rb +63 -94
- data/lib/ai_client/tool.rb +4 -7
- data/lib/ai_client/version.rb +4 -1
- data/lib/ai_client.rb +83 -47
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72666a60d1c49346ce8512fbfe97fbc163801f5cc22e9f7ed3883c3c8e28901b
|
4
|
+
data.tar.gz: bbcaee95e9ef2a6fd8bad5159ff8aa6d8cc88f443302dd37573c0bf7da512a66
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 04045a6f8a78671930429f9481e4319b36cf5955d802f8f6ae5b4ec6c4bd1b4c88d4854bcaa44fbb3b4a0f4b7d68c7a4f3d0c4045c767c590cf417efac905e5f
|
7
|
+
data.tar.gz: d26f1770c0dd38d6c83fed91eca4e8489ac485415b91bb0e0d5ef0496f09af20f0e532974b984fcabedd997562494f20881966b3b5bee0e1a091013431f5e18d
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,21 @@
|
|
2
2
|
|
3
3
|
## Released
|
4
4
|
|
5
|
+
### [0.4.0] - 2024-10-20
|
6
|
+
- Removed Logger.new(STDOUT) from the default configuration
|
7
|
+
> config.logger now returns nil If you want a class or instance logger setup, then you will have to do a config.logger = Logger.new(STDOUT) or whatever you need.
|
8
|
+
- Adding basic @context for chat-bots
|
9
|
+
- Added `context_length` to configuration as the number of responses to remember as context
|
10
|
+
- Added a default model for each major provider; using "auto" for open_router.ai
|
11
|
+
- Added a default provider (OpenAI)
|
12
|
+
> AiClient.new() will use the config.default_provider and that provider's default_model
|
13
|
+
- Fixed problem with advanced block-based prompt construction for chat
|
14
|
+
|
15
|
+
### [0.3.1] - 2024-10-19
|
16
|
+
- updated the open_routed_extensions file
|
17
|
+
- added simplecov to see code coverage
|
18
|
+
- updated README with options doc
|
19
|
+
|
5
20
|
### [0.3.0] - 2024-10-13
|
6
21
|
- Breaking Change
|
7
22
|
- Added new class AiClient::Function to encapsulate the callback functions used as tools in chats.
|
data/README.md
CHANGED
@@ -2,10 +2,14 @@
|
|
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) and [Olympia](https://olympia.chat/) for their [open_router gem](https://github.com/OlympiaAI/open_router) upon which this effort depends.
|
4
4
|
|
5
|
-
Version 0.
|
5
|
+
Version 0.4.0 has two changes which may break your existing application.
|
6
|
+
1. The default configuration no longer has a Logger instance. You will need to add your own instance to either the class or instance configuration using `AiClient.class_config.logger = YourLogger` and/or `client.config.logger = YourLogger`
|
7
|
+
2. The `chat` method now keeps a context window. The window length is defined by the configuration item `context_length` If you do not want to maintain a context window, set the `context_length` configuration item to either nil or zero.
|
6
8
|
|
7
9
|
See the [change log](CHANGELOG.md) for recent modifications.
|
8
10
|
|
11
|
+
You should also checkout the [raix gem](https://github.com/OlympiaAI/raix). I like the way that Obie's API is setup for callback functions. [raix-rails](https://github.com/OlympiaAI/raix-rails) is also available.
|
12
|
+
|
9
13
|
|
10
14
|
<!-- Tocer[start]: Auto-generated, don't remove. -->
|
11
15
|
|
@@ -30,13 +34,28 @@ See the [change log](CHANGELOG.md) for recent modifications.
|
|
30
34
|
- [3. Load Complete Configuration from a YAML File](#3-load-complete-configuration-from-a-yaml-file)
|
31
35
|
- [Top-level Client Methods](#top-level-client-methods)
|
32
36
|
- [chat](#chat)
|
37
|
+
- [Cpmtext](#cpmtext)
|
33
38
|
- [embed](#embed)
|
34
39
|
- [speak](#speak)
|
35
40
|
- [transcribe](#transcribe)
|
36
41
|
- [Options](#options)
|
42
|
+
- [Common Options for All Methods](#common-options-for-all-methods)
|
43
|
+
- [Chat-specific Options](#chat-specific-options)
|
44
|
+
- [Embed-specific Options](#embed-specific-options)
|
45
|
+
- [Speak-specific Options](#speak-specific-options)
|
46
|
+
- [Transcribe-specific Options](#transcribe-specific-options)
|
37
47
|
- [Advanced Prompts](#advanced-prompts)
|
38
48
|
- [Callback Functions (aka Tools)](#callback-functions-aka-tools)
|
39
49
|
- [Defining a Callback Function](#defining-a-callback-function)
|
50
|
+
- [OpenRouter Extensions and AiClient::LLM](#openrouter-extensions-and-aiclientllm)
|
51
|
+
- [Instance Methods](#instance-methods)
|
52
|
+
- [Class Methods](#class-methods)
|
53
|
+
- [AiClient::LLM Data Table](#aiclientllm-data-table)
|
54
|
+
- [Key Features](#key-features)
|
55
|
+
- [Class Methods](#class-methods-1)
|
56
|
+
- [Instance Methods](#instance-methods-1)
|
57
|
+
- [Usage Example](#usage-example)
|
58
|
+
- [Integration with ActiveHash](#integration-with-activehash)
|
40
59
|
- [Best ?? Practices](#best--practices)
|
41
60
|
- [OmniAI and OpenRouter](#omniai-and-openrouter)
|
42
61
|
- [Contributing](#contributing)
|
@@ -96,7 +115,7 @@ AiClient.class_config.envar_api_key_bames = {
|
|
96
115
|
google: 'your_envar_name',
|
97
116
|
mistral: 'your_envar_name',
|
98
117
|
open_router: 'your_envar_name',
|
99
|
-
|
118
|
+
openai: 'your_envar_name'
|
100
119
|
}
|
101
120
|
|
102
121
|
AiClient.class_config.save('path/to/file.yml')
|
@@ -123,13 +142,49 @@ To explicitly designate a provider to use with an AiClient instance use the para
|
|
123
142
|
Basic usage:
|
124
143
|
|
125
144
|
```ruby
|
126
|
-
|
145
|
+
require 'ai_client'
|
146
|
+
ai = AiClient.new # use default model and provider
|
147
|
+
ai.model #=> 'gpt-4o' is the default
|
148
|
+
ai.provider #=> :openai is the default
|
149
|
+
#
|
150
|
+
# To change the class defaults:
|
151
|
+
#
|
152
|
+
AiClient.default_provider = :anthropic
|
153
|
+
AiClient.default_model[:openai] = 'gpt-4o-mini'
|
154
|
+
#
|
155
|
+
# To get an Array of models and providers
|
156
|
+
#
|
157
|
+
AiClient.models # from open_router.ai
|
158
|
+
AiClient.providers # from open_router.ai
|
159
|
+
#
|
160
|
+
# To get details about a specific provider/model pair:
|
161
|
+
#
|
162
|
+
AiClient.model_details('openai/gpt-4o-mini') # from open_router.ai
|
163
|
+
```
|
164
|
+
|
165
|
+
You can specify which model you want to use and `AiClient` will use the provider associated with that model.
|
166
|
+
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
AI = AiClient.new('gpt-4o-mini') # sets provider to :openai
|
170
|
+
#
|
171
|
+
# If you want to use the open_router.ai service instead of
|
172
|
+
# going directly to OpenAI do it this way:
|
173
|
+
#
|
174
|
+
AI = AiClient.new('openai/gpt-4o-mini') # sets provider to :open_router
|
175
|
+
```
|
176
|
+
|
177
|
+
Of course you could specify both the model and the provider that you want to use:
|
178
|
+
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
AI = AiClient.new('mistral', provider: :ollama)
|
127
182
|
```
|
128
183
|
|
129
|
-
That's it.
|
184
|
+
That's it. What could be simpler? If your application is using more than one model, no worries, just create multiple `AiClient` instances.
|
130
185
|
|
131
186
|
```ruby
|
132
|
-
c1 = AiClient.new('nomic-
|
187
|
+
c1 = AiClient.new('nomic-embed-text')
|
133
188
|
c2 = AiClient.new('gpt-4o-mini')
|
134
189
|
```
|
135
190
|
|
@@ -148,6 +203,53 @@ There are three levels of configuration, each inherenting from the level above.
|
|
148
203
|
|
149
204
|
The file [lib/ai_client/configuration.rb] hard codes the default configuration. This is used to update the [lib/ai_client/config.yml] file during development. If you have some changes for this configuration please send me a pull request so we can all benefit from your efforts.
|
150
205
|
|
206
|
+
```ruby
|
207
|
+
{
|
208
|
+
:logger => nil,
|
209
|
+
:timeout => nil,
|
210
|
+
:return_raw => false,
|
211
|
+
:context_length => 5,
|
212
|
+
:providers => {},
|
213
|
+
:envar_api_key_names => {
|
214
|
+
:anthropic => [
|
215
|
+
"ANTHROPIC_API_KEY"
|
216
|
+
],
|
217
|
+
:google => [
|
218
|
+
"GOOGLE_API_KEY"
|
219
|
+
],
|
220
|
+
:mistral => [
|
221
|
+
"MISTRAL_API_KEY"
|
222
|
+
],
|
223
|
+
:open_router => [
|
224
|
+
"OPEN_ROUTER_API_KEY",
|
225
|
+
"OPENROUTER_API_KEY"
|
226
|
+
],
|
227
|
+
:openai => [
|
228
|
+
"OPENAI_API_KEY"
|
229
|
+
]
|
230
|
+
},
|
231
|
+
:provider_patterns => {
|
232
|
+
:anthropic => /^claude/i,
|
233
|
+
:openai => /^(gpt|chatgpt|o1|davinci|curie|babbage|ada|whisper|tts|dall-e)/i,
|
234
|
+
:google => /^(gemini|gemma|palm)/i,
|
235
|
+
:mistral => /^(mistral|codestral|mixtral)/i,
|
236
|
+
:localai => /^local-/i,
|
237
|
+
:ollama => /(llama|nomic)/i,
|
238
|
+
:open_router => /\//
|
239
|
+
},
|
240
|
+
:default_provider => :openai,
|
241
|
+
:default_model => {
|
242
|
+
:anthropic => "claude-3-5-sonnet-20240620",
|
243
|
+
:openai => "gpt-4o",
|
244
|
+
:google => "gemini-pro-1.5",
|
245
|
+
:mistral => "mistral-large",
|
246
|
+
:localai => "llama3.2",
|
247
|
+
:ollama => "llama3.2",
|
248
|
+
:open_router => "auto"
|
249
|
+
}
|
250
|
+
}
|
251
|
+
```
|
252
|
+
|
151
253
|
#### Class Configuration
|
152
254
|
|
153
255
|
The class configuration is derived initially from the default configuration. It can be changed in three ways.
|
@@ -227,6 +329,16 @@ The response will be a simple string or a response object based upon the setting
|
|
227
329
|
|
228
330
|
See the [Advanced Prompts] section to learn how to configure a complex prompt message.
|
229
331
|
|
332
|
+
####### Cpmtext
|
333
|
+
|
334
|
+
**context_length**
|
335
|
+
|
336
|
+
The `context_length` configuration item is used to keep the last "context_length" responses within the chat context window. If you do not want to keep a context window, you should set the value of `config.context_length = 0` When you do either at the class or instance level, the chat response will be provided without the LLM knowing any prior context. If you are implementing a chat-bot, you will want it to have a context of the current conversation.
|
337
|
+
|
338
|
+
```ruby
|
339
|
+
AiClient.config.context_length #=> 5
|
340
|
+
AiClient.config.context_length = 0 # Turns off the context window
|
341
|
+
```
|
230
342
|
|
231
343
|
##### embed
|
232
344
|
|
@@ -242,7 +354,7 @@ Recommendation: Use PostgreSQL, pg_vector and the neighbor gem.
|
|
242
354
|
##### speak
|
243
355
|
|
244
356
|
```ruby
|
245
|
-
|
357
|
+
response = AI.speak("Isn't it nice to have a computer that will talk to you?")
|
246
358
|
```
|
247
359
|
|
248
360
|
The response will contain audio data that can be played, manipulated or saved to a file.
|
@@ -254,17 +366,50 @@ response = AI.transcribe(...)
|
|
254
366
|
```
|
255
367
|
|
256
368
|
|
257
|
-
|
258
369
|
### Options
|
259
370
|
|
260
|
-
|
371
|
+
The four major methods (chat, embed, speak, and transcribe) support various options that can be passed to the underlying client code. Here's a breakdown of the common options for each method:
|
372
|
+
|
373
|
+
##### Common Options for All Methods
|
374
|
+
|
375
|
+
- `provider:` - Specifies the AI provider to use (e.g., `:openai`, `:anthropic`, `:google`, `:mistral`, `:ollama`, `:localai`).
|
376
|
+
- `model:` - Specifies the model to use within the chosen provider.
|
377
|
+
- `api_key:` - Allows passing a specific API key, overriding the default environment variable.
|
378
|
+
- `temperature:` - Controls the randomness of the output (typically a float between 0 and 1).
|
379
|
+
- `max_tokens:` - Limits the length of the generated response.
|
380
|
+
|
381
|
+
##### Chat-specific Options
|
382
|
+
|
383
|
+
- `messages:` - An array of message objects for multi-turn conversations.
|
384
|
+
- `functions:` - An array of available functions/tools for the model to use.
|
385
|
+
- `function_call:` - Specifies how the model should use functions ("auto", "none", or a specific function name).
|
386
|
+
- `stream:` - Boolean to enable streaming responses.
|
387
|
+
|
388
|
+
##### Embed-specific Options
|
389
|
+
|
390
|
+
- `input:` - The text or array of texts to embed.
|
391
|
+
- `dimensions:` - The desired dimensionality of the resulting embeddings (if supported by the model).
|
392
|
+
|
393
|
+
##### Speak-specific Options
|
394
|
+
|
395
|
+
- `voice:` - Specifies the voice to use for text-to-speech (provider-dependent).
|
396
|
+
- `speed:` - Adjusts the speaking rate (typically a float, where 1.0 is normal speed).
|
397
|
+
- `format:` - Specifies the audio format of the output (e.g., "mp3", "wav").
|
398
|
+
|
399
|
+
##### Transcribe-specific Options
|
400
|
+
|
401
|
+
- `file:` - The audio file to transcribe (can be a file path or audio data).
|
402
|
+
- `language:` - Specifies the language of the audio (if known).
|
403
|
+
- `prompt:` - Provides context or specific words to aid in transcription accuracy.
|
404
|
+
|
405
|
+
Note: The availability and exact names of these options may vary depending on the specific provider and model being used. Always refer to the documentation of the chosen provider for the most up-to-date and accurate information on supported options.
|
261
406
|
|
262
407
|
### Advanced Prompts
|
263
408
|
|
264
409
|
In more complex application providing a simple string as your prompt is not sufficient. AiClient can take advantage of OmniAI's complex message builder.
|
265
410
|
|
266
411
|
```ruby
|
267
|
-
client = AiClient.new '
|
412
|
+
client = AiClient.new 'some_model_name'
|
268
413
|
|
269
414
|
completion = client.chat do |prompt|
|
270
415
|
prompt.system('You are an expert biologist with an expertise in animals.')
|
@@ -331,6 +476,130 @@ In this example:
|
|
331
476
|
|
332
477
|
See the [examples/tools.rb file](examples/tools.rb) for additional examples.
|
333
478
|
|
479
|
+
### OpenRouter Extensions and AiClient::LLM
|
480
|
+
|
481
|
+
The `open_router.ai` API provides a service that allows you to download a JSON file containing detailed information about all of the providers and their available models. `AiClient` has saved an old copy of this information in the [`models.yml`](lib/ai_client/models.yml) file. If you want to update this file with the latest information from `open_router.ai`, you must have a valid API key.
|
482
|
+
|
483
|
+
You can still use the included `models.yml` file with the `AiClient::LLM` class. The following sections describe the convenient instance and class methods that are available. See the section on `AiClient::LLM` for complete details.
|
484
|
+
|
485
|
+
##### Instance Methods
|
486
|
+
|
487
|
+
- **`model_details`**: Retrieves details for the current model. Returns a hash containing the model's attributes or `nil` if not found.
|
488
|
+
|
489
|
+
```ruby
|
490
|
+
client = AiClient.new('gpt-3.5-turbo')
|
491
|
+
details = client.model_details
|
492
|
+
details #=>
|
493
|
+
{
|
494
|
+
:id => "openai/gpt-3.5-turbo",
|
495
|
+
:name => "OpenAI: GPT-3.5 Turbo",
|
496
|
+
:created => 1685232000,
|
497
|
+
:description => "GPT-3.5 Turbo is OpenAI's fastest model. It can understand and generate natural language or code, and is optimized for chat and traditional completion tasks.\n\nTraining data up to Sep 2021.",
|
498
|
+
:context_length => 16385,
|
499
|
+
:architecture => {
|
500
|
+
"modality" => "text->text",
|
501
|
+
"tokenizer" => "GPT",
|
502
|
+
"instruct_type" => nil
|
503
|
+
},
|
504
|
+
:pricing => {
|
505
|
+
"prompt" => "0.0000005",
|
506
|
+
"completion" => "0.0000015",
|
507
|
+
"image" => "0",
|
508
|
+
"request" => "0"
|
509
|
+
},
|
510
|
+
:top_provider => {
|
511
|
+
"context_length" => 16385,
|
512
|
+
"max_completion_tokens" => 4096,
|
513
|
+
"is_moderated" => true
|
514
|
+
},
|
515
|
+
:per_request_limits => {
|
516
|
+
"prompt_tokens" => "40395633",
|
517
|
+
"completion_tokens" => "13465211"
|
518
|
+
}
|
519
|
+
}
|
520
|
+
```
|
521
|
+
|
522
|
+
- **`models`**: Retrieves model names for the current provider. Returns an array of strings with the names of the models.
|
523
|
+
|
524
|
+
```ruby
|
525
|
+
models = client.models
|
526
|
+
```
|
527
|
+
|
528
|
+
##### Class Methods
|
529
|
+
|
530
|
+
- **`providers`**: Retrieves all available providers. Returns an array of unique symbols representing provider names.
|
531
|
+
|
532
|
+
```ruby
|
533
|
+
available_providers = AiClient.providers
|
534
|
+
```
|
535
|
+
|
536
|
+
- **`models(substring = nil)`**: Retrieves model IDs, optionally filtered by a substring.
|
537
|
+
|
538
|
+
```ruby
|
539
|
+
available_models = AiClient.models('turbo')
|
540
|
+
```
|
541
|
+
|
542
|
+
- **`model_details(model_id)`**: Retrieves details for a specific model using its ID. Accepts a string representing the model ID. Returns a hash containing the model's attributes or `nil` if not found.
|
543
|
+
|
544
|
+
```ruby
|
545
|
+
model_info = AiClient.model_details('openai/gpt-3.5-turbo')
|
546
|
+
```
|
547
|
+
|
548
|
+
- **`reset_llm_data`**: Resets the LLM data with the available ORC models. Returns `void`.
|
549
|
+
|
550
|
+
```ruby
|
551
|
+
AiClient.reset_llm_data
|
552
|
+
```
|
553
|
+
|
554
|
+
### AiClient::LLM Data Table
|
555
|
+
|
556
|
+
The `AiClient::LLM` class serves as a central point for managing information about large language models (LLMs) available via the `open_router.ai` API service. The YAML file (`models.yml`) contains information about various LLMs and their providers. To update this information to the latest available, you must have an access API key for the `open_router.ai` service.
|
557
|
+
|
558
|
+
`AiClient::LLM` is a subclass of `ActiveHash::Base`, enabling it to act like and interact with `ActiveRecord::Base` defined models. Each entry in this data store is uniquely identified by an `id` in the pattern "provider/model" all lowercase without spaces.
|
559
|
+
|
560
|
+
##### Key Features
|
561
|
+
|
562
|
+
- **Model and Provider Extraction**:
|
563
|
+
- The class provides methods to extract the model name and provider from the LLM's ID.
|
564
|
+
- The `model` method returns the model ID derived from the ID.
|
565
|
+
- The `provider` method extracts the provider name as a Symbol.
|
566
|
+
|
567
|
+
##### Class Methods
|
568
|
+
|
569
|
+
- **`reset_llm_data`**:
|
570
|
+
- A class-level method that fetches the latest model data from the open_router.ai service and updates the `models.yml` file accordingly.
|
571
|
+
|
572
|
+
##### Instance Methods
|
573
|
+
|
574
|
+
- **`model`**:
|
575
|
+
- Returns the name of the model derived from the LLM's ID.
|
576
|
+
|
577
|
+
```ruby
|
578
|
+
llm_instance = AiClient::LLM.find('openai/gpt-3.5-turbo')
|
579
|
+
puts llm_instance.model # Output: gpt-3.5-turbo
|
580
|
+
```
|
581
|
+
|
582
|
+
- **`provider`**:
|
583
|
+
- Returns the name of the provider associated with the LLM's ID.
|
584
|
+
|
585
|
+
```ruby
|
586
|
+
llm_instance = AiClient::LLM.find('openai/gpt-3.5-turbo')
|
587
|
+
puts llm_instance.provider # Output: :openai
|
588
|
+
```
|
589
|
+
|
590
|
+
##### Usage Example
|
591
|
+
|
592
|
+
The `AiClient::LLM` class is predominantly used to interact with different providers of LLMs. By utilizing the `model` and `provider` methods, users can seamlessly retrieve and utilize models in their applications.
|
593
|
+
|
594
|
+
```ruby
|
595
|
+
llm_instance = AiClient::LLM.find('google/bard')
|
596
|
+
puts "Model: #{llm_instance.model}, Provider: #{llm_instance.provider}"
|
597
|
+
```
|
598
|
+
|
599
|
+
##### Integration with ActiveHash
|
600
|
+
|
601
|
+
The `AiClient::LLM` class inherits from `ActiveHash::Base`, which provides an easy way to define a set of data and allows for lookups and easy manipulation of the data structure. The use of ActiveHash makes it easier to manage the LLM data effectively without needing a full database.
|
602
|
+
|
334
603
|
|
335
604
|
## Best ?? Practices
|
336
605
|
|
data/lib/ai_client/chat.rb
CHANGED
@@ -9,8 +9,17 @@ class AiClient
|
|
9
9
|
# stream: @stream [Proc, nil] optional
|
10
10
|
# tools: @tools [Array<OmniAI::Tool>] optional
|
11
11
|
# temperature: @temperature [Float, nil] optional
|
12
|
-
|
13
|
-
|
12
|
+
#
|
13
|
+
# Initiates a chat session.
|
14
|
+
#
|
15
|
+
# @param messages [Array<String>] the messages to send.
|
16
|
+
# @param params [Hash] optional parameters.
|
17
|
+
# @option params [Array<OmniAI::Tool>] :tools an array of tools to use.
|
18
|
+
# @return [String] the result from the chat.
|
19
|
+
#
|
20
|
+
# @raise [RuntimeError] if tools parameter is invalid.
|
21
|
+
#
|
22
|
+
def chat(messages='', **params, &block)
|
14
23
|
if params.has_key? :tools
|
15
24
|
tools = params[:tools]
|
16
25
|
if tools.is_a? Array
|
@@ -23,14 +32,62 @@ class AiClient
|
|
23
32
|
params[:tools] = tools
|
24
33
|
end
|
25
34
|
|
26
|
-
|
27
|
-
|
28
|
-
|
35
|
+
@last_messages = messages
|
36
|
+
messages = add_context(messages)
|
37
|
+
result = call_with_middlewares(
|
38
|
+
:chat_without_middlewares,
|
39
|
+
messages,
|
40
|
+
**params,
|
41
|
+
&block
|
42
|
+
)
|
43
|
+
@last_response = result
|
44
|
+
result = raw? ? result : content
|
45
|
+
|
46
|
+
@context.push(@last_response)
|
47
|
+
|
48
|
+
result
|
29
49
|
end
|
30
50
|
|
31
51
|
|
32
|
-
|
33
|
-
|
52
|
+
# Adds context to the current prompt.
|
53
|
+
#
|
54
|
+
# @param prompt [String, Array<String>] the current prompt.
|
55
|
+
# @return [String, Array<String>] the prompt with context added.
|
56
|
+
#
|
57
|
+
def add_context(prompt)
|
58
|
+
return(prompt) if @config.context_length.nil? ||
|
59
|
+
0 == @config.context_length ||
|
60
|
+
prompt.is_a?(Array) ||
|
61
|
+
@context.empty?
|
62
|
+
|
63
|
+
|
64
|
+
prompt << "\nUse the following context in crafting your response.\n"
|
65
|
+
|
66
|
+
@context[..config.context_length].each do |result|
|
67
|
+
prompt << "You previously responded with:\n"
|
68
|
+
prompt << "#{raw? ? result.inspect : content(result)}"
|
69
|
+
end
|
70
|
+
|
71
|
+
prompt
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
# Clears the current context.
|
76
|
+
#
|
77
|
+
# @return [void]
|
78
|
+
#
|
79
|
+
def clear_context
|
80
|
+
@context = []
|
34
81
|
end
|
35
82
|
|
83
|
+
|
84
|
+
# Chats with the client without middleware processing.
|
85
|
+
#
|
86
|
+
# @param messages [Array<String>] the messages to send.
|
87
|
+
# @param params [Hash] optional parameters.
|
88
|
+
# @return [String] the result from the chat.
|
89
|
+
#
|
90
|
+
def chat_without_middlewares(messages, **params, &block)
|
91
|
+
@client.chat(messages, model: @model, **params, &block)
|
92
|
+
end
|
36
93
|
end
|
data/lib/ai_client/config.yml
CHANGED
@@ -1,23 +1,8 @@
|
|
1
1
|
---
|
2
|
-
: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: 45380
|
18
|
-
level_override: {}
|
2
|
+
:logger:
|
19
3
|
:timeout:
|
20
4
|
:return_raw: false
|
5
|
+
:context_length: 5
|
21
6
|
:providers: {}
|
22
7
|
:envar_api_key_names:
|
23
8
|
:anthropic:
|
@@ -39,3 +24,12 @@
|
|
39
24
|
:localai: !ruby/regexp /^local-/i
|
40
25
|
:ollama: !ruby/regexp /(llama|nomic)/i
|
41
26
|
:open_router: !ruby/regexp /\//
|
27
|
+
:default_provider: :openai
|
28
|
+
:default_model:
|
29
|
+
:anthropic: claude-3-5-sonnet-20240620
|
30
|
+
:openai: gpt-4o
|
31
|
+
:google: gemini-pro-1.5
|
32
|
+
:mistral: mistral-large
|
33
|
+
:localai: llama3.2
|
34
|
+
:ollama: llama3.2
|
35
|
+
:open_router: auto
|
@@ -152,9 +152,10 @@ class AiClient
|
|
152
152
|
#
|
153
153
|
def initialize_defaults
|
154
154
|
@default_config = Config.new(
|
155
|
-
logger: Logger.new(STDOUT),
|
155
|
+
logger: nil, # Logger.new(STDOUT),
|
156
156
|
timeout: nil,
|
157
157
|
return_raw: false,
|
158
|
+
context_length: 5, # number of responses to add as context
|
158
159
|
providers: {},
|
159
160
|
envar_api_key_names: {
|
160
161
|
anthropic: ['ANTHROPIC_API_KEY'],
|
@@ -171,6 +172,16 @@ class AiClient
|
|
171
172
|
localai: /^local-/i,
|
172
173
|
ollama: /(llama|nomic)/i,
|
173
174
|
open_router: /\//
|
175
|
+
},
|
176
|
+
default_provider: :openai,
|
177
|
+
default_model: {
|
178
|
+
anthropic: 'claude-3-5-sonnet-20240620',
|
179
|
+
openai: 'gpt-4o',
|
180
|
+
google: 'gemini-pro-1.5',
|
181
|
+
mistral: 'mistral-large',
|
182
|
+
localai: 'llama3.2',
|
183
|
+
ollama: 'llama3.2',
|
184
|
+
open_router: 'auto'
|
174
185
|
}
|
175
186
|
)
|
176
187
|
|
data/lib/ai_client/llm.rb
CHANGED
@@ -9,6 +9,13 @@ class AiClient
|
|
9
9
|
DATA_PATH = Pathname.new( __dir__ + '/models.yml')
|
10
10
|
self.data = YAML.parse(DATA_PATH.read).to_ruby
|
11
11
|
|
12
|
+
scope :providers, -> {all.map(&:provider).uniq.map(&:to_sym)}
|
13
|
+
|
14
|
+
scope :models, ->(substring=nil) do
|
15
|
+
(substring.nil? ? all : all.where(id: /#{substring}/i))
|
16
|
+
.map(&:model).sort.uniq
|
17
|
+
end
|
18
|
+
|
12
19
|
# Extracts the model name from the LLM ID.
|
13
20
|
#
|
14
21
|
# @return [String] the model name.
|
@@ -17,9 +24,12 @@ class AiClient
|
|
17
24
|
|
18
25
|
# Extracts the provider name from the LLM ID.
|
19
26
|
#
|
20
|
-
# @return [
|
27
|
+
# @return [Symbol] the provider name.
|
21
28
|
#
|
22
|
-
def provider = id.split('/')[0]
|
29
|
+
def provider = id.split('/')[0].to_sym
|
30
|
+
|
31
|
+
def to_h = attributes
|
32
|
+
|
23
33
|
end
|
24
34
|
|
25
35
|
class << self
|
@@ -34,5 +44,6 @@ class AiClient
|
|
34
44
|
AiClient::LLM.data = orc_models
|
35
45
|
AiClient::LLM::DATA_PATH.write(orc_models.to_yaml)
|
36
46
|
end
|
47
|
+
|
37
48
|
end
|
38
49
|
end
|
data/lib/ai_client/middleware.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# lib/ai_client/middleware.rb
|
2
2
|
|
3
|
-
# TODO: As
|
3
|
+
# TODO: As currently designed the middleware must
|
4
4
|
# be set before an instance of AiClient is created.
|
5
5
|
# Any `use` commands for middleware made after
|
6
6
|
# the instance is created will not be available
|
@@ -23,7 +23,7 @@ class AiClient
|
|
23
23
|
#
|
24
24
|
def call_with_middlewares(method, *args, **kwargs, &block)
|
25
25
|
stack = self.class.middlewares.reverse.reduce(-> { send(method, *args, **kwargs, &block) }) do |next_middleware, middleware|
|
26
|
-
-> { middleware.call(self, next_middleware, *args, **kwargs) }
|
26
|
+
-> { middleware.call(self, next_middleware, *args, **kwargs, &block) }
|
27
27
|
end
|
28
28
|
stack.call
|
29
29
|
end
|