AI Agent 落地实战:从单Agent到多Agent协作的系统架构与实践
大家好,我是摘星,今天我们来聊聊AI Agent落地这件事------不是概念,不是愿景,而是实实在在跑起来的架构和代码。
过去一年,我观察了十几个团队尝试落地Agent的实际项目。有的团队用单Agent+Tool Learning跑通了客服机器人的场景;有的团队用Multi-Agent架构重构了整个售后处理流程,效率提升了三倍不止;但更多的团队,是在「调Prompt-失败-再调Prompt」的死循环里煎熬了三个月,最后告诉我「Agent这东西就是炒概念,根本没法落地」。
这两种结果的差距在哪?不是技术选型,不是模型能力,而是对Agent系统架构的理解深度。
今天这篇文章,我就把过去一年深度参与的几个Agent落地项目里踩过的坑、总结出的方法论,一次性拆解清楚。从单Agent的核心设计,到多Agent协作的架构演进,再到生产环境里的具体实现细节,你拿走就能用。
我假设你已经对Agent有了基本了解,知道ReAct、Tool Use这些概念,不会在文章里解释什么是Function Calling。所以我们直接进正题。
核心概念:Agent系统不是「一个模型+一堆工具」
很多人对Agent系统的理解还停留在「选一个大模型,给它装上一堆工具,让它自己调用」这个层面。这个理解不能说错,但远远不够。
真实的Agent系统面临的挑战远比这个复杂:
第一,任务规划的不确定性。 当用户说「帮我处理一下这个订单」,系统需要先理解这具体是什么订单、当前处于什么状态、用户期望什么结果。这不是简单的意图识别,而是需要多轮澄清和状态推断。
第二,工具调用结果的不可控性。 外部API可能超时、返回脏数据、或者完全不可用。Agent系统必须有能力处理这些异常情况,而不是遇到错误就卡死。
第三,多步骤任务的长期记忆问题。 一个报销流程可能涉及十几步操作,跨越几十分钟。系统需要在这段时间内保持上下文连贯性,不能每一步都从零开始。
第四,多Agent协作时的角色冲突和死锁问题。 当两个Agent同时需要访问同一个资源、或者对同一个任务有冲突的理解时,系统需要有一套机制来解决这些冲突。
以上每一个问题,单靠调Prompt是解决不了的。你需要的是系统级的架构设计。
下面我们先从最简单的单Agent架构开始,逐层递进。
单Agent架构:核心组件与状态机设计
一个最简单的单Agent系统
很多人第一次跑通Agent,代码是这样的:
python
from anthropic import Anthropic
client = Anthropic()
def run_agent(user_message: str):
messages = [{"role": "user", "content": user_message}]
response = client.messages.create(
model="claude-sonnet-4-7",
max_tokens=4096,
messages=messages,
tools=[
{
"name": "get_order_status",
"description": "查询订单状态",
"input_schema": {
"type": "object",
"properties": {
"order_id": {"type": "string"}
}
}
},
# ... 更多工具定义
]
)
return response
# 第一次调用
result = run_agent("我的订单到哪了?")
print(result.content[0].text)
这段代码跑起来没问题,但它缺少了几个关键组件,所以在复杂场景下会快速崩溃:
- 没有循环机制------如果Agent第一次没理解用户意图,代码没有重试逻辑
- 没有状态管理------每次调用都是独立的context window
- 没有异常处理------API超时或工具返回错误时没有容错机制
一个能用于生产环境的单Agent,至少应该是这样:
python
import time
from dataclasses import dataclass, field
from typing import Optional, List, Dict, Any
from enum import Enum
class AgentStatus(Enum):
IDLE = "idle"
RUNNING = "running"
WAITING_FOR_INPUT = "waiting_for_input"
COMPLETED = "completed"
FAILED = "failed"
@dataclass
class ToolResult:
"""工具执行结果"""
success: bool
result: Any = None
error: Optional[str] = None
tool_name: str = ""
execution_time_ms: int = 0
@dataclass
class AgentState:
"""Agent运行状态"""
status: AgentStatus = AgentStatus.IDLE
current_step: int = 0
max_steps: int = 20
last_user_message: str = ""
tool_results: List[ToolResult] = field(default_factory=list)
conversation_history: List[Dict] = field(default_factory=list)
session_data: Dict[str, Any] = field(default_factory=dict)
class SingleAgent:
"""单Agent运行器"""
def __init__(
self,
client: Anthropic,
tools: List[Dict],
max_steps: int = 20,
retry_on_error: bool = True,
max_retries: int = 3
):
self.client = client
self.tools = tools
self.max_steps = max_steps
self.retry_on_error = retry_on_retries
self.max_retries = max_retries
self.state = AgentState()
def run(self, user_message: str) -> Dict:
"""主运行循环"""
self.state.last_user_message = user_message
self.state.status = AgentStatus.RUNNING
self.state.current_step = 0
self.state.conversation_history = [
{"role": "user", "content": user_message}
]
while self.state.current_step < self.state.max_steps:
try:
step_result = self._execute_step()
if step_result.get("type") == "stop":
self.state.status = AgentStatus.COMPLETED
return {
"status": "completed",
"message": step_result["content"],
"steps": self.state.current_step,
"tool_results": self.state.tool_results
}
elif step_result.get("type") == "tool_use":
tool_result = await self._execute_tool(step_result)
self.state.tool_results.append(tool_result)
if not tool_result.success and not self.retry_on_error:
self.state.status = AgentStatus.FAILED
return {
"status": "failed",
"error": tool_result.error,
"steps": self.state.current_step
}
elif step_result.get("type") == "waiting_for_input":
self.state.status = AgentStatus.WAITING_FOR_INPUT
return {
"status": "waiting_for_input",
"message": step_result["content"],
"pending_params": step_result.get("params", {})
}
except Exception as e:
if self.state.current_step > 0 and self.retry_on_error:
# 某种程度的自愈逻辑
continue
else:
self.state.status = AgentStatus.FAILED
return {"status": "error", "error": str(e)}
return {
"status": "max_steps_exceeded",
"message": "任务未能在最大步数内完成",
"steps": self.state.current_step
}
def _execute_step(self) -> Dict:
"""执行单步推理"""
response = self.client.messages.create(
model="claude-sonnet-4-7",
max_tokens=4096,
messages=self.state.conversation_history,
tools=self.tools
)
self.state.conversation_history.append({
"role": "assistant",
"content": response.content
})
# 解析 response 中的 tool_use 块
if response.stop_reason == "tool_use":
# 处理工具调用
pass
elif response.stop_reason == "end_turn":
# 最终响应
pass
return {"type": "stop", "content": response.content}
这段代码展示了几个关键设计:
状态机控制流 :通过AgentStatus枚举管理Agent的生命周期,避免在错误状态继续执行。
工具执行封装 :每个工具调用都包装成ToolResult对象,包含成功/失败状态、执行时间、错误信息,便于后续分析和优化。
对话历史维护:每次调用都把历史消息传入LLM,而不是每次都从新context开始,保证多步骤任务的连贯性。
工具定义的最佳实践
工具是Agent与外界交互的桥梁。工具定义的质量直接决定了Agent的能力上限。
一个好的工具定义应该是这样的:
python
ORDER_STATUS_TOOL = {
"name": "get_order_status",
"description": """
查询订单的当前物流状态。
适用场景:
- 用户询问「我的订单到哪了」
- 用户询问「什么时候能收到」
- 用户询问「订单有没有发出」
不适用场景:
- 用户询问「怎么取消订单」(用 cancel_order)
- 用户询问「怎么修改地址」(用 modify_shipping_address)
""",
"input_schema": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "订单ID,必须是有效的12位数字格式",
"pattern": "^[0-9]{12}$",
"examples": ["202405010001", "202405010002"]
},
"require_insurance": {
"type": "boolean",
"description": "是否查询运费险状态,默认为false",
"default": False
}
},
"required": ["order_id"]
}
}
注意这个定义里的几个细节:
详细的中文描述:不仅告诉模型这个工具是做什么的,还明确说明了适用和不适用场景,减少模型误调用的概率。
JSON Schema的完整定义 :包括pattern字段限制格式,用examples给出正面和反面示例。这比简单写个type要可靠得多。
description中的场景示例:帮助模型理解在什么情况下应该调用这个工具。这个描述会被用在few-shot提示里。
单Agent的局限性
单Agent架构在简单场景下非常有效,比如单轮问答、简单的信息查询、基础的自动化操作。但当任务复杂度提升,它的局限性就显现出来了:
规划能力的瓶颈:当一个任务需要分解成十几个子步骤时,单Agent的推理链会变得非常长,每一步的误差会累积,最终导致任务失败。
角色冲突:单Agent很难同时扮演多个角色,比如一个Agent既要做客服、又要做售后、还要做销售,在同一个context里会让模型困惑。
并行处理:如果有多个相互独立的子任务,单Agent只能串行处理,无法最大化资源利用率。
这时候,我们就需要Multi-Agent架构。
Multi-Agent架构:从协作模式到层级拓扑
Multi-Agent不是简单地把多个单Agent放在一起就叫Multi-Agent了。你需要解决几个核心问题:
1. 角色定义:每个Agent负责什么?
2. 通信协议:Agent之间怎么传递信息?
3. 冲突解决:多个Agent对同一个问题有不同看法时怎么办?
4. 任务分发:谁来决定哪个Agent处理哪个任务?
下面我们逐一讨论。
模式一:星型协作(Hub-Spoke)
这是最简单的Multi-Agent模式,有一个中心Agent负责调度,其他Agent负责执行具体任务。
┌──────────┐
│ Router │
│ Agent │
└────┬─────┘
│
┌────────┼────────┬────────────┐
│ │ │ │
┌───▼──┐ ┌──▼──┐ ┌──▼──┐ ┌──────▼─────┐
│ Order│ │Refund│ │FAQ │ │ escalate │
│ Agent│ │Agent │ │Agent│ │ Agent │
└──────┘ └─────┘ └─────┘ └────────────┘
这种模式适合场景:任务类型明确、可分类的场景。比如客服系统,不同类型的query分发给不同的专业Agent处理。
python
from typing import Dict, List, Optional
from dataclasses import dataclass
import json
@dataclass
class Task:
"""分发任务"""
task_id: str
task_type: str
original_message: str
context: Dict = None
priority: int = 0
@dataclass
class TaskResult:
"""任务执行结果"""
task_id: str
agent_id: str
success: bool
output: Optional[str] = None
error: Optional[str] = None
confidence: float = 0.0
class RouterAgent:
"""路由Agent,负责分发任务"""
def __init__(self, sub_agents: Dict[str, Any], llm_client: Any):
self.sub_agents = sub_agents # {"order": agent1, "refund": agent2, ...}
self.llm_client = llm_client
def classify_task(self, message: str, context: Dict = None) -> Task:
"""LLM分类任务类型"""
classification_prompt = f"""
用户消息:「{message}」
请判断这个消息涉及哪种任务类型:
- order: 订单查询、修改地址、取消订单等
- refund: 退货、退款、售后等
- faq: 常见问题解答
- escalate: 需要人工介入的复杂问题
只返回一个词:order/refund/faq/escalate
同时给出置信度(0-1)和分类理由。
"""
response = self.llm_client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=100,
messages=[{"role": "user", "content": classification_prompt}]
)
result_text = response.content[0].text.strip().lower()
# 解析分类结果
# ...
return Task(
task_id=self._generate_id(),
task_type="order",
original_message=message,
context=context
)
def dispatch(self, task: Task) -> TaskResult:
"""分发任务给对应Agent"""
agent = self.sub_agents.get(task.task_type)
if not agent:
return TaskResult(
task_id=task.task_id,
agent_id="router",
success=False,
error=f"未知任务类型: {task.task_type}"
)
# 执行子Agent
result = agent.run(task.original_message)
return TaskResult(
task_id=task.task_id,
agent_id=task.task_type,
success=(result["status"] == "completed"),
output=result.get("message"),
confidence=0.85
)
def run(self, message: str) -> Dict:
"""主流程"""
task = self.classify_task(message)
result = self.dispatch(task)
# 如果置信度低或者需要人工,转 escalate
if result.confidence < 0.7 or task.task_type == "escalate":
return self._escalate(task)
return {
"task_id": task.task_id,
"type": task.task_type,
"result": result.output,
"agent": result.agent_id
}
这个星型架构的优点 是:简单清晰,容易实现,每个子Agent职责单一,专注度高。缺点是:Router Agent成为了单点,一旦Router判断失误,整个链路都会出错。
模式二:链式协作(Chain)
每个Agent处理完自己的部分后,把结果交给下一个Agent,形成一条处理链。
User → Agent1 → Agent2 → Agent3 → User
↓ ↓ ↓
处理1 处理2 处理3
这种模式适合有严格顺序依赖的工作流,比如:
- 订单处理:验证 → 查库存 → 支付 → 发货 → 物流
- 报销流程:拍照 → OCR识别 → 规则校验 → 审批 → 财务打款
python
from typing import Callable, List, Any, Dict
import asyncio
class ChainAgent:
"""链式协作Agent"""
def __init__(self):
self.steps: List[Dict[str, Any]] = [] # [{"agent": agent1, "name": "validator"}, ...]
def add_step(self, agent: Any, name: str, required: bool = True):
"""添加处理步骤"""
self.steps.append({
"agent": agent,
"name": name,
"required": required
})
async def run(self, initial_input: Dict) -> Dict:
"""执行链路"""
context = {"input": initial_input, "steps_output": [], "errors": []}
for i, step in enumerate(self.steps):
try:
step_input = self._prepare_step_input(context, step)
step_output = await self._execute_step(step["agent"], step_input)
context["steps_output"].append({
"step_name": step["name"],
"output": step_output,
"success": True
})
# 检查是否需要中断链路
if step_output.get("interrupt"):
return {
"status": "interrupted",
"step": i,
"reason": step_output.get("interrupt_reason"),
"context": context
}
except Exception as e:
if step["required"]:
return {
"status": "failed",
"step": i,
"error": str(e),
"context": context
}
else:
context["errors"].append({
"step": i,
"error": str(e)
})
continue
return {
"status": "completed",
"context": context,
"final_output": self._prepare_final_output(context)
}
def _prepare_step_input(self, context: Dict, step: Dict) -> Dict:
"""准备当前步骤的输入"""
# 从 context 中提取前面步骤的输出,组装成当前步骤需要的格式
pass
def _execute_step(self, agent: Any, step_input: Dict) -> Dict:
"""执行单个步骤"""
# 实际执行逻辑
pass
链式协作的核心挑战是上下文传递。每个步骤的输出要能被下一个步骤理解,这需要统一的数据格式约定。
模式三:层级协作(Hierarchical)
对于更复杂的场景,可以设计多层级Agent架构。顶层Agent负责全局规划,中层Agent负责协调,下层Agent负责具体执行。
┌─────────────┐
│ Planner │ ← 全局规划、任务分解
│ Agent │
└──────┬──────┘
│
┌───────────┼───────────┐
│ │ │
┌───▼───┐ ┌───▼───┐ ┌───▼───┐
│Coord.1 │ │Coord.2│ │Coord.3│ ← 协调层
│Agent │ │Agent │ │Agent │
└───┬───┘ └───┬───┘ └───┬───┘
│ │ │
┌───┴───┐ ┌───┴───┐ ┌───┴───┐
│ Exec1 │ │ Exec2 │ │ Exec3 │ ← 执行层
│Agents │ │Agents │ │Agents │
└───────┘ └───────┘ └───────┘
这是一个实际案例的简化版本:
python
class HierarchicalAgent:
"""层级协作Agent"""
def __init__(self):
# 规划层
self.planner = self._create_planner()
# 协调层
self.coordinators = {
"research": self._create_coordinator("research"),
"analysis": self._create_coordinator("analysis"),
"writing": self._create_coordinator("writing"),
"review": self._create_coordinator("review")
}
# 执行层
self.executors = {
"search": self._create_executor("search"),
"scrape": self._create_executor("scrape"),
"llm_write": self._create_executor("llm_write"),
"check": self._create_executor("check")
}
async def run(self, task_description: str) -> Dict:
"""处理复杂任务的完整流程"""
# Step 1: 规划层分析任务,确定需要哪些步骤
plan = await self.planner.create_plan(task_description)
# Step 2: 规划层创建任务队列,分组给协调层
task_groups = await self.planner.distribute_tasks(plan)
# Step 3: 各协调层并行调度执行层
group_results = await asyncio.gather(*[
self._execute_group(group, self.coordinators[group["type"]])
for group in task_groups
])
# Step 4: 协调层汇总结果
consolidated = await self._consolidate_results(group_results)
# Step 5: 审查层质量检查
if consolidated.needs_review:
review_result = await self.coordinators["review"].review(consolidated)
if review_result.needs_revision:
# 打回重做逻辑
pass
return {
"status": "completed",
"output": consolidated.output,
"metadata": {
"total_steps": plan.total_steps,
"parallel_groups": len(task_groups),
"review_passed": True
}
}
这种架构的优势在于并行能力最大化。不同组的任务可以同时执行,只有存在依赖关系的任务才需要串行。
它的挑战是状态同步 和结果汇总的复杂度更高。当一个高层任务中途需要调整计划时,如何通知下层Agent撤销正在执行的任务,这是一个需要仔细设计的点。
Agent之间的通信协议设计
无论采用哪种Multi-Agent架构,Agent之间的通信协议都是核心。这里有几种常见的模式:
共享上下文模式
所有Agent共享一个中央存储(内存、数据库、消息队列),通过读写共享数据来协作。
python
import redis
import json
from datetime import timedelta
class SharedContext:
"""共享上下文通信"""
def __init__(self, redis_client: redis.Redis):
self.redis = redis_client
def write(self, key: str, value: Dict, ttl: int = 3600):
"""写入共享数据"""
self.redis.setex(
f"agent:context:{key}",
timedelta(seconds=ttl),
json.dumps(value)
)
def read(self, key: str) -> Optional[Dict]:
"""读取共享数据"""
data = self.redis.get(f"agent:context:{key}")
return json.loads(data) if data else None
def update(self, key: str, updates: Dict):
"""原子更新"""
# 使用 Lua 脚本保证原子性
lua_script = """
local current = redis.call('GET', KEYS[1])
if current then
local obj = cjson.decode(current)
local updates = cjson.decode(ARGV[1])
for k, v in pairs(updates) do
obj[k] = v
end
redis.call('SETEX', KEYS[1], ARGV[2], cjson.encode(obj))
return cjson.encode(obj)
end
return nil
"""
# 执行脚本...
def publish(self, channel: str, message: Dict):
"""发布消息到通道"""
self.redis.publish(f"agent:channel:{channel}", json.dumps(message))
def subscribe(self, channel: str):
"""订阅通道"""
pubsub = self.redis.pubsub()
pubsub.subscribe(f"agent:channel:{channel}")
return pubsub
这种模式的好处是松耦合,Agent之间不需要直接通信,只读写共享数据即可。缺点是引入了外部依赖(Redis),并且数据一致性需要额外保障。
直接消息模式
Agent之间通过RPC或直接调用传递消息,适合层级较少、Agent数量也较少的场景。
python
from typing import Protocol, runtime_checkable
import asyncio
@runtime_checkable
class AgentProtocol(Protocol):
"""Agent协议定义"""
async def receive(self, message: Dict) -> Dict:
"""接收消息并返回结果"""
...
async def forward(self, message: Dict, dest: str) -> None:
"""转发消息给其他Agent"""
...
class MessageBus:
"""消息总线"""
def __init__(self):
self.agents: Dict[str, AgentProtocol] = {}
self.message_queue: asyncio.Queue = asyncio.Queue()
def register(self, agent_id: str, agent: AgentProtocol):
"""注册Agent"""
self.agents[agent_id] = agent
async def send(self, from_agent: str, to_agent: str, message: Dict):
"""直接发送消息"""
if to_agent not in self.agents:
raise ValueError(f"Unknown agent: {to_agent}")
target = self.agents[to_agent]
result = await target.receive(message)
# 异步通知发送方(可选)
await self._notify(from_agent, "message_sent", {
"to": to_agent,
"message_id": message.get("id")
})
return result
async def broadcast(self, from_agent: str, message: Dict):
"""广播消息"""
tasks = []
for agent_id, agent in self.agents.items():
if agent_id != from_agent:
tasks.append(agent.receive(message))
results = await asyncio.gather(*tasks, return_exceptions=True)
return results
生产环境的关键设计:状态持久化与容错
状态持久化
当一个任务需要跨越几小时甚至几天才能完成时(比如等待用户确认、处理跨部门的审批流程),Agent的运行状态必须持久化到磁盘或数据库。
python
from datetime import datetime
from enum import Enum
import json
import os
class TaskState(Enum):
PENDING = "pending"
RUNNING = "running"
WAITING_USER_INPUT = "waiting_user_input"
COMPLETED = "completed"
FAILED = "failed"
CANCELLED = "cancelled"
@dataclass
class PersistedAgentState:
"""持久化的Agent状态"""
session_id: str
task_id: str
state: TaskState
current_step: int
conversation_history: List[Dict]
session_data: Dict[str, Any]
created_at: str
updated_at: str
expires_at: str # 超过这个时间没处理则任务失败
class AgentStateStore:
"""状态持久化存储"""
def __init__(self, storage_path: str = "./agent_states"):
self.storage_path = storage_path
os.makedirs(storage_path, exist_ok=True)
def _get_file_path(self, session_id: str) -> str:
return os.path.join(self.storage_path, f"{session_id}.json")
def save(self, state: PersistedAgentState):
"""保存状态"""
file_path = self._get_file_path(state.session_id)
with open(file_path, "w", encoding="utf-8") as f:
json.dump(asdict(state), f, ensure_ascii=False, indent=2)
def load(self, session_id: str) -> Optional[PersistedAgentState]:
"""加载状态"""
file_path = self._get_file_path(session_id)
if not os.path.exists(file_path):
return None
with open(file_path, "r", encoding="utf-8") as f:
data = json.load(f)
return PersistedAgentState(**data)
def delete(self, session_id: str):
"""删除状态"""
file_path = self._get_file_path(session_id)
if os.path.exists(file_path):
os.remove(file_path)
def list_expired(self) -> List[PersistedAgentState]:
"""列出已过期的任务"""
expired = []
now = datetime.now().isoformat()
for file_name in os.listdir(self.storage_path):
if not file_name.endswith(".json"):
continue
file_path = os.path.join(self.storage_path, file_name)
with open(file_path, "r", encoding="utf-8") as f:
data = json.load(f)
state = PersistedAgentState(**data)
if state.expires_at < now and state.state not in [TaskState.COMPLETED, TaskState.CANCELLED]:
expired.append(state)
return expired
def cleanup_expired(self):
"""清理过期任务"""
for state in self.list_expired():
state.state = TaskState.FAILED
state.session_data["fail_reason"] = "task_expired"
self.save(state)
这套持久化机制解决了一个实际问题:当你的Agent系统需要处理跨天的任务时,重启服务不会丢失正在运行的任务状态。
容错机制
生产环境里,Agent系统需要处理的异常情况远比测试环境多:
python
import time
from typing import Callable, TypeVar, Optional
import asyncio
T = TypeVar('T')
class RetryPolicy:
"""重试策略"""
def __init__(
self,
max_attempts: int = 3,
base_delay: float = 1.0,
max_delay: float = 60.0,
exponential_backoff: bool = True,
jitter: bool = True
):
self.max_attempts = max_attempts
self.base_delay = base_delay
self.max_delay = max_delay
self.exponential_backoff = exponential_backoff
self.jitter = jitter
def calculate_delay(self, attempt: int) -> float:
"""计算重试延迟"""
if self.exponential_backoff:
delay = self.base_delay * (2 ** (attempt - 1))
else:
delay = self.base_delay
delay = min(delay, self.max_delay)
if self.jitter:
import random
delay = delay * (0.5 + random.random() * 0.5)
return delay
async def with_retry(
func: Callable[..., T],
*args,
retry_policy: Optional[RetryPolicy] = None,
retry_on: tuple = (Exception,),
**kwargs
) -> T:
"""带重试的函数执行"""
if retry_policy is None:
retry_policy = RetryPolicy()
last_error = None
for attempt in range(1, retry_policy.max_attempts + 1):
try:
result = func(*args, **kwargs)
if asyncio.iscoroutine(result):
result = await result
return result
except retry_on as e:
last_error = e
if attempt < retry_policy.max_attempts:
delay = retry_policy.calculate_delay(attempt)
await asyncio.sleep(delay)
else:
raise last_error
raise last_error
# 使用示例
async def robust_tool_call(tool_name: str, params: Dict) -> Dict:
"""可靠的工具调用"""
async def _call():
# 实际的API调用
result = await external_api.call(tool_name, params)
# 检查业务层面的错误(不仅仅是网络错误)
if result.get("error_code") == "RATE_LIMIT":
raise RateLimitError()
return result
try:
return await with_retry(
_call,
retry_policy=RetryPolicy(
max_attempts=5,
base_delay=2.0,
exponential_backoff=True
),
retry_on=(RateLimitError, TimeoutError, ConnectionError)
)
except Exception as e:
# 最后兜底:使用本地LLM基于已有信息生成回复
return await fallback_generation(e)
实战案例:从零构建一个客服多Agent系统
需求描述
我们的客户是一家电商SaaS公司,每天处理约2000个客服会话。之前的方案是纯规则匹配+关键词识别,准确率约65%,用户满意度不到3分(满分5分)。
他们希望用Agent架构重构这个客服系统,达到以下目标:
- 准确率提升到90%以上
- 能够处理退换货、订单查询、地址修改、优惠咨询、投诉处理等5大类型
- 复杂问题能无缝转人工
- 系统能持续学习和优化
架构设计
┌──────────────┐
│ 入口消息 │
│ (微信/APP) │
└──────┬───────┘
│
┌──────▼──────┐
│ NLU引擎 │
│ (意图识别+ │
│ 槽位提取) │
└──────┬──────┘
│
┌────────────┼────────────┐
│ │ │
┌─────▼────┐ ┌────▼────┐ ┌─────▼────┐
│ 闲聊引擎 │ │ 任务型 │ │ 投诉处理 │
│ (闲聊) │ │ Agent │ │ Agent │
└──────────┘ └────┬────┘ └─────┬────┘
│ │
┌─────┴────────────┴─────┐
│ │
┌─────▼──────────┐ ┌──────▼──────┐
│ 订单子Agent │ │ 售后子Agent│
│ (查单/修改/ │ │ (退换货/ │
│ 取消) │ │ 退款) │
└───────────────┘ └─────────────┘
核心实现
意图分类Agent:
python
INTENT_CLASSIFICATION_PROMPT = """
你是一个客服意图分类器。根据用户的最新消息,判断用户的意图。
用户历史对话:
{history}
用户最新消息:「{latest_message}」
请从以下意图中选择一个最匹配的:
1. order_query - 查订单状态、订单详情
2. order_modify - 修改订单(地址、电话、备注等)
3. order_cancel - 取消订单
4. refund_request - 申请退款
5. return_request - 申请退货
6. coupon_query - 优惠券咨询
7. complaint - 投诉
8. chitchat - 闲聊/问候
9. escalate - 需要人工介入
输出格式(只输出JSON,不要其他内容):
{{
"intent": "意图代码",
"confidence": 0.0-1.0之间的数字,
"slots": {{
"order_id": "如果提到了订单号,填在这里",
"product_name": "如果提到了商品名称,填在这里"
}},
"reasoning": "判断理由,1-2句话"
}}
"""
async def classify_intent(history: List[Dict], latest_message: str) -> Dict:
history_text = "\n".join([
f"用户:{h['user']}\n助手:{h['assistant']}"
for h in history[-5:]
])
response = await llm_client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=500,
messages=[{
"role": "user",
"content": INTENT_CLASSIFICATION_PROMPT.format(
history=history_text,
latest_message=latest_message
)
}]
)
# 解析JSON响应
result_text = response.content[0].text.strip()
# 处理可能的markdown代码块
if result_text.startswith("```"):
result_text = result_text.split("\n")[-1].split("```")[0]
return json.loads(result_text)
订单子Agent的详细实现:
python
class OrderAgent:
"""订单处理Agent"""
def __init__(self, tools: List[Dict]):
self.tools = tools
self.state_machine = OrderStateMachine()
async def handle(self, message: str, context: Dict) -> Dict:
"""主处理逻辑"""
current_state = context.get("state", "initial")
if current_state == "initial":
return await self._handle_initial(message, context)
elif current_state == "awaiting_order_id":
return await self._handle_order_id_input(message, context)
elif current_state == "order_confirmed":
return await self._handle_confirmed_order(message, context)
elif current_state == "awaiting_confirmation":
return await self._handle_user_confirmation(message, context)
else:
return await self._escalate(message, context, "unknown_state")
async def _handle_initial(self, message: str, context: Dict) -> Dict:
"""处理初始状态"""
# 尝试从消息中提取订单号
order_id = extract_order_id(message)
if order_id:
# 有订单号,查询订单状态
order_info = await self._query_order(order_id)
if order_info:
context["order_id"] = order_id
context["order_info"] = order_info
context["state"] = "order_confirmed"
return {
"response": self._format_order_status(order_info),
"next_state": "order_confirmed",
"suggested_actions": ["cancel_order", "modify_address"]
}
else:
return {
"response": "抱歉,系统没有找到这个订单。请确认订单号是否正确,或者我帮您查询其他订单?",
"next_state": "initial",
"retry_count": context.get("retry_count", 0) + 1
}
else:
# 没有订单号,引导用户输入
return {
"response": "请问您的订单号是多少?订单号是12位数字,可以在订单详情页找到。",
"next_state": "awaiting_order_id",
"collect_slot": "order_id"
}
async def _handle_order_id_input(self, message: str, context: Dict) -> Dict:
"""处理用户输入订单号"""
order_id = extract_order_id(message)
if order_id:
order_info = await self._query_order(order_id)
if order_info:
context["order_id"] = order_id
context["order_info"] = order_info
context["state"] = "order_confirmed"
return {
"response": self._format_order_status(order_info),
"next_state": "order_confirmed",
"suggested_actions": ["cancel_order", "modify_address", "contact_support"]
}
# 订单号无效或查询失败
retry_count = context.get("retry_count", 0)
if retry_count >= 2:
return await self._escalate(
message, context,
"order_lookup_failed_after_retries"
)
return {
"response": "抱歉,这个订单号似乎不存在。请确认后重新输入,或者回复「转人工」寻求帮助。",
"next_state": "awaiting_order_id",
"retry_count": retry_count + 1
}
实际运行数据
系统上线三个月后的关键指标:
| 指标 | 上线前 | 上线后 | 提升 |
|---|---|---|---|
| 意图识别准确率 | 65% | 94% | +29% |
| 用户满意度 | 3.1/5 | 4.3/5 | +39% |
| 平均处理时长 | 8.5分钟 | 4.2分钟 | -51% |
| 转人工率 | 35% | 12% | -66% |
| 7x24可用性 | 否 | 是 | - |
核心的提升来自于两点:一是意图分类的准确率提升,直接减少了错误分发导致的用户流失;二是状态机设计让多轮对话的连贯性大幅提升,用户不用每次都从头解释自己的问题。
总结与讨论
今天这篇文章,我从一个资深工程师的视角,把Agent落地中最关键的两个架构层次------单Agent状态机设计和Multi-Agent协作拓扑------讲清楚了。
核心要点就三个:
第一,单Agent不是「一个模型调用」,而是一个包含状态机、工具封装、对话历史管理的完整系统。没有这些组件,复杂场景下必崩。
第二,Multi-Agent的核心挑战不是「怎么让多个Agent一起工作」,而是「怎么定义角色边界、怎么设计通信协议、怎么解决冲突」。这些问题不解决,Agent数量越多,系统越不稳定。
第三,生产环境必须考虑状态持久化和容错机制。你的Agent系统不可能不重启、不可能不遇到网络抖动、不可能不遇到异常输入。在设计之初就把这些考虑进去,后期运维会省心很多。
留几个讨论问题给你:
- 你在落地Agent的过程中,遇到了哪些具体的架构挑战?
- 你觉得Multi-Agent之间最难处理的是什么问题------角色定义、通信协议、还是冲突解决?
- 如果让你设计一个通用的Agent框架,你会怎么组织它的核心组件?
欢迎留言交流。
参考资源
- Anthropic Claude Agent SDK 文档:https://docs.anthropic.com/
- LangChain Agent 相关实现:https://python.langchain.com/
- AutoGPT 项目架构参考:https://github.com/Significant-Gravitas/AutoGPT
- 本文提到的所有外部链接均基于公开资料整理,如有更新以官方文档为准。