ai-agents 0.1.1 → 0.1.3

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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +29 -106
  3. data/docs/Gemfile +14 -0
  4. data/docs/Gemfile.lock +183 -0
  5. data/docs/_config.yml +53 -0
  6. data/docs/_sass/color_schemes/ruby.scss +72 -0
  7. data/docs/_sass/custom/custom.scss +93 -0
  8. data/docs/architecture.md +353 -0
  9. data/docs/assets/fonts/InterVariable.woff2 +0 -0
  10. data/docs/concepts/agent-tool.md +166 -0
  11. data/docs/concepts/agents.md +43 -0
  12. data/docs/concepts/context.md +110 -0
  13. data/docs/concepts/handoffs.md +81 -0
  14. data/docs/concepts/runner.md +87 -0
  15. data/docs/concepts/tools.md +62 -0
  16. data/docs/concepts.md +21 -0
  17. data/docs/guides/agent-as-tool-pattern.md +242 -0
  18. data/docs/guides/multi-agent-systems.md +261 -0
  19. data/docs/guides/rails-integration.md +440 -0
  20. data/docs/guides/state-persistence.md +451 -0
  21. data/docs/guides.md +18 -0
  22. data/docs/index.md +95 -0
  23. data/examples/collaborative-copilot/README.md +169 -0
  24. data/examples/collaborative-copilot/agents/analysis_agent.rb +48 -0
  25. data/examples/collaborative-copilot/agents/answer_suggestion_agent.rb +50 -0
  26. data/examples/collaborative-copilot/agents/copilot_orchestrator.rb +85 -0
  27. data/examples/collaborative-copilot/agents/integrations_agent.rb +58 -0
  28. data/examples/collaborative-copilot/agents/research_agent.rb +52 -0
  29. data/examples/collaborative-copilot/data/contacts.json +47 -0
  30. data/examples/collaborative-copilot/data/conversations.json +170 -0
  31. data/examples/collaborative-copilot/data/knowledge_base.json +58 -0
  32. data/examples/collaborative-copilot/data/linear_issues.json +83 -0
  33. data/examples/collaborative-copilot/data/stripe_billing.json +71 -0
  34. data/examples/collaborative-copilot/interactive.rb +90 -0
  35. data/examples/collaborative-copilot/tools/create_linear_ticket_tool.rb +58 -0
  36. data/examples/collaborative-copilot/tools/get_article_tool.rb +41 -0
  37. data/examples/collaborative-copilot/tools/get_contact_tool.rb +51 -0
  38. data/examples/collaborative-copilot/tools/get_conversation_tool.rb +53 -0
  39. data/examples/collaborative-copilot/tools/get_stripe_billing_tool.rb +44 -0
  40. data/examples/collaborative-copilot/tools/search_contacts_tool.rb +57 -0
  41. data/examples/collaborative-copilot/tools/search_conversations_tool.rb +54 -0
  42. data/examples/collaborative-copilot/tools/search_knowledge_base_tool.rb +55 -0
  43. data/examples/collaborative-copilot/tools/search_linear_issues_tool.rb +60 -0
  44. data/examples/isp-support/agents_factory.rb +57 -1
  45. data/examples/isp-support/tools/create_lead_tool.rb +16 -2
  46. data/examples/isp-support/tools/crm_lookup_tool.rb +13 -1
  47. data/lib/agents/agent.rb +52 -6
  48. data/lib/agents/agent_tool.rb +113 -0
  49. data/lib/agents/handoff.rb +8 -34
  50. data/lib/agents/tool_context.rb +36 -0
  51. data/lib/agents/version.rb +1 -1
  52. data/lib/agents.rb +1 -0
  53. metadata +44 -2
