LangChain、LangGraph、CrewAI、PydanticAI——AI 编排框架从不缺选择。我全部评估后决定自研。不是 NIH 心态,而是当你需要按失败模式选变异策略、按阶段路由不同模型和温度、在轨迹分析的基础上自适应进化时,通用框架给你的抽象层反而变成了需要绕过的障碍。

框架多到让你不好意思自己写

2024-2025 年的 AI 编排生态,框架密度大概是整个软件工程史上最高的。LangChain 有最大的社区,LangGraph 提供图状态机,CrewAI 做多智能体协作,PydanticAI 把结构化输出做到了极致。

一个理性的工程决策应该是:评估这些框架,选一个最接近需求的,在它的基础上扩展。自己从零写编排引擎,在 2025 年看起来像是重复造轮子。

但我的场景有一个关键特征:编排的目标不是"完成一个任务",而是"通过迭代搜索找到最优解"。AI 不是在执行一条预设的流程链,而是在一个搜索空间里做进化优化——生成代码、执行、评估、根据评估结果决定下一步是精炼、探索、重组还是简化。

这个区别决定了一切。

问题的结构

把"AI 编排"拆开看,它需要回答四个层面的问题:

第一层:状态管理。当前在哪个阶段?可以去哪个阶段?什么情况下应该回退?

第二层:模型路由。不同阶段用什么模型?什么温度?如果模型调用失败,fallback 到哪里?

第三层:迭代策略。连续 3 轮指标下降,是继续精炼还是换方向?什么时候尝试重组之前成功的片段?什么时候简化复杂度?

第四层:安全机制。指标断崖式下跌怎么办?生成了重复代码怎么办?跑了太多轮还没收敛怎么办?

通用框架通常只覆盖第一层和第二层的基础部分。第三层和第四层——进化策略、灾难回滚、反重复检测——这些和业务场景深度耦合,没有框架会替你做。

我评估过的框架

LangChain

LangChain 的核心价值是生态集成——它为几乎所有 LLM 提供商、向量数据库、文档加载器提供了统一接口。如果你的任务是"从文档库里检索信息然后生成回答"(RAG),LangChain 是最合理的选择。

它在我的场景下的问题:抽象层太多。一次 LLM 调用经过 Chain → LLM → Prompt Template → Output Parser 四层抽象。当我需要在 generate 阶段用 deepseek-reasoner(温度 0.8)而 fix 阶段用 deepseek-chat(温度 0.1)时,我需要绕过 LangChain 的 LLM 抽象层来注入阶段感知的路由逻辑。绕过一个框架的抽象层,比不用这个框架更复杂。

另一个实际问题:debug。当 AI 生成的因子表达式执行失败、评估指标为空、触发了回滚机制时,我需要知道是哪一步出了问题。在 LangChain 的调用链里,异常会被层层包装,真正的错误信息藏在三层 traceback 之下。在我自己的引擎里,每个状态转换都是显式的 if/elif,异常发生在哪里一目了然。

LangGraph

LangGraph 比 LangChain 更接近我的需求——它用图结构管理状态转换,支持循环和条件分支。如果把我的 FSM 画成图,LangGraph 理论上能表达。

但 LangGraph 的图状态机和我需要的有本质区别。LangGraph 的节点是"执行函数",边是"转换条件"。状态管理和执行逻辑混在一起——一个节点既定义了"我在哪个阶段"又定义了"这个阶段做什么"。

我的设计刻意把两者分开。状态机是纯逻辑的——它只回答"从 A 能不能到 B",不做任何 I/O。一个 frozenset 字典,7 行代码:

INIT      → {PLAN, FAIL}
PLAN      → {GENERATE_CODE, FAIL}
GENERATE  → {EXECUTE, FAIL}
EXECUTE   → {EVALUATE, ITERATE, FAIL}
EVALUATE  → {ITERATE, FINISH, FAIL}
ITERATE   → {PLAN, GENERATE_CODE, EXECUTE, FAIL}
FINISH    → {}
FAIL      → {}

这个状态机没有副作用、没有依赖、可以独立测试。编排循环在外层调用 can_transition() 做合法性验证,然后自己负责执行。状态逻辑和执行逻辑解耦,意味着我可以替换执行逻辑而不动状态定义,或者在测试里用 mock 执行来验证状态流转。

LangGraph 的设计没法做到这种分离。它的图本身就是执行流,不是纯状态定义。对于简单的线性流程这无所谓,但对于一个需要频繁回退、跳转、回滚的迭代循环,纯状态机更清晰。

