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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8e6cc177ffcd7ac3cdd67678a2cb0bfbd97944470a88a90ca82c7e02ea40845
4
- data.tar.gz: 0e595c60949cfa309e2b72aa5ab417c31966e91df76188f7c3a722ea052e16d2
3
+ metadata.gz: a39c75959d7172f1dfbe23b18f9a02de90563524afa33088a9d83f7d6e1efccb
4
+ data.tar.gz: e27fdca6dc2cbcd027e0bbf515db0eb6d4f57b29b0e2766838819b3f830fb78a
5
5
  SHA512:
6
- metadata.gz: b8fed0d23edcc73a48613681f528f5a7fe8575ef7faf7be34a897d91cfcded505df1de1723becd420394269f11856ea857df09872423c529361d06995e19bcbe
7
- data.tar.gz: 6e1a7db706eced060c26f1bbbd48a2dd57103195b633971a52df48d0fae6345080b4b70b91dc41662c19dd61c67c39718423a8b96589ea1fb34083278a6b6f02
6
+ metadata.gz: 005d079c618c5c5ba45434894d3d9884d5e1feb015f7443c11a1bc92c4609cf34f7a39c02e4f8df4082f74e8fc5ac2152fe0ba380591af0ff1e012cf0e512a85
7
+ data.tar.gz: 463745392a4cfc07206806e29a1c812e8d3b316fb554cf1cd61b27eb59e8eaab9d2bca964cb522f8f3626d1c0c0427e450d1169ea2a93b835369b2b286a55d2d
data/CLI.md CHANGED
@@ -1,5 +1,3 @@
1
- Here's the content for the file CLI.md:
2
-
3
1
  # Durable-LLM Command Line Interface (CLI)
4
2
 
5
3
  Durable-LLM provides a command-line interface (CLI) for interacting with various Large Language Model providers. This document outlines the available commands and their usage.
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
- ## Usage
25
+ ## Quick Start
26
26
 
27
- Here's a basic example of how to use Durable-LLM:
27
+ ### Simple Completion
28
28
 
29
29
  ```ruby
30
30
  require 'durable-llm'
31
31
 
32
- client = Durable::Llm::Client.new(:openai, api_key: 'your-api-key')
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
- response = client.completion(
35
- model: 'gpt-3.5-turbo',
36
- messages: [{ role: 'user', content: 'Hello, how are you?' }]
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 interface for multiple LLM providers
46
- - Consistent input/output format across different models
47
- - Error handling and retries
48
- - Streaming support
49
- - Customizable timeout and request options
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
- - OpenAI
54
- - Anthropic
55
- - Grok
56
- - Huggingface
57
- - Cohere
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
- You can configure Durable-LLM globally or on a per-request basis:
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.default_provider = :openai
66
- config.openai.api_key = 'your-openai-api-key'
67
- config.anthropic.api_key = 'your-anthropic-api-key'
68
- # Add other provider configurations as needed
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
- ## Error Handling
159
+ ### Per-Client Configuration
73
160
 
74
- Durable-LLM provides a unified error handling system:
161
+ Pass configuration directly when creating a client:
75
162
 
76
163
  ```ruby
77
- begin
78
- response = client.completion(model: 'gpt-3.5-turbo', messages: [...])
79
- rescue Durable::Llm::APIError => e
80
- puts "API Error: #{e.message}"
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 Limit Exceeded: #{e.message}"
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]