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,242 @@
1
+ ---
2
+ layout: default
3
+ title: Agent-as-Tool Pattern
4
+ parent: Guides
5
+ nav_order: 4
6
+ ---
7
+
8
+ # Agent-as-Tool Pattern Guide
9
+
10
+ The Agent-as-Tool pattern enables **multi-agent collaboration** where specialized agents work behind the scenes to help each other, without the user knowing multiple agents are involved.
11
+
12
+ ## When to Use This Pattern
13
+
14
+ Use Agent-as-Tool when you need:
15
+
16
+ - **Specialized Processing**: Different agents excel at different tasks
17
+ - **Behind-the-Scenes Coordination**: Agents collaborate invisibly to the user
18
+ - **Multi-step Workflows**: Complex processes requiring different expertise
19
+ - **Modular Architecture**: Clean separation of concerns between agents
20
+
21
+ ## Core Concept
22
+
23
+ ### Handoffs vs Agent-as-Tool
24
+
25
+ **Handoffs**: "Let me transfer you to billing"
26
+ - User-visible conversation transfer
27
+ - Full context shared
28
+ - Agent takes over conversation
29
+
30
+ **Agent-as-Tool**: "Let me check that for you" (uses billing agent internally)
31
+ - Invisible to user
32
+ - Limited context (state only)
33
+ - Returns control to caller
34
+
35
+ ## Basic Implementation
36
+
37
+ ### Step 1: Create Specialized Agents
38
+
39
+ ```ruby
40
+ # Research agent - finds customer information
41
+ research_agent = Agents::Agent.new(
42
+ name: "ResearchAgent",
43
+ instructions: <<~PROMPT
44
+ You research customer information and history.
45
+ Return contact details including email addresses.
46
+ PROMPT,
47
+ tools: [customer_lookup_tool, conversation_search_tool]
48
+ )
49
+
50
+ # Billing agent - handles payment operations
51
+ billing_agent = Agents::Agent.new(
52
+ name: "BillingAgent",
53
+ instructions: <<~PROMPT
54
+ You handle billing operations using Stripe.
55
+ CRITICAL: You need customer email addresses for billing lookups.
56
+ Contact IDs will NOT work.
57
+ PROMPT,
58
+ tools: [stripe_billing_tool]
59
+ )
60
+ ```
61
+
62
+ ### Step 2: Create Orchestrator with Agent Tools
63
+
64
+ ```ruby
65
+ # Main agent coordinates specialists
66
+ orchestrator = Agents::Agent.new(
67
+ name: "SupportCopilot",
68
+ instructions: <<~PROMPT
69
+ You help support agents by coordinating specialist agents.
70
+
71
+ **CRITICAL: Multi-Step Workflow Approach**
72
+
73
+ For complex queries, break them into steps and use tools sequentially:
74
+ 1. Plan your approach: What information do you need?
75
+ 2. Execute sequentially: Use EXACT results from previous tools
76
+ 3. Build context progressively: Each tool builds on previous findings
77
+
78
+ **Tool Requirements:**
79
+ - research_customer: Returns contact details including emails
80
+ - check_billing: Requires customer email (not contact ID)
81
+
82
+ Always think: "What did I learn and how do I use it next?"
83
+ PROMPT,
84
+ tools: [
85
+ research_agent.as_tool(
86
+ name: "research_customer",
87
+ description: "Research customer details. Returns contact info including email."
88
+ ),
89
+ billing_agent.as_tool(
90
+ name: "check_billing",
91
+ description: "Check billing status. Requires customer email address."
92
+ )
93
+ ]
94
+ )
95
+ ```
96
+
97
+ ### Step 3: Use with Context Persistence
98
+
99
+ ```ruby
100
+ # Set up runner with context persistence
101
+ runner = Agents::Runner.with_agents(orchestrator)
102
+ context = {}
103
+
104
+ # Interactive loop maintains context
105
+ loop do
106
+ user_input = gets.chomp
107
+ break if user_input == "exit"
108
+
109
+ # Pass and update context each turn
110
+ result = runner.run(user_input, context: context)
111
+ context = result.context if result.context
112
+
113
+ puts result.output
114
+ end
115
+ ```
116
+
117
+ ## Advanced Features
118
+
119
+ ### Custom Output Extraction
120
+
121
+ Extract specific information for other tools:
122
+
123
+ ```ruby
124
+ research_agent.as_tool(
125
+ name: "get_customer_email",
126
+ description: "Get customer email address",
127
+ output_extractor: ->(result) {
128
+ # Extract just the email instead of full response
129
+ email_match = result.output.match(/Email:\s*([^\s]+)/i)
130
+ email_match&.captures&.first || "Email not found"
131
+ }
132
+ )
133
+ ```
134
+
135
+ ## Best Practices
136
+
137
+ ### 1. Clear Tool Descriptions with Requirements
138
+
139
+ Specify what each tool needs and provides:
140
+
141
+ ```ruby
142
+ # Good: Clear requirements
143
+ billing_agent.as_tool(
144
+ name: "check_stripe_billing",
145
+ description: "Check Stripe billing info. Requires customer email (not contact ID)."
146
+ )
147
+
148
+ research_agent.as_tool(
149
+ name: "research_customer",
150
+ description: "Research customer details. Returns email address and contact info."
151
+ )
152
+
153
+ # Avoid: Vague descriptions
154
+ agent.as_tool(name: "process", description: "Do stuff")
155
+ ```
156
+
157
+ ### 2. Multi-Step Workflow Instructions
158
+
159
+ Guide orchestrators to chain tool calls properly:
160
+
161
+ ```ruby
162
+ orchestrator = Agent.new(
163
+ instructions: <<~PROMPT
164
+ **For complex queries requiring multiple pieces of information:**
165
+
166
+ 1. Plan what information you need to gather
167
+ 2. Use tools sequentially, building on previous results
168
+ 3. Extract specific values from tool outputs for subsequent calls
169
+ 4. Don't pass original parameters - use discovered values
170
+
171
+ **Example:** To check billing for CONTACT-123:
172
+ Step 1: research_customer("Get details for CONTACT-123") → finds email
173
+ Step 2: check_billing("Check billing for [discovered email]") → not original ID
174
+ PROMPT
175
+ )
176
+ ```
177
+
178
+ ### 3. Explicit Parameter Requirements in Agent Instructions
179
+
180
+ Make tool parameter needs crystal clear:
181
+
182
+ ```ruby
183
+ billing_agent = Agent.new(
184
+ instructions: <<~PROMPT
185
+ **CRITICAL: Billing Requirements**
186
+ - Stripe billing lookups REQUIRE customer email addresses
187
+ - Contact IDs, names, phone numbers will NOT work
188
+ - If you don't have email, clearly state you need it
189
+ PROMPT
190
+ )
191
+ ```
192
+
193
+ ### 4. Handle Errors with Guidance
194
+
195
+ Provide helpful error messages that guide next steps:
196
+
197
+ ```ruby
198
+ # In orchestrator instructions
199
+ instructions = <<~PROMPT
200
+ **Error Handling:**
201
+ - If billing fails due to missing email: Use research_customer first
202
+ - If contact not found: Ask for more identifying information
203
+ - Always provide helpful responses even if tools fail
204
+ PROMPT
205
+ ```
206
+
207
+ ### 5. Context Persistence for Multi-Turn Conversations
208
+
209
+ Maintain state across conversation turns:
210
+
211
+ ```ruby
212
+ # Maintain context between interactions
213
+ runner = Agents::Runner.with_agents(orchestrator)
214
+ context = {}
215
+
216
+ # Each turn builds on previous context
217
+ result = runner.run(user_input, context: context)
218
+ context = result.context if result.context
219
+ ```
220
+
221
+ ### 6. Design Focused Agents
222
+
223
+ Keep agent responsibilities clear and narrow:
224
+
225
+ ```ruby
226
+ # Good: Focused responsibility
227
+ customer_agent = Agent.new(
228
+ name: "CustomerAgent",
229
+ instructions: "Handle customer data lookups and history research"
230
+ )
231
+
232
+ # Avoid: Too broad
233
+ everything_agent = Agent.new(
234
+ name: "EverythingAgent",
235
+ instructions: "Handle all customer operations, billing, support, and analysis"
236
+ )
237
+ ```
238
+
239
+ ## See Also
240
+
241
+ - [AgentTool Concept](../concepts/agent-tool.html)
242
+ - [Multi-Agent Systems Guide](multi-agent-systems.html)
@@ -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
+ ```