CrewAI

CrewAI 是多智能体协作框架——多个 Agent 各有角色,通过消息传递协作完成任务。

场景不匹配。我的系统是单 Agent 多阶段,不是多 Agent。在系列第一篇已经详细讨论过为什么不用多智能体架构——核心原因是单 Agent + FSM 的状态转换是确定性的,可回溯可审计,而多 Agent 的消息传递是不确定的,无法保证行为链的可重现性。

PydanticAI

PydanticAI 把结构化输出做到了极致——通过 Pydantic 模型定义 LLM 的输出格式,自动校验和重试。

它在输出校验这个单一维度上确实比我自研的方案更优雅。但它不覆盖我需要的其他维度:按阶段路由模型、进化策略选择、失败模式检测。如果只为了结构化输出引入 PydanticAI,我的模型路由、变异引擎、轨迹分析全部要重写以适配它的接口。一个框架解决了 10% 的问题,但要求 60% 的系统重构来适配,这个交易不值。

自研引擎的三个核心设计

设计一:阶段感知的多模型路由

不同的编排阶段对 LLM 的要求不同。规划阶段需要高创造性(高温度),代码生成阶段需要多样性(中高温度),修复阶段需要精确性(低温度)。

我的路由器用三级 fallback 策略:

查找顺序:task_type:phase → phase → default

例:factor_calc:generate → generate → 兜底配置

实际配置:
  plan:     deepseek-chat,     温度 0.7(创造性规划)
  generate: deepseek-reasoner, 温度 0.8(多样性生成)
  iterate:  deepseek-reasoner, 温度 0.9(探索性迭代)
  fix:      deepseek-chat,     温度 0.1(精确修复)

三级 fallback 意味着:默认情况下所有任务类型共享同一套阶段路由,但可以为特定任务类型(比如因子研究 vs 策略回测)配置不同的模型和参数,不需要改代码。

没有一个主流框架原生支持这种粒度的路由。LangChain 有 Router Chain,但它的路由是基于输入内容的语义匹配,不是基于编排阶段的确定性路由。

设计二:自适应进化策略

这是整个引擎的核心竞争力,也是通用框架完全不覆盖的领域。

编排循环的迭代不是简单地"让 AI 改进上一轮的代码"。它是一个有方向性的搜索过程,方向由四种进化策略决定:

EXPLOIT(精炼):当前分数已经不错,在当前方向上微调。适用于局部优化。

EXPLORE(探索):当前方向走不通,需要完全换一个思路。适用于跳出局部最优。

RECOMBINE(重组):把历史迭代中成功的片段组合在一起。适用于收敛后期。

SIMPLIFY(简化):复杂度过高导致过拟合,减少嵌套操作保留核心逻辑。

策略选择不是随机的,是基于轨迹分析的自适应决策:

高分 + 低多样性 → EXPLOIT(继续精炼,别乱跳)
连续下降 2+ 轮 + 足够迭代 → RECOMBINE(从历史成功中恢复)
低分 + 早期迭代 → EXPLORE(方向不对,换思路)
高多样性 + 低收敛 → EXPLORE(散了,需要聚焦)
中等分 + 高稳定性 → EXPLOIT(稳定推进)
分差大 + 有迭代余量 → RECOMBINE(取长补短)

轨迹分析基于四个维度:探索多样性(分数方差)、收敛速率(分数的线性回归斜率)、稳定性(近几轮分数的一致性)、语义多样性(基于 AST 的代码相似度)。

每种策略还对应不同的变异类型。EXPLOIT 策略下,变异引擎会根据失败模式选择具体的变异操作:

信号方向错误(IC < -0.01)→ 翻转信号方向
预测力为零(|IC| < 0.01)→ 更换算子
不一致(|ICIR| < 0.3)→ 加正则化
过复杂 → 简化结构

这套系统的本质是把进化算法的思想(选择、变异、交叉、适应度评估)嵌入到了 AI 编排循环里。AI 负责生成和变异,引擎负责选择方向和评估适应度。

设计三:灾难回滚和安全边界

迭代搜索有一个风险:AI 可能在一轮迭代中严重退化,产出比之前差得多的结果。

引擎用三层安全机制应对:

第一层:最优版本追踪。每轮评估后,如果当前分数超过历史最优,更新最优记录(版本号、分数、代码、指标)。如果分数下降,递增连续下降计数器。

