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.
- checksums.yaml +4 -4
- data/README.md +29 -106
- data/docs/Gemfile +14 -0
- data/docs/Gemfile.lock +183 -0
- data/docs/_config.yml +53 -0
- data/docs/_sass/color_schemes/ruby.scss +72 -0
- data/docs/_sass/custom/custom.scss +93 -0
- data/docs/architecture.md +353 -0
- data/docs/assets/fonts/InterVariable.woff2 +0 -0
- data/docs/concepts/agent-tool.md +166 -0
- data/docs/concepts/agents.md +43 -0
- data/docs/concepts/context.md +110 -0
- data/docs/concepts/handoffs.md +81 -0
- data/docs/concepts/runner.md +87 -0
- data/docs/concepts/tools.md +62 -0
- data/docs/concepts.md +21 -0
- data/docs/guides/agent-as-tool-pattern.md +242 -0
- data/docs/guides/multi-agent-systems.md +261 -0
- data/docs/guides/rails-integration.md +440 -0
- data/docs/guides/state-persistence.md +451 -0
- data/docs/guides.md +18 -0
- data/docs/index.md +95 -0
- data/examples/collaborative-copilot/README.md +169 -0
- data/examples/collaborative-copilot/agents/analysis_agent.rb +48 -0
- data/examples/collaborative-copilot/agents/answer_suggestion_agent.rb +50 -0
- data/examples/collaborative-copilot/agents/copilot_orchestrator.rb +85 -0
- data/examples/collaborative-copilot/agents/integrations_agent.rb +58 -0
- data/examples/collaborative-copilot/agents/research_agent.rb +52 -0
- data/examples/collaborative-copilot/data/contacts.json +47 -0
- data/examples/collaborative-copilot/data/conversations.json +170 -0
- data/examples/collaborative-copilot/data/knowledge_base.json +58 -0
- data/examples/collaborative-copilot/data/linear_issues.json +83 -0
- data/examples/collaborative-copilot/data/stripe_billing.json +71 -0
- data/examples/collaborative-copilot/interactive.rb +90 -0
- data/examples/collaborative-copilot/tools/create_linear_ticket_tool.rb +58 -0
- data/examples/collaborative-copilot/tools/get_article_tool.rb +41 -0
- data/examples/collaborative-copilot/tools/get_contact_tool.rb +51 -0
- data/examples/collaborative-copilot/tools/get_conversation_tool.rb +53 -0
- data/examples/collaborative-copilot/tools/get_stripe_billing_tool.rb +44 -0
- data/examples/collaborative-copilot/tools/search_contacts_tool.rb +57 -0
- data/examples/collaborative-copilot/tools/search_conversations_tool.rb +54 -0
- data/examples/collaborative-copilot/tools/search_knowledge_base_tool.rb +55 -0
- data/examples/collaborative-copilot/tools/search_linear_issues_tool.rb +60 -0
- data/examples/isp-support/agents_factory.rb +57 -1
- data/examples/isp-support/tools/create_lead_tool.rb +16 -2
- data/examples/isp-support/tools/crm_lookup_tool.rb +13 -1
- data/lib/agents/agent.rb +52 -6
- data/lib/agents/agent_tool.rb +113 -0
- data/lib/agents/handoff.rb +8 -34
- data/lib/agents/tool_context.rb +36 -0
- data/lib/agents/version.rb +1 -1
- data/lib/agents.rb +1 -0
- metadata +44 -2
@@ -0,0 +1,353 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: Architecture
|
4
|
+
nav_order: 4
|
5
|
+
published: false
|
6
|
+
---
|
7
|
+
|
8
|
+
# Architecture
|
9
|
+
|
10
|
+
The AI Agents library is designed around key principles of immutability, thread safety, and separation of concerns. This architecture enables scalable multi-agent systems that can handle concurrent conversations while maintaining state consistency.
|
11
|
+
|
12
|
+
## Core Architecture Principles
|
13
|
+
|
14
|
+
### Immutability by Design
|
15
|
+
- **Agents are immutable** once created, preventing configuration drift
|
16
|
+
- **Context is deep-copied** for each execution to prevent cross-contamination
|
17
|
+
- **Registry is frozen** after initialization to prevent runtime modifications
|
18
|
+
|
19
|
+
### Thread Safety
|
20
|
+
- **No shared mutable state** between concurrent executions
|
21
|
+
- **Context isolation** through deep copying and closure capture
|
22
|
+
- **Stateless tool design** with context injection via parameters
|
23
|
+
|
24
|
+
### Separation of Concerns
|
25
|
+
- **Agent definition** (configuration) vs **Agent execution** (runtime)
|
26
|
+
- **Tool functionality** vs **Tool context** (execution state)
|
27
|
+
- **Conversation orchestration** vs **LLM communication**
|
28
|
+
|
29
|
+
## Component Architecture
|
30
|
+
|
31
|
+
### Two-Tier Execution Model
|
32
|
+
|
33
|
+
The library uses a two-tier architecture separating long-lived configuration from short-lived execution:
|
34
|
+
|
35
|
+
```
|
36
|
+
┌─────────────────┐ ┌─────────────────┐
|
37
|
+
│ AgentRunner │ │ Runner │
|
38
|
+
│ (Long-lived) │────▶│ (Per-request) │
|
39
|
+
│ │ │ │
|
40
|
+
│ • Agent Registry│ │ • Execution │
|
41
|
+
│ • Entry Point │ │ • Context Mgmt │
|
42
|
+
│ • Thread Safety │ │ • Handoff Logic │
|
43
|
+
└─────────────────┘ └─────────────────┘
|
44
|
+
```
|
45
|
+
|
46
|
+
**AgentRunner** (Thread-Safe Manager):
|
47
|
+
- Created once, reused for multiple conversations
|
48
|
+
- Maintains immutable agent registry
|
49
|
+
- Determines conversation continuity
|
50
|
+
- Thread-safe for concurrent use
|
51
|
+
|
52
|
+
**Runner** (Execution Engine):
|
53
|
+
- Created per conversation turn
|
54
|
+
- Handles LLM communication and tool execution
|
55
|
+
- Manages context state during execution
|
56
|
+
- Stateless and garbage-collected after use
|
57
|
+
|
58
|
+
### Context Management Architecture
|
59
|
+
|
60
|
+
Context flows through multiple abstraction layers:
|
61
|
+
|
62
|
+
```
|
63
|
+
┌─────────────────┐
|
64
|
+
│ User Context │ (Serializable across sessions)
|
65
|
+
│ │
|
66
|
+
├─────────────────┤
|
67
|
+
│ RunContext │ (Execution isolation)
|
68
|
+
│ │
|
69
|
+
├─────────────────┤
|
70
|
+
│ ToolContext │ (Tool-specific state)
|
71
|
+
│ │
|
72
|
+
└─────────────────┘
|
73
|
+
```
|
74
|
+
|
75
|
+
**User Context**: Serializable hash for persistence
|
76
|
+
**RunContext**: Execution wrapper with usage tracking
|
77
|
+
**ToolContext**: Tool-specific view with retry metadata
|
78
|
+
|
79
|
+
## Handoff Architecture
|
80
|
+
|
81
|
+
### Tool-Based Handoff System
|
82
|
+
|
83
|
+
Handoffs are implemented as specialized tools rather than instruction-parsing:
|
84
|
+
|
85
|
+
```
|
86
|
+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
87
|
+
│ Triage Agent │ │ HandoffTool │ │ Billing Agent │
|
88
|
+
│ │ │ │ │ │
|
89
|
+
│ • Instructions │────▶│ • Target Agent │────▶│ • Instructions │
|
90
|
+
│ • Handoff List │ │ • Schema │ │ • Specialized │
|
91
|
+
│ • General Role │ │ • Execution │ │ • Tools │
|
92
|
+
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
93
|
+
```
|
94
|
+
|
95
|
+
**Benefits of Tool-Based Handoffs**:
|
96
|
+
- **Reliable invocation**: LLMs consistently use tools when available
|
97
|
+
- **Clear semantics**: Tool schema explicitly defines handoff criteria
|
98
|
+
- **No text parsing**: Eliminates need to parse free-form responses
|
99
|
+
- **Provider agnostic**: Works consistently across OpenAI, Anthropic, etc.
|
100
|
+
|
101
|
+
### First-Call-Wins Handoff Resolution
|
102
|
+
|
103
|
+
To prevent infinite handoff loops, the system implements first-call-wins semantics:
|
104
|
+
|
105
|
+
```
|
106
|
+
LLM Response with Multiple Handoffs:
|
107
|
+
┌─────────────────────────────────────┐
|
108
|
+
│ Call 1: handoff_to_support() │ ✓ Processed
|
109
|
+
│ Call 2: handoff_to_billing() │ ✗ Ignored
|
110
|
+
│ Call 3: handoff_to_support() │ ✗ Ignored
|
111
|
+
└─────────────────────────────────────┘
|
112
|
+
Result: Transfer to Support Agent only
|
113
|
+
```
|
114
|
+
|
115
|
+
This mirrors OpenAI's SDK behavior and prevents conflicting handoff states.
|
116
|
+
|
117
|
+
## Thread Safety Implementation
|
118
|
+
|
119
|
+
### Context Isolation Pattern
|
120
|
+
|
121
|
+
Each execution receives an isolated context copy:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
# Thread-safe execution flow
|
125
|
+
def run(input, context: {})
|
126
|
+
# 1. Deep copy context for isolation
|
127
|
+
context_copy = deep_copy_context(context)
|
128
|
+
|
129
|
+
# 2. Create execution wrapper
|
130
|
+
run_context = RunContext.new(context_copy)
|
131
|
+
|
132
|
+
# 3. Execute with isolated state
|
133
|
+
result = execute_with_context(run_context)
|
134
|
+
|
135
|
+
# 4. Return serializable result
|
136
|
+
return result
|
137
|
+
end
|
138
|
+
```
|
139
|
+
|
140
|
+
### Tool Wrapper Pattern
|
141
|
+
|
142
|
+
Tools remain stateless through the wrapper pattern:
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
# Tool Definition (Stateless)
|
146
|
+
class WeatherTool < Agents::Tool
|
147
|
+
def perform(tool_context, city:)
|
148
|
+
api_key = tool_context.context[:weather_api_key]
|
149
|
+
WeatherAPI.get(city, api_key)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Runtime Wrapping (Context Injection)
|
154
|
+
wrapped_tool = ToolWrapper.new(weather_tool, context_wrapper)
|
155
|
+
```
|
156
|
+
|
157
|
+
The wrapper captures context in its closure, injecting it during execution without modifying the tool instance.
|
158
|
+
|
159
|
+
## Chat Architecture
|
160
|
+
|
161
|
+
### Extended Chat System
|
162
|
+
|
163
|
+
The library extends RubyLLM::Chat with handoff detection:
|
164
|
+
|
165
|
+
```
|
166
|
+
┌─────────────────┐ ┌─────────────────┐
|
167
|
+
│ Agents::Chat │ │ RubyLLM::Chat │
|
168
|
+
│ (Extended) │────▶│ (Base) │
|
169
|
+
│ │ │ │
|
170
|
+
│ • Handoff Detect│ │ • LLM Comm │
|
171
|
+
│ • Tool Classify │ │ • Tool Execution│
|
172
|
+
│ • First-Call-Wins│ │ • Message Mgmt │
|
173
|
+
└─────────────────┘ └─────────────────┘
|
174
|
+
```
|
175
|
+
|
176
|
+
**Handoff Detection Flow**:
|
177
|
+
1. Classify tool calls into handoff vs regular tools
|
178
|
+
2. Execute first handoff only (first-call-wins)
|
179
|
+
3. Return HandoffResponse to signal agent switch
|
180
|
+
4. Execute regular tools normally with continuation
|
181
|
+
|
182
|
+
### Conversation Continuity
|
183
|
+
|
184
|
+
The system maintains conversation state through message attribution:
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
# Messages include agent attribution
|
188
|
+
{
|
189
|
+
role: :assistant,
|
190
|
+
content: "I can help with billing",
|
191
|
+
agent_name: "Billing" # Enables conversation continuity
|
192
|
+
}
|
193
|
+
```
|
194
|
+
|
195
|
+
AgentRunner uses this attribution to determine conversation ownership when resuming.
|
196
|
+
|
197
|
+
## Provider Abstraction
|
198
|
+
|
199
|
+
### RubyLLM Integration
|
200
|
+
|
201
|
+
The library builds on RubyLLM for provider abstraction:
|
202
|
+
|
203
|
+
```
|
204
|
+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
205
|
+
│ AI Agents │ │ RubyLLM │ │ Providers │
|
206
|
+
│ │ │ │ │ │
|
207
|
+
│ • Multi-Agent │────▶│ • Unified API │────▶│ • OpenAI │
|
208
|
+
│ • Handoffs │ │ • Tool Calling │ │ • Anthropic │
|
209
|
+
│ • Context Mgmt │ │ • Streaming │ │ • Gemini │
|
210
|
+
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
211
|
+
```
|
212
|
+
|
213
|
+
This abstraction allows switching between providers without changing agent logic.
|
214
|
+
|
215
|
+
## State Management
|
216
|
+
|
217
|
+
### Context Serialization
|
218
|
+
|
219
|
+
The entire conversation state is serializable:
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
# Serialize context for persistence
|
223
|
+
context_json = result.context.to_json
|
224
|
+
|
225
|
+
# Restore and continue conversation
|
226
|
+
restored_context = JSON.parse(context_json, symbolize_names: true)
|
227
|
+
next_result = runner.run("Continue conversation", context: restored_context)
|
228
|
+
```
|
229
|
+
|
230
|
+
**Serialization includes**:
|
231
|
+
- Conversation history with agent attribution
|
232
|
+
- Current agent state
|
233
|
+
- Tool-specific state
|
234
|
+
- User-defined context data
|
235
|
+
|
236
|
+
### State Persistence Patterns
|
237
|
+
|
238
|
+
The library supports multiple persistence patterns:
|
239
|
+
|
240
|
+
```
|
241
|
+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
242
|
+
│ In-Memory │ │ File-Based │ │ Database │
|
243
|
+
│ │ │ │ │ │
|
244
|
+
│ • Development │ │ • Simple Deploy │ │ • Production │
|
245
|
+
│ • Testing │ │ • Single Server │ │ • Multi-Server │
|
246
|
+
│ • Rapid Iteration│ │ • File Storage │ │ • ActiveRecord │
|
247
|
+
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
248
|
+
```
|
249
|
+
|
250
|
+
All patterns use the same serialization format for consistency.
|
251
|
+
|
252
|
+
## Performance Characteristics
|
253
|
+
|
254
|
+
### Memory Management
|
255
|
+
|
256
|
+
- **Immutable objects** are shared safely across threads
|
257
|
+
- **Context copies** are garbage-collected after execution
|
258
|
+
- **Tool instances** are reused without state accumulation
|
259
|
+
|
260
|
+
### Execution Efficiency
|
261
|
+
|
262
|
+
- **Minimal object creation** during execution
|
263
|
+
- **Direct LLM communication** without additional abstractions
|
264
|
+
- **Efficient handoff detection** through tool classification
|
265
|
+
|
266
|
+
### Scalability
|
267
|
+
|
268
|
+
- **Thread-safe design** enables horizontal scaling
|
269
|
+
- **Stateless execution** supports load balancing
|
270
|
+
- **Serializable state** enables process migration
|
271
|
+
|
272
|
+
## Error Handling Architecture
|
273
|
+
|
274
|
+
### Graceful Degradation
|
275
|
+
|
276
|
+
The system handles errors at multiple levels:
|
277
|
+
|
278
|
+
```
|
279
|
+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
280
|
+
│ Agent Level │ │ Tool Level │ │ LLM Level │
|
281
|
+
│ │ │ │ │ │
|
282
|
+
│ • Handoff Fails │ │ • Tool Errors │ │ • API Errors │
|
283
|
+
│ • Max Turns │ │ • Retry Logic │ │ • Rate Limits │
|
284
|
+
│ • State Errors │ │ • Fallback │ │ • Timeouts │
|
285
|
+
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
286
|
+
```
|
287
|
+
|
288
|
+
### Error Recovery
|
289
|
+
|
290
|
+
- **Context preservation** even during errors
|
291
|
+
- **Partial execution results** for debugging
|
292
|
+
- **Conversation continuity** after error resolution
|
293
|
+
|
294
|
+
## Extension Points
|
295
|
+
|
296
|
+
### Custom Tools
|
297
|
+
|
298
|
+
The tool system is fully extensible:
|
299
|
+
|
300
|
+
```ruby
|
301
|
+
class CustomTool < Agents::Tool
|
302
|
+
name "custom_action"
|
303
|
+
description "Perform custom business logic"
|
304
|
+
param :input, type: "string"
|
305
|
+
|
306
|
+
def perform(tool_context, input:)
|
307
|
+
# Access context for state
|
308
|
+
user_id = tool_context.context[:user_id]
|
309
|
+
|
310
|
+
# Perform custom logic
|
311
|
+
result = BusinessLogic.process(input, user_id)
|
312
|
+
|
313
|
+
# Update shared state
|
314
|
+
tool_context.state[:last_action] = result
|
315
|
+
|
316
|
+
result
|
317
|
+
end
|
318
|
+
end
|
319
|
+
```
|
320
|
+
|
321
|
+
### Custom Providers
|
322
|
+
|
323
|
+
While built on RubyLLM, the architecture supports custom providers:
|
324
|
+
|
325
|
+
```ruby
|
326
|
+
# Custom provider integration
|
327
|
+
class CustomProvider < RubyLLM::Provider
|
328
|
+
def complete(messages, **options)
|
329
|
+
# Custom LLM integration
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
# Use with agents
|
334
|
+
agent = Agents::Agent.new(
|
335
|
+
name: "Assistant",
|
336
|
+
model: CustomProvider.new.model("custom-model")
|
337
|
+
)
|
338
|
+
```
|
339
|
+
|
340
|
+
### Debug Hooks
|
341
|
+
|
342
|
+
The system provides hooks for debugging and tracing:
|
343
|
+
|
344
|
+
```ruby
|
345
|
+
# Enable debug logging
|
346
|
+
ENV["RUBYLLM_DEBUG"] = "true"
|
347
|
+
|
348
|
+
# Custom callbacks
|
349
|
+
chat.on(:new_message) { puts "Sending to LLM..." }
|
350
|
+
chat.on(:end_message) { |response| puts "Received: #{response.content}" }
|
351
|
+
```
|
352
|
+
|
353
|
+
This architecture provides a robust foundation for building production-ready multi-agent systems while maintaining simplicity and developer productivity.
|
Binary file
|
@@ -0,0 +1,166 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: AgentTool
|
4
|
+
parent: Concepts
|
5
|
+
nav_order: 6
|
6
|
+
---
|
7
|
+
|
8
|
+
# AgentTool
|
9
|
+
|
10
|
+
The `AgentTool` class enables **agent-to-agent collaboration** by wrapping agents as callable tools. This pattern allows specialized agents to work behind the scenes to help each other without conversation handoffs.
|
11
|
+
|
12
|
+
## Key Concept
|
13
|
+
|
14
|
+
Unlike handoffs where control transfers between agents with full conversation context, AgentTool creates **constrained execution environments** where wrapped agents:
|
15
|
+
|
16
|
+
- Cannot perform handoffs (empty registry)
|
17
|
+
- Have limited turn counts to prevent infinite loops
|
18
|
+
- Only receive shared state, not conversation history
|
19
|
+
- Always return control to the calling agent
|
20
|
+
|
21
|
+
## Architecture
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
# Specialized agents as tools
|
25
|
+
research_agent = Agent.new(
|
26
|
+
name: "Research Agent",
|
27
|
+
instructions: "Research topics and extract key information"
|
28
|
+
)
|
29
|
+
|
30
|
+
analysis_agent = Agent.new(
|
31
|
+
name: "Analysis Agent",
|
32
|
+
instructions: "Analyze data and provide insights"
|
33
|
+
)
|
34
|
+
|
35
|
+
# Main orchestrator uses other agents as tools
|
36
|
+
orchestrator = Agent.new(
|
37
|
+
name: "Orchestrator",
|
38
|
+
instructions: "Coordinate research and analysis",
|
39
|
+
tools: [
|
40
|
+
research_agent.as_tool(
|
41
|
+
name: "research_topic",
|
42
|
+
description: "Research a specific topic"
|
43
|
+
),
|
44
|
+
analysis_agent.as_tool(
|
45
|
+
name: "analyze_data",
|
46
|
+
description: "Analyze research findings"
|
47
|
+
)
|
48
|
+
]
|
49
|
+
)
|
50
|
+
```
|
51
|
+
|
52
|
+
## Implementation Details
|
53
|
+
|
54
|
+
### Constraints
|
55
|
+
|
56
|
+
AgentTool implements several safety constraints:
|
57
|
+
|
58
|
+
1. **No Handoffs**: Wrapped agents receive an empty registry, preventing handoff calls
|
59
|
+
2. **Limited Turns**: Maximum 3 turns to prevent infinite loops
|
60
|
+
3. **Context Isolation**: Only shared state is passed, not conversation history
|
61
|
+
4. **Return Control**: Always returns to the calling agent
|
62
|
+
|
63
|
+
### Context Isolation
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
# Parent context (full conversation state)
|
67
|
+
parent_context = {
|
68
|
+
state: { user_id: 123, session: "abc" },
|
69
|
+
conversation_history: [...],
|
70
|
+
current_agent: "MainAgent",
|
71
|
+
turn_count: 5
|
72
|
+
}
|
73
|
+
|
74
|
+
# Isolated context (only state)
|
75
|
+
isolated_context = {
|
76
|
+
state: { user_id: 123, session: "abc" }
|
77
|
+
}
|
78
|
+
```
|
79
|
+
|
80
|
+
### Error Handling
|
81
|
+
|
82
|
+
AgentTool provides robust error handling:
|
83
|
+
|
84
|
+
- Execution failures return descriptive error messages
|
85
|
+
- Runtime exceptions are caught and reported
|
86
|
+
- Missing output is handled gracefully
|
87
|
+
|
88
|
+
## Usage Patterns
|
89
|
+
|
90
|
+
### Customer Support Copilot
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
# Specialized agents for different domains
|
94
|
+
conversation_analyzer = Agent.new(
|
95
|
+
name: "ConversationAnalyzer",
|
96
|
+
instructions: "Extract order IDs and customer intent"
|
97
|
+
)
|
98
|
+
|
99
|
+
shopify_agent = Agent.new(
|
100
|
+
name: "ShopifyAgent",
|
101
|
+
instructions: "Perform Shopify operations",
|
102
|
+
tools: [shopify_refund_tool, shopify_lookup_tool]
|
103
|
+
)
|
104
|
+
|
105
|
+
# Main copilot coordinates specialized agents
|
106
|
+
copilot = Agent.new(
|
107
|
+
name: "SupportCopilot",
|
108
|
+
instructions: "Help support agents with customer requests",
|
109
|
+
tools: [
|
110
|
+
conversation_analyzer.as_tool(
|
111
|
+
name: "analyze_conversation",
|
112
|
+
description: "Extract key information from conversation"
|
113
|
+
),
|
114
|
+
shopify_agent.as_tool(
|
115
|
+
name: "shopify_action",
|
116
|
+
description: "Perform Shopify operations"
|
117
|
+
)
|
118
|
+
]
|
119
|
+
)
|
120
|
+
```
|
121
|
+
|
122
|
+
## Output Transformation
|
123
|
+
|
124
|
+
AgentTool supports custom output extractors for transforming results:
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
# Custom output extraction
|
128
|
+
summarizer = Agent.new(
|
129
|
+
name: "Summarizer",
|
130
|
+
instructions: "Summarize long content"
|
131
|
+
)
|
132
|
+
|
133
|
+
summarizer_tool = summarizer.as_tool(
|
134
|
+
name: "summarize",
|
135
|
+
description: "Create a summary",
|
136
|
+
output_extractor: ->(result) {
|
137
|
+
"SUMMARY: #{result.output&.first(200)}..."
|
138
|
+
}
|
139
|
+
)
|
140
|
+
```
|
141
|
+
|
142
|
+
## Best Practices
|
143
|
+
|
144
|
+
1. **Keep it Simple**: Use AgentTool for focused, single-purpose tasks
|
145
|
+
2. **Avoid Deep Nesting**: Don't create agents that use agents that use agents
|
146
|
+
3. **State Management**: Only share necessary state between agents
|
147
|
+
4. **Error Handling**: Always handle potential execution failures
|
148
|
+
5. **Performance**: Consider the overhead of multiple agent calls
|
149
|
+
|
150
|
+
## Comparison with Handoffs
|
151
|
+
|
152
|
+
| Aspect | AgentTool | Handoff |
|
153
|
+
|--------|-----------|---------|
|
154
|
+
| **Purpose** | Behind-the-scenes collaboration | User-facing conversation transfer |
|
155
|
+
| **Context** | Isolated (state only) | Full conversation history |
|
156
|
+
| **Control** | Returns to caller | Transfers to target |
|
157
|
+
| **Constraints** | Limited turns, no handoffs | Full agent capabilities |
|
158
|
+
| **Use Case** | Internal processing | Conversation routing |
|
159
|
+
|
160
|
+
## See Also
|
161
|
+
|
162
|
+
<!-- - [Agent-as-Tool Pattern Guide](../guides/agent-as-tool-pattern.html)
|
163
|
+
- [Multi-Agent Systems](../guides/multi-agent-systems.html) -->
|
164
|
+
|
165
|
+
- [Tools Concept](tools.html)
|
166
|
+
- [Handoffs Concept](handoffs.html)
|
@@ -0,0 +1,43 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: Agents
|
4
|
+
parent: Concepts
|
5
|
+
nav_order: 1
|
6
|
+
---
|
7
|
+
|
8
|
+
# Agents
|
9
|
+
|
10
|
+
An **Agent** is the fundamental building block of the library. It represents an AI assistant with a specific set of capabilities, defined by its instructions, tools, and the underlying language model it uses.
|
11
|
+
|
12
|
+
Agents are immutable and thread-safe by design. Once created, their configuration cannot be changed, ensuring safe sharing across multiple threads without race conditions. Agents can have dynamic instructions using Proc objects that receive runtime context.
|
13
|
+
|
14
|
+
{: .note }
|
15
|
+
This project is in early development. While thread safety is a core design goal and the architecture is built around it, complete thread safety is not yet guaranteed. We're actively working toward this goal.
|
16
|
+
|
17
|
+
### Key Attributes of an Agent
|
18
|
+
|
19
|
+
* **`name`**: A unique name for the agent, used for identification and in handoffs.
|
20
|
+
* **`instructions`**: The system prompt that guides the agent's behavior. This can be a static string or a `Proc` that dynamically generates instructions based on the current context.
|
21
|
+
* **`model`**: The language model the agent will use (e.g., `"gpt-4.1-mini"`).
|
22
|
+
* **`tools`**: An array of `Agents::Tool` instances that the agent can use to perform actions.
|
23
|
+
* **`handoff_agents`**: An array of other agents that this agent can hand off conversations to.
|
24
|
+
|
25
|
+
### Example
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
# Create a simple agent
|
29
|
+
assistant_agent = Agents::Agent.new(
|
30
|
+
name: "Assistant",
|
31
|
+
instructions: "You are a helpful assistant.",
|
32
|
+
model: "gpt-4.1-mini",
|
33
|
+
tools: [CalculatorTool.new]
|
34
|
+
)
|
35
|
+
|
36
|
+
# Create a specialized agent by cloning the base agent
|
37
|
+
specialized_agent = assistant_agent.clone(
|
38
|
+
instructions: "You are a specialized assistant for financial calculations.",
|
39
|
+
tools: assistant_agent.tools + [FinancialDataTool.new]
|
40
|
+
)
|
41
|
+
```
|
42
|
+
|
43
|
+
In this example, we create a base `assistant_agent` and then create a `specialized_agent` by cloning it and adding a new tool. This approach allows for easy composition and reuse of agent configurations.
|
@@ -0,0 +1,110 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: Context
|
4
|
+
parent: Concepts
|
5
|
+
nav_order: 3
|
6
|
+
---
|
7
|
+
|
8
|
+
# Context
|
9
|
+
|
10
|
+
**Context** is the serializable state management system that preserves information across agent interactions, tool executions, and handoffs. Context enables conversation continuity and cross-session persistence through a simple hash-based structure.
|
11
|
+
|
12
|
+
## Context Architecture
|
13
|
+
|
14
|
+
Context flows through multiple abstraction layers:
|
15
|
+
|
16
|
+
### User Context (Serializable)
|
17
|
+
The main context hash that persists across sessions:
|
18
|
+
```ruby
|
19
|
+
context = {
|
20
|
+
user_id: 123,
|
21
|
+
conversation_history: [...],
|
22
|
+
current_agent_name: "Billing",
|
23
|
+
state: { customer_tier: "premium" } # Tools use this nested hash for persistent data
|
24
|
+
}
|
25
|
+
```
|
26
|
+
|
27
|
+
### RunContext (Execution Wrapper)
|
28
|
+
Wraps user context with execution-specific features:
|
29
|
+
- **Context Hash**: Shared data accessible to all tools and agents
|
30
|
+
- **Thread Safety**: Deep copying ensures execution isolation
|
31
|
+
|
32
|
+
### ToolContext (Tool-Specific View)
|
33
|
+
Provides tools with controlled access to execution state:
|
34
|
+
- **Context Access**: Read/write access to shared context hash
|
35
|
+
- **State Management**: Dedicated space for persistent tool state
|
36
|
+
|
37
|
+
## The `ToolContext`
|
38
|
+
|
39
|
+
The `ToolContext` is a wrapper around the `RunContext` that is passed to each tool when it is executed. It provides the tool with controlled access to the execution state, including the shared context hash.
|
40
|
+
|
41
|
+
By passing the context through the `ToolContext`, we ensure that tools can remain stateless and thread-safe, as they do not need to store any execution-specific state in their instance variables.
|
42
|
+
|
43
|
+
## Context Serialization
|
44
|
+
|
45
|
+
Context is fully serializable for persistence across process boundaries:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
# Run conversation
|
49
|
+
result = runner.run("Hello, I'm John")
|
50
|
+
|
51
|
+
# Serialize for storage
|
52
|
+
context_json = result.context.to_json
|
53
|
+
# Store in database, file, session, etc.
|
54
|
+
|
55
|
+
# Later: restore and continue
|
56
|
+
restored_context = JSON.parse(context_json, symbolize_names: true)
|
57
|
+
next_result = runner.run("What's my name?", context: restored_context)
|
58
|
+
# => "Your name is John"
|
59
|
+
```
|
60
|
+
|
61
|
+
## Conversation Continuity
|
62
|
+
|
63
|
+
The AgentRunner automatically manages conversation continuity through context:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
# Create runner
|
67
|
+
runner = Agents::Runner.with_agents(triage_agent, billing_agent)
|
68
|
+
|
69
|
+
# First interaction
|
70
|
+
result1 = runner.run("I need billing help")
|
71
|
+
# Triage agent hands off to billing agent
|
72
|
+
# Context includes: current_agent_name: "Billing"
|
73
|
+
|
74
|
+
# Continue conversation
|
75
|
+
result2 = runner.run("What payment methods do you accept?", context: result1.context)
|
76
|
+
# AgentRunner detects billing agent should continue based on context
|
77
|
+
```
|
78
|
+
|
79
|
+
## State Management
|
80
|
+
|
81
|
+
Tools can use the context for persistent state:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
class CustomerLookupTool < Agents::Tool
|
85
|
+
def perform(tool_context, customer_id:)
|
86
|
+
customer = Customer.find(customer_id)
|
87
|
+
|
88
|
+
# Store in shared state for other tools
|
89
|
+
tool_context.state[:customer_id] = customer_id
|
90
|
+
tool_context.state[:customer_name] = customer.name
|
91
|
+
tool_context.state[:account_type] = customer.account_type
|
92
|
+
|
93
|
+
"Found customer: #{customer.name}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class BillingTool < Agents::Tool
|
98
|
+
def perform(tool_context)
|
99
|
+
# Access state from previous tool
|
100
|
+
customer_id = tool_context.state[:customer_id]
|
101
|
+
account_type = tool_context.state[:account_type]
|
102
|
+
|
103
|
+
return "No customer found" unless customer_id
|
104
|
+
|
105
|
+
# Use customer info for billing operations
|
106
|
+
billing_info = get_billing_info(customer_id, account_type)
|
107
|
+
billing_info.to_s
|
108
|
+
end
|
109
|
+
end
|
110
|
+
```
|