@@ -0,0 +1,261 @@
1
+ ---
2
+ layout: default
3
+ title: Building Multi-Agent Systems
4
+ parent: Guides
5
+ nav_order: 1
6
+ ---
7
+
8
+ # Building Multi-Agent Systems
9
+
10
+ Multi-agent systems allow you to decompose complex problems into specialized agents that collaborate to provide comprehensive solutions. This guide covers patterns and best practices for designing effective multi-agent workflows.
11
+
12
+ ## Core Patterns
13
+
14
+ ### Hub-and-Spoke Architecture
15
+
16
+ The most common and stable pattern is hub-and-spoke, where a central triage agent routes conversations to specialized agents:
17
+
18
+ ```ruby
19
+ # Specialized agents
20
+ billing_agent = Agents::Agent.new(
21
+ name: "Billing",
22
+ instructions: "Handle billing inquiries, payment processing, and account issues."
23
+ )
24
+
25
+ support_agent = Agents::Agent.new(
26
+ name: "Support",
27
+ instructions: "Provide technical troubleshooting and product support."
28
+ )
29
+
30
+ # Central hub agent
31
+ triage_agent = Agents::Agent.new(
32
+ name: "Triage",
33
+ instructions: "Route users to the appropriate specialist. Only hand off, don't solve problems yourself."
34
+ )
35
+
36
+ # Register handoffs (one-way: triage -> specialists)
37
+ triage_agent.register_handoffs(billing_agent, support_agent)
38
+
39
+ # Create runner with triage as entry point
40
+ runner = Agents::AgentRunner.with_agents(triage_agent, billing_agent, support_agent)
41
+ ```
42
+
43
+ ### Dynamic Instructions
44
+
45
+ Use Proc-based instructions to create context-aware agents:
46
+
47
+ ```ruby
48
+ support_agent = Agents::Agent.new(
49
+ name: "Support",
50
+ instructions: ->(context) {
51
+ customer_tier = context[:customer_tier] || "standard"
52
+ <<~INSTRUCTIONS
53
+ You are a technical support specialist for #{customer_tier} tier customers.
54
+ #{customer_tier == "premium" ? "Provide priority white-glove service." : ""}
55
+
56
+ Available tools: diagnostics, escalation
57
+ INSTRUCTIONS
58
+ }
59
+ )
60
+ ```
61
+
62
+ ## Agent Design Principles
63
+
64
+ ### Clear Boundaries
65
+
66
+ Each agent should have a distinct, non-overlapping responsibility:
67
+
68
+ ```ruby
69
+ # GOOD: Clear specialization
70
+ sales_agent = Agents::Agent.new(
71
+ name: "Sales",
72
+ instructions: "Handle product inquiries, pricing, and purchase decisions. Transfer technical questions to support."
73
+ )
74
+
75
+ support_agent = Agents::Agent.new(
76
+ name: "Support",
77
+ instructions: "Handle technical issues and product troubleshooting. Transfer sales questions to sales team."
78
+ )
79
+ ```
80
+
81
+ ### Avoiding Handoff Loops
82
+
83
+ Design instructions to prevent infinite handoffs:
84
+
85
+ ```ruby
86
+ # BAD: Conflicting handoff criteria
87
+ agent_a = Agents::Agent.new(
88
+ instructions: "Handle X. If you need Y info, hand off to Agent B."
89
+ )
90
+ agent_b = Agents::Agent.new(
91
+ instructions: "Handle Y. If you need X context, hand off to Agent A."
92
+ )
93
+
94
+ # GOOD: Clear escalation hierarchy
95
+ specialist = Agents::Agent.new(
96
+ instructions: "Handle specialized requests. Ask users for needed info directly."
97
+ )
98
+ triage = Agents::Agent.new(
99
+ instructions: "Route users to specialists. Don't solve problems yourself."
100
+ )
101
+ ```
102
+
103
+ ## Conversation Flow Management
104
+
105
+ ### Entry Points
106
+
107
+ The first agent in `AgentRunner.with_agents()` becomes the default entry point:
108
+
109
+ ```ruby
110
+ # Triage agent handles all initial conversations
111
+ runner = Agents::AgentRunner.with_agents(triage_agent, billing_agent, support_agent)
112
+
113
+ # Start conversation
114
+ result = runner.run("I need help with my account")
115
+ # -> Automatically starts with triage_agent
116
+ ```
117
+
118
+ ### Context Preservation
119
+
120
+ The AgentRunner automatically maintains conversation history across handoffs:
121
+
122
+ ```ruby
123
+ # First interaction
124
+ result1 = runner.run("My name is John and I have a billing question")
125
+ # -> Triage agent hands off to billing agent
126
+
127
+ # Continue conversation
128
+ result2 = runner.run("What payment methods do you accept?", context: result1.context)
129
+ # -> Billing agent remembers John's name and previous context
130
+ ```
131
+
132
+ ### Handoff Detection
133
+
134
+ The system automatically determines the current agent from conversation history:
135
+
136
+ ```ruby
137
+ # No need to manually specify which agent to use
138
+ result = runner.run("Follow up question", context: previous_result.context)
139
+ # -> AgentRunner automatically selects correct agent based on conversation state
140
+ ```
141
+
142
+ ## Advanced Patterns
143
+
144
+ ### Tool-Specific Agents
145
+
146
+ Create agents specialized for particular tool categories:
147
+
148
+ ```ruby
149
+ data_agent = Agents::Agent.new(
150
+ name: "DataAnalyst",
151
+ instructions: "Analyze data and generate reports using available analytics tools.",
152
+ tools: [DatabaseTool.new, ChartGeneratorTool.new, ReportTool.new]
153
+ )
154
+
155
+ communication_agent = Agents::Agent.new(
156
+ name: "Communications",
157
+ instructions: "Handle notifications and external communications.",
158
+ tools: [EmailTool.new, SlackTool.new, SMSTool.new]
159
+ )
160
+ ```
161
+
162
+ ### Conditional Handoffs
163
+
164
+ Use dynamic instructions to control handoff behavior:
165
+
166
+ ```ruby
167
+ triage_agent = Agents::Agent.new(
168
+ name: "Triage",
169
+ instructions: ->(context) {
170
+ business_hours = context[:business_hours] || false
171
+
172
+ base_instructions = "Route users to appropriate departments."
173
+
174
+ if business_hours
175
+ base_instructions + " All departments are available."
176
+ else
177
+ base_instructions + " Only hand off urgent technical issues to support. Others should wait for business hours."
178
+ end
179
+ }
180
+ )
181
+ ```
182
+
183
+ ## Testing Multi-Agent Systems
184
+
185
+ ### Unit Testing Individual Agents
186
+
187
+ Test each agent in isolation:
188
+
189
+ ```ruby
190
+ RSpec.describe "BillingAgent" do
191
+ let(:agent) { create_billing_agent }
192
+ let(:runner) { Agents::AgentRunner.with_agents(agent) }
193
+
194
+ it "handles payment inquiries" do
195
+ result = runner.run("What payment methods do you accept?")
196
+ expect(result.output).to include("credit card", "bank transfer")
197
+ end
198
+ end
199
+ ```
200
+
201
+ ### Integration Testing Handoffs
202
+
203
+ Test complete workflows:
204
+
205
+ ```ruby
206
+ RSpec.describe "Customer Support Workflow" do
207
+ let(:runner) { create_support_runner } # Creates triage + specialists
208
+
209
+ it "routes billing questions correctly" do
210
+ result = runner.run("I have a billing question")
211
+
212
+ # Verify handoff occurred
213
+ expect(result.context.current_agent_name).to eq("Billing")
214
+
215
+ # Test continued conversation
216
+ followup = runner.run("What are your payment terms?", context: result.context)
217
+ expect(followup.output).to include("payment terms")
218
+ end
219
+ end
220
+ ```
221
+
222
+ ## Common Pitfalls
223
+
224
+ ### Over-Specialization
225
+ Don't create too many narrow agents - it increases handoff complexity and latency.
226
+
227
+ ### Under-Specified Instructions
228
+ Vague instructions lead to inappropriate handoffs. Be explicit about what each agent should and shouldn't handle.
229
+
230
+ ### Circular Dependencies
231
+ Avoid mutual handoffs between agents. Use hub-and-spoke or clear hierarchical patterns instead.
232
+
233
+ ### Context Leakage
234
+ Don't rely on shared mutable state. Use the context hash for inter-agent communication.
235
+
236
+ ## Performance Considerations
237
+
238
+ ### Handoff Overhead
239
+ Each handoff adds latency. Design agents to minimize unnecessary transfers.
240
+
241
+ ### Context Size
242
+ Large contexts increase token usage. Clean up irrelevant data periodically:
243
+
244
+ ```ruby
245
+ # Remove old conversation history
246
+ cleaned_context = result.context.dup
247
+ cleaned_context[:conversation_history] = cleaned_context[:conversation_history].last(10)
248
+ ```
249
+
250
+ ### Concurrent Execution
251
+ AgentRunner is thread-safe, allowing multiple conversations simultaneously:
252
+
253
+ ```ruby
254
+ # Safe to use same runner across threads
255
+ threads = users.map do |user|
256
+ Thread.new do
257
+ user_result = runner.run(user.message, context: user.context)
258
+ # Handle result...
259
+ end
260
+ end
261
+ ```
@@ -0,0 +1,440 @@
1
+ ---
2
+ layout: default
3
+ title: Rails Integration
4
+ parent: Guides
5
+ nav_order: 2
6
+ ---
7
+
8
+ # Rails Integration
9
+
10
+ This guide covers integrating the AI Agents library with Ruby on Rails applications, including conversation persistence with ActiveRecord and session management.
11
+
12
+ ## Setup
13
+
14
+ Add the gem to your Rails application:
15
+
16
+ ```ruby
17
+ # Gemfile
18
+ gem 'ai-agents'
19
+ ```
20
+
21
+ Configure your LLM providers in an initializer:
22
+
23
+ ```ruby
24
+ # config/initializers/ai_agents.rb
25
+ Agents.configure do |config|
26
+ config.openai_api_key = Rails.application.credentials.openai_api_key
27
+ config.anthropic_api_key = Rails.application.credentials.anthropic_api_key
28
+ config.default_model = 'gpt-4o-mini'
29
+ config.debug = Rails.env.development?
30
+ end
31
+ ```
32
+
33
+ ## ActiveRecord Integration
34
+
35
+ ### Conversation Persistence
36
+
37
+ Create a model to store conversation contexts:
38
+
39
+ ```ruby
40
+ # Generate migration
41
+ rails generate model Conversation user:references context:text current_agent:string
42
+
43
+ # db/migrate/xxx_create_conversations.rb
44
+ class CreateConversations < ActiveRecord::Migration[7.0]
45
+ def change
46
+ create_table :conversations do |t|
47
+ t.references :user, null: false, foreign_key: true
48
+ t.text :context, null: false
49
+ t.string :current_agent
50
+ t.timestamps
51
+ end
52
+
53
+ add_index :conversations, [:user_id, :created_at]
54
+ end
55
+ end
56
+ ```
57
+
58
+ Define the Conversation model:
59
+
60
+ ```ruby
61
+ # app/models/conversation.rb
62
+ class Conversation < ApplicationRecord
63
+ belongs_to :user
64
+
65
+ # Serialize context as JSON
66
+ serialize :context, JSON
67
+
68
+ validates :context, presence: true
69
+
70
+ def self.for_user(user)
71
+ where(user: user).order(:created_at)
72
+ end
73
+
74
+ def self.latest_for_user(user)
75
+ for_user(user).last
76
+ end
77
+
78
+ # Convert to agent context hash
79
+ def to_agent_context
80
+ context.deep_symbolize_keys
81
+ end
82
+
83
+ # Create from agent result
84
+ def self.from_agent_result(user, result)
85
+ create!(
86
+ user: user,
87
+ context: result.context.to_h,
88
+ current_agent: result.context.current_agent_name
89
+ )
90
+ end
91
+ end
92
+ ```
93
+
94
+ ### Session Management
95
+
96
+ Create a service to manage agent conversations:
97
+
98
+ ```ruby
99
+ # app/services/agent_conversation_service.rb
100
+ class AgentConversationService
101
+ def initialize(user)
102
+ @user = user
103
+ @runner = create_agent_runner
104
+ end
105
+
106
+ def send_message(message)
107
+ # Get existing conversation context
108
+ context = load_conversation_context
109
+
110
+ # Run agent with message
111
+ result = @runner.run(message, context: context)
112
+
113
+ # Persist updated conversation
114
+ save_conversation(result)
115
+
116
+ result
117
+ end
118
+
119
+ def reset_conversation
120
+ Conversation.where(user: @user).destroy_all
121
+ end
122
+
123
+ private
124
+
125
+ def create_agent_runner
126
+ # Create your agents here
127
+ triage_agent = Agents::Agent.new(
128
+ name: "Triage",
129
+ instructions: build_triage_instructions,
130
+ tools: [CustomerLookupTool.new]
131
+ )
132
+
133
+ billing_agent = Agents::Agent.new(
134
+ name: "Billing",
135
+ instructions: "Handle billing and payment inquiries.",
136
+ tools: [BillingTool.new, PaymentTool.new]
137
+ )
138
+
139
+ support_agent = Agents::Agent.new(
140
+ name: "Support",
141
+ instructions: "Provide technical support and troubleshooting.",
142
+ tools: [TechnicalTool.new]
143
+ )
144
+
145
+ triage_agent.register_handoffs(billing_agent, support_agent)
146
+
147
+ Agents::AgentRunner.with_agents(triage_agent, billing_agent, support_agent)
148
+ end
149
+
150
+ def build_triage_instructions
151
+ ->(context) {
152
+ user_info = context[:user_info] || {}
153
+
154
+ <<~INSTRUCTIONS
155
+ You are a customer service triage agent for #{@user.name}.
156
+
157
+ Customer Details:
158
+ - Name: #{@user.name}
159
+ - Email: #{@user.email}
160
+ - Account Type: #{user_info[:account_type] || 'standard'}
161
+
162
+ Route customers to the appropriate department:
163
+ - Billing: Payment issues, account billing, refunds
164
+ - Support: Technical problems, product questions
165
+
166
+ Always be professional and helpful.
167
+ INSTRUCTIONS
168
+ }
169
+ end
170
+
171
+ def load_conversation_context
172
+ latest_conversation = Conversation.latest_for_user(@user)
173
+ return initial_context unless latest_conversation
174
+
175
+ latest_conversation.to_agent_context
176
+ end
177
+
178
+ def initial_context
179
+ {
180
+ user_id: @user.id,
181
+ user_info: {
182
+ name: @user.name,
183
+ email: @user.email,
184
+ account_type: @user.account_type
185
+ }
186
+ }
187
+ end
188
+
189
+ def save_conversation(result)
190
+ Conversation.from_agent_result(@user, result)
191
+ end
192
+ end
193
+ ```
194
+
195
+ ## Controller Integration
196
+
197
+ Create a controller for handling agent conversations:
198
+
199
+ ```ruby
200
+ # app/controllers/agent_conversations_controller.rb
201
+ class AgentConversationsController < ApplicationController
202
+ before_action :authenticate_user!
203
+
204
+ def create
205
+ service = AgentConversationService.new(current_user)
206
+
207
+ begin
208
+ result = service.send_message(params[:message])
209
+
210
+ render json: {
211
+ response: result.output,
212
+ agent: result.context.current_agent_name,
213
+ conversation_id: result.context[:conversation_id]
214
+ }
215
+ rescue => e
216
+ Rails.logger.error "Agent conversation error: #{e.message}"
217
+ render json: { error: "Unable to process your request" }, status: 500
218
+ end
219
+ end
220
+
221
+ def reset
222
+ service = AgentConversationService.new(current_user)
223
+ service.reset_conversation
224
+
225
+ render json: { message: "Conversation reset successfully" }
226
+ end
227
+
228
+ def history
229
+ conversations = Conversation.for_user(current_user)
230
+ .includes(:user)
231
+ .limit(50)
232
+
233
+ render json: conversations.map do |conv|
234
+ {
235
+ id: conv.id,
236
+ agent: conv.current_agent,
237
+ timestamp: conv.created_at,
238
+ context_keys: conv.context.keys
239
+ }
240
+ end
241
+ end
242
+ end
243
+ ```
244
+
245
+ ## Custom Rails Tools
246
+
247
+ Create Rails-specific tools for database operations:
248
+
249
+ ```ruby
250
+ # app/tools/customer_lookup_tool.rb
251
+ class CustomerLookupTool < Agents::Tool
252
+ name "lookup_customer"
253
+ description "Look up customer information by email or ID"
254
+ param :identifier, type: "string", desc: "Email address or customer ID"
255
+
256
+ def perform(tool_context, identifier:)
257
+ # Access Rails models safely
258
+ customer = User.find_by(email: identifier) || User.find_by(id: identifier)
259
+
260
+ return "Customer not found" unless customer
261
+
262
+ {
263
+ name: customer.name,
264
+ email: customer.email,
265
+ account_type: customer.account_type,
266
+ created_at: customer.created_at,
267
+ last_login: customer.last_sign_in_at
268
+ }
269
+ end
270
+ end
271
+
272
+ # app/tools/billing_tool.rb
273
+ class BillingTool < Agents::Tool
274
+ name "get_billing_info"
275
+ description "Retrieve billing information for a customer"
276
+ param :user_id, type: "integer", desc: "Customer user ID"
277
+
278
+ def perform(tool_context, user_id:)
279
+ user = User.find(user_id)
280
+ billing_info = user.billing_profile
281
+
282
+ return "No billing information found" unless billing_info
283
+
284
+ {
285
+ plan: billing_info.plan_name,
286
+ status: billing_info.status,
287
+ next_billing_date: billing_info.next_billing_date,
288
+ amount: billing_info.monthly_amount
289
+ }
290
+ rescue ActiveRecord::RecordNotFound
291
+ "Customer not found"
292
+ end
293
+ end
294
+ ```
295
+
296
+ ## Background Processing
297
+
298
+ For longer conversations, use background jobs:
299
+
300
+ ```ruby
301
+ # app/jobs/agent_conversation_job.rb
302
+ class AgentConversationJob < ApplicationJob
303
+ queue_as :default
304
+
305
+ def perform(user_id, message, conversation_id = nil)
306
+ user = User.find(user_id)
307
+ service = AgentConversationService.new(user)
308
+
309
+ result = service.send_message(message)
310
+
311
+ # Broadcast result via ActionCable
312
+ ActionCable.server.broadcast(
313
+ "agent_conversation_#{user_id}",
314
+ {
315
+ response: result.output,
316
+ agent: result.context.current_agent_name,
317
+ conversation_id: conversation_id
318
+ }
319
+ )
320
+ end
321
+ end
322
+
323
+ # Enqueue job from controller
324
+ def create_async
325
+ job_id = AgentConversationJob.perform_later(
326
+ current_user.id,
327
+ params[:message],
328
+ params[:conversation_id]
329
+ )
330
+
331
+ render json: { job_id: job_id }
332
+ end
333
+ ```
334
+
335
+ ## Error Handling
336
+
337
+ Implement comprehensive error handling:
338
+
339
+ ```ruby
340
+ # app/services/agent_conversation_service.rb
341
+ class AgentConversationService
342
+ class AgentError < StandardError; end
343
+ class ContextError < StandardError; end
344
+
345
+ def send_message(message)
346
+ validate_message(message)
347
+
348
+ context = load_conversation_context
349
+
350
+ begin
351
+ result = @runner.run(message, context: context)
352
+ save_conversation(result)
353
+ result
354
+ rescue RubyLLM::Error => e
355
+ Rails.logger.error "LLM Error: #{e.message}"
356
+ raise AgentError, "AI service temporarily unavailable"
357
+ rescue JSON::ParserError => e
358
+ Rails.logger.error "Context parsing error: #{e.message}"
359
+ raise ContextError, "Conversation context corrupted"
360
+ end
361
+ end
362
+
363
+ private
364
+
365
+ def validate_message(message)
366
+ raise ArgumentError, "Message cannot be blank" if message.blank?
367
+ raise ArgumentError, "Message too long" if message.length > 5000
368
+ end
369
+ end
370
+ ```
371
+
372
+ ## Testing
373
+
374
+ Test Rails integration with RSpec:
375
+
376
+ ```ruby
377
+ # spec/services/agent_conversation_service_spec.rb
378
+ RSpec.describe AgentConversationService do
379
+ let(:user) { create(:user) }
380
+ let(:service) { described_class.new(user) }
381
+
382
+ describe '#send_message' do
383
+ it 'creates a conversation record' do
384
+ expect {
385
+ service.send_message("Hello")
386
+ }.to change(Conversation, :count).by(1)
387
+ end
388
+
389
+ it 'persists context correctly' do
390
+ result = service.send_message("Hello")
391
+ conversation = Conversation.last
392
+
393
+ expect(conversation.user).to eq(user)
394
+ expect(conversation.context).to include('user_id' => user.id)
395
+ end
396
+ end
397
+
398
+ describe '#reset_conversation' do
399
+ before { service.send_message("Hello") }
400
+
401
+ it 'destroys all conversations for user' do
402
+ expect {
403
+ service.reset_conversation
404
+ }.to change(Conversation, :count).by(-1)
405
+ end
406
+ end
407
+ end
408
+ ```
409
+
410
+ ## Deployment Considerations
411
+
412
+ ### Environment Variables
413
+
414
+ ```ruby
415
+ # config/credentials.yml.enc
416
+ openai_api_key: your_openai_key
417
+ anthropic_api_key: your_anthropic_key
418
+
419
+ # Or use environment variables
420
+ ENV['OPENAI_API_KEY']
421
+ ENV['ANTHROPIC_API_KEY']
422
+ ```
423
+
424
+ ### Database Indexing
425
+
426
+ ```ruby
427
+ # Add indexes for better query performance
428
+ add_index :conversations, [:user_id, :current_agent]
429
+ add_index :conversations, :created_at
430
+ ```
431
+
432
+ ### Memory Management
433
+
434
+ ```ruby
435
+ # Cleanup old conversations
436
+ # config/schedule.rb (whenever gem)
437
+ every 1.day, at: '2:00 am' do
438
+ runner "Conversation.where('created_at < ?', 30.days.ago).destroy_all"
439
+ end
440
+ ```