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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2453f748447f37e755f0087615c845570fe3ea3c4bd06a687947dceedee3e89b
4
- data.tar.gz: 79359bcd209448add248514d9a8ee5dc4e0fa69833666df0e779715b574d450a
3
+ metadata.gz: 72666a60d1c49346ce8512fbfe97fbc163801f5cc22e9f7ed3883c3c8e28901b
4
+ data.tar.gz: bbcaee95e9ef2a6fd8bad5159ff8aa6d8cc88f443302dd37573c0bf7da512a66
5
5
  SHA512:
6
- metadata.gz: 28007804ea1e223b22846cc199c4bd14d7349f5e051bbc7007ced3641e7174c3fac59b7a225bc4926ffc68d5018a35ed96a8809bf959c8007e978bc831770b9a
7
- data.tar.gz: 18ce04f9f83d0c12caadab051b81c48f7dbafa73ff0b2a34df28e3ddfb5ff3c088dcec019a15bc2f2dfc909bb6db0a308b512b6711baaab058629705a1448040
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.3.0 has a breaking change w/r/t how [Callback Functions (aka Tools)](#callback-functions-aka-tools) are defined and used.
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
- opena: 'your_envar_name'
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
- AI = AiClient.new('gpt-4o')
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. Just provide the model name that you want to use. If you application is using more than one model, no worries, just create multiple AiClient instances.
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-embeddings-text')
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
- res[pmse = AI.speak("Isn't it nice to have a computer that will talk to you?")
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
- TODO: document the options like `provider: :ollama`
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 'some_model_bane'
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
 
@@ -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
- def chat(messages, **params)
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
- result = call_with_middlewares(:chat_without_middlewares, messages, **params)
27
- @last_response = result
28
- raw? ? result : content
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
- def chat_without_middlewares(messages, **params)
33
- @client.chat(messages, model: @model, **params)
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
@@ -1,23 +1,8 @@
1
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: 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 [String] the provider name.
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
@@ -1,6 +1,6 @@
1
1
  # lib/ai_client/middleware.rb
2
2
 
3
- # TODO: As concurrently designed the middleware must
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