ruby_llm 0.1.0.pre35 → 0.1.0.pre37

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.
@@ -0,0 +1,325 @@
1
+ ---
2
+ layout: default
3
+ title: Embeddings
4
+ parent: Guides
5
+ nav_order: 7
6
+ permalink: /guides/embeddings
7
+ ---
8
+
9
+ # Embeddings
10
+
11
+ Text embeddings are numerical representations of text that capture semantic meaning. RubyLLM makes it easy to generate embeddings for a variety of applications, including semantic search, clustering, and recommendation systems.
12
+
13
+ ## Basic Embedding Generation
14
+
15
+ The simplest way to create an embedding is with the global `embed` method:
16
+
17
+ ```ruby
18
+ # Create an embedding for a single text
19
+ embedding = RubyLLM.embed("Ruby is a programmer's best friend")
20
+
21
+ # The vector representation
22
+ vector = embedding.vectors
23
+ puts "Vector dimension: #{vector.length}" # => 1536 for text-embedding-3-small
24
+ ```
25
+
26
+ ## Embedding Multiple Texts
27
+
28
+ You can efficiently embed multiple texts at once:
29
+
30
+ ```ruby
31
+ # Create embeddings for multiple texts
32
+ texts = ["Ruby", "Python", "JavaScript"]
33
+ embeddings = RubyLLM.embed(texts)
34
+
35
+ # Each text gets its own vector
36
+ puts "Number of vectors: #{embeddings.vectors.length}" # => 3
37
+ puts "First vector dimensions: #{embeddings.vectors.first.length}"
38
+ ```
39
+
40
+ ## Choosing Models
41
+
42
+ By default, RubyLLM uses OpenAI's `text-embedding-3-small`, but you can specify a different model:
43
+
44
+ ```ruby
45
+ # Use a specific model
46
+ embedding = RubyLLM.embed(
47
+ "This is a test sentence",
48
+ model: "text-embedding-3-large"
49
+ )
50
+
51
+ # Or use a Google model
52
+ google_embedding = RubyLLM.embed(
53
+ "This is a test sentence",
54
+ model: "text-embedding-004"
55
+ )
56
+ ```
57
+
58
+ You can configure the default embedding model globally:
59
+
60
+ ```ruby
61
+ RubyLLM.configure do |config|
62
+ config.default_embedding_model = "text-embedding-3-large"
63
+ end
64
+ ```
65
+
66
+ ## Using Embedding Results
67
+
68
+ ### Vector Properties
69
+
70
+ The embedding result contains useful information:
71
+
72
+ ```ruby
73
+ embedding = RubyLLM.embed("Example text")
74
+
75
+ # The vector representation
76
+ puts embedding.vectors.class # => Array
77
+ puts embedding.vectors.first.class # => Float
78
+
79
+ # The model used
80
+ puts embedding.model # => "text-embedding-3-small"
81
+
82
+ # Token usage
83
+ puts embedding.input_tokens # => 3
84
+ ```
85
+
86
+ ### Calculating Similarity
87
+
88
+ Embeddings are commonly used to calculate similarity between texts:
89
+
90
+ ```ruby
91
+ require 'matrix'
92
+
93
+ # Create embeddings for two texts
94
+ embedding1 = RubyLLM.embed("I love Ruby programming")
95
+ embedding2 = RubyLLM.embed("Ruby is my favorite language")
96
+
97
+ # Convert to Vector objects
98
+ vector1 = Vector.elements(embedding1.vectors)
99
+ vector2 = Vector.elements(embedding2.vectors)
100
+
101
+ # Calculate cosine similarity
102
+ similarity = vector1.inner_product(vector2) / (vector1.norm * vector2.norm)
103
+ puts "Similarity: #{similarity}" # Higher values (closer to 1) mean more similar
104
+ ```
105
+
106
+ ### Simple Semantic Search
107
+
108
+ ```ruby
109
+ # Create a simple search index
110
+ class SearchIndex
111
+ def initialize(texts, model: nil)
112
+ @texts = texts
113
+ @embeddings = RubyLLM.embed(texts, model: model).vectors
114
+ end
115
+
116
+ def search(query, top_k: 3)
117
+ query_embedding = RubyLLM.embed(query).vectors
118
+ query_vector = Vector.elements(query_embedding)
119
+
120
+ # Calculate similarities
121
+ similarities = @embeddings.map.with_index do |embedding, idx|
122
+ vector = Vector.elements(embedding)
123
+ similarity = query_vector.inner_product(vector) / (query_vector.norm * vector.norm)
124
+ [idx, similarity]
125
+ end
126
+
127
+ # Return top results
128
+ similarities.sort_by { |_, similarity| -similarity }
129
+ .take(top_k)
130
+ .map { |idx, similarity| { text: @texts[idx], similarity: similarity } }
131
+ end
132
+ end
133
+
134
+ # Create an index
135
+ documents = [
136
+ "Ruby is a dynamic, interpreted language",
137
+ "Python is known for its readability",
138
+ "JavaScript runs in the browser",
139
+ "Ruby on Rails is a web framework",
140
+ "Django is a Python web framework"
141
+ ]
142
+
143
+ index = SearchIndex.new(documents)
144
+
145
+ # Search for similar documents
146
+ results = index.search("web development frameworks")
147
+ results.each do |result|
148
+ puts "#{result[:text]} (Similarity: #{result[:similarity].round(4)})"
149
+ end
150
+ ```
151
+
152
+ ## Error Handling
153
+
154
+ Handle errors that may occur during embedding generation:
155
+
156
+ ```ruby
157
+ begin
158
+ embedding = RubyLLM.embed("Example text")
159
+ rescue RubyLLM::UnauthorizedError
160
+ puts "Please check your API key"
161
+ rescue RubyLLM::BadRequestError => e
162
+ puts "Invalid request: #{e.message}"
163
+ rescue RubyLLM::Error => e
164
+ puts "Error generating embedding: #{e.message}"
165
+ end
166
+ ```
167
+
168
+ ## Performance Considerations
169
+
170
+ When working with embeddings, keep these best practices in mind:
171
+
172
+ 1. **Batch processing** - Embedding multiple texts at once is more efficient than making separate calls
173
+ 2. **Caching** - Store embeddings in your database rather than regenerating them
174
+ 3. **Dimensionality** - Different models produce embeddings with different dimensions
175
+ 4. **Normalization** - Consider normalizing vectors to improve similarity calculations
176
+
177
+ ## Working with Large Datasets
178
+
179
+ For larger datasets, process embeddings in batches:
180
+
181
+ ```ruby
182
+ def embed_in_batches(texts, batch_size: 100, model: nil)
183
+ all_embeddings = []
184
+
185
+ texts.each_slice(batch_size) do |batch|
186
+ batch_embeddings = RubyLLM.embed(batch, model: model).vectors
187
+ all_embeddings.concat(batch_embeddings)
188
+
189
+ # Optional: add a small delay to avoid rate limiting
190
+ sleep(0.1)
191
+ end
192
+
193
+ all_embeddings
194
+ end
195
+
196
+ # Usage
197
+ documents = File.readlines("documents.txt", chomp: true)
198
+ embeddings = embed_in_batches(documents)
199
+ ```
200
+
201
+ ## Rails Integration
202
+
203
+ In a Rails application, you might integrate embeddings like this:
204
+
205
+ ```ruby
206
+ class Document < ApplicationRecord
207
+ serialize :embedding, Array
208
+
209
+ before_save :generate_embedding, if: -> { content_changed? }
210
+
211
+ def self.search(query, limit: 10)
212
+ # Generate query embedding
213
+ query_embedding = RubyLLM.embed(query).vectors
214
+
215
+ # Convert to SQL for similarity search
216
+ where.not(embedding: nil)
217
+ .select("*, (embedding <=> ?) AS similarity", query_embedding)
218
+ .order("similarity DESC")
219
+ .limit(limit)
220
+ end
221
+
222
+ private
223
+
224
+ def generate_embedding
225
+ return if content.blank?
226
+
227
+ self.embedding = RubyLLM.embed(content).vectors
228
+ rescue RubyLLM::Error => e
229
+ errors.add(:base, "Failed to generate embedding: #{e.message}")
230
+ throw :abort
231
+ end
232
+ end
233
+ ```
234
+
235
+ Note: The above example assumes you're using PostgreSQL with the `pgvector` extension for vector similarity search.
236
+
237
+ ## Example Use Cases
238
+
239
+ ### Document Classification
240
+
241
+ ```ruby
242
+ # Train a simple classifier
243
+ class SimpleClassifier
244
+ def initialize
245
+ @categories = {}
246
+ end
247
+
248
+ def train(text, category)
249
+ @categories[category] ||= []
250
+ @categories[category] << RubyLLM.embed(text).vectors
251
+ end
252
+
253
+ def classify(text)
254
+ # Get embedding for the query text
255
+ query_embedding = RubyLLM.embed(text).vectors
256
+ query_vector = Vector.elements(query_embedding)
257
+
258
+ # Find the closest category
259
+ best_similarity = -1
260
+ best_category = nil
261
+
262
+ @categories.each do |category, embeddings|
263
+ # Calculate average similarity to this category
264
+ similarity = embeddings.map do |embedding|
265
+ vector = Vector.elements(embedding)
266
+ query_vector.inner_product(vector) / (query_vector.norm * vector.norm)
267
+ end.sum / embeddings.size
268
+
269
+ if similarity > best_similarity
270
+ best_similarity = similarity
271
+ best_category = category
272
+ end
273
+ end
274
+
275
+ { category: best_category, confidence: best_similarity }
276
+ end
277
+ end
278
+
279
+ # Usage
280
+ classifier = SimpleClassifier.new
281
+
282
+ # Train with examples
283
+ classifier.train("How do I install Ruby?", :installation)
284
+ classifier.train("Setting up Ruby environment", :installation)
285
+ classifier.train("What are blocks in Ruby?", :language_features)
286
+ classifier.train("Understanding Ruby modules", :language_features)
287
+
288
+ # Classify new queries
289
+ puts classifier.classify("How to install Ruby on Ubuntu?")
290
+ # => {:category=>:installation, :confidence=>0.92}
291
+ ```
292
+
293
+ ### Content Recommendation
294
+
295
+ ```ruby
296
+ def recommend_similar_content(content_id, library, count: 3)
297
+ # Get the target content
298
+ target = library.find(content_id)
299
+ target_embedding = RubyLLM.embed(target.description).vectors
300
+ target_vector = Vector.elements(target_embedding)
301
+
302
+ # Compare with all other content
303
+ similarities = library.reject { |item| item.id == content_id }.map do |item|
304
+ next if item.embedding.nil?
305
+
306
+ item_vector = Vector.elements(item.embedding)
307
+ similarity = target_vector.inner_product(item_vector) / (target_vector.norm * item_vector.norm)
308
+
309
+ [item, similarity]
310
+ end.compact
311
+
312
+ # Return top matches
313
+ similarities.sort_by { |_, similarity| -similarity }
314
+ .take(count)
315
+ .map { |item, similarity| { item: item, similarity: similarity } }
316
+ end
317
+ ```
318
+
319
+ ## Next Steps
320
+
321
+ Now that you understand embeddings, you might want to explore:
322
+
323
+ - [Chat]({% link guides/chat.md %}) for interactive AI conversations
324
+ - [Tools]({% link guides/tools.md %}) to extend AI capabilities
325
+ - [Error Handling]({% link guides/error-handling.md %}) for robust applications
@@ -0,0 +1,301 @@
1
+ ---
2
+ layout: default
3
+ title: Error Handling
4
+ parent: Guides
5
+ nav_order: 8
6
+ permalink: /guides/error-handling
7
+ ---
8
+
9
+ # Error Handling
10
+
11
+ Proper error handling is crucial when working with AI services. RubyLLM provides a comprehensive error handling system that helps you build robust applications.
12
+
13
+ ## Error Hierarchy
14
+
15
+ RubyLLM uses a structured error hierarchy:
16
+
17
+ ```ruby
18
+ RubyLLM::Error # Base error class
19
+ RubyLLM::BadRequestError # Invalid request parameters (400)
20
+ RubyLLM::UnauthorizedError # API key issues (401)
21
+ RubyLLM::PaymentRequiredError # Billing issues (402)
22
+ RubyLLM::RateLimitError # Rate limit exceeded (429)
23
+ RubyLLM::ServerError # Provider server error (500)
24
+ RubyLLM::ServiceUnavailableError # Service unavailable (503)
25
+ RubyLLM::ModelNotFoundError # Invalid model ID
26
+ RubyLLM::InvalidRoleError # Invalid message role
27
+ ```
28
+
29
+ ## Basic Error Handling
30
+
31
+ Wrap your AI interactions in `begin/rescue` blocks:
32
+
33
+ ```ruby
34
+ begin
35
+ chat = RubyLLM.chat
36
+ response = chat.ask "What's the capital of France?"
37
+ puts response.content
38
+ rescue RubyLLM::Error => e
39
+ puts "AI interaction failed: #{e.message}"
40
+ end
41
+ ```
42
+
43
+ ## Handling Specific Errors
44
+
45
+ Target specific error types for more precise handling:
46
+
47
+ ```ruby
48
+ begin
49
+ chat = RubyLLM.chat
50
+ response = chat.ask "Generate a detailed analysis"
51
+ rescue RubyLLM::UnauthorizedError
52
+ puts "Please check your API credentials"
53
+ rescue RubyLLM::PaymentRequiredError
54
+ puts "Payment required - please check your account balance"
55
+ rescue RubyLLM::RateLimitError
56
+ puts "Rate limit exceeded - please try again later"
57
+ rescue RubyLLM::ServiceUnavailableError
58
+ puts "Service temporarily unavailable - please try again later"
59
+ rescue RubyLLM::BadRequestError => e
60
+ puts "Bad request: #{e.message}"
61
+ rescue RubyLLM::Error => e
62
+ puts "Other error: #{e.message}"
63
+ end
64
+ ```
65
+
66
+ ## API Response Details
67
+
68
+ The `Error` class contains the original response, allowing for detailed error inspection:
69
+
70
+ ```ruby
71
+ begin
72
+ chat = RubyLLM.chat
73
+ chat.ask "Some question"
74
+ rescue RubyLLM::Error => e
75
+ puts "Error: #{e.message}"
76
+ puts "Status: #{e.response.status}"
77
+ puts "Body: #{e.response.body}"
78
+ end
79
+ ```
80
+
81
+ ## Error Handling with Streaming
82
+
83
+ When using streaming, errors can occur during the stream:
84
+
85
+ ```ruby
86
+ begin
87
+ chat = RubyLLM.chat
88
+ chat.ask "Generate a long response" do |chunk|
89
+ print chunk.content
90
+ end
91
+ rescue RubyLLM::Error => e
92
+ puts "\nStreaming error: #{e.message}"
93
+ end
94
+ ```
95
+
96
+ ## Handling Tool Errors
97
+
98
+ When using tools, errors can be handled within the tool or in the calling code:
99
+
100
+ ```ruby
101
+ # Error handling within tools
102
+ class Calculator < RubyLLM::Tool
103
+ description "Performs calculations"
104
+
105
+ param :expression,
106
+ type: :string,
107
+ desc: "Math expression to evaluate"
108
+
109
+ def execute(expression:)
110
+ eval(expression).to_s
111
+ rescue StandardError => e
112
+ # Return error as structured data
113
+ { error: "Calculation error: #{e.message}" }
114
+ end
115
+ end
116
+
117
+ # Error handling when using tools
118
+ begin
119
+ chat = RubyLLM.chat.with_tool(Calculator)
120
+ chat.ask "What's 1/0?"
121
+ rescue RubyLLM::Error => e
122
+ puts "Error using tools: #{e.message}"
123
+ end
124
+ ```
125
+
126
+ ## Automatic Retries
127
+
128
+ RubyLLM automatically retries on certain transient errors:
129
+
130
+ ```ruby
131
+ # Configure retry behavior
132
+ RubyLLM.configure do |config|
133
+ config.max_retries = 5 # Maximum number of retries
134
+ end
135
+ ```
136
+
137
+ The following errors trigger automatic retries:
138
+ - Network timeouts
139
+ - Connection failures
140
+ - Rate limit errors (429)
141
+ - Server errors (500, 502, 503, 504)
142
+
143
+ ## Provider-Specific Errors
144
+
145
+ Each provider may return slightly different error messages. RubyLLM normalizes these into standard error types, but the original error details are preserved:
146
+
147
+ ```ruby
148
+ begin
149
+ chat = RubyLLM.chat
150
+ chat.ask "Some question"
151
+ rescue RubyLLM::Error => e
152
+ if e.response.body.include?("organization_quota_exceeded")
153
+ puts "Your organization's quota has been exceeded"
154
+ else
155
+ puts "Error: #{e.message}"
156
+ end
157
+ end
158
+ ```
159
+
160
+ ## Error Handling in Rails
161
+
162
+ When using RubyLLM in a Rails application, you can handle errors at different levels:
163
+
164
+ ### Controller Level
165
+
166
+ ```ruby
167
+ class ChatController < ApplicationController
168
+ rescue_from RubyLLM::Error, with: :handle_ai_error
169
+
170
+ def create
171
+ @chat = Chat.create!(chat_params)
172
+ @chat.ask(params[:message])
173
+ redirect_to @chat
174
+ end
175
+
176
+ private
177
+
178
+ def handle_ai_error(exception)
179
+ flash[:error] = "AI service error: #{exception.message}"
180
+ redirect_to chats_path
181
+ end
182
+ end
183
+ ```
184
+
185
+ ### Background Job Level
186
+
187
+ ```ruby
188
+ class AiChatJob < ApplicationJob
189
+ retry_on RubyLLM::RateLimitError, RubyLLM::ServiceUnavailableError,
190
+ wait: :exponentially_longer, attempts: 5
191
+
192
+ discard_on RubyLLM::UnauthorizedError, RubyLLM::BadRequestError
193
+
194
+ def perform(chat_id, message)
195
+ chat = Chat.find(chat_id)
196
+ chat.ask(message)
197
+ rescue RubyLLM::Error => e
198
+ # Log error and notify user
199
+ ErrorNotifier.notify(chat.user, "AI chat error: #{e.message}")
200
+ end
201
+ end
202
+ ```
203
+
204
+ ## Monitoring Errors
205
+
206
+ For production applications, monitor AI service errors:
207
+
208
+ ```ruby
209
+ # Custom error handler
210
+ module AiErrorMonitoring
211
+ def self.track_error(error, context = {})
212
+ # Record error in your monitoring system
213
+ Sentry.capture_exception(error, extra: context)
214
+
215
+ # Log details
216
+ Rails.logger.error "[AI Error] #{error.class}: #{error.message}"
217
+ Rails.logger.error "Context: #{context.inspect}"
218
+
219
+ # Return or re-raise as needed
220
+ error
221
+ end
222
+ end
223
+
224
+ # Usage
225
+ begin
226
+ chat.ask "Some question"
227
+ rescue RubyLLM::Error => e
228
+ AiErrorMonitoring.track_error(e, {
229
+ model: chat.model.id,
230
+ tokens: chat.messages.sum(&:input_tokens)
231
+ })
232
+
233
+ # Show appropriate message to user
234
+ flash[:error] = "Sorry, we encountered an issue with our AI service"
235
+ end
236
+ ```
237
+
238
+ ## Graceful Degradation
239
+
240
+ For critical applications, implement fallback strategies:
241
+
242
+ ```ruby
243
+ def get_ai_response(question, fallback_message = nil)
244
+ begin
245
+ chat = RubyLLM.chat
246
+ response = chat.ask(question)
247
+ response.content
248
+ rescue RubyLLM::Error => e
249
+ Rails.logger.error "AI error: #{e.message}"
250
+
251
+ # Fallback to alternative model
252
+ begin
253
+ fallback_chat = RubyLLM.chat(model: 'gpt-3.5-turbo')
254
+ fallback_response = fallback_chat.ask(question)
255
+ fallback_response.content
256
+ rescue RubyLLM::Error => e2
257
+ Rails.logger.error "Fallback AI error: #{e2.message}"
258
+ fallback_message || "Sorry, our AI service is currently unavailable"
259
+ end
260
+ end
261
+ end
262
+ ```
263
+
264
+ ## Best Practices
265
+
266
+ 1. **Always wrap AI calls in error handling** - Don't assume AI services will always be available
267
+ 2. **Implement timeouts** - Configure appropriate request timeouts
268
+ 3. **Use background jobs** - Process AI requests asynchronously when possible
269
+ 4. **Set up monitoring** - Track error rates and response times
270
+ 5. **Have fallback content** - Prepare fallback responses when AI services fail
271
+ 6. **Gracefully degrade** - Implement multiple fallback strategies
272
+ 7. **Communicate to users** - Provide clear error messages when AI services are unavailable
273
+
274
+ ## Error Recovery
275
+
276
+ When dealing with errors, consider recovery strategies:
277
+
278
+ ```ruby
279
+ MAX_RETRIES = 3
280
+
281
+ def ask_with_recovery(chat, question, retries = 0)
282
+ chat.ask(question)
283
+ rescue RubyLLM::RateLimitError, RubyLLM::ServiceUnavailableError => e
284
+ if retries < MAX_RETRIES
285
+ # Exponential backoff
286
+ sleep_time = 2 ** retries
287
+ puts "Error: #{e.message}. Retrying in #{sleep_time} seconds..."
288
+ sleep sleep_time
289
+ ask_with_recovery(chat, question, retries + 1)
290
+ else
291
+ raise e
292
+ end
293
+ end
294
+ ```
295
+
296
+ ## Next Steps
297
+
298
+ Now that you understand error handling in RubyLLM, you might want to explore:
299
+
300
+ - [Rails Integration]({% link guides/rails.md %}) for using RubyLLM in Rails applications
301
+ - [Tools]({% link guides/tools.md %}) for using tools with error handling