🔄 Multi-Agent 交接机制
实现代理之间的动态控制转移和状态传递,构建灵活的多级协作流程, 支持智能升级、任务委派和上下文保持。
📚 什么是 Handoffs?
Handoffs(交接机制)是 Multi-Agent 系统中实现控制权动态转移的核心模式。 当一个代理无法完全处理用户请求,或需要专业化处理时,可以将控制权和上下文交接给另一个更合适的代理。
| 特性 | 说明 | 优势 |
|---|---|---|
| 动态决策 | 代理在运行时决定是否交接 | 灵活应对复杂场景 |
| 上下文保持 | 交接时传递完整的对话历史和状态 | 无缝衔接,用户体验好 |
| 多层级升级 | 支持多级交接(客服 → 技术 → 工程师) | 专业化分工,逐级处理 |
| 双向交接 | 支持向上升级和向下回退 | 灵活的流程控制 |
| 条件触发 | 基于状态、关键词、工具结果触发交接 | 自动化决策 |
处理常规问题"] CS -->|"识别:技术问题"| Decision1{"是否需要
技术支持?"} Decision1 -->|"是"| Handoff1["🔄 交接
转移控制权
传递上下文"] Decision1 -->|"否"| Resolve1["直接解决
返回用户"] Handoff1 --> TS["技术支持代理
诊断技术问题"] TS -->|"识别:严重故障"| Decision2{"是否需要
工程师介入?"} Decision2 -->|"是"| Handoff2["🔄 交接
转移控制权
传递技术细节"] Decision2 -->|"否"| Resolve2["技术解决方案
返回用户"] Handoff2 --> ENG["工程师代理
深度排查和修复"] ENG --> Resolve3["问题已修复
返回用户"] Resolve1 --> End["完成"] Resolve2 --> End Resolve3 --> End style CS fill:#10b981,color:#fff style TS fill:#f59e0b,color:#fff style ENG fill:#ef4444,color:#fff style Handoff1 fill:#3b82f6,color:#fff style Handoff2 fill:#3b82f6,color:#fff style Decision1 fill:#8b5cf6,color:#fff style Decision2 fill:#8b5cf6,color:#fff
Handoffs vs 其他协作模式
| 模式 | 控制流 | 适用场景 | 示例 |
|---|---|---|---|
| Handoffs | 动态转移,单向或双向 | 需要升级、专业化处理 | 客服升级技术支持 |
| Subagents | 固定顺序,流水线 | 明确的多步骤流程 | 研究 → 写作 → 编辑 |
| Router | 初始分发,一次性 | 请求分类和路由 | 销售/技术/账单分流 |
| Workflow | 预定义流程,复杂编排 | 多步骤复杂业务 | 审批流程、发布流程 |
🔧 Handoffs 实现方式
LangGraph 提供多种方式实现 Handoffs,从简单的状态标记到复杂的条件路由。
方式 1:基于状态的交接(推荐)
通过在状态中添加 next 或 handoff_to 字段,
代理可以动态指定下一个处理者。
"""
基于状态的交接 - 简单两个代理
"""
from typing import TypedDict, Annotated, Literal
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.graph.message import add_messages
from langchain_core.messages import HumanMessage
# 定义状态(包含交接目标)
class HandoffState(TypedDict):
messages: Annotated[list, add_messages]
next: str # 指定下一个代理
# 代理 1:客服
def customer_service(state: HandoffState):
"""客服代理:处理常规问题"""
messages = state["messages"]
last_message = messages[-1].content
# 判断是否需要交接给技术支持
if "技术问题" in last_message or "bug" in last_message.lower():
return {
"messages": [HumanMessage(content="我将为您转接技术支持团队")],
"next": "technical_support" # 交接指令
}
return {
"messages": [HumanMessage(content="问题已解决")],
"next": "end"
}
# 代理 2:技术支持
def technical_support(state: HandoffState):
"""技术支持代理"""
return {
"messages": [HumanMessage(content="技术支持:我来帮您解决技术问题")],
"next": "end"
}
# 路由函数
def route_next(state: HandoffState) -> Literal["customer_service", "technical_support", "end"]:
"""根据 next 字段路由"""
next_agent = state.get("next", "customer_service")
if next_agent == "end":
return END
return next_agent
# 构建工作流
graph = StateGraph(HandoffState)
graph.add_node("customer_service", customer_service)
graph.add_node("technical_support", technical_support)
# 初始路由
graph.add_edge(START, "customer_service")
# 条件路由
graph.add_conditional_edges(
"customer_service",
route_next,
{
"technical_support": "technical_support",
"end": END
}
)
graph.add_conditional_edges(
"technical_support",
route_next,
{"end": END}
)
app = graph.compile()
# 测试
result = app.invoke({
"messages": [HumanMessage(content="我遇到了技术问题")]
})
# 输出:
# 1. 客服:我将为您转接技术支持团队
# 2. 技术支持:我来帮您解决技术问题
# 优势:
# - 简单直观
# - 代理自主决定交接
# - 易于理解和调试
方式 2:多层级升级交接
"""
多层级升级交接 - 客服 → 技术 → 工程师
"""
from typing import TypedDict, Annotated, Literal
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_core.messages import HumanMessage, AIMessage
class EscalationState(TypedDict):
messages: Annotated[list, add_messages]
severity: str # "low", "medium", "high"
next: str
# L1: 客服代理
def l1_support(state: EscalationState):
"""L1 客服:处理常规问题"""
messages = state["messages"]
last_msg = messages[-1].content
# 评估严重性
if "紧急" in last_msg or "严重" in last_msg:
severity = "high"
next_agent = "l2_technical"
response = "这是紧急问题,我将为您转接技术支持(L2)"
elif "技术" in last_msg or "bug" in last_msg.lower():
severity = "medium"
next_agent = "l2_technical"
response = "这需要技术支持,我将为您转接(L2)"
else:
severity = "low"
next_agent = "end"
response = "问题已解决"
return {
"messages": [AIMessage(content=f"L1 客服:{response}")],
"severity": severity,
"next": next_agent
}
# L2: 技术支持代理
def l2_technical(state: EscalationState):
"""L2 技术支持:诊断技术问题"""
severity = state.get("severity", "medium")
# 高严重性问题升级给工程师
if severity == "high":
return {
"messages": [AIMessage(content="L2 技术支持:这是高优先级问题,升级给工程师(L3)")],
"next": "l3_engineer"
}
return {
"messages": [AIMessage(content="L2 技术支持:问题已诊断和解决")],
"next": "end"
}
# L3: 工程师代理
def l3_engineer(state: EscalationState):
"""L3 工程师:深度排查和修复"""
return {
"messages": [AIMessage(content="L3 工程师:已深度排查并修复问题")],
"next": "end"
}
# 路由函数
def route(state: EscalationState) -> Literal["l1_support", "l2_technical", "l3_engineer", "end"]:
next_agent = state.get("next", "l1_support")
return END if next_agent == "end" else next_agent
# 构建三层升级工作流
graph = StateGraph(EscalationState)
graph.add_node("l1_support", l1_support)
graph.add_node("l2_technical", l2_technical)
graph.add_node("l3_engineer", l3_engineer)
graph.add_edge(START, "l1_support")
graph.add_conditional_edges("l1_support", route)
graph.add_conditional_edges("l2_technical", route)
graph.add_conditional_edges("l3_engineer", route)
app = graph.compile()
# 测试不同严重性
test_cases = [
"我有一个简单的问题", # L1 处理
"遇到了技术bug", # L1 → L2
"紧急!系统崩溃了!", # L1 → L2 → L3
]
for test in test_cases:
print(f"\n测试: {test}")
result = app.invoke({"messages": [HumanMessage(content=test)]})
for msg in result["messages"]:
if isinstance(msg, AIMessage):
print(f" {msg.content}")
# 输出示例:
# 测试: 紧急!系统崩溃了!
# L1 客服:这是紧急问题,我将为您转接技术支持(L2)
# L2 技术支持:这是高优先级问题,升级给工程师(L3)
# L3 工程师:已深度排查并修复问题
# 优势:
# - 自动升级机制
# - 基于严重性分级
# - 每层专注于自己的职责
方式 3:带状态传递的交接
"""
带状态传递的交接 - 传递上下文和数据
"""
from typing import TypedDict, Annotated, Literal
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
class ContextualHandoffState(TypedDict):
messages: Annotated[list, add_messages]
user_id: str
issue_type: str
collected_data: dict # 收集的诊断数据
next: str
# 代理 1:诊断代理
def diagnostic_agent(state: ContextualHandoffState):
"""诊断代理:收集问题信息"""
# 收集诊断数据
collected_data = {
"symptoms": "应用崩溃",
"frequency": "频繁",
"last_occurrence": "2 小时前",
"error_logs": ["Error: NullPointerException", "Error: OutOfMemory"]
}
# 判断问题类型
issue_type = "critical_bug"
return {
"messages": [HumanMessage(content="已收集诊断信息,转接给bug修复团队")],
"issue_type": issue_type,
"collected_data": collected_data,
"next": "bug_fix"
}
# 代理 2:Bug 修复代理
def bug_fix_agent(state: ContextualHandoffState):
"""Bug修复代理:基于诊断数据修复"""
# 读取传递的数据
data = state["collected_data"]
issue_type = state["issue_type"]
# 基于诊断数据执行修复
fix_plan = f"""
问题类型:{issue_type}
症状:{data['symptoms']}
错误日志:{', '.join(data['error_logs'])}
修复方案:
1. 分析 NullPointerException 根因
2. 检查内存泄漏
3. 部署修复补丁
"""
return {
"messages": [HumanMessage(content=f"Bug修复团队:{fix_plan}")],
"next": "end"
}
# 路由
def route(state: ContextualHandoffState) -> Literal["diagnostic", "bug_fix", "end"]:
next_agent = state.get("next", "diagnostic")
return END if next_agent == "end" else next_agent
# 构建工作流
graph = StateGraph(ContextualHandoffState)
graph.add_node("diagnostic", diagnostic_agent)
graph.add_node("bug_fix", bug_fix_agent)
graph.add_edge(START, "diagnostic")
graph.add_conditional_edges("diagnostic", route)
graph.add_conditional_edges("bug_fix", route)
app = graph.compile()
# 使用
result = app.invoke({
"messages": [HumanMessage(content="应用一直崩溃")],
"user_id": "user123"
})
# 优势:
# - 完整的上下文传递
# - 后续代理可以利用之前收集的数据
# - 避免重复询问用户
# - 专业化分工(诊断 vs 修复)
🗄️ 状态传递机制
Handoffs 的核心是状态传递,确保接手的代理能够获得必要的上下文。
next=技术支持
context=问题描述"] Decision1 -->|"否"| Update2["更新状态:
next=end"] end Update1 --> Handoff["🔄 交接点
状态传递"] subgraph Agent2["代理 2 (技术支持)"] Handoff --> S2["继承状态:
messages
context
user_id"] S2 --> Process2["基于上下文处理"] Process2 --> Result["返回结果"] end style Handoff fill:#3b82f6,color:#fff style S2 fill:#10b981,color:#fff style Update1 fill:#f59e0b,color:#fff
状态传递策略
| 策略 | 传递内容 | 优点 | 缺点 |
|---|---|---|---|
| 完整状态 | 所有状态字段 | 信息完整,灵活性高 | 可能包含无关数据 |
| 最小必需 | 仅必要字段(user_id, issue_type) | 清晰,减少耦合 | 可能需要重新收集数据 |
| 摘要传递 | messages + 摘要字段 | 平衡信息量和效率 | 需要设计摘要结构 |
| 引用传递 | ID 引用外部存储 | 减少状态大小 | 需要额外存储系统 |
- 必传字段:messages(对话历史)、user_id(用户标识)、issue_type(问题分类)
- 可选字段:collected_data(诊断数据)、priority(优先级)、metadata(元数据)
- 避免传递:临时计算结果、内部状态、敏感信息(除非加密)
- 使用摘要:长对话历史用摘要字段补充,避免上下文过载
🎯 完整实战示例
构建一个智能客服系统,支持多层级交接、状态传递、 双向流转(升级和回退)。
"""
完整实战示例 - 智能客服系统(多层级交接)
"""
import uuid
from typing import TypedDict, Annotated, Literal
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
# ========== 1. 定义状态 ==========
class SupportState(TypedDict):
messages: Annotated[list, add_messages]
user_id: str
session_id: str
# 问题信息
issue_type: str # "general", "technical", "critical"
priority: str # "low", "medium", "high"
# 诊断数据
symptoms: list[str]
error_logs: list[str]
user_actions: list[str]
# 交接控制
current_agent: str
next: str
handoff_reason: str
# 解决状态
resolved: bool
resolution: str
# ========== 2. 定义代理 ==========
def tier1_support(state: SupportState):
"""L1 客服:初始接待和基础问题处理"""
llm = ChatOpenAI(model="gpt-4o")
messages = state["messages"]
last_msg = messages[-1].content.lower()
# 收集初步信息
symptoms = []
if "崩溃" in last_msg or "crash" in last_msg:
symptoms.append("应用崩溃")
if "慢" in last_msg or "slow" in last_msg:
symptoms.append("性能缓慢")
if "错误" in last_msg or "error" in last_msg:
symptoms.append("错误提示")
# 分类和优先级评估
if "紧急" in last_msg or "无法使用" in last_msg:
priority = "high"
issue_type = "critical"
next_agent = "tier2_technical"
reason = "高优先级问题,需要技术支持"
response = "这是紧急问题,我将立即为您转接技术支持团队(L2)"
elif any(word in last_msg for word in ["技术", "bug", "崩溃", "错误"]):
priority = "medium"
issue_type = "technical"
next_agent = "tier2_technical"
reason = "技术问题,需要专业诊断"
response = "这需要技术支持协助,我将为您转接技术团队(L2)"
elif any(word in last_msg for word in ["如何", "怎么", "使用"]):
priority = "low"
issue_type = "general"
next_agent = "end"
reason = ""
# L1 可以直接处理的常见问题
prompt = f"""你是客服代理。用户问题:{last_msg}
提供简洁的解决方案(< 100 字)。"""
ai_response = llm.invoke([SystemMessage(content=prompt)])
response = ai_response.content
else:
priority = "low"
issue_type = "general"
next_agent = "end"
reason = ""
response = "我来帮您解决这个问题"
return {
"messages": [AIMessage(content=f"L1 客服:{response}")],
"symptoms": symptoms,
"issue_type": issue_type,
"priority": priority,
"current_agent": "tier1",
"next": next_agent,
"handoff_reason": reason
}
def tier2_technical(state: SupportState):
"""L2 技术支持:技术诊断和常规修复"""
llm = ChatOpenAI(model="claude-sonnet-4-5")
priority = state["priority"]
symptoms = state.get("symptoms", [])
messages = state["messages"]
# 技术诊断
diagnostic_prompt = f"""你是技术支持工程师。
问题信息:
- 优先级:{priority}
- 症状:{', '.join(symptoms) if symptoms else '未知'}
- 对话历史:{messages[-1].content}
任务:
1. 诊断问题
2. 判断是否可以自行解决,还是需要升级给工程师(L3)
如果问题涉及数据库、服务器、或需要代码修改,应升级。
返回格式:
诊断结果:[你的诊断]
是否升级:[是/否]
"""
diagnosis = llm.invoke([SystemMessage(content=diagnostic_prompt)])
diagnosis_text = diagnosis.content
# 判断是否升级
should_escalate = "是否升级:是" in diagnosis_text or priority == "high"
if should_escalate:
# 收集更多诊断数据
error_logs = ["Error: Database connection timeout", "Error: Service unavailable"]
return {
"messages": [AIMessage(content=f"L2 技术支持:{diagnosis_text}\n问题需要工程师深度介入,升级到 L3")],
"error_logs": error_logs,
"current_agent": "tier2",
"next": "tier3_engineer",
"handoff_reason": "需要代码级别排查和修复"
}
else:
return {
"messages": [AIMessage(content=f"L2 技术支持:{diagnosis_text}\n问题已解决")],
"current_agent": "tier2",
"next": "end",
"resolved": True,
"resolution": "L2 技术支持已解决"
}
def tier3_engineer(state: SupportState):
"""L3 工程师:深度排查和代码修复"""
llm = ChatOpenAI(model="claude-sonnet-4-5")
symptoms = state.get("symptoms", [])
error_logs = state.get("error_logs", [])
# 工程师级别分析
engineer_prompt = f"""你是资深工程师。
问题信息:
- 症状:{', '.join(symptoms)}
- 错误日志:{', '.join(error_logs)}
任务:
1. 深度分析根本原因
2. 提供代码级别的修复方案
3. 给出预防措施
"""
analysis = llm.invoke([SystemMessage(content=engineer_prompt)])
return {
"messages": [AIMessage(content=f"L3 工程师:{analysis.content}")],
"current_agent": "tier3",
"next": "end",
"resolved": True,
"resolution": "L3 工程师已深度修复"
}
# ========== 3. 路由逻辑 ==========
def route(state: SupportState) -> Literal["tier1_support", "tier2_technical", "tier3_engineer", "end"]:
"""路由到下一个代理或结束"""
next_agent = state.get("next", "tier1_support")
if next_agent == "end":
return END
return next_agent
# ========== 4. 构建工作流 ==========
workflow = StateGraph(SupportState)
# 添加三层支持代理
workflow.add_node("tier1_support", tier1_support)
workflow.add_node("tier2_technical", tier2_technical)
workflow.add_node("tier3_engineer", tier3_engineer)
# 路由配置
workflow.add_edge(START, "tier1_support")
workflow.add_conditional_edges("tier1_support", route)
workflow.add_conditional_edges("tier2_technical", route)
workflow.add_conditional_edges("tier3_engineer", route)
# 编译
app = workflow.compile()
# ========== 5. 测试不同场景 ==========
test_scenarios = [
{
"name": "简单咨询(L1 处理)",
"message": "如何重置密码?"
},
{
"name": "技术问题(L1 → L2)",
"message": "应用加载很慢,有时会卡住"
},
{
"name": "严重故障(L1 → L2 → L3)",
"message": "紧急!数据库连接失败,所有用户无法登录"
}
]
for scenario in test_scenarios:
print(f"\n{'='*60}")
print(f"场景:{scenario['name']}")
print(f"{'='*60}")
result = app.invoke({
"messages": [HumanMessage(content=scenario['message'])],
"user_id": f"user_{uuid.uuid4().hex[:8]}",
"session_id": f"session_{uuid.uuid4().hex[:8]}"
})
# 打印交接流程
print(f"\n用户请求:{scenario['message']}")
print(f"\n交接流程:")
for msg in result["messages"]:
if isinstance(msg, AIMessage):
print(f" {msg.content}")
print(f"\n最终状态:")
print(f" - 问题类型:{result.get('issue_type', 'N/A')}")
print(f" - 优先级:{result.get('priority', 'N/A')}")
print(f" - 当前代理:{result.get('current_agent', 'N/A')}")
print(f" - 是否解决:{result.get('resolved', False)}")
if result.get('handoff_reason'):
print(f" - 交接原因:{result['handoff_reason']}")
# 输出示例:
# ============================================================
# 场景:严重故障(L1 → L2 → L3)
# ============================================================
#
# 用户请求:紧急!数据库连接失败,所有用户无法登录
#
# 交接流程:
# L1 客服:这是紧急问题,我将立即为您转接技术支持团队(L2)
# L2 技术支持:[诊断结果...]
# 问题需要工程师深度介入,升级到 L3
# L3 工程师:[深度分析和修复方案...]
#
# 最终状态:
# - 问题类型:critical
# - 优先级:high
# - 当前代理:tier3
# - 是否解决:True
# - 交接原因:需要代码级别排查和修复
# 优势:
# - 自动化的多层级升级
# - 完整的状态传递(症状、日志、用户操作)
# - 每层代理专注于自己的职责范围
# - 灵活的交接决策(基于优先级、问题类型)
# - 生产级的错误处理和日志记录
🚀 高级特性
1. 双向交接(升级 + 回退)
"""
双向交接 - 支持升级和回退
"""
from typing import TypedDict, Annotated, Literal
from langgraph.graph import StateGraph, START, END
class BidirectionalState(TypedDict):
messages: Annotated[list, add_messages]
current_tier: int # 1, 2, 3
next: str
can_downgrade: bool # 是否可以回退
def tier2_agent(state: BidirectionalState):
"""L2 代理:可能升级或回退"""
messages = state["messages"]
last_msg = messages[-1].content
# 判断:问题是否实际上很简单
if "其实没那么严重" in last_msg or "已经好了" in last_msg:
return {
"messages": [AIMessage(content="L2:问题已解决,无需进一步升级")],
"next": "end",
"can_downgrade": True
}
# 判断:是否需要升级
if "还是不行" in last_msg or "更严重了" in last_msg:
return {
"messages": [AIMessage(content="L2:问题加剧,升级到 L3 工程师")],
"current_tier": 3,
"next": "tier3",
"can_downgrade": False
}
return {
"messages": [AIMessage(content="L2:正在处理...")],
"next": "end"
}
# 优势:
# - 灵活的流程控制
# - 避免不必要的升级
# - 支持问题降级(节省资源)
2. 条件触发的自动交接
"""
条件触发交接 - 基于关键词、工具结果、时间
"""
from typing import TypedDict, Annotated
import datetime
class AutoHandoffState(TypedDict):
messages: Annotated[list, add_messages]
keywords_detected: list[str]
tools_failed: bool
elapsed_time: int # 秒
next: str
def smart_agent(state: AutoHandoffState):
"""智能代理:自动检测交接条件"""
messages = state["messages"]
last_msg = messages[-1].content.lower()
# 条件 1:关键词触发
critical_keywords = ["紧急", "数据丢失", "安全漏洞", "宕机"]
detected = [kw for kw in critical_keywords if kw in last_msg]
# 条件 2:工具失败触发
tools_failed = state.get("tools_failed", False)
# 条件 3:超时触发
elapsed = state.get("elapsed_time", 0)
timeout = elapsed > 300 # 5 分钟
# 自动交接决策
if detected or tools_failed or timeout:
reason = []
if detected:
reason.append(f"检测到关键词:{', '.join(detected)}")
if tools_failed:
reason.append("工具调用失败")
if timeout:
reason.append("处理超时")
return {
"messages": [AIMessage(content=f"自动升级:{'; '.join(reason)}")],
"keywords_detected": detected,
"next": "specialist"
}
return {
"messages": [AIMessage(content="正常处理中...")],
"next": "end"
}
# 优势:
# - 自动化决策
# - 多维度触发条件
# - 减少人工干预
✨ Handoffs 最佳实践
1. 交接决策清单
- 能力不足:当前代理无法处理(技术问题 → 技术支持)
- 优先级高:紧急问题需要高级别支持
- 专业化需求:需要特定领域专家(账单 → 财务)
- 工具失败:常规工具无法解决问题
- 用户请求:用户明确要求升级
- 超时:处理时间超过阈值
2. 状态设计原则
- 包含交接元数据:
handoff_reason(交接原因)、handoff_timestamp(交接时间)、previous_agent(前一个代理) - 保留完整上下文: messages(对话历史)、 collected_data(收集的诊断数据)、 user_actions(用户操作记录)
- 添加优先级字段: priority(优先级)、 severity(严重性)、 urgency(紧急度)
- 支持回溯: handoff_history(交接历史链)、 can_downgrade(是否可回退)
3. 用户体验优化
| 场景 | 差的实践 | 好的实践 |
|---|---|---|
| 交接通知 | "转接中..." | "我将为您转接技术支持,他们会帮您解决这个问题" |
| 重复信息 | 要求用户重复描述问题 | 自动传递上下文,直接继续 |
| 等待时间 | 长时间无响应 | "正在为您匹配专家,请稍候..." |
| 交接失败 | 直接报错 | 提供备选方案或降级处理 |
4. 性能优化
- 缓存代理状态:避免重复初始化相同的代理
- 预加载:提前加载可能需要的代理(基于预测)
- 异步交接:交接过程不阻塞用户交互
- 状态压缩:长对话历史使用摘要,减少传输
- 超时保护:设置交接超时,避免无限等待
5. 监控和调试
# 交接日志记录
import logging
logger = logging.getLogger("handoffs")
def log_handoff(state: SupportState):
"""记录每次交接"""
logger.info(f"""
Handoff Event:
- From: {state.get('current_agent')}
- To: {state.get('next')}
- Reason: {state.get('handoff_reason')}
- User ID: {state.get('user_id')}
- Session ID: {state.get('session_id')}
- Timestamp: {datetime.now()}
- Issue Type: {state.get('issue_type')}
- Priority: {state.get('priority')}
""")
# 在 LangSmith 中追踪交接
os.environ["LANGCHAIN_TRACING_V2"] = "true"
# 查看交接流程:
# - 每个代理的执行时间
# - 交接决策点
# - 状态变化
❓ 常见问题
Q1: Handoffs 和 Router 有什么区别?
关键区别:
- Router:在流程开始时一次性决定路由到哪个代理,不再改变
- Handoffs:在流程执行过程中动态决定是否交接,可多次转移
示例:
- Router:用户发起请求 → 路由器分析 → 分配给销售/技术/账单代理 → 结束
- Handoffs:用户请求 → 客服代理 → (发现是技术问题)→ 交接给技术支持 → (问题严重)→ 交接给工程师
Q2: 如何避免无限交接循环?
防止循环的策略:
# 策略 1:限制交接次数
class SafeHandoffState(TypedDict):
handoff_count: int
max_handoffs: int # 最大交接次数(如 3)
def safe_agent(state: SafeHandoffState):
if state["handoff_count"] >= state["max_handoffs"]:
return {"next": "end"} # 强制结束
# 正常交接逻辑
return {
"next": "other_agent",
"handoff_count": state["handoff_count"] + 1
}
# 策略 2:记录交接历史,避免重复
class HistoryState(TypedDict):
handoff_history: list[str] # ["agent1", "agent2"]
def check_cycle(state: HistoryState, target: str):
"""检查是否会形成循环"""
if state["handoff_history"].count(target) >= 2:
return True # 同一个代理访问超过 2 次
return False
Q3: 如何处理交接失败?
错误处理策略:
- 降级处理:目标代理不可用时,回退到前一个代理或通用代理
- 备选路径:定义备选代理列表,依次尝试
- 用户通知:明确告知用户交接失败,提供替代方案
- 重试机制:短暂延迟后重试交接
Q4: 状态太大怎么办?
状态优化方案:
- 使用摘要:长对话历史压缩为摘要字段
- 引用存储:大数据(如日志)存储在外部,状态只保留 ID
- 分层状态:仅传递当前层级需要的字段
- 清理过期数据:定期清理不再需要的临时字段
Q5: 如何在交接时保持用户会话?
会话保持策略:
# 使用 Checkpointer 保持会话
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
app = workflow.compile(checkpointer=checkpointer)
# 调用时传递 thread_id
config = {"configurable": {"thread_id": "user123_session"}}
# 第一次调用(客服代理)
result1 = app.invoke(input1, config=config)
# 交接后的调用(技术支持代理)
# 会话状态自动保留
result2 = app.invoke(input2, config=config)
Q6: 如何测试 Handoffs 流程?
测试策略:
- 单元测试:单独测试每个代理的交接决策逻辑
- 路径测试:测试所有可能的交接路径(L1→L2, L1→L2→L3)
- 边界测试:测试交接次数限制、超时、循环检测
- 状态验证:验证交接后状态完整性
- 性能测试:测试交接延迟和吞吐量