第二层:灾难回滚。如果当前分数比历史最优下降超过 30%,判定为灾难性衰退,自动回滚到最优版本的代码,重置下降计数器,然后从最优版本重新开始迭代。这保证了即使 AI 走错方向,系统不会丢失已经找到的最优解。

第三层:早停。连续 3 轮下降且已超过 5 轮迭代且最优分数 > 0.15,判定为收敛,停止迭代,返回最优版本的结果。这避免了在已经收敛的区域浪费计算资源。

另外,每次代码生成后都做反重复检测——先文本规范化精确匹配,再用 AST 语义相似度检测结构性重复(阈值 0.85)。如果检测到重复,用显式的提示要求 AI 生成结构性不同的代码。

关键判断点

做出自研决策的转折点是一个认知:通用框架解决的是"如何调用 LLM"的问题,我需要解决的是"如何搜索最优解"的问题。

这两个问题的复杂度重心不同。“如何调用 LLM"的复杂度在连接层——适配不同 API、处理重试和限流、管理上下文窗口。框架在这里有明显优势。

“如何搜索最优解"的复杂度在决策层——根据历史轨迹选策略、根据失败模式选变异、根据分数趋势决定回退还是继续。这些决策逻辑和业务场景深度耦合,不可能被通用框架抽象掉。

如果我用了 LangGraph,我会得到一个图状态机来管理 INIT → PLAN → GENERATE → EXECUTE → EVALUATE → ITERATE 的流转。但进化策略、变异引擎、灾难回滚、轨迹分析——这些全部要自己写,而且要绕过 LangGraph 的节点抽象来注入。最终代码量可能比纯自研还多,因为多了一层适配。

这和操作系统设计里的一个经典取舍一样:微内核 vs 宏内核。微内核(框架)提供最小的核心机制,功能通过插件扩展。宏内核(自研)把核心功能全放在内核里。当你的"插件"比"核心"更复杂时,微内核的架构优势就消失了——你只是在内核和插件之间增加了通信开销。

实施效果

自研引擎跑了几个月,处理了上百个研究任务,每个任务 5-20 轮迭代。进化策略的自适应选择让因子研究的收敛速度比固定策略快了约 30%——EXPLOIT 在高分区域精炼细节,EXPLORE 在低分区域跳出局部最优,RECOMBINE 在停滞时从历史成功中恢复。

灾难回滚触发了十几次,每次都成功保住了历史最优解。没有灾难回滚的话,这些任务会在 AI 的一次失误迭代后丢失之前所有的优化成果。

整个引擎的代码量约 2000 行(状态机 50 行、编排循环 500 行、进化策略 200 行、变异引擎 200 行、模型路由 150 行、评估器 300 行、其他辅助 600 行)。如果用 LangGraph + 自定义插件,LangGraph 本身的抽象层代码 + 适配代码估计也要 1500 行以上,而且 debug 更困难。

Protocol-based DI 让整个引擎可测试可替换。Kernel 客户端是一个 Protocol 接口,测试时注入 Mock,生产时注入 HTTP 客户端。换 LLM 提供商只需要改路由配置,引擎代码零改动。

这个决策教会我什么

我从这次实践中提炼了一条设计原则:

当你的核心复杂度在决策逻辑而不是连接层时,通用框架给你的抽象层是成本而不是收益。自研的代价是初始投入更高,收益是你拥有决策逻辑的完全控制权——不需要在框架的抽象层和你的业务逻辑之间做翻译。

这条原则不是在说"永远不要用框架”。如果你的核心复杂度在连接层——对接 10 种 LLM API、5 种向量数据库、3 种文档格式——那 LangChain 的生态集成是真正的价值,自研是浪费时间。

判断标准是:你花更多时间在"调用 LLM"上,还是在"根据结果做决策"上?如果是前者,用框架。如果是后者,框架帮不了你太多——它只会在你和你的决策逻辑之间加一层间接。

更一般地说,框架的价值 = 它抽象掉的复杂度 / 它引入的适配复杂度。当分子大于分母时用框架。当分母大于分子时——就像我的场景——自研是更经济的选择。


系列第七篇。上一篇:研究不可复现,就不是研究。第五篇:让 AI 写的代码跑起来,但别让它跑出去。第四篇:终局思维。第三篇:AI as Operator, Kernel as Law。第二篇:MCP 的问题不在协议层。第一篇:为什么我没有用多智能体架构