LangChain 的选择?—— 从选型困境聊起
写 Agent 这件事,框架选的往往不是功能列表,而是你是否愿意被绑定在生态里。
前阵子团队在技术选型上有些讨论。需求很具体:用 Go 写个能调内部 API 的 Agent,支持工具调用和多轮对话。有人觉得直接封一层 OpenAI SDK 就完了,没必要引入框架的复杂度。另一派觉得,等需求膨胀到 RAG、多 Agent 协作、Prompt 版本管理的时候,裸写的那层封装迟早变成一套自研的"山寨框架"。
Python 生态里 LangChain 是事实标准,但 Go 这边有两个玩家值得认真看看 —— langchaingo(LangChain 的 Go 社区移植)和字节开源的 Eino。这篇文章会把架构、代码和取舍摊开,方便判断。
LangChain 架构:不止是"调 LLM 的库"
先看全景。下面这张图是我理解的 LangChain 分层结构:
是否
底层:Core Runtime
LangChain 的核心运行时解决的是LLM 应用的 plumbing 问题——调用模型只是开始,真正的复杂度在模型周围。
Model I/O 是入口抽象层。它把 LLM、Chat Model、Embedding 统一成标准接口,换模型基本就是改一行配置的事。BaseChatModel 这个抽象的价值不在于多精巧,而在于它让上层代码和具体 provider 解耦了。OpenAI 挂了切 Claude,或者线上用 GPT-4o、本地开发用 Ollama,都只改一个参数。
Retrieval 是 LangChain 集成深度最能打的一块。从 Document Loaders(拉数据)到 Text Splitters(切块),再到 Embeddings + Vector Stores(存向量),最后 Retrievers(取回),一条 RAG 管线基本不用自己写胶水代码。LangChain 在这块的真正优势不是设计,是数量——40+ 向量数据库、几十种文档格式的 Loader,这些都是社区一个一个接出来的。
Chains 已经从早期的 LLMChain 演进了。现在推荐用 LCEL(LangChain Expression Language),通过 | 管道符把 Runnable 串起来。语法上有点像 Unix pipe,但背后会自动处理并行调用和流式传输。比如 retriever | prompt | llm | output_parser 这行代码,框架自己知道哪些步骤可以并行、哪些需要等待上游。
Agents 让 LLM 从"你说一句它回一句"变成"你说目标它想步骤"。核心是 ReAct(Reasoning + Acting)循环:模型拿到用户输入,自己判断该调用哪个 Tool,拿到结果后再判断是继续调工具还是直接回复。LangChain 提供了 create_react_agent、create_tool_calling_agent 等工厂方法,大部分场景不需要自己写 Agent 循环。但说实话,LangChain Agent 的抽象层数有点多,出了问题 debug 起来不太友好,这是它被吐槽最多的地方之一。
Callbacks 是横切面。on_llm_start、on_tool_end、on_chain_error 这些钩子贯穿整个生命周期,日志、计费、监控都挂在上面。理解 Callbacks 机制是用好 LangSmith 的前置条件。
上层:Platform 三件套
LangChain 不只是一套 Python 库,它上面的平台层才是商业化的重心。三个组件的定位不同,但底层有清晰的承接关系。
LangSmith —— 可观测性的工程化
LangSmith 的核心原理不复杂:它将自己注册为 LangChain 的 BaseCallbackHandler,拦截每一次 LLM 调用、Chain 步骤、Tool 执行,序列化为结构化的 "Run" 对象,通过 HTTP 推送到后端存储。
但有意思的不是"怎么采数据",而是数据模型本身。LangSmith 把每一次执行建模成一棵 Trace Tree:根节点是一次用户请求(比如 "帮我查一下上季度营收"),下面挂着一层层子节点——Agent 的思考步骤、Tool 调用的入参和返回值、LLM 的 token 消耗。每个节点携带 run_type(llm/chain/tool/retriever)、父子关系、起止时间戳、输入输出。这套模型的好处是:哪怕一个 Agent 跑了几十步,你也能在 UI 里一眼看出来哪一步耗时最多、哪个 Tool 返回了空结果、哪个 LLM 调用花了大把 token 却给出无关回复。
在这套 Trace 基础设施之上,LangSmith 又叠了两层:评估层(Datasets + Evaluators)和自动化层(Rules + Webhooks)。评估层让你把历史 Trace 中的输入输出对抽取成数据集,每次改 Prompt 后自动跑回归;自动化层让你定义规则——比如"Tool 调用失败率超过 10% 自动告警"——挂在生产环境的 Trace 流上实时触发。这本质上是在 Agent 领域复刻了软件工程里 CI/CD + Observability 的范式。
LangGraph —— 把 Agent 建模成状态机
LangGraph 的底层其实是一个受 Google Pregel 启发的图执行引擎。你定义一个 StateGraph,状态是一个带 Reducer 函数的字典(比如 messages 字段的 Reducer 是 append,新消息追加到列表末尾而不是覆盖)。图中的每个 Node 是一个签名为 (state) -> state_update 的函数,它不直接修改全局状态,而是返回一个部分更新字典,由框架按 Reducer 规则合并。
这种设计的好处在于并发安全:同一个 superstep 里,多个 Node 可以并行执行,各自产出一个状态更新片段,最后合并。Conditional Edge 则让路由逻辑变成纯函数——输入当前 state,输出下一个 Node 的名字。因为是纯函数,所以天然可测试。
真正拉开差距的是 Checkpointer。每次 superstep 结束,LangGraph 把完整状态序列化(默认用 SQLite,生产上换 Postgres),打一个 checkpoint。这意味着你可以随时中断一个跑了 20 步的 Agent,改一行 Prompt,从第 15 步的 checkpoint 恢复继续跑——不用从头来。更关键的是,这个机制天然支持 Human-in-the-loop:在敏感操作前设一个 interrupt point,Agent 暂停,等人点了"批准"再继续。对比传统的 Chain 模型,Chain 跑起来就是一串鞭炮——点着了只能等它响完;LangGraph 让你能在任意节点掐灭、回退、重新点火。这也是为什么我说它是整个生态里最有价值的部分——它解决的已经不是"怎么调 LLM",而是"怎么管理 LLM 驱动的业务流程"。
Deep Agents —— 元代理与自我纠错
Deep Agents 是 LangGraph 之上的一层封装,目前还在快速迭代。它的核心思路是让 Agent 自己扮演"项目经理":拿到一个模糊目标后,先规划子任务(plan),再逐个执行(execute),执行完反思结果(reflect),如果不满意就重新规划(replan)。
具体实现上,这是一条 LangGraph 构建的循环图:Plan Node 用 LLM 把目标拆成步骤列表 → Execute Node 逐个调用工具/子 Agent → Observe Node 收集结果 → Evaluate Node 用另一个 LLM 评分,判断是继续执行、重新规划还是直接输出。两个 LLM 的角色不一样——Plan/Execute 阶段用能力最强的模型(如 GPT-4o),Evaluate 阶段可以用更快更便宜的模型,因为评判"结果对不对"比"怎么规划"简单得多。
不过目前这个阶段,Deep Agents 的自我纠错能力还不够稳定。LLM 天然倾向对自己的输出过度自信,导致 Evaluate Node 经常给出虚高的评分。我自己的经验是,在 Evaluate 阶段加一个"必须指出至少一个问题"的强制约束,能把漏检率降到可接受的水平。方向是对的,但离"设个目标就能放手去睡"还有不小的距离。
聊完架构,写两段代码感受一下实际开发体验。
两种语言,两种范式
Python + LangChain:RAG 链式调用
下面是一个简单的 RAG QA 场景——把文档向量化后做语义检索,再丢给 GPT 回答:
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_chroma import Chroma
# 1. 准备向量存储(实际项目里这一步在初始化时做一次)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(docs, embeddings)
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
# 2. 用 LCEL 管道串起整个 RAG 流程
llm = ChatOpenAI(model="gpt-4o", temperature=0)
prompt = ChatPromptTemplate.from_template(
"根据以下上下文回答问题。如果上下文中没有答案,就说不知道。\n\n"
"上下文:{context}\n\n问题:{question}"
)
rag_chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| llm
)
# 3. 调用
response = rag_chain.invoke("LangChain 的 Callbacks 机制有什么用?")
print(response.content)
这段代码有意思的地方不在于它短,而在于 {context: retriever, question: RunnablePassthrough()} 这个字典——LCEL 能自动识别字典里的 Runnable 值,先并行执行 retriever 和传参,再把结果填进 prompt。你不写一行并发代码,框架替你做了。
Go + langchaingo:ReAct Agent
Go 这边用 langchaingo 实现一个带工具的 Agent。和 Python 版本不同,Go 的 Agent 代码更显式——你得自己管理 context、处理错误、组装组件:
import (
"context"
"github.com/tmc/langchaingo/agents"
"github.com/tmc/langchaingo/llms/openai"
"github.com/tmc/langchaingo/tools"
)
func main() {
ctx := context.Background()
// 1. 初始化 LLM
llm, err := openai.New(openai.WithModel("gpt-4o"))
if err != nil {
panic(err)
}
// 2. 定义工具 —— Go 的静态类型在这里反而是优势
searchTool := tools.NewGoogleSearch()
calcTool := tools.NewCalculator()
// 3. 创建 Agent
agent := agents.NewOpenAIFunctionsAgent(
llm,
[]tools.Tool{searchTool, calcTool},
agents.WithMaxIterations(5),
)
// 4. 执行 —— 框架接管 ReAct 循环
executor := agents.NewExecutor(agent)
result, err := executor.Call(ctx, map[string]any{
"input": "今天北京气温多少度,换算成华氏度",
})
if err != nil {
panic(err)
}
fmt.Println(result)
}
两种语言一对比很有意思。Python 版用 | 管道符串流程,读起来像声明式 DSL;Go 版每一步都是显式的构造和错误处理,读起来像标准的 Go 服务代码。没有谁更好——如果你团队全是 Python 开发,LCEL 的魔法感是效率;如果团队写惯了 Go,langchaingo 的显式风格反而是可控感。
正面对比:LangChain vs Eino
字节的 Eino 是 CloudWeGo 生态下的 Go 原生 LLM 编排框架。它借鉴了 LangChain 的模块设计,但做了大量 Go-idiomatic 的改造。下面是几个关键维度的对比:
| 维度 | LangChain (Python) | langchaingo (Go) | Eino (CloudWeGo) |
|---|---|---|---|
| 语言生态 | Python 原生,社区最成熟 | Go 社区移植,API 跟 Python 版对齐 | Go 原生,遵循 Go 惯例 |
| 类型安全 | 运行时 duck-typing,IDE 支持弱 | 编译期检查,但部分接口用 any |
Go 泛型,编译期强制类型安全 |
| Agent 模型 | create_react_agent() / create_tool_calling_agent() |
NewOpenAIFunctionsAgent() 等工厂 |
ADK:ChatModelAgent + DeepAgent 两种模式 |
| 编排方式 | LCEL 管道符 + LangGraph 状态图 | Chain + Agent Executor | compose.NewGraph[I,O]() 泛型图编排 |
| 流式处理 | 每组件各自实现流式接口 | 同 Python 模式 | 框架自动处理流转换,组件只需实现有意义的流范式 |
| 多 Agent | 通过 LangGraph 构建 | 手动编排 | DeepAgent 原生子 Agent 委托 |
| Interrupt/Resume | LangGraph 支持 checkpointer | 需自行实现 | 框架内置,支持人类审批暂停 |
| 学习曲线 | 中等,概念较多 | 中等,概念跟 Python 版同步 | 中等偏低,Go 开发者上手更快 |
| 生产成熟度 | 最高,大量企业验证 | 社区维护,质量看 contributor | 字节内部验证,开源时间较短 |
| License | MIT | MIT | Apache-2.0 |
说点我自己的看法。如果你团队主力是 Python,LangChain 几乎不用犹豫——生态厚度在那。但如果团队是 Go 栈,选 langchaingo 还是 Eino 就值得认真想想了:
- 选 langchaingo:你想复用 LangChain 社区的知识——教程、博客、踩坑经验。API 设计跟 Python 版对齐,Python 那边出新 pattern,Go 这边大概率有人跟进。
- 选 Eino:你重视编译期类型安全和 Go 的并发模型。Eino 的泛型图编排让你在编译期就能抓到类型不匹配的问题,这对大型项目是实打实的好处。代价是社区还小,遇到冷门问题可能找不到现成答案。
说到底,框架选型比技术更重要的考量是团队接下来 2-3 年的技术栈走向。如果你们的后端基础设施全是 Go,硬塞一个 Python LangChain 服务进去,运维成本可能比框架本身的价值还高。
框架之上:不会过时的是什么
写完架构对比和代码,有一个感受越来越清晰:具体框架是临时的,但它背后的范式会留下来。
ReAct 模式是理解 Agent 的钥匙。Reasoning + Acting 的循环不是 LangChain 发明的,它来自 Yao et al. 2022 的论文。LangChain 只是把它包装成了 create_react_agent()。你哪怕不用任何框架,用 while True: llm.think() → tool.act() → observe() 这个循环也能写出一个 Agent。理解了模式,框架就是工具;不理解模式,框架就是黑盒。
LangGraph 解决的是 Agent 的生产化问题。真实的 Agent 系统不是链式流水线——用户可能中途改主意、某个工具调用失败要重试、合规要求某些操作必须人工确认。这些都是"状态机问题",不是"LLM 调用问题"。LangGraph 把 Agent 建模成带状态的有向图,这是对的。我猜接下来 1-2 年,"Graph-based Agent"会成为生产环境的主流范式。
LangSmith / Harness 代表的是 Agent 全生命周期管理的方向。LangChain 公司显然不想只做开源库——Prompt 版本管理、数据集评估、生产 Tracing、Agent Engine 自动修复,这是一整套"AgentOps"平台的野望。对于上了生产、管理着几十个 Agent 的团队来说,这套东西比框架本身更刚需。
至于未来,我觉得三个方向值得跟:
- Agent 编排标准化:现在每个框架都有自己的编排 DSL(LCEL、LangGraph、Eino Compose)。MCP(Model Context Protocol)已经在做工具调用的标准化,编排层迟早也会出现事实标准。
- 从 Co-pilot 到 Agent Swarm:单个 Agent 的能力天花板很明显,多 Agent 协作的生产化才是下一步要解决的问题。LangGraph 的 subgraph 和 Eino 的 DeepAgent 都在往这个方向探。
- 评估驱动的 Agent 开发:写 Agent 不难,知道 Agent 好不好很难。数据集驱动的评估 + 自动回归测试,这可能是 Agent 从 Demo 走向生产的最后一公里。
框架会来会去,但 ReAct 循环、有状态图编排、工具调用标准化——这些东西会成为 LLM 应用开发的基础词汇。选什么框架重要吗?重要。但理解框架为什么这样设计,比记住 API 重要得多。