openai-agents-python 深度指南:Handoff 输入过滤器与agents.extensions.handoff_filters扩展模块全解析
【免费下载链接】openai-agents-pythonA lightweight, powerful framework for multi-agent workflows项目地址: https://gitcode.com/GitHub_Trending/op/openai-agents-python
Handoff(交接)是 openai-agents-python 多 Agent 协作的核心机制,而输入过滤器(input filter)决定了被交接的下一个 Agent 能看到多少历史对话。本文以官方参考文档 docs/ref/extensions/handoff_filters.md 所指向的agents.extensions.handoff_filters模块为主线,完整讲解remove_all_tools、nest_handoff_history、default_handoff_history_mapper三个内置工具函数的原理、用法与取舍,并结合源码与测试说明其底层实现。读完本文,你将掌握如何精准裁剪交接历史、如何启用嵌套历史压缩,以及如何在真实多 Agent 工作流中落地这些模式。
背景:为什么需要 Handoff 输入过滤器
在 docs/handoffs.md 中定义:Handoff 让一个 Agent 将任务委托给另一个 Agent。默认情况下,交接发生时,新 Agent 接管对话并看到完整的先前对话历史——包括上一轮所有工具调用、工具输出、推理(reasoning)片段,甚至包括交接工具自身的调用记录。
这在很多场景下既不经济也不安全:
- 上一个 Agent 执行了大量工具调用,这些
function_call/function_call_output对下一个 Agent 毫无意义,还白白占用上下文窗口; - 交接工具调用本身是"元信息",新 Agent 无需看到;
- 敏感的工具输出不应透传给下一个专业 Agent。
因此 SDK 在Handoff.input_filter上提供了钩子:一个接收HandoffInputData、返回新HandoffInputData的函数。而agents.extensions.handoff_filters正是官方预置的"开箱即用"过滤器集合,其导出的三个成员为(见 src/agents/extensions/handoff_filters.py):
| 导出名称 | 作用 |
|---|---|
remove_all_tools | 从交接输入中剔除所有工具相关条目 |
nest_handoff_history | 将先前对话压缩为有序的 summary 片段(opt-in beta) |
default_handoff_history_mapper | 将完整 transcript 映射为单条 assistant 摘要消息 |
数据结构基础:HandoffInputData与HandoffInputFilter
要理解过滤器,先要看清它操作的数据。HandoffInputData定义在 src/agents/handoffs/init.py,包含五个字段:
input_history:调用Runner.run(...)之前的输入历史(字符串或TResponseInputItem元组);pre_handoff_items:发起交接的那个 Agent 回合之前产生的所有RunItem;new_items:当前 Agent 回合期间新产生的条目,包含触发交接的工具调用以及表示交接输出的 tool output 消息;input_items(可选):转发给下一个 Agent 的条目。设置后,它替代new_items构建下一个 Agent 的输入,从而在不破坏new_items(用于会话历史)的前提下过滤模型输入;run_context:交接触发时的RunContextWrapper(后加字段,向后兼容故为可选)。
过滤器类型定义为:
HandoffInputFilter = Callable[[HandoffInputData], MaybeAwaitable[HandoffInputData]]即"输入HandoffInputData、输出HandoffInputData"的可调用对象,且可以是同步或异步函数。官方在 tests/test_agent_runner.py 的test_handoff_filters中演示了通过handoff(agent=..., input_filter=...)挂载过滤器的完整运行流程。
内置过滤器一:remove_all_tools
remove_all_tools是官方最常用的预置过滤器,其 docstring 明确定义了语义:过滤掉所有工具条目——文件搜索、网页搜索以及函数调用及其输出(见 src/agents/extensions/handoff_filters.py)。
它对三类数据分别处理
input_history:若为元组,调用_remove_tool_types_from_input按type字段剔除工具类型条目(src/agents/extensions/handoff_filters.py)。它维护了一张完整的工具类型黑名单:
tool_types = [ "function_call", "function_call_output", "computer_call", "computer_call_output", "file_search_call", "tool_search_call", "tool_search_output", "web_search_call", "mcp_call", "mcp_list_tools", "mcp_approval_request", "mcp_approval_response", "reasoning", "code_interpreter_call", "image_generation_call", "local_shell_call", "local_shell_call_output", "shell_call", "shell_call_output", "apply_patch_call", "apply_patch_call_output", "custom_tool_call", "custom_tool_call_output", "hosted_tool_call", "program", "program_output", ]注意:若input_history是字符串(例如直接传入纯文本历史),则原样保留,不做处理。
pre_handoff_items与new_items:调用_remove_tools_from_items(src/agents/extensions/handoff_filters.py)按RunItem的具体类型逐一过滤,被剔除的类型包括:HandoffCallItem、HandoffOutputItem、ToolSearchCallItem、ToolSearchOutputItem、ToolCallItem、ToolCallOutputItem、ReasoningItem、MCPListToolsItem、MCPApprovalRequestItem、MCPApprovalResponseItem、ToolApprovalItem。这里有两个值得注意的设计细节:
- 推理条目(
ReasoningItem)一并被移除。原因正如测试注释所言:工具调用被剥离后,推理条目会变成"孤儿"(orphaned),失去上下文意义(见 tests/test_extension_filters.py); - 交接自身的调用与输出(
HandoffCallItem/HandoffOutputItem)也在删除之列,新 Agent 不会看到"我是被一个 transfer 工具调过来的"这类元信息。
- 推理条目(
input_items:如果存在,同样经_remove_tools_from_items过滤。源码注释特别说明这是为了支持过滤器链——例如先执行nest_handoff_history再执行remove_all_tools时,input_items不会被丢弃或重新引入工具条目(src/agents/extensions/handoff_filters.py)。
使用方式
from agents import Agent, handoff from agents.extensions import handoff_filters faq_agent = Agent(name="FAQ agent") handoff_obj = handoff( agent=faq_agent, # 当 FAQ agent 被调用时,自动从历史中移除所有工具相关条目 input_filter=handoff_filters.remove_all_tools, ) triage_agent = Agent( name="Triage agent", handoffs=[handoff_obj], )测试 tests/test_extension_filters.py 覆盖了该过滤器的各种组合场景:纯消息历史、字符串历史、工具搜索结果(tool_search_call/tool_search_output)、程序化工具 transcript(program/program_output)、handoff 条目以及 reasoning 条目等,均验证过滤结果符合预期。
内置过滤器二:nest_handoff_history(嵌套历史,opt-in beta)
nest_handoff_history是 SDK 提供的对话历史压缩能力,属于 opt-in beta 特性(默认关闭)。它把"可摘要的历史"压缩成有序的 assistant summary 片段,同时把无损消息条目(lossless message items)保留在原始位置,避免长对话交接时上下文无限膨胀。其实现位于 src/agents/handoffs/history.py(注意该函数同时在agents.extensions.handoff_filters与agents.handoffs中导出,两者指向同一实现)。
如何启用
在 RunConfig 上设置:
from agents import Agent, RunConfig agent = Agent(name="Delegation agent") # 方式一:run 级别全局启用 run_config = RunConfig(nest_handoff_history=True)也可以在单个交接上覆盖运行级配置(None表示回退到 run 配置):
from agents import Agent, handoff handoff_obj = handoff( agent=Agent(name="Specialist agent"), nest_handoff_history=True, # 仅此 handoff 覆盖为 True/False )工作原理与摘要格式
当nest_handoff_history生效时,runner 会把历史划分成两种条目:
- 进入摘要的条目:
function_call、function_call_output、reasoning等(见_SUMMARY_ONLY_INPUT_TYPES,src/agents/handoffs/history.py),它们被打包进 assistant summary 消息,不逐字转发,避免重复; - 无损保留的条目:带
role的用户/助手消息等,保留原始位置原样转发。
生成的 summary 消息格式由 src/agents/handoffs/history.py 的_build_summary_message构造,默认形如:
For context, here is the conversation so far between the user and the previous agent: <CONVERSATION HISTORY> 1. user: 你好,帮我查一下订单状态 2. assistant: 好的,我来查询 ... </CONVERSATION HISTORY>默认包裹标记为<CONVERSATION HISTORY>/</CONVERSATION HISTORY>。若需修改标记文本,可在运行前调用set_conversation_history_wrappers(start=..., end=...);需要恢复默认值则调用reset_conversation_history_wrappers()(src/agents/handoffs/history.py)。测试 tests/test_extension_filters.py 验证了自定义标记可被正确解析并支持二次嵌套。
自定义历史映射:default_handoff_history_mapper与RunConfig.handoff_history_mapper
default_handoff_history_mapper(src/agents/handoffs/history.py)是默认的映射策略:把整个 transcript 压缩为单条 assistant 摘要消息并返回单元素列表。
如果你需要完全控制下一个 Agent 看到的输入,可通过RunConfig.handoff_history_mapper传入自定义映射函数(仅当nest_handoff_history=True时生效,src/agents/run_config.py):
from agents import RunConfig def my_mapper(transcript): # transcript: 规范化后的完整历史 + handoff 条目 # 返回的列表将作为下一个 Agent 的精确输入历史 return [{"role": "user", "content": "摘要:" + summarize(transcript)}] run_config = RunConfig( nest_handoff_history=True, handoff_history_mapper=my_mapper, )测试 tests/test_extension_filters.py 演示了自定义 mapper 可以返回任意顺序的输入条目。此外,后续 handoff 会先展平之前生成的 summary 片段再重建有序 transcript(_flatten_nested_history_messages,src/agents/handoffs/history.py),从而避免多层嵌套摘要层层叠加。
与remove_all_tools的协作与边界
源码中两个过滤器设计为可链式使用:nest_handoff_history将input_items置为()并把可摘要条目移入 summary,remove_all_tools则在此基础上继续清理input_items与new_items中的工具条目。
需要注意的边界条件(均来自源码与官方文档):
- 嵌套历史仅当该 handoff 的
input_filter和运行级RunConfig.handoff_input_filter都未设置时才生效;已有自定义载荷的代码保持原行为不变(docs/handoffs.md); - 运行级
RunConfig.handoff_input_filter是全局兜底,而单个Handoff.input_filter优先级更高,两者同时设置时以 per-handoff 为准(docs/handoffs.md、src/agents/run_config.py); - 服务端托管会话(使用
conversation_id、previous_response_id或auto_previous_response_id)不支持 handoff 输入过滤器,也会自动禁用嵌套历史并给出警告; - 流式模式下,过滤器的结果不会被流式输出——之前的条目已经流式发出;
- Sessions、
RunState与RunResult.to_input_list()会追踪被移入 SDK 默认历史的精确消息出现位置,避免这些出现被重复追加(docs/handoffs.md)。
过滤器优先级与全局配置
除了 per-handoff 的input_filter,SDK 还支持运行级全局过滤器:
from agents import Agent, RunConfig from agents.extensions import handoff_filters run_config = RunConfig( handoff_input_filter=handoff_filters.remove_all_tools, # 应用到所有 handoff )优先级规则(src/agents/run_config.py):
- 单个
Handoff.input_filter(最高); RunConfig.handoff_input_filter(全局兜底);- 两者都未设置时,默认透传完整历史;若开启
nest_handoff_history则走压缩逻辑。
测试验证:过滤器行为的可观测证据
本模块的单元测试集中在 tests/test_extension_filters.py,覆盖关键行为:
test_removes_tools_from_history:历史中的function_call_output被剔除,消息保留;test_removes_tools_from_new_items:new_items中的工具输出被剔除,消息保留;test_removes_programmatic_tool_transcript_from_history:程序化工具 transcript(program、program_output、带caller的函数调用)被整体剔除;test_removes_handoffs_from_history:交接调用与输出条目也被移除;test_nest_handoff_history_*系列:验证摘要包裹标记、自定义 wrapper、自定义 mapper、空 transcript(输出(no previous turns recorded))、多层嵌套展平、带名字的角色(user (Alice): Hello)格式化等。
运行级集成验证见 tests/test_agent_runner.py 的test_opt_in_handoff_history_nested_and_filters_respected,确认RunConfig(nest_handoff_history=True)下交接后result.input行为符合预期。
总结:如何选择
| 需求 | 推荐方案 |
|---|---|
| 新 Agent 不需要看到上一轮的任意工具调用/输出/推理 | input_filter=handoff_filters.remove_all_tools |
| 长对话交接,希望压缩历史、节省上下文 | RunConfig(nest_handoff_history=True)或单 handoffnest_handoff_history=True |
| 需要自定义摘要格式或顺序 | RunConfig(handoff_history_mapper=...) |
| 所有 handoff 统一裁剪 | RunConfig(handoff_input_filter=...) |
| 需要按字段做鉴权/副作用 | 使用input_type+on_handoff(见 docs/handoffs.md),注意is_enabled在参数解析前求值,无法基于参数授权 |
相关源码速查:模块实现 src/agents/extensions/handoff_filters.py,数据结构与handoff()工厂 src/agents/handoffs/init.py,嵌套历史核心 src/agents/handoffs/history.py,运行级配置 src/agents/run_config.py,单元测试 tests/test_extension_filters.py,完整使用指南 docs/handoffs.md。
【免费下载链接】openai-agents-pythonA lightweight, powerful framework for multi-agent workflows项目地址: https://gitcode.com/GitHub_Trending/op/openai-agents-python
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考