news 2026/9/17 21:16:30

CAI Handoff Filters 实战指南:用输入过滤器与提示词前缀精确控制多智能体交接

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CAI Handoff Filters 实战指南:用输入过滤器与提示词前缀精确控制多智能体交接

CAI Handoff Filters 实战指南:用输入过滤器与提示词前缀精确控制多智能体交接

【免费下载链接】caiCybersecurity AI (CAI), the framework for AI Security项目地址: https://gitcode.com/GitHub_Trending/cai3/cai

CAI(Cybersecurity AI)在cai.sdk.agents.extensions模块中提供了开箱即用的 handoff 输入过滤器(handoff_filters)与推荐提示词前缀(handoff_prompt),用于在多智能体交接时裁剪对话历史、隔离工具调用噪声。本文以 handoff_filters.py 与 handoff_prompt.py 为主线,结合 handoffs.py、单元测试 test_extension_filters.py 与完整示例 message_filter.py 展开,读完你将掌握过滤器的工作原理、自定义过滤器的写法以及推荐的交接提示词规范。

一、为什么需要 Handoff Filters

在基于 Agent + Handoff 的多智能体系统中,交接(handoff)意味着当前 Agent 把对话控制权转交给另一个 Agent。默认情况下,下一个 Agent 会看到完整的对话历史——包括:

  • 触发交接的输入项(触发 handoff 的那条 tool call);
  • 交接工具的输出项(表示 handoff 结果的 tool output 消息);
  • 此前 Agent 执行过的所有工具调用、工具输出、计算机操作等中间过程。

这在很多场景下是多余的,甚至有害:

  • 上下文污染:下一 Agent 看到大量工具调用细节,与它的任务无关,浪费 token 且干扰判断;
  • 信息泄漏:交接给下游 Agent 时,不需要把上游所有内部工具行为带过去;
  • 历史截断:多轮长会话中,可能需要丢弃较早的消息,只保留近期上下文。

HandoffInputFilter正是为此设计的机制:它是一个接收HandoffInputData、返回HandoffInputData的函数,在交接发生时对传给下一 Agent 的输入做任意裁剪或改写。从 handoffs.py 的类型定义可以看到其签名:

HandoffInputFilter: TypeAlias = Callable[[HandoffInputData], HandoffInputData]

二、HandoffInputData:过滤器收到的数据结构

要写过滤器,先要理解它操作的数据。HandoffInputData是定义在 handoffs.py 中的冻结数据类(frozen dataclass),包含三个字段:

字段类型含义
input_historystr \| tuple[TResponseInputItem, ...]Runner.run()被调用之前的输入历史
pre_handoff_itemstuple[RunItem, ...]在触发交接的那一轮 Agent 运行之前生成的所有运行项(RunItem)
new_itemstuple[RunItem, ...]当前这一轮 Agent 运行期间新生成的运行项,包括触发交接的输入项,以及代表交接输出响应的 tool output 消息

关键点:

  • input_history可能是字符串(当直接以字符串作为初始输入时),也可能是TResponseInputItem(即 OpenAI Responses API 的输入项字典)元组,写过滤器时要做类型判断;
  • RunItem是运行期产物对象,包括MessageOutputItemHandoffCallItemHandoffOutputItemToolCallItemToolCallOutputItem等,它们定义在 items.py;
  • 过滤器返回的新HandoffInputData将直接决定下一 Agent 看到的输入——注释中明确说明:The next agent that runs will receive handoff_input_data.all_items(由三个字段组合而成)。

三、内置过滤器 remove_all_tools 源码解析

handoff_filters模块目前提供一个开箱即用的过滤器remove_all_tools(handoff_filters.py),作用是一键移除所有工具相关的条目:file search、web search 以及函数调用与函数输出。

def remove_all_tools(handoff_input_data: HandoffInputData) -> HandoffInputData: """Filters out all tool items: file search, web search and function calls+output.""" history = handoff_input_data.input_history new_items = handoff_input_data.new_items filtered_history = ( _remove_tool_types_from_input(history) if isinstance(history, tuple) else history ) filtered_pre_handoff_items = _remove_tools_from_items(handoff_input_data.pre_handoff_items) filtered_new_items = _remove_tools_from_items(new_items) return HandoffInputData( input_history=filtered_history, pre_handoff_items=filtered_pre_handoff_items, new_items=filtered_new_items, )

其内部由两个私有辅助函数分工:

1._remove_tools_from_items(针对RunItem元组)(handoff_filters.py):遍历pre_handoff_itemsnew_items,凡是以下四种运行项一律跳过:

  • HandoffCallItem(交接调用)
  • HandoffOutputItem(交接输出)
  • ToolCallItem(工具调用)
  • ToolCallOutputItem(工具输出)

只保留MessageOutputItem等纯消息项。

