Skip to content

Commit 298ef8a

Browse files
committed
fix
1 parent 3694c7e commit 298ef8a

File tree

1 file changed

+98
-1
lines changed

1 file changed

+98
-1
lines changed

src/agents/handoffs/__init__.py

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@
2828
from ..agent import Agent, AgentBase
2929

3030

31+
# The handoff input type is the type of data passed when the agent is called via a handoff.
3132
THandoffInput = TypeVar("THandoffInput", default=Any)
33+
34+
# The agent type that the handoff returns.
3235
TAgent = TypeVar("TAgent", bound="AgentBase[Any]", default="Agent[Any]")
3336

3437
OnHandoffWithInput = Callable[[RunContextWrapper[Any], THandoffInput], Any]
@@ -38,31 +41,104 @@
3841
@dataclass(frozen=True)
3942
class HandoffInputData:
4043
input_history: str | tuple[TResponseInputItem, ...]
44+
"""
45+
The input history before `Runner.run()` was called.
46+
"""
47+
4148
pre_handoff_items: tuple[RunItem, ...]
49+
"""
50+
The items generated before the agent turn where the handoff was invoked.
51+
"""
52+
4253
new_items: tuple[RunItem, ...]
54+
"""
55+
The new items generated during the current agent turn, including the item that triggered the
56+
handoff and the tool output message representing the response from the handoff output.
57+
"""
58+
4359
run_context: RunContextWrapper[Any] | None = None
60+
"""
61+
The run context at the time the handoff was invoked. Note that, since this property was added
62+
later on, it is optional for backwards compatibility.
63+
"""
4464

4565
def clone(self, **kwargs: Any) -> HandoffInputData:
66+
"""
67+
Make a copy of the handoff input data, with the given arguments changed. For example, you
68+
could do:
69+
70+
```
71+
new_handoff_input_data = handoff_input_data.clone(new_items=())
72+
```
73+
"""
74+
4675
return dataclasses_replace(self, **kwargs)
4776

4877

4978
HandoffInputFilter: TypeAlias = Callable[[HandoffInputData], MaybeAwaitable[HandoffInputData]]
79+
"""A function that filters the input data passed to the next agent."""
80+
5081
HandoffHistoryMapper: TypeAlias = Callable[[list[TResponseInputItem]], list[TResponseInputItem]]
82+
"""A function that maps the previous transcript to the nested summary payload."""
5183

5284

5385
@dataclass
5486
class Handoff(Generic[TContext, TAgent]):
87+
"""A handoff is when an agent delegates a task to another agent.
88+
89+
For example, in a customer support scenario you might have a "triage agent" that determines
90+
which agent should handle the user's request, and sub-agents that specialize in different areas
91+
like billing, account management, etc.
92+
"""
93+
5594
tool_name: str
95+
"""The name of the tool that represents the handoff."""
96+
5697
tool_description: str
98+
"""The description of the tool that represents the handoff."""
99+
57100
input_json_schema: dict[str, Any]
101+
"""The JSON schema for the handoff input. Can be empty if the handoff does not take an input."""
102+
58103
on_invoke_handoff: Callable[[RunContextWrapper[Any], str], Awaitable[TAgent]]
104+
"""The function that invokes the handoff.
105+
106+
The parameters passed are: (1) the handoff run context, (2) the arguments from the LLM as a
107+
JSON string (or an empty string if ``input_json_schema`` is empty). Must return an agent.
108+
"""
109+
59110
agent_name: str
111+
"""The name of the agent that is being handed off to."""
112+
60113
input_filter: HandoffInputFilter | None = None
114+
"""A function that filters the inputs that are passed to the next agent.
115+
116+
By default, the new agent sees the entire conversation history. In some cases, you may want to
117+
filter inputs (for example, to remove older inputs or remove tools from existing inputs). The
118+
function receives the entire conversation history so far, including the input item that
119+
triggered the handoff and a tool call output item representing the handoff tool's output. You
120+
are free to modify the input history or new items as you see fit. The next agent that runs will
121+
receive ``handoff_input_data.all_items``. IMPORTANT: in streaming mode, we will not stream
122+
anything as a result of this function. The items generated before will already have been
123+
streamed.
124+
"""
125+
61126
nest_handoff_history: bool | None = None
127+
"""Override the run-level ``nest_handoff_history`` behavior for this handoff only."""
128+
62129
strict_json_schema: bool = True
130+
"""Whether the input JSON schema is in strict mode. We strongly recommend setting this to True
131+
because it increases the likelihood of correct JSON input."""
132+
63133
is_enabled: bool | Callable[[RunContextWrapper[Any], AgentBase[Any]], MaybeAwaitable[bool]] = (
64134
True
65135
)
136+
"""Whether the handoff is enabled.
137+
138+
Either a bool or a callable that takes the run context and agent and returns whether the
139+
handoff is enabled. You can use this to dynamically enable or disable a handoff based on your
140+
context or state.
141+
"""
66142

67143
def get_transfer_message(self, agent: AgentBase[Any]) -> str:
68144
return json.dumps({"assistant": agent.name})
@@ -129,6 +205,24 @@ def handoff(
129205
is_enabled: bool
130206
| Callable[[RunContextWrapper[Any], Agent[TContext]], MaybeAwaitable[bool]] = True,
131207
) -> Handoff[TContext, Agent[TContext]]:
208+
"""Create a handoff from an agent.
209+
210+
Args:
211+
agent: The agent to handoff to, or a function that returns an agent.
212+
tool_name_override: Optional override for the name of the tool that represents the handoff.
213+
tool_description_override: Optional override for the description of the tool that
214+
represents the handoff.
215+
on_handoff: A function that runs when the handoff is invoked.
216+
input_type: The type of the input to the handoff. If provided, the input will be validated
217+
against this type. Only relevant if you pass a function that takes an input.
218+
input_filter: A function that filters the inputs that are passed to the next agent.
219+
nest_handoff_history: Optional override for the RunConfig-level ``nest_handoff_history``
220+
flag. If ``None`` we fall back to the run's configuration.
221+
is_enabled: Whether the handoff is enabled. Can be a bool or a callable that takes the run
222+
context and agent and returns whether the handoff is enabled. Disabled handoffs are
223+
hidden from the LLM at runtime.
224+
"""
225+
132226
assert (on_handoff and input_type) or not (on_handoff and input_type), (
133227
"You must provide either both on_handoff and input_type, or neither"
134228
)
@@ -183,6 +277,9 @@ async def _invoke_handoff(
183277

184278
tool_name = tool_name_override or Handoff.default_tool_name(agent)
185279
tool_description = tool_description_override or Handoff.default_tool_description(agent)
280+
281+
# Always ensure the input JSON schema is in strict mode. If needed, we can make this
282+
# configurable in the future.
186283
input_json_schema = ensure_strict_json_schema(input_json_schema)
187284

188285
async def _is_enabled(ctx: RunContextWrapper[Any], agent_base: AgentBase[Any]) -> bool:
@@ -193,7 +290,7 @@ async def _is_enabled(ctx: RunContextWrapper[Any], agent_base: AgentBase[Any]) -
193290
result = is_enabled(ctx, agent_base)
194291
if inspect.isawaitable(result):
195292
return await result
196-
return result
293+
return bool(result)
197294

198295
return Handoff(
199296
tool_name=tool_name,

0 commit comments

Comments
 (0)