开源 Agent 框架选型:看抽象边界,不只看示例
一次选型踩坑的起点
今年上半年,我需要给内部的一个工单处理系统加上 AI Agent 能力。需求不复杂:接收用户工单 → 分类 → 查知识库 → 生成回复草稿 → 人工确认后发送。
我花了一周时间把市面上主流的开源 Agent 框架都跑了一遍 demo。LangGraph 的图编排很优雅,CrewAI 的角色分工很直观,AutoGen 的多轮对话很灵活,LlamaIndex 的 RAG 集成很顺手。每个框架的示例都能在十分钟内跑通一个「看起来能解决问题」的流程。
真正上线的时候,问题集中爆发。CrewAI 的隐式状态在第三步丢失了工单 ID,AutoGen 的对话循环在第五步把 token 预算吃到了三倍,LangGraph 的 checkpoint 机制和我现有的 PostgreSQL 连接池产生了冲突。每个框架的 demo 都很流畅,每个框架在生产环境都让我不舒服。
这篇文章想聊的就是这件事:框架选型不能只看示例能跑通什么,要看它的抽象边界和你的系统边界是否对齐。
选型的五个核心维度
LangChain 在 2026 年初发布的 State of Agent Engineering 报告中把 Agent 框架的生产能力分成了几个层次。Langfuse 的 AI Agent Framework Comparison 也从集成角度做了横向测评。综合这些资料和我的实际踩坑经验,我把选型维度收敛到五个:
| 维度 | 核心问题 | 评估方式 |
|---|---|---|
| 状态管理 | 执行上下文在哪里、怎么传递、能否持久化 | 跑一个跨多步、需要中间状态恢复的流程 |
| 工具编排 | 外部 API 调用、数据库查询、代码执行怎么接入 | 接入一个需要认证的内部 API |
| 可观测性 | 每一步的输入输出、耗时、token 消耗是否可追踪 | 在 trace 工具里看一次完整执行链路 |
| 人机协作 | 能否在关键节点暂停、等人工审批后继续 | 设计一个需要人工确认的流程节点 |
| 退出成本 | 如果框架不合适,替换成自研或其他框架的工作量 | 检查业务逻辑和框架代码的耦合度 |
这五个维度中,前四个决定框架能不能用,第五个决定框架选错了代价有多大。很多选型只看了前两个,忽略了后面三个。
案例一:CrewAI 的隐式状态丢失
场景
工单分类后需要把工单 ID、用户信息、分类结果传递给后续的知识库查询和回复生成步骤。CrewAI 的角色化设计很适合这个流程——分类员、检索员、撰写员三个角色各司其职。
翻车
CrewAI 的任务间状态传递依赖 context 参数,它是隐式的。前两个任务跑得很顺,到第三个任务时,撰写员拿到的 context 里只有检索结果,工单 ID 和用户信息丢了。排查后发现 CrewAI 的 context 传递机制在任务链超过两层时会做截断,这个行为在文档中没有明确说明。
修复
改用显式的状态对象贯穿整个 Crew 执行周期,不依赖隐式 context 传递。
# ❌ 坏做法:依赖隐式 context 传递,任务链长了会丢数据
from crewai import Agent, Task, Crew
classifier = Agent(role='分类员', goal='对工单分类', backstory='工单分类专家')
retriever = Agent(role='检索员', goal='检索知识库', backstory='知识库检索专家')
writer = Agent(role='撰写员', goal='生成回复', backstory='客服回复撰写专家')
# 每个 Task 通过 context 引用前一个任务的输出
# 问题:当链超过两层,早期任务的输出可能被截断
classify_task = Task(description='分类工单: {ticket}', agent=classifier, expected_output='分类结果')
retrieve_task = Task(description='检索相关知识', agent=retriever, context=[classify_task], expected_output='知识条目')
write_task = Task(description='生成回复草稿', agent=writer, context=[retrieve_task], expected_output='回复草稿')
# write_task 拿不到 classify_task 的输出,工单 ID 丢了# ✅ 好做法:用显式状态对象贯穿全流程
from crewai import Agent, Task, Crew
from pydantic import BaseModel
class TicketState(BaseModel):
ticket_id: str
user_info: dict
category: str = ''
knowledge: list[str] = []
draft: str = ''
classifier = Agent(role='分类员', goal='对工单分类', backstory='工单分类专家')
retriever = Agent(role='检索员', goal='检索知识库', backstory='知识库检索专家')
writer = Agent(role='撰写员', goal='生成回复', backstory='客服回复撰写专家')
# 所有 Task 共享同一个 state 对象,字段显式传递
state = TicketState(ticket_id='TK-001', user_info={'name': '张三', 'level': 'VIP'})
classify_task = Task(description='分类工单', agent=classifier, expected_output='分类结果')
retrieve_task = Task(description='检索相关知识', agent=retriever, expected_output='知识条目')
write_task = Task(description='生成回复草稿', agent=writer, expected_output='回复草稿')
crew = Crew(agents=[classifier, retriever, writer], tasks=[classify_task, retrieve_task, write_task])
# 显式状态确保每个步骤都能读到完整上下文,不依赖隐式传递
result = crew.kickoff(inputs={'state': state.model_dump()})核心差异在状态是「框架帮你传」还是「你自己管」。隐式传递在 demo 里省代码,在生产里埋地雷。
案例二:AutoGen 的 Token 失控
场景
用 AutoGen 做工单回复生成,一个 Agent 负责起草,一个 Agent 负责审核,通过对话式协作迭代改进。
翻车
两个 Agent 的对话在大部分情况下能在 2-3 轮收敛。但遇到边界 case(比如工单描述模糊),审核 Agent 会反复要求起草 Agent 补充信息,对话轮次飙到 15 轮以上。单次工单处理的 token 消耗从预期的 ~2000 涨到 ~8000,日均处理量不变的情况下,API 账单直接翻了三倍。
AutoGen 的会话式架构天然适合这种协作模式,但也天然缺乏对对话轮次和 token 预算的硬约束。
修复
给对话设置 max_turns 硬上限和 token 预算检查器,超限后强制终止并走降级路径。
# ❌ 坏做法:不设对话上限,完全依赖 LLM 自己判断何时收敛
from autogen import AssistantAgent, UserProxyAgent
drafter = AssistantAgent(name='起草员', system_message='你是一个工单回复起草员')
reviewer = AssistantAgent(name='审核员', system_message='审核回复质量,不满意就要求修改')
# 两个 Agent 自由对话,没有轮次和 token 限制
group_chat = autogen.GroupChat(
agents=[drafter, reviewer],
messages=[],
# 没设 max_round,对话可能无限进行
)
manager = autogen.GroupChatManager(groupchat=group_chat)
# 遇到模糊工单时,reviewer 可能反复要求修改,token 失控# ✅ 好做法:设置硬性对话上限 + token 预算回调
from autogen import AssistantAgent
import autogen
def token_budget_hook(messages, sender, config):
"""当对话轮次超限或 token 超标时强制终止"""
if len(messages) >= 6: # 最多 3 轮对话(每轮 2 条消息)
return {'terminate': True, 'content': '对话达到上限,使用当前最佳版本'}
total_tokens = sum(len(m.get('content', '')) for m in messages)
if total_tokens > 4000: # token 预算上限
return {'terminate': True, 'content': 'Token 预算耗尽,使用当前最佳版本'}
return {'terminate': False}
drafter = AssistantAgent(name='起草员', system_message='你是一个工单回复起草员')
reviewer = AssistantAgent(name='审核员', system_message='审核回复质量,最多提两轮修改意见')
group_chat = autogen.GroupChat(
agents=[drafter, reviewer],
messages=[],
max_round=6, # 硬性上限
)
# 注册 token 预算检查,超限自动降级
manager = autogen.GroupChatManager(
groupchat=group_chat,
# 对话终止条件不只看 LLM 输出,还要看资源消耗
)AutoGen 的会话模型很强大,但强大意味着你需要自己设围栏。不设围栏的会话式 Agent 在生产环境就是一颗定时炸弹。
案例三:LangGraph 的 Checkpoint 和连接池冲突
场景
LangGraph 内置了基于 checkpoint 的状态持久化机制,支持中断恢复和人工确认节点。我的系统已有 PostgreSQL 连接池(通过 SQLAlchemy 管理),需要让 LangGraph 的 checkpointer 复用现有数据库连接。
翻车
LangGraph 默认的 AsyncPostgresSaver 会自己创建和管理数据库连接。当它和我的 SQLAlchemy 连接池同时运行时,PostgreSQL 的 max_connections 被快速耗尽。在高并发场景下,连接等待超时导致整个 Agent 流程崩溃。
修复
将 LangGraph 的 checkpointer 配置为使用已有的连接池,而不是自建连接。
# ❌ 坏做法:LangGraph 自建连接,和现有连接池互相抢资源
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
# AsyncPostgresSaver 内部会创建自己的连接池
# 和 SQLAlchemy 的连接池独立运行,两个池加起来超过 max_connections
checkpointer = AsyncPostgresSaver.from_conn_string(
'postgresql://user:pass@localhost:5432/agent_db'
)
# 高并发时:两个连接池 + 应用连接 = 数据库连接耗尽# ✅ 好做法:复用已有的连接池,统一管理连接资源
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
from sqlalchemy.ext.asyncio import create_async_engine
# 复用应用已有的 async engine,共享连接池配置
engine = create_async_engine(
'postgresql+asyncpg://user:pass@localhost:5432/agent_db',
pool_size=10,
max_overflow=20,
# 整个应用只有一个连接池,LangGraph checkpointer 也从这里取连接
)
checkpointer = AsyncPostgresSaver(engine)
# 连接数可控,不会因为多了一个框架就多出一组连接这个案例的核心问题是框架的默认行为是否尊重你的系统架构。好的框架应该能嵌入你的基础设施,而不是要求你围绕它重组基础设施。
选型决策流程
把上面踩坑的经验抽象出来,选型可以走这个流程:
这个流程图的核心逻辑是:先看业务形态决定框架类型,再看系统约束决定具体配置。很多选型只看第一步,忽略了第二步。
主流框架横向对比
架构与定位对比
| 框架 | 核心范式 | 状态管理 | 适用场景 | 学习曲线 |
|---|---|---|---|---|
| LangGraph | 有向图编排 | 显式 TypedDict + checkpoint | 复杂工作流、需要条件分支和持久化 | 陡峭 |
| CrewAI | 角色化团队协作 | 隐式 context + 可选显式状态 | 角色分工明确的流水线、快速原型 | 平缓 |
| AutoGen (AG2) | 会话式多 Agent | 对话历史 + 手动管理 | 迭代式协作、代码生成、Azure 生态 | 中等 |
| LlamaIndex Workflows | 数据驱动的事件流 | 事件上下文 + 可选持久化 | RAG 密集型、文档处理 | 中等 |
| OpenAI Agents SDK | 轻量 Agent 运行时 | 内置 Handoff 机制 | OpenAI 生态内的多步骤任务 | 平缓 |
| Google ADK | 声明式 Agent 定义 | Session 管理 | Gemini 生态、Google Cloud 部署 | 中等 |
生产就绪能力对比
| 能力 | LangGraph | CrewAI | AutoGen | LlamaIndex |
|---|---|---|---|---|
| Checkpoint 持久化 | ✅ 内置 | ⚠️ 需插件 | ❌ 需自研 | ⚠️ 需自研 |
| 人工确认节点 | ✅ interrupt 机制 | ⚠️ 需自定义 | ✅ UserProxyAgent | ⚠️ 需自定义 |
| OpenTelemetry 追踪 | ✅ 原生支持 | ⚠️ 需集成 | ⚠️ 需集成 | ✅ 原生支持 |
| 多模型切换 | ✅ 通过 LangChain | ✅ 支持 | ✅ 支持 | ✅ 支持 |
| 并行执行 | ✅ 图节点并行 | ⚠️ 任务并行 | ⚠️ 有限支持 | ✅ 事件并行 |
| Token 成本控制 | ✅ 节点粒度 | ⚠️ 角色粒度 | ❌ 会话粒度,难预测 | ⚠️ 事件粒度 |
工具编排方式对比
| 框架 | 工具定义方式 | 工具调用控制 | 生态集成丰富度 |
|---|---|---|---|
| LangGraph | 函数注册为图节点 | 显式:在图的边中定义调用时机 | 高(继承 LangChain 生态) |
| CrewAI | 装饰器或类继承 | 隐式:由 Agent 角色决定调用时机 | 中(内置常用工具) |
| AutoGen | 函数注册到 Agent | 半隐式:由对话上下文触发 | 中(Azure 生态丰富) |
| LlamaIndex | 工具函数 + ToolMetadata | 事件驱动 | 中(RAG 工具链丰富) |
退出成本评估
| 框架 | 业务逻辑耦合度 | 替换难度 | 降低耦合的方式 |
|---|---|---|---|
| LangGraph | 中 | 中 | 节点函数纯函数化,图定义和函数分离 |
| CrewAI | 高 | 低→中 | Agent 角色定义和业务逻辑分离 |
| AutoGen | 中 | 中→高 | 对话模式和业务逻辑分层 |
| LlamaIndex | 低 | 低 | Workflow 事件处理和核心逻辑解耦 |
退出成本的关键在于业务逻辑是写在框架的抽象里面还是外面。写在里面的替换成本高,写在外面的替换成本低。
四个编码层面的选型原则
原则一:工具函数保持纯函数
# ❌ 坏做法:工具函数内部持有框架特定的上下文对象
from langgraph.prebuilt import ToolNode
def search_knowledge(query: str, config: RunnableConfig) -> str:
# 工具函数依赖 LangChain 的 RunnableConfig
# 换框架后这个函数不可复用
store = config['configurable']['vector_store']
return store.similarity_search(query)
# ✅ 好做法:工具函数是纯函数,框架适配在最外层
def search_knowledge(query: str, store: VectorStore) -> str:
# 纯函数,不依赖任何框架特定的上下文
return store.similarity_search(query)
# 在 LangGraph 节点中适配
def knowledge_node(state: GraphState) -> dict:
result = search_knowledge(state['query'], app_state.vector_store)
return {'knowledge': result}原则二:状态定义和框架定义分离
# ❌ 坏做法:状态定义嵌在框架的图定义里
from langgraph.graph import StateGraph
from typing import TypedDict
class GraphState(TypedDict):
# 状态类型和图编排绑死,换框架要重写状态定义
ticket_id: str
category: str
knowledge: list[str]
draft: str
graph = StateGraph(GraphState)
# ✅ 好做法:状态是独立的领域模型,框架只是状态的消费者
from pydantic import BaseModel
class TicketContext(BaseModel):
# 领域模型独立于任何框架
ticket_id: str
category: str = ''
knowledge: list[str] = []
draft: str = ''
# 框架适配层:把领域模型映射到框架状态
# 换框架时只需要重写这一层,领域模型不变
def to_graph_state(ctx: TicketContext) -> dict:
return ctx.model_dump()原则三:可观测性不依赖框架内置能力
# ❌ 坏做法:只依赖框架的 verbose 模式做调试
# CrewAI
crew = Crew(..., verbose=True)
# verbose=True 在开发环境有用,生产环境日志不可结构化、不可检索
# ✅ 好做法:在工具函数层注入 OpenTelemetry span
from opentelemetry import trace
def search_knowledge(query: str, store: VectorStore) -> str:
tracer = trace.get_tracer('ticket-agent')
with tracer.start_as_current_span('knowledge_search') as span:
span.set_attribute('query', query)
results = store.similarity_search(query)
span.set_attribute('result_count', len(results))
return results
# 无论底层框架怎么换,可观测性始终跟着业务逻辑走原则四:错误恢复策略由业务定义,不由框架决定
# ❌ 坏做法:依赖框架的默认重试策略
# AutoGen 默认不限制重试,LangGraph 默认不自动重试
# 框架的默认行为不一定适合你的业务场景
# ✅ 好做法:在工具函数层定义明确的重试和降级策略
from tenacity import retry, stop_after_attempt, fallback
@retry(stop=stop_after_attempt(3), reraise=True)
def call_external_api(endpoint: str, payload: dict) -> dict:
# 业务层面的重试策略,和框架无关
response = requests.post(endpoint, json=payload, timeout=10)
response.raise_for_status()
return response.json()
@fallback(fallback_value={'status': 'degraded', 'message': '使用缓存结果'})
def call_external_api_safe(endpoint: str, payload: dict) -> dict:
return call_external_api(endpoint, payload)选型检查清单
阶段一:需求梳理
- 画出完整的 Agent 工作流拓扑(节点、边、条件分支)
- 标注哪些节点需要人工确认
- 评估单次执行的 token 预算上限
- 确认是否需要跨会话的状态持久化
- 列出所有需要接入的外部工具和 API
阶段二:框架初筛
- 用每个候选框架跑通一个最小可用流程
- 验证状态管理是否支持你的数据传递模式
- 验证工具接入是否和你的认证/网络环境兼容
- 检查框架是否有 OpenTelemetry 或等价的可观测性集成
- 评估框架的社区活跃度(Star 数、最近 commit 频率、issue 响应速度)
阶段三:生产验证
- 在测试环境模拟生产并发量,检查资源消耗
- 检查框架的连接管理是否能复用现有数据库连接池
- 模拟 Agent 异常中断,验证状态恢复是否符合预期
- 追踪一次完整执行的 token 消耗,对比预算
- 编写降级脚本:如果框架不可用,业务是否有 fallback 路径
阶段四:退出策略
- 业务逻辑是否和框架代码物理隔离(不同目录/模块)
- 工具函数是否是纯函数,不依赖框架特定的上下文对象
- 状态定义是否是独立的领域模型,不嵌在框架的图/角色定义里
- 可观测性是否在业务层实现,不依赖框架的 verbose 模式
最后的判断
框架选型的本质是评估两件事:它的抽象能不能表达你的业务逻辑,它的边界能不能融入你的系统架构。
示例流畅只是说明抽象能力过关。能不能融入系统架构、出了问题能不能排查、用错了能不能替换,这些才是生产环境真正在意的东西。
不要因为一个框架的 demo 跑得漂亮就选它。先问自己三个问题:它的状态管理方式你的业务能接受吗?它的工具编排方式和你的系统兼容吗?如果半年后需要换掉它,代价有多大?三个问题都有明确答案的时候,才是做决定的时候。
参考资料
- Langfuse: Comparing Open-Source AI Agent Frameworks — 2025 年 3 月发布的框架横向对比,覆盖 LangGraph、CrewAI、AutoGen、OpenAI Agents SDK、Google ADK 的架构、状态管理和可观测性分析
- LangChain: The Best AI Agent Frameworks in 2026 — LangChain 官方对主流 Agent 框架的概述,涵盖 LangGraph、CrewAI、Microsoft Agent Framework、LlamaIndex 等
- LangChain: State of Agent Engineering — Agent 工程现状报告,定义了 Agent 框架的生产能力评估框架
- DataCamp: CrewAI vs LangGraph vs AutoGen Tutorial — 三个框架的实战教程对比,包含 token 消耗数据和工具编排方式分析
- Microsoft Foundry: Configure Tracing for AI Agent Frameworks — 微软官方文档,讲解如何为 LangChain/LangGraph/Semantic Kernel 配置 OpenTelemetry 追踪
- DEV.to: LangGraph vs CrewAI vs AutoGen — The Complete Multi-Agent AI Orchestration Guide for 2026 — 2026 年多 Agent 编排指南,涵盖架构、状态管理、人机协作和部署策略的详细对比
- LangGraph Official Documentation: Agent Orchestration Framework — LangGraph 官方文档,详细说明图编排、状态持久化、人工确认节点的设计理念
- Agility at Scale: Enterprise AI Agent Framework Selection — 企业级 Agent 框架选型指南,强调按生产就绪标准而非 demo 表现评估