CAI 多 Agent 交接提示词扩展解析:用 RECOMMENDED_PROMPT_PREFIX 让 Handoff 协作更稳定
【免费下载链接】caiCybersecurity AI (CAI), the framework for AI Security项目地址: https://gitcode.com/GitHub_Trending/cai3/cai
CAI(Cybersecurity AI)的 Agent SDK 提供了一套基于 Agents 与 Handoffs 两种抽象的多 Agent 协作机制,而cai.sdk.agents.extensions.handoff_prompt扩展则负责解决协作中最容易被忽视的问题——如何让 LLM 正确理解并优雅地执行交接。本文将围绕该扩展的RECOMMENDED_PROMPT_PREFIX常量与prompt_with_handoff_instructions()函数,结合 handoffs 核心文档、扩展源码 及仓库中的真实示例,讲解推荐提示词的完整语义、注入方式、配套输入过滤机制与底层实现原理,帮助你在 CAI 中搭建稳定可靠的专家 Agent 协作流水线。
一、为什么需要专门的 Handoff 提示词
在 CAI 的 Agent SDK 中,Handoff(交接)是 Agent 之间委托任务的核心机制:当一个 Agent 遇到自己不擅长的问题时,可以调用一个交接工具,把会话转交给另一个更专业的 Agent。从 handoffs.md 的定义看:
Handoffs allow an agent to delegate tasks to another agent. This is particularly useful in scenarios where different agents specialize in distinct areas.
交接在底层被建模为 LLM 可见的工具调用:如果存在一个指向名为Flag Discriminator的 Agent 的交接,那么对 LLM 而言就会出现一个名为transfer_to_flag_discriminator的工具。也就是说,模型是通过"调用工具"来完成交接决策的。
然而,模型并不会天然理解这套抽象。如果没有在系统提示中解释清楚"什么是 Agent、什么是 Handoff、如何调用交接函数、交接后应该如何表现",LLM 往往会出现两类典型问题:
- 对话层面穿帮:模型在用户面前主动提及"我将把你转交给另一个 Agent",破坏多 Agent 协作对用户的无缝性;
- 工具使用偏差:模型不清楚
transfer_to_<agent_name>函数的职责边界,可能用普通工具去模拟交接,或在错误的时机发起交接。
handoff_prompt扩展存在的意义,正是把"如何使用交接"这一元认知信息以标准化、可复用的形式注入每个参与协作的 Agent 的系统提示中。该扩展由 docs/ref/extensions/handoff_prompt.md 作为 API 参考入口进行索引,公开两个成员:RECOMMENDED_PROMPT_PREFIX与prompt_with_handoff_instructions,二者完整实现位于 src/cai/sdk/agents/extensions/handoff_prompt.py。
二、RECOMMENDED_PROMPT_PREFIX:官方推荐的系统提示前缀
RECOMMENDED_PROMPT_PREFIX是一个预定义的提示前缀常量,源码中的完整定义如下:
# src/cai/sdk/agents/extensions/handoff_prompt.py RECOMMENDED_PROMPT_PREFIX = ( "# System context\n" "You are part of a multi-agent system called the Agents SDK, designed to make agent " "coordination and execution easy. Agents uses two primary abstraction: **Agents** and " "**Handoffs**. An agent encompasses instructions and tools and can hand off a " "conversation to another agent when appropriate. " "Handoffs are achieved by calling a handoff function, generally named " "`transfer_to_<agent_name>`. Transfers between agents are handled seamlessly in the background;" " do not mention or draw attention to these transfers in your conversation with the user.\n" )逐句拆解这段前缀,可以看到它精准地覆盖了模型协作所需的全部上下文:
| 前缀内容要点 | 目的与作用 |
|---|---|
# System context段落标记 | 以 Markdown 标题划分出系统上下文区块,与用户任务指令形成清晰的语义分层,帮助模型区分"关于协作本身的元信息"与"具体任务指令" |
| "You are part of a multi-agent system…" | 告知模型自己身处多 Agent 系统之中,为后续"可以交接"的行为授权 |
| "Agents uses two primary abstraction:AgentsandHandoffs" | 明确系统只有两种核心抽象,降低模型对复杂框架的认知负担 |
| "An agent encompasses instructions and tools…" | 解释 Agent 的本质构成(指令 + 工具),帮助模型理解自身能力边界 |
"Handoffs are achieved by calling a handoff function, generally namedtransfer_to_<agent_name>" | 直接给出交接的操作方式:通过调用命名规范的交接函数实现。这与 handoffs.py 中Handoff.default_tool_name()的默认命名规则transfer_to_{agent.name}(经transform_string_function_style转换)完全一致 |
| "Transfers between agents are handled seamlessly in the background; do not mention or draw attention to these transfers in your conversation with the user" | 约束对话行为:交接在后台无缝完成,不得在用户对话中提及或强调交接过程,保证用户体验的一致性 |
从源码注释可见,CAI 明确建议所有使用 handoffs 的 Agent 都包含此前缀或类似指令("We recommend including this or similar instructions in any agents that use handoffs")。它解决的是多 Agent 协作中模型侧的"行为规范问题",是整个交接机制稳定运行的前提条件之一。
三、prompt_with_handoff_instructions():一键注入推荐指令
为了免去手动拼接前缀的繁琐,扩展提供了prompt_with_handoff_instructions()辅助函数,源码实现只有短短几行:
# src/cai/sdk/agents/extensions/handoff_prompt.py 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}"该函数接收你为 Agent 编写的原始指令字符串prompt,返回"推荐前缀 + 空行 + 原始指令"的拼接结果。它有两点值得注意:
- 签名极简:只接受一个
str参数并返回str,因此可以直接内联到Agent(instructions=...)中,也可在f-string场景下与RECOMMENDED_PROMPT_PREFIX混用; - 职责单一:只负责"加前缀",不校验、不重写你的业务指令,任何合法的提示字符串都可安全传入,拼接后即得一份完整的、符合官方推荐的 Agent 系统提示。
从仓库使用情况看,prompt_with_handoff_instructions被广泛用于语音管线等多 Agent 场景,例如 docs/voice/quickstart.md、examples/voice/static/main.py 与 examples/voice/streamed/my_workflow.py 均通过from agents.extensions.handoff_prompt import prompt_with_handoff_instructions导入,并以instructions=prompt_with_handoff_instructions(...)的形式构造 Agent;而RECOMMENDED_PROMPT_PREFIX则更多出现在直接以 f-string 组织指令的示例中。
四、实战:在 Agent 中注入推荐提示词
两种 API 对应两种典型的注入姿势,均可直接用于 CAI 的 Agent 定义。
4.1 方式一:直接内联 RECOMMENDED_PROMPT_PREFIX(f-string 拼接)
参考 handoffs.md 中的示例,适合需要保留更多自定义指令的情况:
from cai.sdk.agents import Agent from cai.sdk.agents.extensions.handoff_prompt import RECOMMENDED_PROMPT_PREFIX billing_agent = Agent( name="Phising Agent", instructions=f"""{RECOMMENDED_PROMPT_PREFIX} <Fill in the rest of your prompt here>.""", )4.2 方式二:调用 prompt_with_handoff_instructions(函数包装)
适合将"指令生成"收敛为纯函数、便于在多个 Agent 间复用的场景:
from cai.sdk.agents import Agent from cai.sdk.agents.extensions.handoff_prompt import prompt_with_handoff_instructions triage_agent = Agent( name="Triage Agent", instructions=prompt_with_handoff_instructions( "You are a helpful triaging agent. You can use your tools to delegate questions " "to other appropriate agents." ), )两种方式产出的最终系统提示在语义上等价,差别仅在于拼接发生在 f-string 内还是函数内。实际项目中,examples/customer_service/main.py 采用方式一为 FAQ Agent、Seat Booking Agent、Triage Agent 统一注入前缀(并在指令中写明"如果正在与客户对话,你很可能是被 triage agent 转接过来的"等交接后行为),充分展示了前缀与业务指令的组合使用模式。
4.3 完整案例:CTF 挑战中的专家交接链
仓库中 examples/cai/agent_patterns/handoffs_and_tools.py 给出了一个直接可运行的多 Agent 协作范例:Cybersecurity Lead Agent → Bash Agent → Flag Discriminator Agent 的交接链,其中每个 Agent 都通过 f-string 注入了RECOMMENDED_PROMPT_PREFIX:
from cai.sdk.agents import Agent, OpenAIChatCompletionsModel from openai import AsyncOpenAI from cai.sdk.agents import handoff, function_tool, trace from cai.sdk.agents import Runner from cai.tools.common import run_command from cai.sdk.agents.extensions.handoff_prompt import RECOMMENDED_PROMPT_PREFIX import os import asyncio @function_tool def execute_cli_command(command: str) -> str: """Execute a command-line command and return its output.""" return run_command(command) flag_discriminator = Agent( name="Flag Discriminator Agent", description="Agent specialized in verifying if content matches the expected flag format in CTF challenges", instructions=f"""{RECOMMENDED_PROMPT_PREFIX} You are an expert at identifying CTF flags. The final flag is the first file of a -ls command. You return the flag with this format: flag{{filename}}""", handoff_description="Specialized agent in determining whether the content corresponds to the flag of the CTF challenge", handoffs=[], model=OpenAIChatCompletionsModel( model=os.getenv('CAI_MODEL', "qwen2.5:72b"), openai_client=AsyncOpenAI(), ) ) bash_agent = Agent( name="Bash Agent", description="Agent specialized in Bash commands and Linux operations", instructions=f"""{RECOMMENDED_PROMPT_PREFIX} You are a Bash and Linux expert. Use your knowledge to navigate the file system and execute commands. The final flag is the first element listed in the directory. When you find the name of the file, handoff to the Flag Discriminator Agent for verification.""", tools=[execute_cli_command], handoffs=[handoff(flag_discriminator)], handoff_description="Specialized agent in Bash commands and Linux operations", model=OpenAIChatCompletionsModel( model=os.getenv('CAI_MODEL', "qwen2.5:14b"), openai_client=AsyncOpenAI(), ) ) cybersecurity_lead = Agent( name="Cybersecurity Lead Agent", description="Lead agent focused on conquering security challenges by delegating to specialist agents", instructions=f"""{RECOMMENDED_PROMPT_PREFIX} You are a Cybersecurity expert Leader facing cybersecurity challenges. - Hand off to the Bash Agent when you need to execute Linux commands or navigate the file system. - Hand off to the Cryptography Agent when you encounter encrypted data or codes that need deciphering.""", tools=[execute_cli_command], handoffs=[ handoff(bash_agent), handoff(crypto_agent) ], handoff_description="Lead agent in cybersecurity operations", model=OpenAIChatCompletionsModel( model=os.getenv('CAI_MODEL', "qwen2.5:14b"), openai_client=AsyncOpenAI(), ) ) async def main(): # Trace the entire run as a single workflow with trace(workflow_name="CTF Workflow"): result = await Runner.run(cybersecurity_lead, "List directories to find the flag") print(result.final_output) if __name__ == "__main__": asyncio.run(main())该示例中的关键配合点值得注意:
- 每个参与交接的 Agent 都必须注入前缀——Lead、Bash、Flag Discriminator 无一例外,因为任何一层都可能发起或接收交接;
- 前缀之后紧跟领域指令——前缀提供协作元认知,领域指令(如"flag 是目录第一个文件")提供任务知识,二者缺一不可;
handoff_description与交接工具描述联动——由 handoffs.py 中Handoff.default_tool_description()可知,交接工具的默认描述为Handoff to the {agent.name} agent to handle the request. {agent.handoff_description or ''},即每个 Agent 的handoff_description会成为模型判断"何时交接、交给谁"的依据,与推荐前缀中的transfer_to_<agent_name>约定共同指导模型决策。
五、配套机制:handoff_filters 输入过滤
推荐提示词解决的是"模型如何理解交接",而交接后的"新 Agent 能看到哪些上下文"则由输入过滤器负责。二者共同构成 handoff 扩展体系,参考 handoff_filters 扩展文档。
在 handoffs.py 中,Handoff.input_filter是类型为HandoffInputFilter(即Callable[[HandoffInputData], HandoffInputData])的回调:默认情况下新 Agent 可以看到全部历史对话,而过滤器可以在交接发生时裁减历史,例如剔除过旧输入或移除工具调用记录。HandoffInputData数据类包含三个字段:
input_history:Runner.run()被调用前的输入历史;pre_handoff_items:发起交接的那个 Agent turn 之前产生的 items;new_items:当前 turn 新产生的 items(含触发交接的 item 与交接输出的 tool output)。
handoff_filters.py 内置了开箱即用的remove_all_tools过滤器,它通过_remove_tools_from_items()过滤掉HandoffCallItem、HandoffOutputItem、ToolCallItem、ToolCallOutputItem等工具类 item,并通过_remove_tool_types_from_input()从输入历史中剔除function_call、function_call_output、computer_call、web_search_call、file_search_call等工具类型消息,最终返回一个不含任何工具痕迹的HandoffInputData。用法如下:
from cai.sdk.agents import Agent, handoff from cai.sdk.agents.extensions import handoff_filters network_agent = Agent(name="Network Agent") handoff_obj = handoff( agent=network_agent, input_filter=handoff_filters.remove_all_tools, # 交接时自动移除历史中的全部工具记录 )提示词 + 过滤器的组合策略是:提示词在前端约束模型"何时、如何交接",过滤器在后端净化"交接后的视野",前后配合才能保证交接链路的稳定与信息安全。
六、底层原理:handoff() 如何生成交接工具
要真正理解推荐前缀中transfer_to_<agent_name>的由来,需要回到 handoffs.py 的handoff()工厂函数。它接受一个Agent,可选地接受tool_name_override、tool_description_override、on_handoff、input_type、input_filter,并返回一个Handoff对象:
- 工具名:默认取
Handoff.default_tool_name(agent),即把transfer_to_{agent.name}经transform_string_function_style转换为函数风格命名(如transfer_to_flag_discriminator),这正是推荐前缀要求模型调用的名称; - 工具描述:默认取
Handoff.default_tool_description(agent),拼接agent.name与agent.handoff_description,为模型提供交接决策依据; - 调用回调:
on_invoke_handoff会依据input_type判断是否需要对 LLM 传入的 JSON 参数做 Pydantic 校验,再执行on_handoff回调(支持同步与协程),最终返回目标 Agent; - 输入过滤:
input_filter原样透传给Handoff,在交接发生时对HandoffInputData做变换; - 严格模式:交接工具的输入 JSON Schema 会经
ensure_strict_json_schema强制开启 strict mode,以提升模型生成合法 JSON 参数的概率。
因此,推荐前缀中"handoff function, generally namedtransfer_to_<agent_name>"这句话,与handoff()的默认命名规则在实现层面严格对齐;同时Agent.handoffs参数既可以直接接收Agent实例(SDK 内部会为其构造默认 Handoff),也可以接收定制的handoff()返回值,两种方式对模型而言都会呈现为命名规范的交接工具。由于交接对 LLM 而言就是一个工具调用,function_tool、trace等既有设施可以无缝参与工作流编排,正如 CTF 示例中用with trace(workflow_name="CTF Workflow")包裹整个多 Agent 运行过程。
七、最佳实践小结
综合上述文档、源码与示例,在 CAI 中使用 Handoff 推荐提示词时,建议遵循以下实践:
- 全员注入:凡是通过
handoffs参数参与交接的 Agent(包括只接收交接、不主动发起的叶子 Agent),都应注入RECOMMENDED_PROMPT_PREFIX或调用prompt_with_handoff_instructions,避免某一环缺失元认知导致行为漂移; - 前缀与领域指令分离:前缀固定用于协作元信息,领域知识写在紧随其后的业务指令中,便于统一维护与局部修改;
- 善用
handoff_description:它为模型的交接决策提供关键依据,应与推荐前缀中的交接工具约定一并设计,形成完整的"何时交接 + 交给谁"信息闭环; - 按需使用输入过滤:默认新 Agent 可见全部历史;当历史中包含敏感工具输出或大量噪音时,使用
handoff_filters.remove_all_tools等过滤器裁剪上下文(注意 handoffs.py 提示:流式模式下输入过滤器不会产生新的流式输出,此前已流式发送的内容保持不变); - 保持对话无缝性:推荐前缀明确要求模型不在用户面前提及交接过程,设计自定义指令时也不要破坏这一约定。
通过handoff_prompt扩展(源码)、handoffs 核心文档、handoff_filters 扩展 以及仓库中的 CTF 交接链示例、客服多 Agent 示例、语音管线示例,你可以快速搭建并稳定运行属于 CAI 的专家协作流水线。
【免费下载链接】caiCybersecurity AI (CAI), the framework for AI Security项目地址: https://gitcode.com/GitHub_Trending/cai3/cai
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考