durable-llm 0.1.5 → 0.1.6
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/CLI.md +0 -2
- data/README.md +564 -31
- data/Rakefile +10 -0
- data/devenv.lock +76 -8
- data/devenv.nix +4 -1
- data/devenv.yaml +5 -12
- data/durable-llm.gemspec +16 -8
- data/examples/openai_quick_complete.rb +1 -1
- data/lib/durable/llm/cli.rb +25 -22
- data/lib/durable/llm/client.rb +149 -8
- data/lib/durable/llm/convenience.rb +102 -0
- data/lib/durable/llm/provider_utilities.rb +201 -0
- data/lib/durable/llm/providers/base.rb +146 -19
- data/lib/durable/llm/providers/cohere.rb +19 -0
- data/lib/durable/llm/providers/fireworks.rb +26 -0
- data/lib/durable/llm/providers/google.rb +26 -0
- data/lib/durable/llm/providers/groq.rb +26 -0
- data/lib/durable/llm/providers.rb +10 -0
- data/lib/durable/llm/response_helpers.rb +185 -0
- data/lib/durable/llm/version.rb +1 -1
- data/lib/durable/llm.rb +75 -2
- data/lib/durable.rb +1 -1
- data/sig/durable/llm.rbs +2 -1
- metadata +71 -5
- data/Gemfile.lock +0 -103
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a39c75959d7172f1dfbe23b18f9a02de90563524afa33088a9d83f7d6e1efccb
|
|
4
|
+
data.tar.gz: e27fdca6dc2cbcd027e0bbf515db0eb6d4f57b29b0e2766838819b3f830fb78a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 005d079c618c5c5ba45434894d3d9884d5e1feb015f7443c11a1bc92c4609cf34f7a39c02e4f8df4082f74e8fc5ac2152fe0ba380591af0ff1e012cf0e512a85
|
|
7
|
+
data.tar.gz: 463745392a4cfc07206806e29a1c812e8d3b316fb554cf1cd61b27eb59e8eaab9d2bca964cb522f8f3626d1c0c0427e450d1169ea2a93b835369b2b286a55d2d
|
data/CLI.md
CHANGED
data/README.md
CHANGED
|
@@ -12,75 +12,608 @@ gem 'durable-llm'
|
|
|
12
12
|
|
|
13
13
|
And then execute:
|
|
14
14
|
|
|
15
|
-
```
|
|
15
|
+
```bash
|
|
16
16
|
$ bundle install
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
Or install it yourself as:
|
|
20
20
|
|
|
21
|
-
```
|
|
21
|
+
```bash
|
|
22
22
|
$ gem install durable-llm
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
##
|
|
25
|
+
## Quick Start
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
### Simple Completion
|
|
28
28
|
|
|
29
29
|
```ruby
|
|
30
30
|
require 'durable-llm'
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
# Quick and simple - just provide an API key and model
|
|
33
|
+
client = Durable::Llm.new(:openai, api_key: 'your-api-key', model: 'gpt-4')
|
|
34
|
+
response = client.complete('What is the capital of France?')
|
|
35
|
+
puts response # => "The capital of France is Paris."
|
|
36
|
+
```
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
### Using Global Configuration
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
require 'durable-llm'
|
|
42
|
+
|
|
43
|
+
# Configure once, use everywhere
|
|
44
|
+
Durable::Llm.configure do |config|
|
|
45
|
+
config.openai.api_key = 'your-openai-api-key'
|
|
46
|
+
config.anthropic.api_key = 'your-anthropic-api-key'
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Create clients without passing API keys
|
|
50
|
+
client = Durable::Llm.new(:openai, model: 'gpt-4')
|
|
51
|
+
response = client.complete('Hello, world!')
|
|
52
|
+
puts response
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Chat Conversations
|
|
38
56
|
|
|
57
|
+
```ruby
|
|
58
|
+
client = Durable::Llm.new(:openai, model: 'gpt-4')
|
|
59
|
+
|
|
60
|
+
response = client.chat(
|
|
61
|
+
messages: [
|
|
62
|
+
{ role: 'system', content: 'You are a helpful assistant.' },
|
|
63
|
+
{ role: 'user', content: 'What is Ruby?' }
|
|
64
|
+
]
|
|
65
|
+
)
|
|
39
66
|
puts response.choices.first.message.content
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Streaming Responses
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
client = Durable::Llm.new(:openai, model: 'gpt-4')
|
|
73
|
+
|
|
74
|
+
client.stream(messages: [{ role: 'user', content: 'Count to 10' }]) do |chunk|
|
|
75
|
+
content = chunk.dig('choices', 0, 'delta', 'content')
|
|
76
|
+
print content if content
|
|
77
|
+
end
|
|
78
|
+
puts # New line after streaming
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Embeddings
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
client = Durable::Llm.new(:openai)
|
|
85
|
+
|
|
86
|
+
response = client.embed(
|
|
87
|
+
model: 'text-embedding-ada-002',
|
|
88
|
+
input: 'Ruby is a dynamic programming language'
|
|
89
|
+
)
|
|
40
90
|
|
|
91
|
+
embedding = response.data.first.embedding
|
|
92
|
+
puts "Vector dimensions: #{embedding.length}"
|
|
41
93
|
```
|
|
42
94
|
|
|
43
95
|
## Features
|
|
44
96
|
|
|
45
|
-
- Unified
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
97
|
+
- **Unified Interface**: Consistent API across 15+ LLM providers
|
|
98
|
+
- **Multiple Providers**: OpenAI, Anthropic, Google, Cohere, Mistral, and more
|
|
99
|
+
- **Streaming Support**: Real-time streaming responses
|
|
100
|
+
- **Embeddings**: Generate text embeddings for semantic search
|
|
101
|
+
- **Configuration Management**: Flexible configuration via environment variables or code
|
|
102
|
+
- **Error Handling**: Comprehensive error types for precise handling
|
|
103
|
+
- **CLI Tool**: Command-line interface for quick testing and exploration
|
|
50
104
|
|
|
51
105
|
## Supported Providers
|
|
52
106
|
|
|
53
|
-
-
|
|
54
|
-
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
107
|
+
Durable-LLM supports the following LLM providers:
|
|
108
|
+
|
|
109
|
+
- **OpenAI** - GPT-3.5, GPT-4, GPT-4 Turbo, and embeddings
|
|
110
|
+
- **Anthropic** - Claude 3 (Opus, Sonnet, Haiku)
|
|
111
|
+
- **Google** - Gemini models
|
|
112
|
+
- **Cohere** - Command and embedding models
|
|
113
|
+
- **Mistral AI** - Mistral models
|
|
114
|
+
- **Groq** - Fast inference with various models
|
|
115
|
+
- **Fireworks AI** - High-performance model serving
|
|
116
|
+
- **Together AI** - Open-source model hosting
|
|
117
|
+
- **DeepSeek** - DeepSeek models
|
|
118
|
+
- **OpenRouter** - Access to multiple models via single API
|
|
119
|
+
- **Perplexity** - Perplexity models
|
|
120
|
+
- **xAI** - Grok models
|
|
121
|
+
- **Azure OpenAI** - Microsoft Azure-hosted OpenAI models
|
|
122
|
+
- **HuggingFace** - Open-source models via Hugging Face
|
|
123
|
+
- **OpenCode** - Code-specialized models
|
|
58
124
|
|
|
59
125
|
## Configuration
|
|
60
126
|
|
|
61
|
-
|
|
127
|
+
### Environment Variables
|
|
128
|
+
|
|
129
|
+
Set API keys using environment variables with the `DLLM__` prefix:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
export DLLM__OPENAI__API_KEY=your-openai-key
|
|
133
|
+
export DLLM__ANTHROPIC__API_KEY=your-anthropic-key
|
|
134
|
+
export DLLM__GOOGLE__API_KEY=your-google-key
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Programmatic Configuration
|
|
138
|
+
|
|
139
|
+
Configure API keys and settings in your code:
|
|
62
140
|
|
|
63
141
|
```ruby
|
|
142
|
+
# Environment variables (recommended)
|
|
143
|
+
# export DLLM__OPENAI__API_KEY=your-openai-key
|
|
144
|
+
# export DLLM__ANTHROPIC__API_KEY=your-anthropic-key
|
|
145
|
+
|
|
146
|
+
# Or configure programmatically
|
|
64
147
|
Durable::Llm.configure do |config|
|
|
65
|
-
config.
|
|
66
|
-
config.
|
|
67
|
-
config.
|
|
68
|
-
|
|
148
|
+
config.openai.api_key = 'sk-...'
|
|
149
|
+
config.anthropic.api_key = 'sk-ant-...'
|
|
150
|
+
config.google.api_key = 'your-google-key'
|
|
151
|
+
config.cohere.api_key = 'your-cohere-key'
|
|
69
152
|
end
|
|
153
|
+
|
|
154
|
+
# Create clients with automatic configuration
|
|
155
|
+
client = Durable::Llm.new(:openai) # Uses configured API key
|
|
156
|
+
client = Durable::Llm.new(:anthropic, model: 'claude-3-sonnet-20240229')
|
|
70
157
|
```
|
|
71
158
|
|
|
72
|
-
|
|
159
|
+
### Per-Client Configuration
|
|
73
160
|
|
|
74
|
-
|
|
161
|
+
Pass configuration directly when creating a client:
|
|
75
162
|
|
|
76
163
|
```ruby
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
164
|
+
client = Durable::Llm.new(
|
|
165
|
+
:openai,
|
|
166
|
+
api_key: 'your-api-key',
|
|
167
|
+
model: 'gpt-4',
|
|
168
|
+
timeout: 120
|
|
169
|
+
)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## API Reference
|
|
173
|
+
|
|
174
|
+
### Client Methods
|
|
175
|
+
|
|
176
|
+
#### `new(provider, options = {})`
|
|
177
|
+
|
|
178
|
+
Creates a new LLM client for the specified provider.
|
|
179
|
+
|
|
180
|
+
**Parameters:**
|
|
181
|
+
- `provider` (Symbol) - Provider name (`:openai`, `:anthropic`, etc.)
|
|
182
|
+
- `options` (Hash) - Configuration options
|
|
183
|
+
- `:model` - Default model to use
|
|
184
|
+
- `:api_key` - API key for authentication
|
|
185
|
+
- Other provider-specific options
|
|
186
|
+
|
|
187
|
+
**Returns:** `Durable::Llm::Client` instance
|
|
188
|
+
|
|
189
|
+
#### `complete(text, opts = {})`
|
|
190
|
+
|
|
191
|
+
Performs a simple text completion with minimal configuration.
|
|
192
|
+
|
|
193
|
+
**Parameters:**
|
|
194
|
+
- `text` (String) - Input text to complete
|
|
195
|
+
- `opts` (Hash) - Additional options (reserved for future use)
|
|
196
|
+
|
|
197
|
+
**Returns:** String with the completion text
|
|
198
|
+
|
|
199
|
+
**Note:** The older method name `quick_complete` is still supported as an alias for backward compatibility.
|
|
200
|
+
|
|
201
|
+
**Example:**
|
|
202
|
+
```ruby
|
|
203
|
+
client = Durable::Llm.new(:openai, model: 'gpt-4')
|
|
204
|
+
response = client.complete('Explain quantum computing in one sentence')
|
|
205
|
+
puts response
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
#### `completion(params = {})`
|
|
209
|
+
|
|
210
|
+
Performs a completion request with full control over parameters.
|
|
211
|
+
|
|
212
|
+
**Parameters:**
|
|
213
|
+
- `params` (Hash) - Completion parameters
|
|
214
|
+
- `:model` - Model to use (overrides default)
|
|
215
|
+
- `:messages` - Array of message hashes with `:role` and `:content`
|
|
216
|
+
- `:temperature` - Sampling temperature (0.0-2.0)
|
|
217
|
+
- `:max_tokens` - Maximum tokens to generate
|
|
218
|
+
- Other provider-specific parameters
|
|
219
|
+
|
|
220
|
+
**Returns:** Response object with completion data
|
|
221
|
+
|
|
222
|
+
**Example:**
|
|
223
|
+
```ruby
|
|
224
|
+
response = client.completion(
|
|
225
|
+
messages: [
|
|
226
|
+
{ role: 'system', content: 'You are a helpful coding assistant.' },
|
|
227
|
+
{ role: 'user', content: 'Write a Ruby method to reverse a string' }
|
|
228
|
+
],
|
|
229
|
+
temperature: 0.7,
|
|
230
|
+
max_tokens: 500
|
|
231
|
+
)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
#### `chat(params = {})`
|
|
235
|
+
|
|
236
|
+
Alias for `completion` - performs a chat completion request.
|
|
237
|
+
|
|
238
|
+
#### `stream(params = {}, &block)`
|
|
239
|
+
|
|
240
|
+
Performs a streaming completion request, yielding chunks as they arrive.
|
|
241
|
+
|
|
242
|
+
**Parameters:**
|
|
243
|
+
- `params` (Hash) - Same as `completion`
|
|
244
|
+
- `block` - Block to process each chunk
|
|
245
|
+
|
|
246
|
+
**Example:**
|
|
247
|
+
```ruby
|
|
248
|
+
client.stream(messages: [{ role: 'user', content: 'Write a story' }]) do |chunk|
|
|
249
|
+
content = chunk.dig('choices', 0, 'delta', 'content')
|
|
250
|
+
print content if content
|
|
251
|
+
end
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
#### `embed(params = {})`
|
|
255
|
+
|
|
256
|
+
Generates embeddings for the given text.
|
|
257
|
+
|
|
258
|
+
**Parameters:**
|
|
259
|
+
- `params` (Hash) - Embedding parameters
|
|
260
|
+
- `:model` - Embedding model to use
|
|
261
|
+
- `:input` - Text or array of texts to embed
|
|
262
|
+
|
|
263
|
+
**Returns:** Response object with embedding vectors
|
|
264
|
+
|
|
265
|
+
**Example:**
|
|
266
|
+
```ruby
|
|
267
|
+
response = client.embed(
|
|
268
|
+
model: 'text-embedding-ada-002',
|
|
269
|
+
input: ['First text', 'Second text']
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
embeddings = response.data.map(&:embedding)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
## CLI Tool
|
|
277
|
+
|
|
278
|
+
Durable-LLM includes a command-line tool (`dllm`) for quick interactions:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
# One-shot completion
|
|
282
|
+
$ dllm prompt "What is Ruby?" -m gpt-3.5-turbo
|
|
283
|
+
|
|
284
|
+
# Interactive chat
|
|
285
|
+
$ dllm chat -m gpt-4
|
|
286
|
+
|
|
287
|
+
# List available models
|
|
288
|
+
$ dllm models
|
|
289
|
+
|
|
290
|
+
# Manage conversations
|
|
291
|
+
$ dllm conversations
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Advanced Usage
|
|
295
|
+
|
|
296
|
+
### Fluent API with Method Chaining
|
|
297
|
+
|
|
298
|
+
```ruby
|
|
299
|
+
client = Durable::Llm.new(:openai, model: 'gpt-3.5-turbo')
|
|
300
|
+
|
|
301
|
+
# Chain configuration methods for cleaner code
|
|
302
|
+
result = client
|
|
303
|
+
.with_model('gpt-4')
|
|
304
|
+
.with_temperature(0.7)
|
|
305
|
+
.with_max_tokens(500)
|
|
306
|
+
.complete('Write a haiku about Ruby')
|
|
307
|
+
|
|
308
|
+
puts result
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Response Helpers
|
|
312
|
+
|
|
313
|
+
Extract information from responses easily:
|
|
314
|
+
|
|
315
|
+
```ruby
|
|
316
|
+
require 'durable-llm'
|
|
317
|
+
|
|
318
|
+
response = client.chat(messages: [{ role: 'user', content: 'Hello!' }])
|
|
319
|
+
|
|
320
|
+
# Extract content directly
|
|
321
|
+
content = Durable::Llm::ResponseHelpers.extract_content(response)
|
|
322
|
+
|
|
323
|
+
# Get token usage
|
|
324
|
+
tokens = Durable::Llm::ResponseHelpers.token_usage(response)
|
|
325
|
+
puts "Used #{tokens[:total_tokens]} tokens"
|
|
326
|
+
|
|
327
|
+
# Check why the response finished
|
|
328
|
+
reason = Durable::Llm::ResponseHelpers.finish_reason(response)
|
|
329
|
+
puts "Finished: #{reason}"
|
|
330
|
+
|
|
331
|
+
# Estimate cost
|
|
332
|
+
cost = Durable::Llm::ResponseHelpers.estimate_cost(response)
|
|
333
|
+
puts "Estimated cost: $#{cost}"
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Provider Utilities
|
|
337
|
+
|
|
338
|
+
Discover and compare providers:
|
|
339
|
+
|
|
340
|
+
```ruby
|
|
341
|
+
# Find which provider supports a model
|
|
342
|
+
provider = Durable::Llm::ProviderUtilities.provider_for_model('gpt-4')
|
|
343
|
+
# => :openai
|
|
344
|
+
|
|
345
|
+
# List all available providers
|
|
346
|
+
providers = Durable::Llm::ProviderUtilities.available_providers
|
|
347
|
+
# => [:openai, :anthropic, :google, ...]
|
|
348
|
+
|
|
349
|
+
# Check provider capabilities
|
|
350
|
+
Durable::Llm::ProviderUtilities.supports_capability?(:openai, :streaming)
|
|
351
|
+
# => true
|
|
352
|
+
|
|
353
|
+
# Get all provider info
|
|
354
|
+
info = Durable::Llm::ProviderUtilities.all_provider_info
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Fallback Chains for Resilience
|
|
358
|
+
|
|
359
|
+
Create robust systems with automatic fallback:
|
|
360
|
+
|
|
361
|
+
```ruby
|
|
362
|
+
# Execute with fallback providers
|
|
363
|
+
result = Durable::Llm::ProviderUtilities.complete_with_fallback(
|
|
364
|
+
'What is Ruby?',
|
|
365
|
+
providers: [:openai, :anthropic, :google],
|
|
366
|
+
model_map: {
|
|
367
|
+
openai: 'gpt-4',
|
|
368
|
+
anthropic: 'claude-3-opus-20240229',
|
|
369
|
+
google: 'gemini-pro'
|
|
370
|
+
}
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
puts result
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Global Convenience Functions
|
|
377
|
+
|
|
378
|
+
Quick access without module qualification:
|
|
379
|
+
|
|
380
|
+
```ruby
|
|
381
|
+
require 'durable-llm'
|
|
382
|
+
|
|
383
|
+
# Quick client creation
|
|
384
|
+
client = DLLM(:openai, model: 'gpt-4')
|
|
385
|
+
|
|
386
|
+
# One-liner completions
|
|
387
|
+
result = LlmComplete('Hello!', model: 'gpt-4')
|
|
388
|
+
|
|
389
|
+
# Configure globally
|
|
390
|
+
LlmConfigure do |config|
|
|
391
|
+
config.openai.api_key = 'sk-...'
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
# List models
|
|
395
|
+
models = LlmModels(:openai)
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Custom Timeout
|
|
399
|
+
|
|
400
|
+
```ruby
|
|
401
|
+
client = Durable::Llm.new(
|
|
402
|
+
:openai,
|
|
403
|
+
model: 'gpt-4',
|
|
404
|
+
timeout: 120 # 2 minutes
|
|
405
|
+
)
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Provider-Specific Options
|
|
409
|
+
|
|
410
|
+
Some providers support additional options:
|
|
411
|
+
|
|
412
|
+
```ruby
|
|
413
|
+
# Azure OpenAI with custom endpoint
|
|
414
|
+
client = Durable::Llm.new(
|
|
415
|
+
:azureopenai,
|
|
416
|
+
api_key: 'your-key',
|
|
417
|
+
endpoint: 'https://your-resource.openai.azure.com',
|
|
418
|
+
api_version: '2024-02-15-preview'
|
|
419
|
+
)
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Model Discovery
|
|
423
|
+
|
|
424
|
+
```ruby
|
|
425
|
+
# Get list of available models for a provider
|
|
426
|
+
models = Durable::Llm.models(:openai)
|
|
427
|
+
puts models.inspect
|
|
428
|
+
|
|
429
|
+
# Or using provider utilities
|
|
430
|
+
models = Durable::Llm::ProviderUtilities.models_for_provider(:anthropic)
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Cloning Clients with Different Settings
|
|
434
|
+
|
|
435
|
+
```ruby
|
|
436
|
+
base_client = Durable::Llm.new(:openai, model: 'gpt-3.5-turbo')
|
|
437
|
+
|
|
438
|
+
# Create variant clients for different use cases
|
|
439
|
+
fast_client = base_client.clone_with(model: 'gpt-3.5-turbo')
|
|
440
|
+
powerful_client = base_client.clone_with(model: 'gpt-4')
|
|
441
|
+
|
|
442
|
+
# Use them for different tasks
|
|
443
|
+
summary = fast_client.complete('Summarize: ...')
|
|
444
|
+
analysis = powerful_client.complete('Analyze in depth: ...')
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
## Practical Examples
|
|
448
|
+
|
|
449
|
+
### Building a Simple Chatbot
|
|
450
|
+
|
|
451
|
+
```ruby
|
|
452
|
+
require 'durable-llm'
|
|
453
|
+
|
|
454
|
+
client = Durable::Llm.new(:openai, model: 'gpt-4')
|
|
455
|
+
conversation = [{ role: 'system', content: 'You are a helpful assistant.' }]
|
|
456
|
+
|
|
457
|
+
loop do
|
|
458
|
+
print "You: "
|
|
459
|
+
user_input = gets.chomp
|
|
460
|
+
break if user_input.downcase == 'exit'
|
|
461
|
+
|
|
462
|
+
conversation << { role: 'user', content: user_input }
|
|
463
|
+
|
|
464
|
+
response = client.chat(messages: conversation)
|
|
465
|
+
assistant_message = Durable::Llm::ResponseHelpers.extract_content(response)
|
|
466
|
+
|
|
467
|
+
conversation << { role: 'assistant', content: assistant_message }
|
|
468
|
+
puts "Assistant: #{assistant_message}"
|
|
469
|
+
end
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### Multi-Provider Text Analysis
|
|
473
|
+
|
|
474
|
+
```ruby
|
|
475
|
+
require 'durable-llm'
|
|
476
|
+
|
|
477
|
+
text = "Ruby is a dynamic, open source programming language with a focus on simplicity and productivity."
|
|
478
|
+
|
|
479
|
+
providers = {
|
|
480
|
+
openai: 'gpt-4',
|
|
481
|
+
anthropic: 'claude-3-opus-20240229',
|
|
482
|
+
google: 'gemini-pro'
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
results = providers.map do |provider, model|
|
|
486
|
+
client = Durable::Llm.new(provider, model: model)
|
|
487
|
+
response = client.complete("Summarize this in 5 words: #{text}")
|
|
488
|
+
|
|
489
|
+
{ provider: provider, model: model, summary: response }
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
results.each do |result|
|
|
493
|
+
puts "#{result[:provider]} (#{result[:model]}): #{result[:summary]}"
|
|
494
|
+
end
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### Batch Processing with Progress Tracking
|
|
498
|
+
|
|
499
|
+
```ruby
|
|
500
|
+
require 'durable-llm'
|
|
501
|
+
|
|
502
|
+
client = Durable::Llm.new(:openai, model: 'gpt-3.5-turbo')
|
|
503
|
+
|
|
504
|
+
texts = [
|
|
505
|
+
"Ruby is elegant",
|
|
506
|
+
"Python is versatile",
|
|
507
|
+
"JavaScript is ubiquitous"
|
|
508
|
+
]
|
|
509
|
+
|
|
510
|
+
results = texts.map.with_index do |text, i|
|
|
511
|
+
puts "Processing #{i + 1}/#{texts.length}..."
|
|
512
|
+
|
|
513
|
+
response = client.chat(
|
|
514
|
+
messages: [{ role: 'user', content: "Expand on: #{text}" }]
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
content = Durable::Llm::ResponseHelpers.extract_content(response)
|
|
518
|
+
tokens = Durable::Llm::ResponseHelpers.token_usage(response)
|
|
519
|
+
|
|
520
|
+
{
|
|
521
|
+
input: text,
|
|
522
|
+
output: content,
|
|
523
|
+
tokens: tokens[:total_tokens]
|
|
524
|
+
}
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
total_tokens = results.sum { |r| r[:tokens] }
|
|
528
|
+
puts "\nProcessed #{results.length} texts using #{total_tokens} tokens"
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
### Sentiment Analysis with Error Handling
|
|
532
|
+
|
|
533
|
+
```ruby
|
|
534
|
+
require 'durable-llm'
|
|
535
|
+
|
|
536
|
+
def analyze_sentiment(text)
|
|
537
|
+
client = Durable::Llm.new(:openai, model: 'gpt-4')
|
|
538
|
+
|
|
539
|
+
prompt = <<~PROMPT
|
|
540
|
+
Analyze the sentiment of this text and respond with only one word:
|
|
541
|
+
positive, negative, or neutral.
|
|
542
|
+
|
|
543
|
+
Text: #{text}
|
|
544
|
+
PROMPT
|
|
545
|
+
|
|
546
|
+
response = client.complete(prompt)
|
|
547
|
+
response.strip.downcase
|
|
81
548
|
rescue Durable::Llm::RateLimitError => e
|
|
82
|
-
puts "Rate
|
|
549
|
+
puts "Rate limited, waiting..."
|
|
550
|
+
sleep 5
|
|
551
|
+
retry
|
|
552
|
+
rescue Durable::Llm::APIError => e
|
|
553
|
+
puts "API error: #{e.message}"
|
|
554
|
+
'unknown'
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
texts = [
|
|
558
|
+
"I love this product!",
|
|
559
|
+
"This is terrible.",
|
|
560
|
+
"It's okay, I guess."
|
|
561
|
+
]
|
|
562
|
+
|
|
563
|
+
texts.each do |text|
|
|
564
|
+
sentiment = analyze_sentiment(text)
|
|
565
|
+
puts "\"#{text}\" -> #{sentiment}"
|
|
566
|
+
end
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
### Code Generation Assistant
|
|
570
|
+
|
|
571
|
+
```ruby
|
|
572
|
+
require 'durable-llm'
|
|
573
|
+
|
|
574
|
+
client = Durable::Llm.new(:openai, model: 'gpt-4')
|
|
575
|
+
|
|
576
|
+
def generate_code(description)
|
|
577
|
+
prompt = <<~PROMPT
|
|
578
|
+
Generate Ruby code for: #{description}
|
|
579
|
+
|
|
580
|
+
Provide only the code, no explanations.
|
|
581
|
+
PROMPT
|
|
582
|
+
|
|
583
|
+
client
|
|
584
|
+
.with_temperature(0.3) # Lower temperature for more deterministic code
|
|
585
|
+
.with_max_tokens(500)
|
|
586
|
+
.complete(prompt)
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
# Example usage
|
|
590
|
+
code = generate_code("a method that reverses a string")
|
|
591
|
+
puts code
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### Streaming Real-Time Translation
|
|
595
|
+
|
|
596
|
+
```ruby
|
|
597
|
+
require 'durable-llm'
|
|
598
|
+
|
|
599
|
+
client = Durable::Llm.new(:openai, model: 'gpt-4')
|
|
600
|
+
|
|
601
|
+
def translate_streaming(text, target_language)
|
|
602
|
+
messages = [{
|
|
603
|
+
role: 'user',
|
|
604
|
+
content: "Translate to #{target_language}: #{text}"
|
|
605
|
+
}]
|
|
606
|
+
|
|
607
|
+
print "Translation: "
|
|
608
|
+
client.stream(messages: messages) do |chunk|
|
|
609
|
+
content = chunk.dig('choices', 0, 'delta', 'content')
|
|
610
|
+
print content if content
|
|
611
|
+
end
|
|
612
|
+
puts
|
|
83
613
|
end
|
|
614
|
+
|
|
615
|
+
translate_streaming("Hello, how are you?", "Spanish")
|
|
616
|
+
translate_streaming("The weather is nice today.", "French")
|
|
84
617
|
```
|
|
85
618
|
|
|
86
619
|
## Acknowledgements
|
data/Rakefile
CHANGED
|
@@ -13,4 +13,14 @@ require 'rubocop/rake_task'
|
|
|
13
13
|
|
|
14
14
|
RuboCop::RakeTask.new
|
|
15
15
|
|
|
16
|
+
begin
|
|
17
|
+
require 'yard'
|
|
18
|
+
YARD::Rake::YardocTask.new(:doc) do |t|
|
|
19
|
+
t.files = ['lib/**/*.rb']
|
|
20
|
+
t.options = ['--markup', 'markdown', '--output-dir', 'doc']
|
|
21
|
+
end
|
|
22
|
+
rescue LoadError
|
|
23
|
+
# YARD not available
|
|
24
|
+
end
|
|
25
|
+
|
|
16
26
|
task default: %i[test rubocop]
|