praisonaiagents 0.0.109__tar.gz → 0.0.111__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/PKG-INFO +1 -1
  2. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/__init__.py +6 -0
  3. praisonaiagents-0.0.111/praisonaiagents/agent/__init__.py +6 -0
  4. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/agent/agent.py +37 -0
  5. praisonaiagents-0.0.111/praisonaiagents/agent/handoff.py +317 -0
  6. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/llm/llm.py +217 -68
  7. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/mcp/mcp.py +27 -7
  8. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/memory/memory.py +48 -0
  9. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/telemetry/integration.py +1 -1
  10. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/duckdb_tools.py +47 -16
  11. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/file_tools.py +52 -10
  12. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/python_tools.py +84 -4
  13. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/shell_tools.py +18 -8
  14. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/spider_tools.py +55 -0
  15. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents.egg-info/PKG-INFO +1 -1
  16. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents.egg-info/SOURCES.txt +2 -0
  17. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/pyproject.toml +1 -1
  18. praisonaiagents-0.0.111/tests/test_handoff_compatibility.py +238 -0
  19. praisonaiagents-0.0.109/praisonaiagents/agent/__init__.py +0 -5
  20. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/README.md +0 -0
  21. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/agent/image_agent.py +0 -0
  22. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/agents/__init__.py +0 -0
  23. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/agents/agents.py +0 -0
  24. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/agents/autoagents.py +0 -0
  25. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/approval.py +0 -0
  26. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/guardrails/__init__.py +0 -0
  27. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/guardrails/guardrail_result.py +0 -0
  28. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/guardrails/llm_guardrail.py +0 -0
  29. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/knowledge/__init__.py +0 -0
  30. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/knowledge/chunking.py +0 -0
  31. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/knowledge/knowledge.py +0 -0
  32. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/llm/__init__.py +0 -0
  33. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/main.py +0 -0
  34. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/mcp/__init__.py +0 -0
  35. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/mcp/mcp_sse.py +0 -0
  36. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/memory/__init__.py +0 -0
  37. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/process/__init__.py +0 -0
  38. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/process/process.py +0 -0
  39. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/session.py +0 -0
  40. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/task/__init__.py +0 -0
  41. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/task/task.py +0 -0
  42. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/telemetry/__init__.py +0 -0
  43. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/telemetry/telemetry.py +0 -0
  44. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/README.md +0 -0
  45. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/__init__.py +0 -0
  46. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/arxiv_tools.py +0 -0
  47. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/calculator_tools.py +0 -0
  48. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/csv_tools.py +0 -0
  49. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/duckduckgo_tools.py +0 -0
  50. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/excel_tools.py +0 -0
  51. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/json_tools.py +0 -0
  52. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/newspaper_tools.py +0 -0
  53. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/pandas_tools.py +0 -0
  54. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/searxng_tools.py +0 -0
  55. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/test.py +0 -0
  56. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/tools.py +0 -0
  57. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/train/data/generatecot.py +0 -0
  58. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/wikipedia_tools.py +0 -0
  59. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/xml_tools.py +0 -0
  60. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/yaml_tools.py +0 -0
  61. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents/tools/yfinance_tools.py +0 -0
  62. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents.egg-info/dependency_links.txt +0 -0
  63. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents.egg-info/requires.txt +0 -0
  64. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/praisonaiagents.egg-info/top_level.txt +0 -0
  65. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/setup.cfg +0 -0
  66. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/tests/test-graph-memory.py +0 -0
  67. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/tests/test.py +0 -0
  68. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/tests/test_ollama_async_fix.py +0 -0
  69. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/tests/test_ollama_fix.py +0 -0
  70. {praisonaiagents-0.0.109 → praisonaiagents-0.0.111}/tests/test_posthog_fixed.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: praisonaiagents
3
- Version: 0.0.109
3
+ Version: 0.0.111
4
4
  Summary: Praison AI agents for completing complex tasks with Self Reflection Agents
5
5
  Author: Mervin Praison
6
6
  Requires-Python: >=3.10
@@ -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',
@@ -0,0 +1,6 @@
1
+ """Agent module for AI agents"""
2
+ from .agent import Agent
3
+ from .image_agent import ImageAgent
4
+ from .handoff import Handoff, handoff, handoff_filters, RECOMMENDED_PROMPT_PREFIX, prompt_with_handoff_instructions
5
+
6
+ __all__ = ['Agent', 'ImageAgent', 'Handoff', 'handoff', 'handoff_filters', 'RECOMMENDED_PROMPT_PREFIX', 'prompt_with_handoff_instructions']
@@ -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:
@@ -374,6 +375,7 @@ class Agent:
374
375
  reasoning_steps: bool = False,
375
376
  guardrail: Optional[Union[Callable[['TaskOutput'], Tuple[bool, Any]], str]] = None,
376
377
  max_guardrail_retries: int = 3,
378
+ handoffs: Optional[List[Union['Agent', 'Handoff']]] = None,
377
379
  base_url: Optional[str] = None,
378
380
  api_key: Optional[str] = None
379
381
  ):
@@ -459,6 +461,9 @@ class Agent:
459
461
  description string for LLM-based validation. Defaults to None.
460
462
  max_guardrail_retries (int, optional): Maximum number of retry attempts when guardrail
461
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.
462
467
  base_url (Optional[str], optional): Base URL for custom LLM endpoints (e.g., Ollama).
463
468
  If provided, automatically creates a custom LLM instance. Defaults to None.
464
469
  api_key (Optional[str], optional): API key for LLM provider. If not provided,
@@ -623,6 +628,10 @@ Your Goal: {self.goal}
623
628
  self._guardrail_fn = None
624
629
  self._setup_guardrail()
625
630
 
631
+ # Process handoffs and convert them to tools
632
+ self.handoffs = handoffs if handoffs else []
633
+ self._process_handoffs()
634
+
626
635
  # Check if knowledge parameter has any values
627
636
  if not knowledge:
628
637
  self.knowledge = None
@@ -696,6 +705,34 @@ Your Goal: {self.goal}
696
705
  else:
697
706
  raise ValueError("Agent guardrail must be either a callable or a string description")
698
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
+
699
736
  def _process_guardrail(self, task_output):
700
737
  """Process the guardrail validation for a task output.
701
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