praisonaiagents 0.0.108__py3-none-any.whl → 0.0.110__py3-none-any.whl
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.
- praisonaiagents/__init__.py +6 -0
- praisonaiagents/agent/__init__.py +2 -1
- praisonaiagents/agent/agent.py +79 -3
- praisonaiagents/agent/handoff.py +317 -0
- praisonaiagents/llm/llm.py +217 -68
- praisonaiagents/mcp/mcp.py +27 -7
- praisonaiagents/memory/memory.py +48 -0
- praisonaiagents/tools/duckdb_tools.py +47 -16
- praisonaiagents/tools/file_tools.py +52 -10
- praisonaiagents/tools/python_tools.py +84 -4
- praisonaiagents/tools/shell_tools.py +18 -8
- praisonaiagents/tools/spider_tools.py +55 -0
- {praisonaiagents-0.0.108.dist-info → praisonaiagents-0.0.110.dist-info}/METADATA +1 -1
- {praisonaiagents-0.0.108.dist-info → praisonaiagents-0.0.110.dist-info}/RECORD +16 -15
- {praisonaiagents-0.0.108.dist-info → praisonaiagents-0.0.110.dist-info}/WHEEL +0 -0
- {praisonaiagents-0.0.108.dist-info → praisonaiagents-0.0.110.dist-info}/top_level.txt +0 -0
praisonaiagents/__init__.py
CHANGED
@@ -14,6 +14,7 @@ from .mcp.mcp import MCP
|
|
14
14
|
from .session import Session
|
15
15
|
from .memory.memory import Memory
|
16
16
|
from .guardrails import GuardrailResult, LLMGuardrail
|
17
|
+
from .agent.handoff import Handoff, handoff, handoff_filters, RECOMMENDED_PROMPT_PREFIX, prompt_with_handoff_instructions
|
17
18
|
from .main import (
|
18
19
|
TaskOutput,
|
19
20
|
ReflectionOutput,
|
@@ -102,6 +103,11 @@ __all__ = [
|
|
102
103
|
'MCP',
|
103
104
|
'GuardrailResult',
|
104
105
|
'LLMGuardrail',
|
106
|
+
'Handoff',
|
107
|
+
'handoff',
|
108
|
+
'handoff_filters',
|
109
|
+
'RECOMMENDED_PROMPT_PREFIX',
|
110
|
+
'prompt_with_handoff_instructions',
|
105
111
|
'get_telemetry',
|
106
112
|
'enable_telemetry',
|
107
113
|
'disable_telemetry',
|
@@ -1,5 +1,6 @@
|
|
1
1
|
"""Agent module for AI agents"""
|
2
2
|
from .agent import Agent
|
3
3
|
from .image_agent import ImageAgent
|
4
|
+
from .handoff import Handoff, handoff, handoff_filters, RECOMMENDED_PROMPT_PREFIX, prompt_with_handoff_instructions
|
4
5
|
|
5
|
-
__all__ = ['Agent', 'ImageAgent']
|
6
|
+
__all__ = ['Agent', 'ImageAgent', 'Handoff', 'handoff', 'handoff_filters', 'RECOMMENDED_PROMPT_PREFIX', 'prompt_with_handoff_instructions']
|
praisonaiagents/agent/agent.py
CHANGED
@@ -33,6 +33,7 @@ _shared_apps = {} # Dict of port -> FastAPI app
|
|
33
33
|
if TYPE_CHECKING:
|
34
34
|
from ..task.task import Task
|
35
35
|
from ..main import TaskOutput
|
36
|
+
from ..handoff import Handoff
|
36
37
|
|
37
38
|
@dataclass
|
38
39
|
class ChatCompletionMessage:
|
@@ -373,7 +374,10 @@ class Agent:
|
|
373
374
|
user_id: Optional[str] = None,
|
374
375
|
reasoning_steps: bool = False,
|
375
376
|
guardrail: Optional[Union[Callable[['TaskOutput'], Tuple[bool, Any]], str]] = None,
|
376
|
-
max_guardrail_retries: int = 3
|
377
|
+
max_guardrail_retries: int = 3,
|
378
|
+
handoffs: Optional[List[Union['Agent', 'Handoff']]] = None,
|
379
|
+
base_url: Optional[str] = None,
|
380
|
+
api_key: Optional[str] = None
|
377
381
|
):
|
378
382
|
"""Initialize an Agent instance.
|
379
383
|
|
@@ -457,6 +461,13 @@ class Agent:
|
|
457
461
|
description string for LLM-based validation. Defaults to None.
|
458
462
|
max_guardrail_retries (int, optional): Maximum number of retry attempts when guardrail
|
459
463
|
validation fails before giving up. Defaults to 3.
|
464
|
+
handoffs (Optional[List[Union['Agent', 'Handoff']]], optional): List of agents or
|
465
|
+
handoff configurations that this agent can delegate tasks to. Enables agent-to-agent
|
466
|
+
collaboration and task specialization. Defaults to None.
|
467
|
+
base_url (Optional[str], optional): Base URL for custom LLM endpoints (e.g., Ollama).
|
468
|
+
If provided, automatically creates a custom LLM instance. Defaults to None.
|
469
|
+
api_key (Optional[str], optional): API key for LLM provider. If not provided,
|
470
|
+
falls back to environment variables. Defaults to None.
|
460
471
|
|
461
472
|
Raises:
|
462
473
|
ValueError: If all of name, role, goal, backstory, and instructions are None.
|
@@ -503,10 +514,40 @@ class Agent:
|
|
503
514
|
# Check for model name in environment variable if not provided
|
504
515
|
self._using_custom_llm = False
|
505
516
|
|
517
|
+
# If base_url is provided, always create a custom LLM instance
|
518
|
+
if base_url:
|
519
|
+
try:
|
520
|
+
from ..llm.llm import LLM
|
521
|
+
# Handle different llm parameter types with base_url
|
522
|
+
if isinstance(llm, dict):
|
523
|
+
# Merge base_url and api_key into the dict
|
524
|
+
llm_config = llm.copy()
|
525
|
+
llm_config['base_url'] = base_url
|
526
|
+
if api_key:
|
527
|
+
llm_config['api_key'] = api_key
|
528
|
+
self.llm_instance = LLM(**llm_config)
|
529
|
+
else:
|
530
|
+
# Create LLM with model string and base_url
|
531
|
+
model_name = llm or os.getenv('OPENAI_MODEL_NAME', 'gpt-4o')
|
532
|
+
self.llm_instance = LLM(
|
533
|
+
model=model_name,
|
534
|
+
base_url=base_url,
|
535
|
+
api_key=api_key
|
536
|
+
)
|
537
|
+
self._using_custom_llm = True
|
538
|
+
except ImportError as e:
|
539
|
+
raise ImportError(
|
540
|
+
"LLM features requested but dependencies not installed. "
|
541
|
+
"Please install with: pip install \"praisonaiagents[llm]\""
|
542
|
+
) from e
|
506
543
|
# If the user passes a dictionary (for advanced configuration)
|
507
|
-
|
544
|
+
elif isinstance(llm, dict) and "model" in llm:
|
508
545
|
try:
|
509
546
|
from ..llm.llm import LLM
|
547
|
+
# Add api_key if provided and not in dict
|
548
|
+
if api_key and 'api_key' not in llm:
|
549
|
+
llm = llm.copy()
|
550
|
+
llm['api_key'] = api_key
|
510
551
|
self.llm_instance = LLM(**llm) # Pass all dict items as kwargs
|
511
552
|
self._using_custom_llm = True
|
512
553
|
except ImportError as e:
|
@@ -519,7 +560,10 @@ class Agent:
|
|
519
560
|
try:
|
520
561
|
from ..llm.llm import LLM
|
521
562
|
# Pass the entire string so LiteLLM can parse provider/model
|
522
|
-
|
563
|
+
llm_params = {'model': llm}
|
564
|
+
if api_key:
|
565
|
+
llm_params['api_key'] = api_key
|
566
|
+
self.llm_instance = LLM(**llm_params)
|
523
567
|
self._using_custom_llm = True
|
524
568
|
|
525
569
|
# Ensure tools are properly accessible when using custom LLM
|
@@ -584,6 +628,10 @@ Your Goal: {self.goal}
|
|
584
628
|
self._guardrail_fn = None
|
585
629
|
self._setup_guardrail()
|
586
630
|
|
631
|
+
# Process handoffs and convert them to tools
|
632
|
+
self.handoffs = handoffs if handoffs else []
|
633
|
+
self._process_handoffs()
|
634
|
+
|
587
635
|
# Check if knowledge parameter has any values
|
588
636
|
if not knowledge:
|
589
637
|
self.knowledge = None
|
@@ -657,6 +705,34 @@ Your Goal: {self.goal}
|
|
657
705
|
else:
|
658
706
|
raise ValueError("Agent guardrail must be either a callable or a string description")
|
659
707
|
|
708
|
+
def _process_handoffs(self):
|
709
|
+
"""Process handoffs and convert them to tools that can be used by the agent."""
|
710
|
+
if not self.handoffs:
|
711
|
+
return
|
712
|
+
|
713
|
+
# Import here to avoid circular imports
|
714
|
+
from .handoff import Handoff
|
715
|
+
|
716
|
+
for handoff_item in self.handoffs:
|
717
|
+
try:
|
718
|
+
if isinstance(handoff_item, Handoff):
|
719
|
+
# Convert Handoff object to a tool function
|
720
|
+
tool_func = handoff_item.to_tool_function(self)
|
721
|
+
self.tools.append(tool_func)
|
722
|
+
elif hasattr(handoff_item, 'name') and hasattr(handoff_item, 'chat'):
|
723
|
+
# Direct agent reference - create a simple handoff
|
724
|
+
from .handoff import handoff
|
725
|
+
handoff_obj = handoff(handoff_item)
|
726
|
+
tool_func = handoff_obj.to_tool_function(self)
|
727
|
+
self.tools.append(tool_func)
|
728
|
+
else:
|
729
|
+
logging.warning(
|
730
|
+
f"Invalid handoff item type: {type(handoff_item)}. "
|
731
|
+
"Expected Agent or Handoff instance."
|
732
|
+
)
|
733
|
+
except Exception as e:
|
734
|
+
logging.error(f"Failed to process handoff item {handoff_item}: {e}")
|
735
|
+
|
660
736
|
def _process_guardrail(self, task_output):
|
661
737
|
"""Process the guardrail validation for a task output.
|
662
738
|
|
@@ -0,0 +1,317 @@
|
|
1
|
+
"""
|
2
|
+
Handoff functionality for agent-to-agent delegation.
|
3
|
+
|
4
|
+
This module provides handoff capabilities that allow agents to delegate tasks
|
5
|
+
to other agents, similar to the OpenAI Agents SDK implementation.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import Optional, Any, Callable, Dict, TYPE_CHECKING
|
9
|
+
from dataclasses import dataclass, field
|
10
|
+
import inspect
|
11
|
+
import logging
|
12
|
+
|
13
|
+
if TYPE_CHECKING:
|
14
|
+
from .agent import Agent
|
15
|
+
|
16
|
+
logger = logging.getLogger(__name__)
|
17
|
+
|
18
|
+
|
19
|
+
@dataclass
|
20
|
+
class HandoffInputData:
|
21
|
+
"""Data passed to a handoff target agent."""
|
22
|
+
messages: list = field(default_factory=list)
|
23
|
+
context: Dict[str, Any] = field(default_factory=dict)
|
24
|
+
|
25
|
+
|
26
|
+
class Handoff:
|
27
|
+
"""
|
28
|
+
Represents a handoff configuration for delegating tasks to another agent.
|
29
|
+
|
30
|
+
Handoffs are represented as tools to the LLM, allowing agents to transfer
|
31
|
+
control to specialized agents for specific tasks.
|
32
|
+
"""
|
33
|
+
|
34
|
+
def __init__(
|
35
|
+
self,
|
36
|
+
agent: 'Agent',
|
37
|
+
tool_name_override: Optional[str] = None,
|
38
|
+
tool_description_override: Optional[str] = None,
|
39
|
+
on_handoff: Optional[Callable] = None,
|
40
|
+
input_type: Optional[type] = None,
|
41
|
+
input_filter: Optional[Callable[[HandoffInputData], HandoffInputData]] = None
|
42
|
+
):
|
43
|
+
"""
|
44
|
+
Initialize a Handoff configuration.
|
45
|
+
|
46
|
+
Args:
|
47
|
+
agent: The target agent to hand off to
|
48
|
+
tool_name_override: Custom tool name (defaults to transfer_to_<agent_name>)
|
49
|
+
tool_description_override: Custom tool description
|
50
|
+
on_handoff: Callback function executed when handoff is invoked
|
51
|
+
input_type: Type of input expected by the handoff (for structured data)
|
52
|
+
input_filter: Function to filter/transform input before passing to target agent
|
53
|
+
"""
|
54
|
+
self.agent = agent
|
55
|
+
self.tool_name_override = tool_name_override
|
56
|
+
self.tool_description_override = tool_description_override
|
57
|
+
self.on_handoff = on_handoff
|
58
|
+
self.input_type = input_type
|
59
|
+
self.input_filter = input_filter
|
60
|
+
|
61
|
+
@property
|
62
|
+
def tool_name(self) -> str:
|
63
|
+
"""Get the tool name for this handoff."""
|
64
|
+
if self.tool_name_override:
|
65
|
+
return self.tool_name_override
|
66
|
+
return self.default_tool_name()
|
67
|
+
|
68
|
+
@property
|
69
|
+
def tool_description(self) -> str:
|
70
|
+
"""Get the tool description for this handoff."""
|
71
|
+
if self.tool_description_override:
|
72
|
+
return self.tool_description_override
|
73
|
+
return self.default_tool_description()
|
74
|
+
|
75
|
+
def default_tool_name(self) -> str:
|
76
|
+
"""Generate default tool name based on agent name."""
|
77
|
+
# Convert agent name to snake_case for tool name
|
78
|
+
agent_name = self.agent.name.lower().replace(' ', '_')
|
79
|
+
return f"transfer_to_{agent_name}"
|
80
|
+
|
81
|
+
def default_tool_description(self) -> str:
|
82
|
+
"""Generate default tool description based on agent role and goal."""
|
83
|
+
agent_desc = f"Transfer task to {self.agent.name}"
|
84
|
+
if hasattr(self.agent, 'role') and self.agent.role:
|
85
|
+
agent_desc += f" ({self.agent.role})"
|
86
|
+
if hasattr(self.agent, 'goal') and self.agent.goal:
|
87
|
+
agent_desc += f" - {self.agent.goal}"
|
88
|
+
return agent_desc
|
89
|
+
|
90
|
+
def to_tool_function(self, source_agent: 'Agent') -> Callable:
|
91
|
+
"""
|
92
|
+
Convert this handoff to a tool function that can be called by the LLM.
|
93
|
+
|
94
|
+
Args:
|
95
|
+
source_agent: The agent that will be using this handoff
|
96
|
+
|
97
|
+
Returns:
|
98
|
+
A callable function that performs the handoff
|
99
|
+
"""
|
100
|
+
def handoff_tool(**kwargs):
|
101
|
+
"""Execute the handoff to the target agent."""
|
102
|
+
try:
|
103
|
+
# Execute on_handoff callback if provided
|
104
|
+
if self.on_handoff:
|
105
|
+
try:
|
106
|
+
sig = inspect.signature(self.on_handoff)
|
107
|
+
# Get parameters excluding those with defaults and varargs/varkwargs
|
108
|
+
required_params = [
|
109
|
+
p for p in sig.parameters.values()
|
110
|
+
if p.default == inspect.Parameter.empty
|
111
|
+
and p.kind not in (p.VAR_POSITIONAL, p.VAR_KEYWORD)
|
112
|
+
]
|
113
|
+
num_required = len(required_params)
|
114
|
+
|
115
|
+
if num_required == 0:
|
116
|
+
self.on_handoff()
|
117
|
+
elif num_required == 1:
|
118
|
+
self.on_handoff(source_agent)
|
119
|
+
elif num_required == 2:
|
120
|
+
if self.input_type and kwargs:
|
121
|
+
try:
|
122
|
+
input_data = self.input_type(**kwargs)
|
123
|
+
self.on_handoff(source_agent, input_data)
|
124
|
+
except TypeError as e:
|
125
|
+
logger.error(f"Failed to create input_type instance: {e}")
|
126
|
+
self.on_handoff(source_agent, kwargs)
|
127
|
+
else:
|
128
|
+
# No input_type or no kwargs: pass raw kwargs or empty dict
|
129
|
+
self.on_handoff(source_agent, kwargs or {})
|
130
|
+
else:
|
131
|
+
raise ValueError(
|
132
|
+
f"Callback {self.on_handoff.__name__} requires {num_required} parameters, "
|
133
|
+
"but only 0-2 are supported"
|
134
|
+
)
|
135
|
+
except Exception as e:
|
136
|
+
logger.error(f"Error invoking callback {self.on_handoff.__name__}: {e}")
|
137
|
+
# Continue with handoff even if callback fails
|
138
|
+
|
139
|
+
# Prepare handoff data
|
140
|
+
handoff_data = HandoffInputData(
|
141
|
+
messages=getattr(source_agent, 'chat_history', []),
|
142
|
+
context={'source_agent': source_agent.name}
|
143
|
+
)
|
144
|
+
|
145
|
+
# Apply input filter if provided
|
146
|
+
if self.input_filter:
|
147
|
+
handoff_data = self.input_filter(handoff_data)
|
148
|
+
|
149
|
+
# Get the last user message or context to pass to target agent
|
150
|
+
last_message = None
|
151
|
+
for msg in reversed(handoff_data.messages):
|
152
|
+
if isinstance(msg, dict) and msg.get('role') == 'user':
|
153
|
+
last_message = msg.get('content', '')
|
154
|
+
break
|
155
|
+
|
156
|
+
if not last_message and handoff_data.messages:
|
157
|
+
# If no user message, use the last message
|
158
|
+
last_msg = handoff_data.messages[-1]
|
159
|
+
if isinstance(last_msg, dict):
|
160
|
+
last_message = last_msg.get('content', '')
|
161
|
+
else:
|
162
|
+
last_message = str(last_msg)
|
163
|
+
|
164
|
+
# Prepare context information
|
165
|
+
context_info = f"[Handoff from {source_agent.name}] "
|
166
|
+
if kwargs and self.input_type:
|
167
|
+
# Include structured input data in context
|
168
|
+
context_info += f"Context: {kwargs} "
|
169
|
+
|
170
|
+
# Execute the target agent
|
171
|
+
if last_message:
|
172
|
+
prompt = context_info + last_message
|
173
|
+
logger.info(f"Handing off to {self.agent.name} with prompt: {prompt}")
|
174
|
+
response = self.agent.chat(prompt)
|
175
|
+
return f"Handoff successful. {self.agent.name} response: {response}"
|
176
|
+
return f"Handoff to {self.agent.name} completed, but no specific task was provided."
|
177
|
+
|
178
|
+
except Exception as e:
|
179
|
+
logger.error(f"Error during handoff to {self.agent.name}: {str(e)}")
|
180
|
+
return f"Error during handoff to {self.agent.name}: {str(e)}"
|
181
|
+
|
182
|
+
# Set function metadata for tool definition generation
|
183
|
+
handoff_tool.__name__ = self.tool_name
|
184
|
+
handoff_tool.__doc__ = self.tool_description
|
185
|
+
|
186
|
+
# Add input type annotations if provided
|
187
|
+
if self.input_type and hasattr(self.input_type, '__annotations__'):
|
188
|
+
sig_params = []
|
189
|
+
for field_name, field_type in self.input_type.__annotations__.items():
|
190
|
+
sig_params.append(
|
191
|
+
inspect.Parameter(
|
192
|
+
field_name,
|
193
|
+
inspect.Parameter.KEYWORD_ONLY,
|
194
|
+
annotation=field_type
|
195
|
+
)
|
196
|
+
)
|
197
|
+
handoff_tool.__signature__ = inspect.Signature(sig_params)
|
198
|
+
|
199
|
+
return handoff_tool
|
200
|
+
|
201
|
+
|
202
|
+
def handoff(
|
203
|
+
agent: 'Agent',
|
204
|
+
tool_name_override: Optional[str] = None,
|
205
|
+
tool_description_override: Optional[str] = None,
|
206
|
+
on_handoff: Optional[Callable] = None,
|
207
|
+
input_type: Optional[type] = None,
|
208
|
+
input_filter: Optional[Callable[[HandoffInputData], HandoffInputData]] = None
|
209
|
+
) -> Handoff:
|
210
|
+
"""
|
211
|
+
Create a handoff configuration for delegating tasks to another agent.
|
212
|
+
|
213
|
+
This is a convenience function that creates a Handoff instance with the
|
214
|
+
specified configuration.
|
215
|
+
|
216
|
+
Args:
|
217
|
+
agent: The target agent to hand off to
|
218
|
+
tool_name_override: Custom tool name (defaults to transfer_to_<agent_name>)
|
219
|
+
tool_description_override: Custom tool description
|
220
|
+
on_handoff: Callback function executed when handoff is invoked
|
221
|
+
input_type: Type of input expected by the handoff (for structured data)
|
222
|
+
input_filter: Function to filter/transform input before passing to target agent
|
223
|
+
|
224
|
+
Returns:
|
225
|
+
A configured Handoff instance
|
226
|
+
|
227
|
+
Example:
|
228
|
+
```python
|
229
|
+
from praisonaiagents import Agent, handoff
|
230
|
+
|
231
|
+
billing_agent = Agent(name="Billing Agent")
|
232
|
+
refund_agent = Agent(name="Refund Agent")
|
233
|
+
|
234
|
+
triage_agent = Agent(
|
235
|
+
name="Triage Agent",
|
236
|
+
handoffs=[billing_agent, handoff(refund_agent)]
|
237
|
+
)
|
238
|
+
```
|
239
|
+
"""
|
240
|
+
return Handoff(
|
241
|
+
agent=agent,
|
242
|
+
tool_name_override=tool_name_override,
|
243
|
+
tool_description_override=tool_description_override,
|
244
|
+
on_handoff=on_handoff,
|
245
|
+
input_type=input_type,
|
246
|
+
input_filter=input_filter
|
247
|
+
)
|
248
|
+
|
249
|
+
|
250
|
+
# Handoff filters - common patterns for filtering handoff data
|
251
|
+
class handoff_filters:
|
252
|
+
"""Common handoff input filters."""
|
253
|
+
|
254
|
+
@staticmethod
|
255
|
+
def remove_all_tools(data: HandoffInputData) -> HandoffInputData:
|
256
|
+
"""Remove all tool calls from the message history."""
|
257
|
+
filtered_messages = []
|
258
|
+
for msg in data.messages:
|
259
|
+
if isinstance(msg, dict) and (msg.get('tool_calls') or msg.get('role') == 'tool'):
|
260
|
+
# Skip messages with tool calls
|
261
|
+
continue
|
262
|
+
filtered_messages.append(msg)
|
263
|
+
|
264
|
+
data.messages = filtered_messages
|
265
|
+
return data
|
266
|
+
|
267
|
+
@staticmethod
|
268
|
+
def keep_last_n_messages(n: int) -> Callable[[HandoffInputData], HandoffInputData]:
|
269
|
+
"""Keep only the last n messages in the history."""
|
270
|
+
def filter_func(data: HandoffInputData) -> HandoffInputData:
|
271
|
+
data.messages = data.messages[-n:]
|
272
|
+
return data
|
273
|
+
return filter_func
|
274
|
+
|
275
|
+
@staticmethod
|
276
|
+
def remove_system_messages(data: HandoffInputData) -> HandoffInputData:
|
277
|
+
"""Remove all system messages from the history."""
|
278
|
+
filtered_messages = []
|
279
|
+
for msg in data.messages:
|
280
|
+
if (isinstance(msg, dict) and msg.get('role') != 'system') or not isinstance(msg, dict):
|
281
|
+
filtered_messages.append(msg)
|
282
|
+
|
283
|
+
data.messages = filtered_messages
|
284
|
+
return data
|
285
|
+
|
286
|
+
|
287
|
+
# Recommended prompt prefix for agents that use handoffs
|
288
|
+
RECOMMENDED_PROMPT_PREFIX = """You have the ability to transfer tasks to specialized agents when appropriate.
|
289
|
+
When you determine that a task would be better handled by another agent with specific expertise,
|
290
|
+
use the transfer tool to hand off the task. The receiving agent will have the full context of
|
291
|
+
the conversation and will continue helping the user."""
|
292
|
+
|
293
|
+
|
294
|
+
def prompt_with_handoff_instructions(base_prompt: str, agent: 'Agent') -> str:
|
295
|
+
"""
|
296
|
+
Add handoff instructions to an agent's prompt.
|
297
|
+
|
298
|
+
Args:
|
299
|
+
base_prompt: The original prompt/instructions
|
300
|
+
agent: The agent that will use handoffs
|
301
|
+
|
302
|
+
Returns:
|
303
|
+
Updated prompt with handoff instructions
|
304
|
+
"""
|
305
|
+
if not hasattr(agent, 'handoffs') or not agent.handoffs:
|
306
|
+
return base_prompt
|
307
|
+
|
308
|
+
handoff_info = "\n\nAvailable handoff agents:\n"
|
309
|
+
for h in agent.handoffs:
|
310
|
+
if isinstance(h, Handoff):
|
311
|
+
handoff_info += f"- {h.agent.name}: {h.tool_description}\n"
|
312
|
+
else:
|
313
|
+
# Direct agent reference - create a temporary Handoff to get the default description
|
314
|
+
temp_handoff = Handoff(agent=h)
|
315
|
+
handoff_info += f"- {h.name}: {temp_handoff.tool_description}\n"
|
316
|
+
|
317
|
+
return RECOMMENDED_PROMPT_PREFIX + handoff_info + "\n\n" + base_prompt
|