1. 为什么需要重构一个生产级Agent架构?
去年我在金融行业落地AI Agent时,遇到一个典型困境:基于LangChain搭建的原型在演示环境跑得风生水起,但一到生产环境就暴露出三个致命问题——API响应超时、任务状态丢失、分布式扩展困难。这促使我重新审视2K Star的参考项目架构,发现其设计存在几个关键缺陷:
- HTTP层与业务逻辑强耦合:原始项目将LangGraph的工作流直接暴露在FastAPI路由中,导致单个长耗时任务阻塞整个事件循环
- 状态管理缺失:Agent执行过程中的中间状态仅保存在内存,服务器重启即丢失
- 扩展性不足:无法动态增减工作节点,且缺乏任务优先级调度机制
经过三个月重构,新架构在日均处理20万+任务的证券行业QA系统中实现了:
- 平均响应时间从8.3s降至1.2s
- 任务恢复成功率从0%提升至100%
- 支持50+节点的弹性伸缩
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心架构设计:分层解耦与持久化
2.1 服务分层模型
采用五层隔离设计(从上至下):
code复制HTTP接入层 → 消息队列层 → 工作流引擎层 → Agent执行层 → 持久化层
每层的关键技术选型:
- HTTP层:FastAPI + Uvicorn(异步模式)
- 消息队列:RabbitMQ with Celery(替代原始项目的直接内存队列)
- 工作流引擎:LangGraph + 自定义Checkpoint机制
- 持久化:Redis(缓存)+ PostgreSQL(结构化存储)
特别提示:Celery配置必须设置
task_acks_late=True,否则在K8s滚动更新时会导致任务中断
2.2 状态持久化方案
原始项目最大的架构缺陷是将LangGraph的State保存在内存中。我的解决方案是:
python复制class CustomStateManager(StateGraph):
def __init__(self):
self.redis = RedisCluster()
def update_state(self, state: Dict) -> str:
task_id = str(uuid4())
self.redis.hset(
f"agent:{task_id}",
mapping={
"state": json.dumps(state),
"timestamp": int(time.time())
}
)
return task_id
配合FastAPI的中间件实现自动恢复:
python复制@app.middleware("http")
async def restore_state(request: Request, call_next):
if task_id := request.headers.get("X-Task-ID"):
if state := redis.get(f"agent:{task_id}"):
request.state.agent_context = json.loads(state)
return await call_next(request)
3. 生产环境关键优化点
3.1 工作流断点续传
LangGraph原生不支持中断恢复,通过以下改造实现:
- 在每个Node执行后强制快照状态
- 使用PostgreSQL记录DAG执行路径
- 重启时从最后一个成功节点恢复
mermaid复制graph TD
A[收到新任务] --> B{是否存在task_id?}
B -->|否| C[初始化新工作流]
B -->|是| D[从数据库加载状态]
D --> E[定位中断节点]
E --> F[重新实例化该节点]
F --> G[继续后续流程]
3.2 资源隔离策略
为避免多个Agent任务竞争资源,采用三级隔离:
- 进程级:Celery worker按任务类型独立部署
- 内存级:为每个Agent分配私有Redis数据库
- 计算级:通过Cgroups限制每个任务的CPU用量
实测表明,该方案使系统在80%负载下仍能保证SLA。
4. 典型问题排查实录
4.1 内存泄漏排查案例
上线首周发现Worker内存持续增长,通过以下步骤定位:
- 用
mprof绘制内存增长曲线 - 在增长拐点处dump内存快照
- 使用objgraph定位到LangGraph的
Node对象未释放 - 根源:自定义节点中误用
functools.lru_cache
修复方案:
python复制# 错误示范
@lru_cache
def tool_loader(name: str):
return load_tool(name)
# 正确做法
_tool_cache = {}
def get_tool(name: str):
if name not in _tool_cache:
_tool_cache[name] = load_tool(name)
return _tool_cache[name]
4.2 分布式死锁问题
当多个Agent需要互斥访问资源时,原始项目的简单锁方案会导致分布式死锁。改进方案:
python复制def acquire_lock(resource_id: str, timeout=30):
"""使用Redis红锁算法实现分布式锁"""
lock = RedLock(f"resource:{resource_id}", [
{"host": "redis-node1"},
{"host": "redis-node2"},
{"host": "redis-node3"}
])
return lock.acquire(timeout=timeout)
5. 性能压测数据对比
使用Locust模拟100并发下的表现:
| 指标 | 原始架构 | 重构后 |
|---|---|---|
| 吞吐量(QPS) | 12 | 83 |
| 99线(ms) | 8900 | 2100 |
| 错误率 | 23% | 0.1% |
| 内存占用(MB) | 420 | 180 |
关键优化手段:
- 将LLM调用从同步改为异步流式
- 预编译所有Pydantic响应模型
- 对工作流DAG进行拓扑排序缓存
6. 部署架构建议
对于不同规模场景的部署方案:
中小规模(日任务<1万)
code复制Docker Compose方案:
- 1个FastAPI实例(2核4G)
- 1个Celery Worker(4核8G)
- Redis哨兵模式(3节点)
大规模部署(日任务>50万)
code复制K8s方案:
- FastAPI:HPA自动扩缩(CPU>60%触发)
- Celery:分片部署(工具类/计算类分离)
- Redis:Cluster模式(16分片)
- PostgreSQL:读写分离+连接池
我在阿里云ACK上的具体配置:
yaml复制# Celery HPA配置示例
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: celery-worker
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: celery-worker
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
7. 开发调试技巧
7.1 可视化工作流
通过添加FastAPI路由暴露LangGraph的DAG结构:
python复制@app.get("/flow/{flow_id}/graph")
def show_graph(flow_id: str):
flow = load_flow(flow_id) # 自定义加载逻辑
return Response(
generate_mermaid(flow), # 转换为Mermaid语法
media_type="text/plain"
)
7.2 单元测试策略
针对Agent的特殊测试方案:
- Mock LLM:使用
unittest.mock替换ChatCompletion - 状态快照测试:保存并比对各节点输入输出
- 混沌测试:随机kill worker进程测试恢复能力
示例测试用例:
python复制def test_loan_approval_flow():
with mock.patch('llm.chat', return_value="APPROVED"):
state = run_flow("loan_flow", {"amount": 5000})
assert state["final_decision"] == "approved"
assert state["risk_score"] < 0.3
8. 典型业务场景实现
以金融风控审批为例的完整实现:
- 定义状态Schema:
python复制class LoanState(BaseModel):
application_id: str
credit_score: int = Field(default=0)
risk_flags: List[str] = Field(default_factory=list)
final_decision: Literal["pending", "approved", "rejected"] = "pending"
- 构建审批工作流:
python复制builder = StateGraph(LoanState)
builder.add_node("credit_check", credit_check_node)
builder.add_node("fraud_detect", fraud_detection_node)
builder.add_node("final_review", final_review_node)
builder.set_entry_point("credit_check")
builder.add_edge("credit_check", "fraud_detect")
builder.add_conditional_edges(
"fraud_detect",
lambda x: "reject" if x.risk_score > 0.7 else "continue",
{"continue": "final_review", "reject": END}
)
- 暴露为API:
python复制@app.post("/loan/apply")
async def apply_loan(data: LoanApplication):
task_id = str(uuid4())
process_loan.delay(task_id, data.dict())
return {"task_id": task_id}
