ai-agents 0.1.2 → 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/lib/agents/agent.rb +34 -0
- data/lib/agents/agent_tool.rb +113 -0
- data/lib/agents/handoff.rb +8 -34
- data/lib/agents/version.rb +1 -1
- data/lib/agents.rb +1 -0
- metadata +43 -1
@@ -0,0 +1,81 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: Handoffs
|
4
|
+
parent: Concepts
|
5
|
+
nav_order: 4
|
6
|
+
---
|
7
|
+
|
8
|
+
# Handoffs
|
9
|
+
|
10
|
+
**Handoffs** are a powerful feature of the Ruby Agents library that allow you to build sophisticated multi-agent systems. A handoff is the process of transferring a conversation from one agent to another, more specialized agent.
|
11
|
+
|
12
|
+
This is particularly useful when you have a general-purpose agent that can handle a wide range of queries, but you also have specialized agents that are better equipped to handle specific tasks. For example, you might have a triage agent that routes users to a billing agent or a technical support agent.
|
13
|
+
|
14
|
+
## How Handoffs Work
|
15
|
+
|
16
|
+
Handoffs are implemented as a special type of tool called a `HandoffTool`. When you configure an agent with `handoff_agents`, the library automatically creates a `HandoffTool` for each of the specified agents.
|
17
|
+
|
18
|
+
Here's how the handoff process works:
|
19
|
+
|
20
|
+
1. **The user sends a message:** The user sends a message that indicates they need help with a specific task (e.g., "I have a problem with my bill").
|
21
|
+
2. **The LLM decides to hand off:** The current agent's language model determines that the query is best handled by another agent and decides to call the corresponding `HandoffTool`.
|
22
|
+
3. **The `HandoffTool` signals the handoff:** The `HandoffTool` sets a `pending_handoff` flag in the `RunContext`, indicating which agent to hand off to.
|
23
|
+
4. **The Runner switches agents:** The `Runner` detects the `pending_handoff` flag and switches the `current_agent` to the new agent.
|
24
|
+
5. **The conversation continues:** The conversation continues with the new agent, which now has access to the full conversation history.
|
25
|
+
|
26
|
+
### Loop Prevention
|
27
|
+
|
28
|
+
To prevent infinite handoff loops, the library automatically processes only the first handoff tool call in any LLM response. If multiple handoff tools are called in a single response, only the first one is executed and subsequent calls are ignored. This prevents conflicting handoff states and ensures clean agent transitions.
|
29
|
+
|
30
|
+
## Why Use Tools for Handoffs?
|
31
|
+
|
32
|
+
Using tools for handoffs has several advantages over simply instructing the LLM to hand off the conversation:
|
33
|
+
|
34
|
+
* **Reliability:** LLMs are very good at using tools when they are available. By representing handoffs as tools, we can be more confident that the LLM will use them when appropriate.
|
35
|
+
* **Clarity:** The tool's schema clearly defines when each handoff is suitable, making it easier for the LLM to make the right decision.
|
36
|
+
* **Simplicity:** We don't need to parse free-text responses from the LLM to determine if a handoff is needed.
|
37
|
+
* **Consistency:** This approach works consistently across different LLM providers.
|
38
|
+
|
39
|
+
## Example
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
# Create the specialized agents
|
43
|
+
billing_agent = Agents::Agent.new(name: "Billing", instructions: "Handle billing and payment issues.")
|
44
|
+
support_agent = Agents::Agent.new(name: "Support", instructions: "Provide technical support.")
|
45
|
+
|
46
|
+
# Create the triage agent with handoff agents
|
47
|
+
triage_agent = Agents::Agent.new(
|
48
|
+
name: "Triage",
|
49
|
+
instructions: "You are a triage agent. Your job is to route users to the correct department.",
|
50
|
+
handoff_agents: [billing_agent, support_agent]
|
51
|
+
)
|
52
|
+
|
53
|
+
# Run the triage agent
|
54
|
+
result = Agents::Runner.run(triage_agent, "I have a problem with my bill.")
|
55
|
+
|
56
|
+
# The runner will automatically hand off to the billing agent
|
57
|
+
```
|
58
|
+
|
59
|
+
In this example, the `triage_agent` will automatically hand off the conversation to the `billing_agent` when the user asks a question about their bill. This allows you to create a seamless user experience where the user is always talking to the most qualified agent for their needs.
|
60
|
+
|
61
|
+
## Troubleshooting Handoffs
|
62
|
+
|
63
|
+
### Infinite Handoff Loops
|
64
|
+
|
65
|
+
**Problem:** Agents keep handing off to each other in an endless loop.
|
66
|
+
|
67
|
+
**Common Causes:**
|
68
|
+
- Agent instructions that conflict with each other
|
69
|
+
- Agents configured to hand off for overlapping scenarios
|
70
|
+
- Poor instruction clarity about when to hand off vs. when to handle directly
|
71
|
+
|
72
|
+
**Solutions:**
|
73
|
+
1. **Review agent instructions:** Ensure each agent has a clear, distinct responsibility
|
74
|
+
2. **Use hub-and-spoke pattern:** Have specialized agents only hand off back to a central triage agent
|
75
|
+
3. **Add specific scenarios:** Include examples in instructions of when to handle vs. hand off
|
76
|
+
4. **Enable debug logging:** Use `ENV["RUBYLLM_DEBUG"] = "true"` to see handoff decisions
|
77
|
+
|
78
|
+
|
79
|
+
### Multiple Handoffs in One Response
|
80
|
+
|
81
|
+
The library automatically handles cases where an LLM tries to call multiple handoff tools in a single response. Only the first handoff will be processed, and subsequent calls will be ignored. This is normal behavior and prevents conflicting handoff states.
|
@@ -0,0 +1,87 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: Runner
|
4
|
+
parent: Concepts
|
5
|
+
nav_order: 5
|
6
|
+
---
|
7
|
+
|
8
|
+
# AgentRunner
|
9
|
+
|
10
|
+
The **AgentRunner** is the thread-safe execution manager that provides the main API for multi-agent conversations. It separates agent registry management from execution, enabling safe concurrent use across multiple threads while maintaining conversation continuity.
|
11
|
+
|
12
|
+
## Two-Tier Architecture
|
13
|
+
|
14
|
+
The library uses a two-tier design separating long-lived configuration from short-lived execution:
|
15
|
+
|
16
|
+
### AgentRunner (Thread-Safe Manager)
|
17
|
+
- Created once at application startup
|
18
|
+
- Maintains immutable agent registry
|
19
|
+
- Determines conversation continuity from history
|
20
|
+
- Thread-safe for concurrent conversations
|
21
|
+
|
22
|
+
### Runner (Internal Execution Engine)
|
23
|
+
- Created per conversation turn
|
24
|
+
- Handles LLM communication and tool execution
|
25
|
+
- Manages context state during execution
|
26
|
+
- Stateless and garbage-collected after use
|
27
|
+
|
28
|
+
## Conversation Flow
|
29
|
+
|
30
|
+
Each conversation follows this flow:
|
31
|
+
|
32
|
+
1. **Agent Selection**: AgentRunner determines current agent from conversation history
|
33
|
+
2. **Context Isolation**: Creates deep copy of context for thread safety
|
34
|
+
3. **LLM Communication**: Sends message with context to language model
|
35
|
+
4. **Tool Execution**: Executes any requested tools through RubyLLM
|
36
|
+
5. **Handoff Detection**: Checks for agent handoffs and switches if needed
|
37
|
+
6. **State Persistence**: Updates context with conversation state
|
38
|
+
|
39
|
+
## Thread Safety
|
40
|
+
|
41
|
+
The AgentRunner ensures thread safety through several key mechanisms:
|
42
|
+
|
43
|
+
* **Immutable Registry**: Agent registry is frozen after initialization, preventing runtime modifications
|
44
|
+
* **Context Isolation**: Each execution receives a deep copy of context to prevent cross-contamination
|
45
|
+
* **Stateless Execution**: Internal Runner instances store no execution-specific state
|
46
|
+
* **Tool Wrapping**: ToolWrapper injects context through parameters, keeping tools stateless
|
47
|
+
|
48
|
+
## Usage Pattern
|
49
|
+
|
50
|
+
Create an AgentRunner once and reuse it for multiple conversations:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
# Create agents
|
54
|
+
triage_agent = Agents::Agent.new(
|
55
|
+
name: "Triage",
|
56
|
+
instructions: "Route users to appropriate specialists"
|
57
|
+
)
|
58
|
+
billing_agent = Agents::Agent.new(
|
59
|
+
name: "Billing",
|
60
|
+
instructions: "Handle billing inquiries"
|
61
|
+
)
|
62
|
+
|
63
|
+
# Register handoffs
|
64
|
+
triage_agent.register_handoffs(billing_agent)
|
65
|
+
|
66
|
+
# Create runner once (thread-safe)
|
67
|
+
runner = Agents::Runner.with_agents(triage_agent, billing_agent)
|
68
|
+
|
69
|
+
# Use from multiple threads safely
|
70
|
+
result1 = runner.run("I have a billing question")
|
71
|
+
result2 = runner.run("Follow up", context: result1.context)
|
72
|
+
```
|
73
|
+
|
74
|
+
## Conversation Continuity
|
75
|
+
|
76
|
+
The AgentRunner automatically maintains conversation continuity by analyzing message history to determine which agent should handle each turn:
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
# First message -> Uses triage agent (default entry point)
|
80
|
+
result1 = runner.run("I need help with my bill")
|
81
|
+
|
82
|
+
# Triage hands off to billing agent
|
83
|
+
# Next message -> AgentRunner detects billing agent should continue
|
84
|
+
result2 = runner.run("What payment methods do you accept?", context: result1.context)
|
85
|
+
```
|
86
|
+
|
87
|
+
The `run` method returns a `RunResult` with output, conversation history, usage metrics, and updated context.
|
@@ -0,0 +1,62 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: Tools
|
4
|
+
parent: Concepts
|
5
|
+
nav_order: 2
|
6
|
+
---
|
7
|
+
|
8
|
+
# Tools
|
9
|
+
|
10
|
+
**Tools** are the components that allow agents to interact with the outside world. They are the primary way to extend an agent's capabilities beyond what the language model can do on its own. A tool can be anything from a simple calculator to a complex integration with an external API.
|
11
|
+
|
12
|
+
In Ruby Agents, tools are designed to be thread-safe and stateless. This is a critical design principle that ensures the stability and reliability of your agent system, especially in concurrent environments.
|
13
|
+
|
14
|
+
## Thread-Safe Design
|
15
|
+
|
16
|
+
The key to the thread-safe design of tools is that they do not store any execution-specific state in their instance variables. All the data a tool needs to perform its action is passed to it through the `perform` method, which receives a `ToolContext` object.
|
17
|
+
|
18
|
+
### The `ToolContext`
|
19
|
+
|
20
|
+
The `ToolContext` provides access to the current execution context, including:
|
21
|
+
|
22
|
+
* **Shared context data:** A hash of data that is shared across all tools and agents in a given run.
|
23
|
+
* **Usage tracking:** An object that tracks token usage for the current run.
|
24
|
+
* **Retry count:** The number of times the current tool execution has been retried.
|
25
|
+
|
26
|
+
By passing all the necessary data through the `ToolContext`, we ensure that tool instances can be safely shared across multiple threads without the risk of data corruption.
|
27
|
+
|
28
|
+
## Creating a Tool
|
29
|
+
|
30
|
+
You can create a tool in two ways:
|
31
|
+
|
32
|
+
1. **Creating a Tool Class:** For more complex tools, you can create a class that inherits from `Agents::Tool` and implements the `perform` method.
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
class WeatherTool < Agents::Tool
|
36
|
+
name "get_weather"
|
37
|
+
description "Get the current weather for a location."
|
38
|
+
param :location, type: "string", desc: "The city and state, e.g., San Francisco, CA"
|
39
|
+
|
40
|
+
def perform(tool_context, location:)
|
41
|
+
# Access the API key from the shared context
|
42
|
+
api_key = tool_context.context[:weather_api_key]
|
43
|
+
|
44
|
+
# Call the weather API and return the result
|
45
|
+
WeatherApi.get(location, api_key)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
2. **Using the Functional Tool Definition:** For simpler tools, you can use the `Agents::Tool.tool` helper to define a tool with a block.
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
calculator_tool = Agents::Tool.tool(
|
54
|
+
"calculate",
|
55
|
+
description: "Perform a mathematical calculation."
|
56
|
+
) do |tool_context, expression:|
|
57
|
+
# Perform the calculation and return the result
|
58
|
+
eval(expression).to_s
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
In both cases, the `perform` method receives the `tool_context` and the tool's parameters as arguments. This design ensures that your tools are always thread-safe and easy to test.
|
data/docs/concepts.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: Concepts
|
4
|
+
nav_order: 2
|
5
|
+
has_children: true
|
6
|
+
---
|
7
|
+
|
8
|
+
# Concepts
|
9
|
+
|
10
|
+
This section covers the core concepts of the AI Agents library. Understanding these concepts is essential for building robust and scalable AI agent systems.
|
11
|
+
|
12
|
+
## Overview
|
13
|
+
|
14
|
+
The AI Agents library is built around several key concepts that work together to provide a powerful framework for multi-agent AI workflows:
|
15
|
+
|
16
|
+
- **[Agents](concepts/agents.html)** - Immutable, thread-safe AI assistants with specific roles and capabilities
|
17
|
+
- **[AgentRunner](concepts/runner.html)** - Thread-safe execution manager for multi-agent conversations
|
18
|
+
- **[Context](concepts/context.html)** - Serializable state management that persists across agent interactions
|
19
|
+
- **[Handoffs](concepts/handoffs.html)** - Tool-based mechanism for seamless agent transitions
|
20
|
+
- **[Tools](concepts/tools.html)** - Stateless extensions for external system integration
|
21
|
+
- **[AgentTool](concepts/agent-tool.html)** - Agent-to-agent collaboration without conversation handoffs
|
@@ -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)
|