深入浅出langchain

July 28, 2026 · 科技 #high-tech

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_agentcreate_tool_calling_agent 等工厂方法,大部分场景不需要自己写 Agent 循环。但说实话,LangChain Agent 的抽象层数有点多,出了问题 debug 起来不太友好,这是它被吐槽最多的地方之一。

Callbacks 是横切面。on_llm_starton_tool_endon_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 的团队来说,这套东西比框架本身更刚需。

至于未来,我觉得三个方向值得跟:

  1. Agent 编排标准化:现在每个框架都有自己的编排 DSL(LCEL、LangGraph、Eino Compose)。MCP(Model Context Protocol)已经在做工具调用的标准化,编排层迟早也会出现事实标准。
  2. 从 Co-pilot 到 Agent Swarm:单个 Agent 的能力天花板很明显,多 Agent 协作的生产化才是下一步要解决的问题。LangGraph 的 subgraph 和 Eino 的 DeepAgent 都在往这个方向探。
  3. 评估驱动的 Agent 开发:写 Agent 不难,知道 Agent 好不好很难。数据集驱动的评估 + 自动回归测试,这可能是 Agent 从 Demo 走向生产的最后一公里。

框架会来会去,但 ReAct 循环、有状态图编排、工具调用标准化——这些东西会成为 LLM 应用开发的基础词汇。选什么框架重要吗?重要。但理解框架为什么这样设计,比记住 API 重要得多。