2._remove_tool_types_from_input(针对TResponseInputItem元组)(handoff_filters.py):检查每条输入项的type字段,命中以下工具类型即剔除:

function_call function_call_output computer_call computer_call_output file_search_call web_search_call

由此可见,remove_all_tools历史输入、交接前运行项、当轮新运行项三个层面同时做了清理,保证交接后到达下一 Agent 的只剩纯对话消息(user/assistant 文本),工具痕迹被彻底剥离。

行为边界:从测试看过滤语义

remove_all_tools的行为在 test_extension_filters.py 中有详尽验证,几个关键用例说明了它的边界:

  • test_empty_data/test_str_historyonly:输入为空或input_history为字符串时,原样返回(字符串历史不会被过滤);
  • test_removes_tools_from_historyinput_history中混入function_call_output类型条目时,仅该条目被移除,普通消息保留(断言历史从 3 条减为 2 条);
  • test_removes_tools_from_new_itemsnew_itemsToolCallOutputItem被移除,MessageOutputItem保留;
  • test_removes_handoffs_from_historyHandoffCallItemHandoffOutputItemToolCallOutputItem在历史与运行项中都会被清除。

这些测试同时证明了过滤是非破坏性的:没有工具项的数据会保持原样,纯消息流不受影响。

四、编写自定义过滤器:以 message_filter 为例

内置的remove_all_tools只解决"去工具化"这一种需求。实际场景往往还需要截断历史、改写消息、注入上下文等,此时应编写自己的HandoffInputFilter。仓库在 message_filter.py 给出了标准范式:

def spanish_handoff_message_filter(handoff_message_data: HandoffInputData) -> HandoffInputData: # 第一步:复用内置过滤器,先移除所有工具相关消息 handoff_message_data = handoff_filters.remove_all_tools(handoff_message_data) # 第二步:从历史中再删掉前两条(仅作演示) history = ( tuple(handoff_message_data.input_history[2:]) if isinstance(handoff_message_data.input_history, tuple) else handoff_message_data.input_history ) return HandoffInputData( input_history=history, pre_handoff_items=tuple(handoff_message_data.pre_handoff_items), new_items=tuple(handoff_message_data.new_items), )

自定义过滤器的要点:

  1. 先复用内置过滤器:组合式写法(先remove_all_tools,再做自定义裁剪)是官方推荐模式;
  2. 注意input_history可能是字符串:做切片前必须isinstance(..., tuple)判断,否则对字符串切片会得到错误的字符级结果;
  3. 返回新的HandoffInputData:过滤器是纯函数式的,必须显式构造并返回新对象,而不是修改原对象(HandoffInputData本身也是 frozen dataclass)。

将该过滤器挂接到交接上,只需在构造handoff时传入input_filter参数(message_filter.py):

second_agent = Agent( name="Assistant", instructions=( "Be a helpful assistant. If the user speaks Spanish, handoff to the Spanish assistant." ), handoffs=[handoff(spanish_agent, input_filter=spanish_handoff_message_filter)], )

运行python examples/handoffs/message_filter.py后可以看到:交接给西班牙语助手时,最终消息列表里不再包含任何 tool call,且前两条历史消息被裁掉,只有用户问题与纯文本回复。流式版本见 message_filter_streaming.py,二者逻辑一致。

关于流式模式的一个重要约束

Handoff.input_filter的文档注释中(handoffs.py)明确说明:流式模式下,过滤器执行期间不会产生任何新的流式输出——过滤器之前已经生成并流式传输给用户的内容不会回退或重放。这意味着过滤器只影响"传给下一 Agent 的上下文",不影响用户已经看到的内容,设计时无需担心过滤导致输出重复。

五、handoff_prompt:推荐交接提示词前缀

除了输入过滤,handoff_prompt模块提供两个与交接配套的提示词工具(handoff_prompt.py):

1.RECOMMENDED_PROMPT_PREFIX(推荐提示词前缀):一段固定的系统上下文文本,向模型说明它处于一个基于 Agents SDK 的多智能体系统中,包含两种核心抽象:AgentsHandoffs。其关键指令是:

  • Agent 封装了指令与工具,在合适时可以把对话交接给另一个 Agent;
  • 交接通过调用一个通常命名为transfer_to_<agent_name>的交接函数实现;
  • 后台自动完成的 Agent 间转移是无缝的,不要在对话中向用户提及或强调这些转移。

这一点对用户体验至关重要——模型若在回答里说出"我现在把你转给另一个助手"之类的元话术,会破坏对话的自然感。

2.prompt_with_handoff_instructions(prompt):一个便捷封装函数,把推荐前缀追加到任意自定义提示词前面:

def prompt_with_handoff_instructions(prompt: str) -> str: """ Add recommended instructions to the prompt for agents that use handoffs. """ return f"{RECOMMENDED_PROMPT_PREFIX}\n\n{prompt}"

