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.
- checksums.yaml +4 -4
- data/.claude/commands/bump-version.md +44 -0
- data/CHANGELOG.md +35 -0
- data/CLAUDE.md +59 -15
- 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/callbacks.md +42 -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 +22 -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 +97 -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/interactive.rb +43 -4
- data/lib/agents/agent.rb +34 -0
- data/lib/agents/agent_runner.rb +66 -1
- data/lib/agents/agent_tool.rb +113 -0
- data/lib/agents/callback_manager.rb +54 -0
- data/lib/agents/handoff.rb +8 -34
- data/lib/agents/message_extractor.rb +82 -0
- data/lib/agents/run_context.rb +5 -2
- data/lib/agents/runner.rb +16 -27
- data/lib/agents/tool_wrapper.rb +11 -1
- data/lib/agents/version.rb +1 -1
- data/lib/agents.rb +3 -0
- metadata +48 -1
data/docs/_config.yml
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
title: AI Agents
|
2
|
+
description: A Ruby SDK for building multi-agent AI workflows
|
3
|
+
baseurl: ""
|
4
|
+
url: ""
|
5
|
+
|
6
|
+
# Theme
|
7
|
+
theme: just-the-docs
|
8
|
+
|
9
|
+
# Color scheme
|
10
|
+
color_scheme: ruby
|
11
|
+
|
12
|
+
# Search
|
13
|
+
search_enabled: true
|
14
|
+
search:
|
15
|
+
heading_level: 2
|
16
|
+
previews: 3
|
17
|
+
preview_words_before: 5
|
18
|
+
preview_words_after: 10
|
19
|
+
tokenizer_separator: /[\s/]+/
|
20
|
+
rel_url: true
|
21
|
+
button: false
|
22
|
+
|
23
|
+
# Navigation
|
24
|
+
nav_sort: case_insensitive
|
25
|
+
nav_external_links:
|
26
|
+
- title: GitHub
|
27
|
+
url: https://github.com/chatwoot/ai-agents
|
28
|
+
|
29
|
+
# Footer
|
30
|
+
footer_content: "Copyright © 2025 Chatwoot Inc."
|
31
|
+
|
32
|
+
# Plugins
|
33
|
+
plugins:
|
34
|
+
- jekyll-remote-theme
|
35
|
+
- jekyll-seo-tag
|
36
|
+
- jekyll-sitemap
|
37
|
+
|
38
|
+
# Markdown
|
39
|
+
markdown: kramdown
|
40
|
+
highlighter: rouge
|
41
|
+
|
42
|
+
# Exclude from processing
|
43
|
+
exclude:
|
44
|
+
- .sass-cache/
|
45
|
+
- .jekyll-cache/
|
46
|
+
- gemfiles/
|
47
|
+
- Gemfile
|
48
|
+
- Gemfile.lock
|
49
|
+
- node_modules/
|
50
|
+
- vendor/bundle/
|
51
|
+
- vendor/cache/
|
52
|
+
- vendor/gems/
|
53
|
+
- vendor/ruby/
|
@@ -0,0 +1,72 @@
|
|
1
|
+
// Ruby red color scheme for AI Agents documentation
|
2
|
+
// Inspired by Ruby's signature red color
|
3
|
+
|
4
|
+
// Define red color variables
|
5
|
+
$red-000: #fff5f5;
|
6
|
+
$red-100: #fed7d7;
|
7
|
+
$red-200: #feb2b2;
|
8
|
+
$red-300: #fc8181;
|
9
|
+
$red-400: #e53e3e;
|
10
|
+
$red-500: #d30001; // Primary Ruby red
|
11
|
+
$red-600: #b30001;
|
12
|
+
$red-700: #930001;
|
13
|
+
$red-800: #730001;
|
14
|
+
$red-900: #530001;
|
15
|
+
|
16
|
+
// Grey colors with subtle reddish tint for text and backgrounds
|
17
|
+
$grey-dk-000: #959396;
|
18
|
+
$grey-dk-100: #5c5962;
|
19
|
+
$grey-dk-200: #44434d;
|
20
|
+
$grey-dk-250: #302d36;
|
21
|
+
$grey-dk-300: #27262b;
|
22
|
+
|
23
|
+
$grey-lt-000: #faf6f6; // Very slightly reddish background
|
24
|
+
$grey-lt-100: #f7f2f2; // Very subtle reddish tint
|
25
|
+
$grey-lt-200: #f4efef; // Very subtle reddish tint
|
26
|
+
$grey-lt-300: #f0e8e8; // Very subtle reddish tint
|
27
|
+
|
28
|
+
// Override theme variables
|
29
|
+
$link-color: $red-500;
|
30
|
+
$btn-primary-color: $red-500;
|
31
|
+
$base-button-color: #f7f7f7;
|
32
|
+
|
33
|
+
// Navigation
|
34
|
+
$nav-child-link-color: $grey-dk-100;
|
35
|
+
$nav-list-item-color: $grey-dk-100;
|
36
|
+
$nav-list-item-active-color: $red-500;
|
37
|
+
|
38
|
+
// Sidebar background
|
39
|
+
$sidebar-color: $grey-lt-100; // Subtle reddish tint for sidebar
|
40
|
+
|
41
|
+
// Search
|
42
|
+
$search-active-color: $red-500;
|
43
|
+
$search-background-color: $grey-lt-000;
|
44
|
+
|
45
|
+
// Code
|
46
|
+
$code-background-color: $grey-lt-000;
|
47
|
+
$code-border-color: $grey-lt-300;
|
48
|
+
|
49
|
+
// Tables
|
50
|
+
$table-background-color: $grey-lt-000;
|
51
|
+
$border-color: $grey-lt-300;
|
52
|
+
|
53
|
+
// Buttons
|
54
|
+
$btn-outline-color: $red-500;
|
55
|
+
|
56
|
+
// Focus states
|
57
|
+
$focus-color: $red-400;
|
58
|
+
|
59
|
+
// Feedback states
|
60
|
+
$feedback-color: $grey-lt-300;
|
61
|
+
|
62
|
+
// Headings
|
63
|
+
$h1-color: $grey-dk-300;
|
64
|
+
$h2-color: $grey-dk-300;
|
65
|
+
$h3-color: $grey-dk-200;
|
66
|
+
$h4-color: $grey-dk-200;
|
67
|
+
$h5-color: $grey-dk-200;
|
68
|
+
$h6-color: $grey-dk-200;
|
69
|
+
|
70
|
+
// Body text
|
71
|
+
$body-text-color: $grey-dk-100;
|
72
|
+
$body-heading-color: $grey-dk-300;
|
@@ -0,0 +1,93 @@
|
|
1
|
+
// Self-hosted Inter Variable font
|
2
|
+
@font-face {
|
3
|
+
font-family: "Inter";
|
4
|
+
src: url("/assets/fonts/InterVariable.woff2") format("woff2-variations");
|
5
|
+
font-weight: 100 900;
|
6
|
+
font-style: normal;
|
7
|
+
font-display: swap;
|
8
|
+
font-named-instance: "Regular";
|
9
|
+
}
|
10
|
+
|
11
|
+
// Override font variables
|
12
|
+
$body-font-family:
|
13
|
+
"Inter",
|
14
|
+
-apple-system,
|
15
|
+
BlinkMacSystemFont,
|
16
|
+
"Segoe UI",
|
17
|
+
Helvetica,
|
18
|
+
Arial,
|
19
|
+
sans-serif;
|
20
|
+
$mono-font-family: "SF Mono", Monaco, Inconsolata, "Roboto Mono", monospace;
|
21
|
+
|
22
|
+
// Apply Inter to all text elements
|
23
|
+
body {
|
24
|
+
font-family: $body-font-family;
|
25
|
+
font-feature-settings:
|
26
|
+
"cv02", "cv03", "cv04", "cv11"; // Display-like features
|
27
|
+
}
|
28
|
+
|
29
|
+
// Ensure headings use Inter as well
|
30
|
+
h1,
|
31
|
+
h2,
|
32
|
+
h3,
|
33
|
+
h4,
|
34
|
+
h5,
|
35
|
+
h6 {
|
36
|
+
font-family: $body-font-family;
|
37
|
+
}
|
38
|
+
|
39
|
+
// Custom logo styling
|
40
|
+
.site-logo {
|
41
|
+
padding: 1rem;
|
42
|
+
border-bottom: 1px solid var(--border-color);
|
43
|
+
|
44
|
+
.site-logo-link {
|
45
|
+
display: flex;
|
46
|
+
align-items: center;
|
47
|
+
text-decoration: none;
|
48
|
+
color: inherit;
|
49
|
+
|
50
|
+
&:hover {
|
51
|
+
text-decoration: none;
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
.site-logo-image {
|
56
|
+
width: 32px;
|
57
|
+
height: 32px;
|
58
|
+
margin-right: 0.75rem;
|
59
|
+
border-radius: 6px;
|
60
|
+
}
|
61
|
+
|
62
|
+
.site-title {
|
63
|
+
font-weight: 600;
|
64
|
+
font-size: 1.1rem;
|
65
|
+
color: var(--body-heading-color);
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
// Note callout styling
|
70
|
+
.note {
|
71
|
+
padding: 1rem;
|
72
|
+
margin: 1rem 0;
|
73
|
+
background-color: #faf6f6;
|
74
|
+
border: 1px solid #f0e8e8;
|
75
|
+
border-left: 4px solid #d30001;
|
76
|
+
border-radius: 6px;
|
77
|
+
position: relative;
|
78
|
+
|
79
|
+
&::before {
|
80
|
+
content: "NOTE";
|
81
|
+
font-weight: 600;
|
82
|
+
font-size: 0.75rem;
|
83
|
+
color: #d30001;
|
84
|
+
text-transform: uppercase;
|
85
|
+
letter-spacing: 0.05em;
|
86
|
+
display: block;
|
87
|
+
margin-bottom: 0.5rem;
|
88
|
+
}
|
89
|
+
|
90
|
+
p:last-child {
|
91
|
+
margin-bottom: 0;
|
92
|
+
}
|
93
|
+
}
|
@@ -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
|