PraisonAI Agent Handoffs 完全指南:多智能体任务委派、结构化交接与安全边界实战
【免费下载链接】PraisonAIPraisonAI 🦞 — Hire a 24/7 AI Workforce. Stop writing boilerplate and start shipping autonomous self-improving agents that research, plan, code, and execute tasks. Deployed in 5 lines of code with built-in memory, RAG, and support for 100+ LLMs.项目地址: https://gitcode.com/GitHub_Trending/pr/PraisonAI
导读
Agent Handoffs(智能体交接)是 PraisonAI 中让不同专长的智能体相互协作的核心机制:当一个 Agent 判断任务超出自身能力范围时,可以自动将任务"交接"给更专业的 Agent,并携带必要的对话上下文。本指南以官方文档 handoffs.md 为主线,结合 handoff.py 与 agent.py 的源码实现,带你掌握手写交接配置、结构化输入、输入过滤器、回调钩子、上下文策略与工具安全边界,并最终落地一个完整的客服多智能体路由系统。
一、什么是 Agent Handoffs
在手写代码中,Agent 之间的协作通常要靠"主控脚本"硬编码调度逻辑,谁来做什么、什么时候切换都由外部决定。PraisonAI 的 Handoffs 机制把这种决策权交给 Agent 自己:交接被自动转换成 LLM 可调用的工具(Tool),模型根据对话上下文自主决定是否、以及向哪个 Agent 交接。
从源码结构看,handoff.py 的模块 docstring 明确指出这是一套"Unified Handoff System",同时支持:
- LLM 驱动的交接:通过工具调用(tool call)完成;
- 程序化交接:通过 Python API(
Agent.handoff_to())直接调用; - 异步交接与并发控制:支持
max_concurrent并发限制; - 环检测与深度限制:防止无限循环交接;
- 可配置的上下文策略:决定把多少历史上下文传给目标 Agent。
在 agent.py 中,Agent.__init__接受handoffs参数,其类型为List[Union['Agent', 'Handoff']]——也就是说,你既可以直接传 Agent 对象,也可以传封装了高级配置的Handoff实例。
基本用法
最简单的交接只需两步:先创建几个专职 Agent,再把它们挂到主 Agent 的handoffs列表上:
from praisonaiagents import Agent, handoff # 创建专职 Agent billing_agent = Agent(name="Billing Agent", role="Billing Specialist") refund_agent = Agent(name="Refund Agent", role="Refund Specialist") # 创建带交接能力的主 Agent triage_agent = Agent( name="Triage Agent", role="Customer Service", handoffs=[billing_agent, refund_agent] # 可交接给这些 Agent )交接如何工作
官方文档给出了四条核心机制,结合源码可以看得更细:
- 交接自动转换为工具:在 agent.py 的
_process_handoffs()中,每个交接项都会被转换为一个可调用工具并追加到 Agent 的tools列表中。直接传入的 Agent 对象会被包装成默认Handoff,而Handoff实例则调用其to_tool_function()生成工具函数(见 handoff.py)。 - Agent 根据上下文自主决策:转换后的工具会出现在 LLM 的工具 schema 中,模型在对话中自行决定是否调用"交接工具"。
- 目标 Agent 接收会话历史:
Handoff会把源 Agent 的chat_history按配置策略过滤后,临时"播种"(seed)到目标 Agent 的chat_history上,目标 Agent 因此能看到前序对话。 - 目标 Agent 的响应返回给用户:工具函数返回
"Handoff successful. {target} response: {response}",源 Agent 把该响应作为最终答案呈现。
关于第 3 点的实现细节:
_seed_target_history()(handoff.py)采用调用级作用域——先保存目标 Agent 原有chat_history,在交接期间把过滤后的上下文前插,交接结束后立即恢复。这样既能让目标 Agent 感知上下文,又不会污染后续普通对话,也防止上下文在连续交接中无限膨胀。
二、默认工具命名与描述规则
每个交接在未指定覆盖参数时,会生成默认的工具名称与描述(handoff.py):
- 默认工具名:
transfer_to_<agent_name>,其中 Agent 名称会转为小写并用下划线替换空格。例如Agent(name="Refund Agent")会生成工具transfer_to_refund_agent; - 默认工具描述:
Transfer task to <agent_name> (<role>) - <goal>,会尽量拼入 role 与 goal 信息,帮助 LLM 判断何时应该交接。
这也是为什么在handoff_unified_config.py示例中,协调者可以直接在instructions里写"使用transfer_to_research_agent"——工具名是确定可预期的。
三、高级特性:handoff()函数与完整配置
3.1 自定义交接配置
当默认行为不够时,使用handoff()工厂函数获得更细粒度的控制(handoff.py):
from praisonaiagents import Agent, handoff, handoff_filters agent = Agent(name="Target Agent") custom_handoff = handoff( agent=agent, tool_name_override="escalate_to_specialist", tool_description_override="Escalate complex issues to a specialist", on_handoff=lambda ctx: print(f"Handoff from {ctx.name}"), input_filter=handoff_filters.remove_all_tools ) main_agent = Agent( name="Main Agent", handoffs=[custom_handoff] )各参数说明:
| 参数 | 作用 | 默认值 |
|---|---|---|
agent | 交接的目标 Agent(必填) | 无 |
tool_name_override | 覆盖默认工具名 | transfer_to_<agent_name> |
tool_description_override | 覆盖默认工具描述 | 由 name/role/goal 拼接 |
on_handoff | 交接被触发时执行的回调 | 无 |
input_type | 交接所需的结构化输入类型(Pydantic 模型) | 无 |
input_filter | 过滤/转换传入目标 Agent 的输入,支持单函数或函数列表(列表按顺序链式执行) | 无 |
config | HandoffConfig高级配置(上下文策略、超时、并发、安全) | 默认HandoffConfig() |
此外handoff()还提供一组快捷参数,直接映射到HandoffConfig字段:context_policy、timeout_seconds、max_concurrent、detect_cycles、max_depth,以及安全相关的tool_policy_mode与blocked_tools(详见下文"统一配置"一节)。
3.2 交接回调(Handoff Callbacks)
回调用于在交接发生时执行自定义逻辑,最典型的是日志与监控:
# 创建目标 Agent target_agent = Agent(name="Target Agent", role="Specialist") def log_handoff(source_agent): print(f"Handoff initiated from {source_agent.name}") handoff_with_callback = handoff( target_agent, on_handoff=log_handoff )从源码看,回调执行器_execute_callback()(handoff.py)通过inspect.signature自动适配回调的函数签名:
- 0 个必填参数:
callback()无参调用; - 1 个必填参数:传入
source_agent(若存在result则传入HandoffResult); - 2 个及以上必填参数:优先
callback(source_agent, result);若配置了input_type且有工具参数,则尝试构造结构化输入对象callback(source_agent, input_data)。
因此你可以为同一个回调编写"只关心来源"或"同时关心来源与数据"的不同签名,框架会自动适配。
3.3 结构化输入(Structured Input)
要求交接时携带特定数据,可以用 Pydantic 模型定义载荷契约:
from pydantic import BaseModel class EscalationData(BaseModel): reason: str priority: str # 创建升级 Agent escalation_agent = Agent(name="Escalation Agent", role="Senior Manager") def handle_escalation(source_agent, data: EscalationData): print(f"Escalation: {data.reason} (Priority: {data.priority})") escalation_handoff = handoff( escalation_agent, on_handoff=handle_escalation, input_type=EscalationData )设置input_type后,to_tool_function()会把模型的字段注解转换为工具函数的签名(inspect.Parameter,见 handoff.py),LLM 就会按 schema 生成结构化参数;回调则能收到反序列化好的模型实例。
在 handoff_basic.py 中,官方示例更进一步演示了多载荷契约:同一个 Triage Agent 对三个不同目标分别定义BillingPayload(Pydantic)、RefundPayload(TypedDict)、TechnicalPayload(Pydantic),交接时从用户请求中提取对应字段填充载荷,实现"带数据路由"。
3.4 更强的类型安全:TypedHandoff
如果你需要在校验失败时主动抛出结构化错误,可以使用TypedHandoff(handoff.py)。它要求input_schema必须是 PydanticBaseModel子类,在边界处用model_validate()校验载荷,失败时抛出HandoffValidationError(携带validation_errors明细);通过校验后,载荷会被序列化为格式化 JSON(model_dump_json(indent=2))拼进提示词,而不是字符串拼接,从而保证结构数据可被目标 Agent 正确反序列化:
from pydantic import BaseModel from praisonaiagents.agent.handoff import TypedHandoff, HandoffValidationError class ResearchResult(BaseModel): summary: str citations: list[str] confidence: float typed_handoff = TypedHandoff( agent=writer_agent, input_schema=ResearchResult ) # 合法载荷正常执行 result = ResearchResult(summary="AI research findings", citations=["ref1"], confidence=0.92) response = typed_handoff.execute_programmatic(source_agent, result) # 非法载荷抛出 HandoffValidationError bad_payload = {"summary": "...", "citations": "not-a-list"} typed_handoff.execute_programmatic(source_agent, bad_payload)3.5 输入过滤器(Input Filters)
输入过滤器控制哪些会话历史被传给目标 Agent,是保护隐私、控制 token 消耗、防止工具噪声干扰目标 Agent 的关键手段。框架内置了handoff_filters静态工具类(handoff.py):
from praisonaiagents import handoff_filters # 创建目标 Agent 用于过滤示例 agent = Agent(name="Target Agent", role="Specialist") # 移除历史中所有工具调用消息 filtered_handoff = handoff( agent, input_filter=handoff_filters.remove_all_tools ) # 仅保留最后 N 条消息 limited_handoff = handoff( agent, input_filter=handoff_filters.keep_last_n_messages(5) ) # 移除系统消息 clean_handoff = handoff( agent, input_filter=handoff_filters.remove_system_messages )内置过滤器一览(除文档列出的三种外,源码还提供了第四种):
| 过滤器 | 行为 |
|---|---|
remove_all_tools | 剔除包含tool_calls或role == "tool"的消息 |
keep_last_n_messages(n) | 工厂函数,只保留最后 n 条消息 |
remove_system_messages | 删除所有系统角色消息 |
compress_history | 把所有消息内容压缩成单条用户摘要消息,降低 token 占用同时保留上下文要点 |
过滤器既可以传单个函数,也可以传函数列表——_prepare_context()会按顺序链式应用(handoff.py)。在 handoff_advanced.py 中甚至有自定义组合过滤器的范例:
def custom_filter(data): """只保留最后 3 条消息并移除系统消息""" data = handoff_filters.keep_last_n_messages(3)(data) data = handoff_filters.remove_system_messages(data) return data四、上下文策略与统一配置(HandoffConfig)
PraisonAI 将交接相关的所有设置收敛进HandoffConfig数据类(handoff.py),通过config=参数传入handoff(),或使用快捷参数。完整字段如下:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
context_policy | ContextPolicy | SUMMARY | 上下文共享策略(见下表) |
max_context_tokens | int | 4000 | 上下文最大 token 数 |
max_context_messages | int | 10 | LAST_N策略下最多保留的消息数 |
preserve_system | bool | True | 是否在过滤时保留系统消息 |
tool_policy | HandoffToolPolicy | intersect模式 | 工具边界策略 |
timeout_seconds | float | 300.0 | 交接执行超时(秒),<=0表示不限制 |
max_concurrent | int | 5 | 最大并发交接数,<=0表示不限 |
detect_cycles | bool | True | 环检测,防止 A→B→A 无限循环 |
max_depth | int | 10 | 交接链最大深度 |
async_mode | bool | False | 是否异步执行 |
allow_parallel | bool | False | 是否允许并行交接 |
on_handoff/on_complete/on_error | Callable | 无 | 交接开始/成功/失败回调 |
ContextPolicy:上下文共享策略
handoff.py 定义了四种策略:
| 策略 | 值 | 行为 |
|---|---|---|
FULL | "full" | 共享完整会话历史 |
SUMMARY | "summary" | 共享摘要化上下文(默认,安全)——保留系统消息加最后 3 条非系统消息 |
NONE | "none" | 不共享任何上下文 |
LAST_N | "last_n" | 只共享最近 N 条消息(由max_context_messages控制) |
注意SUMMARY为默认策略,说明框架默认"安全优先",不会把完整历史直接交给目标 Agent。NONE模式有一个值得注意的联动行为:当没有上下文且未配置input_type时,工具签名会自动暴露一个显式的task参数(handoff.py),让调用方必须显式给出任务指令,避免"空壳交接"。
工具边界策略(HandoffToolPolicy)
这是交接系统的安全核心。HandoffToolPolicy(handoff.py)有两个模式:
intersect(默认,安全):目标 Agent 只能获得源 Agent 与目标 Agent 工具集的交集。源 Agent 没有的工具,目标 Agent 在交接期间也无法使用——这从源头限制了交接后的工具权限放大;passthrough(传统行为,需显式开启):目标 Agent 保留自己的完整工具集,仅剔除blocked_tools列表中的工具。
blocked_tools在两种模式下都生效,用于"永远禁用"某些危险工具(如execute_code、shell_tools)。使用示例:
from praisonaiagents import Agent, handoff, HandoffConfig, HandoffToolPolicy triage_agent = Agent( name="Triage Agent", handoffs=[ handoff(billing_agent, tool_policy_mode="intersect", # 仅共享工具 blocked_tools=["execute_code", "shell_tools"]), handoff(refund_agent, config=HandoffConfig( tool_policy=HandoffToolPolicy( mode="passthrough", # 传统行为 blocked_tools=["dangerous_tool"] ) )) ] )_compute_effective_tools()(handoff.py)在每次交接时实时计算生效工具集并传给agent.chat(prompt, tools=...),而不是在构造期固定,保证边界策略始终生效。
完整配置示例
handoff_unified_config.py 给出了三种交接的差异化配置:
from praisonaiagents import Agent, handoff, HandoffConfig, ContextPolicy coordinator = Agent( name="Coordinator", role="Project Coordinator", handoffs=[ # 摘要上下文 + 120s 超时 + 深度上限 5 handoff(research_agent, context_policy="summary", timeout_seconds=120, max_depth=5), # 完整上下文 + 180s 超时 handoff(writer_agent, context_policy="full", timeout_seconds=180), # 自定义配置:最近 5 条消息、环检测、深度 3 handoff(editor_agent, config=HandoffConfig( context_policy=ContextPolicy.LAST_N, max_context_messages=5, detect_cycles=True, max_depth=3, )), ] )五、程序化交接与并行交接
5.1Agent.handoff_to():代码直接发起交接
除了让 LLM 自主调用交接工具,你也可以在代码里直接决定交接。Agent.handoff_to()(agent.py)是统一程序化交接 API,内部构造Handoff并调用execute_programmatic(),返回带类型化结果的HandoffResult:
result = source_agent.handoff_to( target_agent, prompt="Summarize the key benefits of multi-agent systems", config=HandoffConfig( context_policy=ContextPolicy.SUMMARY, timeout_seconds=60, detect_cycles=True, ), ) print(f"Success: {result.success}") print(f"Target: {result.target_agent}") print(f"Duration: {result.duration_seconds:.2f}s") print(f"Response: {result.response}")HandoffResult(handoff.py)字段包括:success、response、target_agent、source_agent、duration_seconds、error、handoff_depth,以及类型化结果outcome(AgentRunOutcome,成功/超时/失败三种状态)。同步版本handoff_to()之外还有异步版本handoff_to_async()(agent.py)。
5.2parallel_handoffs():并行任务委派
当需要同时向多个 Agent 委派任务时,parallel_handoffs()(handoff.py)用asyncio.gather并发执行多个交接,并通过信号量限制并发数:
results = await parallel_handoffs( source=main_agent, targets=[ (research_agent, "Research topic X"), (analysis_agent, "Analyze data Y"), (summary_agent, "Summarize findings Z") ], max_concurrent=3 )并行交接时,框架会为每个子任务复制contextvars中的交接链(handoff.py),避免兄弟任务互相污染环检测/深度状态;同时每个目标 Agent 的"历史播种"通过DualLock串行化,防止并发写入同一chat_history造成上下文交错。
六、推荐提示词与交接指令注入
让 LLM 理解"何时该交接"是交接成功的关键。框架提供两个配套工具(handoff.py 与 handoff.py):
RECOMMENDED_PROMPT_PREFIX:一段建议前缀,告诉 Agent 它有能力把任务转交给更专业的 Agent;prompt_with_handoff_instructions(base_prompt, agent):在基础提示词上自动追加"可用交接 Agent 列表"(名称 + 工具描述),并拼接推荐前缀。
from praisonaiagents import RECOMMENDED_PROMPT_PREFIX, prompt_with_handoff_instructions # 创建专职 Agent billing_agent = Agent(name="Billing Agent", role="Billing Specialist") technical_agent = Agent(name="Technical Agent", role="Technical Support") agent = Agent( name="Support Agent", handoffs=[billing_agent, technical_agent] ) # 创建 Agent 后更新其指令 agent.instructions = prompt_with_handoff_instructions( "Help customers and transfer to specialists when needed.", agent # 传入 Agent 以自动生成交接信息 )生成的提示词结构大致为:
You have the ability to transfer tasks to specialized agents when appropriate. ... Available handoff agents: - Billing Agent: Transfer task to Billing Agent (Billing Specialist) - ... - Technical Agent: Transfer task to Technical Agent (Technical Support) - ... <你的基础提示词>prompt_with_handoff_instructions对Handoff实例使用其tool_description,对直接传入的 Agent 对象会临时构造默认Handoff来生成描述——两种交接写法都能正确生成清单。注意:在Agent.__init__中,handoffs必须在调用该函数前传入(或在之后手动赋值agent.instructions),否则函数因检测不到agent.handoffs而直接返回原提示词。
七、安全机制:环检测与深度限制
多 Agent 协作最怕两类事故:A→B→A 无限循环,以及交接链无限拉长。Handoff 系统内置了两道防线(_check_safety(),handoff.py):
- 环检测(默认开启,
detect_cycles=True):每个交接任务在contextvars中维护自己的交接链(handoff_chain),交接前检查目标是否已在链中;命中则抛出HandoffCycleError,并附上完整环路径A -> B -> A; - 深度限制(默认
max_depth=10):当交接深度达到上限时抛出HandoffDepthError。
这两类错误与HandoffTimeoutError、HandoffValidationError一起构成了完整的错误层级(见 errors.py 的导入列表)。使用contextvars.ContextVar而非threading.local()存储交接链(handoff.py),使得每个asyncio.Task/线程的交接链彼此隔离,并发场景(如服务器并行请求、parallel_handoffs)不会互相污染。
八、完整实战:客服多智能体路由系统
handoff_customer_service.py 提供了一个可直接运行的真实场景:一个客服主 Agent 路由到订单、退款、FAQ、技术支持、升级五个专职 Agent,其中前四个直接传 Agent 对象,升级 Agent 则用handoff()定制了工具描述:
from praisonaiagents import Agent, handoff, RECOMMENDED_PROMPT_PREFIX # 专职 Agent 各自携带工具 order_agent = Agent( name="Order Specialist", role="Order Management Specialist", tools=[check_order_status], instructions=f"""{RECOMMENDED_PROMPT_PREFIX} I can help with: - Checking order status - Tracking shipments ... For refunds, I'll transfer you to our Refund Specialist. For technical issues, I'll connect you with Technical Support.""" ) customer_service_agent = Agent( name="Customer Service", role="Customer Service Representative", instructions=f"""{RECOMMENDED_PROMPT_PREFIX} - For order tracking and status → Order Specialist - For refunds and returns → Refund Specialist - For common questions → FAQ Assistant - For technical problems → Technical Support - For complaints or special requests → Senior Manager""", handoffs=[ order_agent, refund_agent, faq_agent, technical_agent, handoff( escalation_agent, tool_description_override="Escalate to senior management for complex issues or complaints" ) ] ) # 调用 response = customer_service_agent.chat("I want a refund for order #67890, the product was damaged")这个例子完整展示了:主 Agent 用RECOMMENDED_PROMPT_PREFIX声明交接能力、在instructions中明确路由规则、混合使用"直接 Agent 引用"与"定制handoff()"两种交接写法。运行它即可观察 LLM 自主把不同类型的请求交给对应专员。
另外,handoff_basic.py 展示了带类型化载荷的分诊系统,handoff_advanced.py 则集中演示了回调、结构化输入、自定义过滤器的组合使用——三者覆盖了从入门到进阶的完整路径。
九、最佳实践
官方文档给出了五条实践准则,这里结合源码补充落地要点:
- 明确角色定义(Clear Role Definition):给每个 Agent 清晰的
role与goal。它们不仅影响目标 Agent 的行为,还直接参与默认交接工具描述的生成(Transfer task to <name> (<role>) - <goal>),是 LLM 判断何时交接的重要依据。 - 在指令中写明交接时机(Handoff Instructions):使用
prompt_with_handoff_instructions()或手动拼接RECOMMENDED_PROMPT_PREFIX,明确"什么情况下转给谁",并像客服示例那样给出显式映射表。 - 谨慎使用输入过滤器(Context Preservation):默认
SUMMARY策略已相对安全;需要保留关键上下文时优先选择LAST_N+preserve_system=True,避免过度过滤导致目标 Agent 缺乏必要信息。涉及隐私场景可用remove_system_messages等过滤器脱敏。 - 用回调跟踪交接(Logging):在
on_handoff/on_complete/on_error中记录交接来源、目标、耗时与结果,便于调试与数据分析。回调签名会自动适配,简单场景只需一个参数。 - 测试所有交接路径(Testing):确保每个目标 Agent 都能被正确触发、上下文能正确传递、环检测与深度限制按预期工作,尤其要覆盖"交接失败回退"(
HandoffResult.success=False)的路径。
十、向后兼容性
官方文档明确声明 Handoff 特性完全向后兼容:
- 现有 Agent 无需任何修改即可继续工作——
handoffs是Agent.__init__的可选参数(agent.py),不传即为空列表[],_process_handoffs()会直接返回; - 旧的
allow_delegation参数已被弃用,代码会给出use 'handoffs=[other_agent]' instead的替代提示(agent.py); - 所有既有 Agent 功能(工具、记忆、守卫等)在交接机制下保持不变,工具边界策略默认
intersect提供"安全优先"的默认行为,追求旧语义时可显式开启passthrough。
所有与 Handoff 相关的符号(Handoff、handoff、handoff_filters、parallel_handoffs、HandoffConfig、HandoffResult、ContextPolicy、TypedHandoff及各类错误)都通过 agent/init.py 导出,并支持懒加载,可直接从praisonaiagents顶层导入。
结语
PraisonAI 的 Agent Handoffs 把"任务委派"从外部编排脚本中解放出来,交给 Agent 自己决策:通过handoffs=[...]一行声明即可获得 LLM 驱动的自主交接;通过handoff()、HandoffConfig与handoff_filters可以实现结构化载荷、上下文策略、超时/并发控制、环检测与工具安全边界等生产级能力。无论是构建客服路由、研究-写作流水线,还是多步骤任务编排,这套机制都能让多智能体协作更可靠、更可观测、更安全。建议从 handoff_basic.py 起步,逐步迁移到 handoff_advanced.py 的高级用法,最后参考 handoff_customer_service.py 落地真实业务。
【免费下载链接】PraisonAIPraisonAI 🦞 — Hire a 24/7 AI Workforce. Stop writing boilerplate and start shipping autonomous self-improving agents that research, plan, code, and execute tasks. Deployed in 5 lines of code with built-in memory, RAG, and support for 100+ LLMs.项目地址: https://gitcode.com/GitHub_Trending/pr/PraisonAI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考