-
Notifications
You must be signed in to change notification settings - Fork 3k
Description
Please read this first
- Have you read the docs?Agents SDK docs [Yes]
- Have you searched for related issues? Others may have faced similar issues. [Yes]
Describe the bug
When nest_handoff_history is enabled (which is the default behavior), the SDK summarizes the conversation history into a single assistant message containing <CONVERSATION HISTORY>. This summary correctly includes the handoff tool calls (function_call) and their outputs.
However, the logic that filters the summarized items from the input history (in agents.handoffs.history.nest_handoff_history) only removes items where role == "assistant". Since Tool Calls (type="function_call") and Tool Outputs (type="function_call_output") do not have a role attribute (or it is None), they are NOT filtered out.
As a result, the next agent receives inputs containing duplicate information:
- The "Summary Message" (which mentions the handoff tool call).
- The original "Handoff Tool Call" and "Handoff Tool Output" items in the raw message list.
This duplication causes the model to see the same action twice, confusing the context and consuming unnecessary tokens.
Debug information
- Agents SDK version: (
v0.6.2) - Python version: 3.12
Repro steps
Run the following script. It sets up Agent A to hand off to Agent B, and uses call_model_input_filter to inspect the actual input sent to Agent B. It requires a valid OPENAI_API_KEY.
import asyncio
import os
from typing import Any
from agents import Agent, Runner, RunConfig
from agents.run import CallModelData, ModelInputData
# Ensure OPENAI_API_KEY is set in your environment
os.environ["OPENAI_API_KEY"] = "sk-proj-***"
def check_duplication_filter(data: CallModelData[Any]) -> ModelInputData:
"""
Inspects the input for Agent B to check for duplicates.
"""
if data.agent.name == "AgentB":
print(f"\n=== Inspecting Input for {data.agent.name} ===")
summary_found = False
tool_call_found = False
inputs = data.model_data.input
for i, item in enumerate(inputs):
role = item.get('role')
msg_type = item.get('type')
content = str(item.get('content', '')).replace('\n', ' ')
name = item.get('name', '')
print(f"[{i}] role={role}, type={msg_type}, name={name}, content={content}...")
# 1. Check for Summary Message
if role == "assistant" and "<CONVERSATION HISTORY>" in content:
summary_found = True
# 2. Check for Raw Handoff Tool Call
if msg_type == "function_call" and "transfer_to" in name:
tool_call_found = True
if summary_found and tool_call_found:
print("\n[FAIL] Duplication detected: Both Summary and Raw Tool Call exist in input!")
else:
print("\n[PASS] No duplication detected.")
return data.model_data
async def main():
agent_b = Agent(
name="AgentB",
instructions="You are Agent B. Just say hello."
)
agent_a = Agent(
name="AgentA",
instructions="You are Agent A. Just transfer to AgentB",
handoffs=[agent_b]
)
print("Running reproduction flow...")
# Run Agent A and let it hand off to Agent B
await Runner.run(
agent_a,
"Start conversation",
run_config=RunConfig(
call_model_input_filter=check_duplication_filter
)
)
if __name__ == "__main__":
asyncio.run(main())Output:
Running reproduction flow...
=== Inspecting Input for AgentB ===
[0] role=assistant, type=None, name=, content=For context, here is the conversation so far between the user and the previous agent: <CONVERSATION HISTORY> 1. user: Start conversation 2. function_call: {"arguments": "{}", "call_id": "call_DjTNgeg17pk5Y2c8peaG2i6Z", "name": "transfer_to_agentb", "id": "fc_025c33410193e02200693a4ad2e8e08190b56f9e509fc3dee9", "status": "completed"} 3. function_call_output: {"call_id": "call_DjTNgeg17pk5Y2c8peaG2i6Z", "output": "{\"assistant\": \"AgentB\"}"} </CONVERSATION HISTORY>...
[1] role=None, type=function_call, name=transfer_to_agentb, content=...
[2] role=None, type=function_call_output, name=, content=...
[FAIL] Duplication detected: Both Summary and Raw Tool Call exist in input!
Expected behavior
The input list for the next agent (Agent B) should ONLY contain the summarized history message. The raw handoff tool calls and outputs should be removed from the input list because they are already represented in the <CONVERSATION HISTORY> summary.
Suggested Fix
In agents/handoffs/history.py, the filtering logic in nest_handoff_history should be updated to also filter out items based on their type, not just their role.
filtered_pre_items = tuple(
item
for item in handoff_input_data.pre_handoff_items
if _get_run_item_role(item) != "assistant"
and item.type not in ("handoff_call_item", "handoff_output_item", "tool_call_item", "tool_call_output_item")
)