Skip to content

编排框架的过度设计

一篇观点文章

本文是一篇观点文章,不是技术分析。它表达的是一个想清楚了的工程判断。

这个判断是:LLM 应用领域最流行的编排框架,解决了开发者实际上不需要解决的问题,同时带来了开发者不得不面对的复杂性。

抽象层级膨胀

以 LangChain 为代表的编排框架的核心问题是抽象层级膨胀。一个简单的 LLM 调用被包装在多层抽象之后:Prompt Template → Chain → Agent → Executor → Memory → Callback → OutputParser。

每一层抽象都有设计理由,但抽象的叠加产生了组合爆炸。当你需要调试一个行为不符合预期的 Chain 时,你需要理解:Prompt Template 如何渲染、Chain 如何传递变量、Memory 如何注入历史、OutputParser 如何解析结果、Callback 如何影响执行流程。每一层都可能是问题的来源。

对比没有框架的实现:

python
# 无框架:约 15 行代码完成同样的功能
messages = [{"role": "system", "content": system_prompt}]
messages.extend(history[-10:])  # 最近10轮历史
messages.append({"role": "user", "content": user_input})

response = client.chat.completions.create(
    model="gpt-4",
    messages=messages,
    response_format={"type": "json_object"}
)

result = json.loads(response.choices[0].message.content)

这段代码没有抽象。它做什么、按什么顺序做、每一步的输入输出是什么——全部可见。当行为不符预期时,调试点是明确的。当需要修改行为时,修改点是直接的。

概念过载

编排框架引入了大量领域特有的概念。Chain、Agent、Tool、Memory、VectorStore、Retriever、Loader、Splitter、Embedding、Callback、OutputParser——每个概念有自己的接口、配置和行为。

这些概念并非都是必要的。很多概念只是对标准编程操作的重新命名:

  • Chain 是函数组合(function composition)。
  • Memory 是变量存储(variable storage)。
  • Callback 是事件钩子(event hook)。
  • Loader 是文件读取(file I/O)。
  • Splitter 是文本切分(string splitting)。

用框架特有的术语重新命名这些操作,让人误以为"LLM 编程需要特殊工具"。实际上,Python 的函数、列表、字典和标准库已经提供了这些能力。框架只是把这些已有能力包了一层统一接口。

统一接口有价值——当你需要快速切换不同的实现(不同的向量数据库、不同的 embedding 模型)时。但要掂量一下代价:学习框架概念的时间、调试框架行为的难度、以及随框架版本演进的维护负担。

不必要的间接性

框架的核心卖点之一是"可组合性"——将小组件组合成复杂的流程。但在实践中,LLM 应用的流程通常是简单的线性链条或浅层的条件分支。它们不需要 DAG 级别的组合能力。

当流程是"先检索、再生成"时,一个函数调用另一个函数就够了。当流程是"根据意图路由到不同的处理器"时,一个 if-else 就够了。为这些简单的流程引入 Chain 的抽象和 LCEL(LangChain Expression Language)的语法,纯属自找麻烦。

真正需要复杂编排的场景——分布式执行、持久化状态机、可视化工作流——应该使用为这些场景设计的通用工具(Temporal、Prefect、Airflow)。通用编排工具经过了更多生产环境的考验,有更活跃的社区和更稳定的 API。

框架的正当使用场景

以上批评不意味着框架毫无价值。框架在以下场景中有正当的使用理由:

快速原型。 在探索阶段,框架提供了快速实验的脚手架。用 LangChain 在半小时内搭建一个 RAG 原型,比从头编写快得多。关键是:原型验证后,生产实现应该考虑是否需要保留框架。

标准化集成。 当团队需要在多个项目中重用相同的组件配置(embedding 模型、向量数据库、文档加载器),框架的统一接口减少了重复代码。

新手入门。 对 LLM 应用开发不熟悉的开发者,框架的入门门槛比从零摸索低。

替代方案

不用框架,用什么?

直接使用 LLM API 客户端。 OpenAI 的 Python SDK、Anthropic 的 SDK——这些官方客户端提供了干净的接口,不包含不必要的抽象。

Pydantic + 标准库。 第四章讨论的 Pydantic 模型提供了结构化输出的定义和验证。Python 的 asyncio 提供了并发。json 模块提供了序列化。这些工具的组合覆盖了绝大多数 LLM 应用的编排需求。

只引入你需要的组件。 如果你只需要向量检索,引入一个向量数据库客户端就够了。如果你只需要文本切分,写一个 50 行的切分函数就够了。

能用的方案里,最简单的就是最好的。这是软件工程的基本原则,LLM 应用也不例外。