ai-agents 0.1.2 → 0.2.0

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/bump-version.md +44 -0
  3. data/CHANGELOG.md +35 -0
  4. data/CLAUDE.md +59 -15
  5. data/README.md +29 -106
  6. data/docs/Gemfile +14 -0
  7. data/docs/Gemfile.lock +183 -0
  8. data/docs/_config.yml +53 -0
  9. data/docs/_sass/color_schemes/ruby.scss +72 -0
  10. data/docs/_sass/custom/custom.scss +93 -0
  11. data/docs/architecture.md +353 -0
  12. data/docs/assets/fonts/InterVariable.woff2 +0 -0
  13. data/docs/concepts/agent-tool.md +166 -0
  14. data/docs/concepts/agents.md +43 -0
  15. data/docs/concepts/callbacks.md +42 -0
  16. data/docs/concepts/context.md +110 -0
  17. data/docs/concepts/handoffs.md +81 -0
  18. data/docs/concepts/runner.md +87 -0
  19. data/docs/concepts/tools.md +62 -0
  20. data/docs/concepts.md +22 -0
  21. data/docs/guides/agent-as-tool-pattern.md +242 -0
  22. data/docs/guides/multi-agent-systems.md +261 -0
  23. data/docs/guides/rails-integration.md +440 -0
  24. data/docs/guides/state-persistence.md +451 -0
  25. data/docs/guides.md +18 -0
  26. data/docs/index.md +97 -0
  27. data/examples/collaborative-copilot/README.md +169 -0
  28. data/examples/collaborative-copilot/agents/analysis_agent.rb +48 -0
  29. data/examples/collaborative-copilot/agents/answer_suggestion_agent.rb +50 -0
  30. data/examples/collaborative-copilot/agents/copilot_orchestrator.rb +85 -0
  31. data/examples/collaborative-copilot/agents/integrations_agent.rb +58 -0
  32. data/examples/collaborative-copilot/agents/research_agent.rb +52 -0
  33. data/examples/collaborative-copilot/data/contacts.json +47 -0
  34. data/examples/collaborative-copilot/data/conversations.json +170 -0
  35. data/examples/collaborative-copilot/data/knowledge_base.json +58 -0
  36. data/examples/collaborative-copilot/data/linear_issues.json +83 -0
  37. data/examples/collaborative-copilot/data/stripe_billing.json +71 -0
  38. data/examples/collaborative-copilot/interactive.rb +90 -0
  39. data/examples/collaborative-copilot/tools/create_linear_ticket_tool.rb +58 -0
  40. data/examples/collaborative-copilot/tools/get_article_tool.rb +41 -0
  41. data/examples/collaborative-copilot/tools/get_contact_tool.rb +51 -0
  42. data/examples/collaborative-copilot/tools/get_conversation_tool.rb +53 -0
  43. data/examples/collaborative-copilot/tools/get_stripe_billing_tool.rb +44 -0
  44. data/examples/collaborative-copilot/tools/search_contacts_tool.rb +57 -0
  45. data/examples/collaborative-copilot/tools/search_conversations_tool.rb +54 -0
  46. data/examples/collaborative-copilot/tools/search_knowledge_base_tool.rb +55 -0
  47. data/examples/collaborative-copilot/tools/search_linear_issues_tool.rb +60 -0
  48. data/examples/isp-support/interactive.rb +43 -4
  49. data/lib/agents/agent.rb +34 -0
  50. data/lib/agents/agent_runner.rb +66 -1
  51. data/lib/agents/agent_tool.rb +113 -0
  52. data/lib/agents/callback_manager.rb +54 -0
  53. data/lib/agents/handoff.rb +8 -34
  54. data/lib/agents/message_extractor.rb +82 -0
  55. data/lib/agents/run_context.rb +5 -2
  56. data/lib/agents/runner.rb +16 -27
  57. data/lib/agents/tool_wrapper.rb +11 -1
  58. data/lib/agents/version.rb +1 -1
  59. data/lib/agents.rb +3 -0
  60. metadata +48 -1
@@ -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
+ ```