因此,任何使用了 handoffs 的 Agent,建议要么直接在自己的instructions中内嵌RECOMMENDED_PROMPT_PREFIX,要么用prompt_with_handoff_instructions(...)包裹,以保证模型理解交接语义并避免向用户暴露内部转移行为。

六、接入流程总结:过滤器的完整生命周期

把上述内容串起来,一个带输入过滤的交接完整工作流是:

  1. 构造目标 Agent(如spanish_agent),定义其instructionshandoff_description
  2. 编写过滤函数:接收HandoffInputData,内部复用handoff_filters.remove_all_tools并叠加自定义裁剪,返回新的HandoffInputData
  3. 通过handoff(spanish_agent, input_filter=my_filter)创建带过滤器的交接对象;
  4. 把该Handoff放入源 Agent 的handoffs列表;
  5. 运行时,当模型调用交接工具(transfer_to_<agent_name>),SDK 在_invoke_handoff内部完成交接(handoffs.py),随后过滤器对历史、交接前项与当轮新项执行裁剪,再启动下一 Agent。

从 handoffs.py 的Handoff数据类定义看,除了input_filter,交接还支持tool_name_override(工具名覆盖,默认由default_tool_nametransfer_to_{agent.name}生成)、tool_description_overrideon_handoff(交接触发时的回调)、input_type(交接输入的结构化校验,默认开启 strict JSON schema,见 strict_schema.py)等能力,过滤器只是整个交接控制面中的一个维度。

七、最佳实践与注意事项

  1. 优先组合而非重写:多数过滤需求 =remove_all_tools+ 少量定制。先调用内置过滤器,再叠加自己的逻辑,既省代码又降低遗漏工具类型的风险。
  2. 字符串历史防御input_history可能是str,所有切片、索引操作前先做isinstance判断。
  3. 流式场景预期管理:流式模式下过滤器不会产生新的流式事件,已输出内容不受影响,不要依赖过滤器去"修正"已经流式输出的文本。
  4. 提示词配套:只要 Agent 使用了 handoffs,就应把RECOMMENDED_PROMPT_PREFIX(或prompt_with_handoff_instructions的产物)放入instructions,否则模型可能不了解交接语义、或在回复中暴露内部转移过程。
  5. 用测试验证过滤语义:参考 test_extension_filters.py 的用例设计,为自定义过滤器补充"历史含工具、新项含工具、纯消息流"三类用例,确保边界行为可预期。

八、延伸阅读

  • Handoff 核心数据结构与 API:HandoffInputDataHandoffhandoff()工厂函数
  • 运行项类型定义:HandoffCallItemHandoffOutputItemToolCallItem等被过滤的目标类型
  • 过滤器单元测试:remove_all_tools全部行为用例
  • 非流式完整示例 与 流式完整示例
  • Handoff 相关文档 与 CAI 多智能体文档

【免费下载链接】caiCybersecurity AI (CAI), the framework for AI Security项目地址: https://gitcode.com/GitHub_Trending/cai3/cai

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 19:49:52

Betterfox如何提升Firefox隐私与性能

Betterfox如何提升Firefox隐私与性能 【免费下载链接】Betterfox Firefox user.js for optimal privacy and security. Your favorite browser, but better. 项目地址: https://gitcode.com/GitHub_Trending/be/Betterfox Betterfox 是一套 Firefox user.js 优化配置&am…

作者头像 李华
网站建设 2026/9/16 19:48:35

MIC33153+R7薄膜电容构建工业级电源稳定性闭环

1. 项目概述&#xff1a;这不是一个“调参实验”&#xff0c;而是一次面向工业级电源管理的系统性加固你手头有一块正在跑关键任务的控制板&#xff0c;可能是PLC扩展模块、边缘网关的主控单元&#xff0c;也可能是医疗设备里的信号调理子系统——它不追求炫酷的新功能&#xf…

作者头像 李华
网站建设 2026/9/16 19:47:34

从工程视角拆解 Java 缺陷系统中的状态机与事务设计

简介&#xff1a;一份面向Java开发者的缺陷检查系统源码&#xff0c;聚焦静态代码分析、语法树遍历与规则引擎设计&#xff0c;适合希望掌握代码质量检测原理并动手实践的中级开发者。压缩包共73个文件&#xff0c;以53个Java源码为主&#xff0c;辅以9个XML配置、前端样式与脚…

作者头像 李华
网站建设 2026/9/16 19:46:30

YOLOv8环境配置全指南:Win10安装CUDA 11.6与cuDNN实操

刚接触YOLOv8的时候&#xff0c;大部分人上来就pip install ultralytics&#xff0c;结果一跑就报各种 CUDA 相关的错&#xff0c;不是torch.cuda.is_available()返回 False&#xff0c;就是训练的时候直接提示找不到 GPU。问题出在哪&#xff1f;十有八九是底层环境没配对。我…

作者头像 李华