首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Agent全栈第1篇:从凌晨3点的RAG事故,搞懂Chain/Tool/Agent三件套

Agent全栈第1篇:从凌晨3点的RAG事故,搞懂Chain/Tool/Agent三件套

作者头像
陆业聪
发布2026-06-05 20:10:12
发布2026-06-05 20:10:12
90
举报

📰 科技要闻

• Uber 限制员工使用 Claude Code 等 AI 编码工具,理由是成本失控——这恰好印证企业级 AI 应用必须有自己可控的管线层。

• LangChain 1.2.1 已成稳定版,官方明确以 LCEL + LangGraph 作为 2026 年构建 Agent 与 RAG 的主流范式。

• Sebastian Raschka 最新综述指出 KV 共享、mHC、压缩注意力正在重塑 LLM 底层结构,应用层框架的稳定抽象越发重要。

本文是【Agent全栈工程师:企业级知识库项目】系列第 1 篇,全系列共 20 篇,从 LangChain 入门一路打到生产部署。这一篇我们把 Chain / Agent / Tool 三件套讲透,再用 30 行 LCEL 跑出一个可工作的 RAG。

一、那次线上事故,我才真正搞懂 LangChain

去年一个内部知识库 RAG 上线两周,凌晨三点突然炸了——所有问答接口超时,告警刷了几百条。值班同事拉起来,打开日志一看,整个人都不好了。

错误堆栈里夹着一行:FAISS index size mismatch。原因后来定位到是当天有人手动把向量库重建了一遍,但代码里检索逻辑、相似度阈值、重排策略和向量库的耦合写得乱七八糟,重建一次就有三处地方要改。我们当时漏了一处。

更糟的是回滚都难——因为「回滚」本身要改五个文件,每个文件里都散着对 FAISS 的直接调用。我那天凌晨写紧急修复 PR 写到天亮,提交完就在工位上趴着睡了俩小时。

后来复盘时我才意识到一件事:问题不是代码写得烂,是没有抽象层。检索源、Prompt、Model、解析、兜底,五个东西在我们 500 行的代码里互相纠缠,谁动谁坏。

那次事故之后我才开始认真看 LangChain。说实话,我之前对它有偏见——觉得「不就是个胶水库吗」、「重得没必要」、「文档天天变」。直到那次事故让我明白:LangChain 真正的价值不是省代码,是给你强行划出抽象边界

这一篇,就是想把这次事故里学到的东西讲清楚:Chain / Tool / Agent 三件套的本质是什么、为什么 LCEL 是 2026 年必须掌握的写法、以及——什么场景下 LangChain 真的不该用。

先聊聊「为什么不直接用 SaaS」

有人问过我:现在 ChatGPT、Claude、Gemini 都能上传文档做问答,干嘛自己折腾框架?

巧了,就在上周,Simon Willison 报了一条新闻:Uber 内部限制员工使用 Claude Code 等 AI 编码工具,理由是费用增长失控、上下文不透明、审计困难。这不是 Uber 一家的事——任何把核心数据丢给黑盒 SaaS 的企业,迟早都会撞上同样的墙。

所以企业级 AI 必然要自己掌控管线。而掌控管线就必然要写胶水代码——胶水代码的复杂度,正是 LangChain 这类框架要消化的。我那次事故,本质就是没把「框架该做的事」交给框架做。

二、Chain:可组合的处理单元

LangChain 的第一个核心抽象叫 Chain。说人话就是:把一连串处理步骤拼起来,每一步的输出作为下一步的输入。

放在 0.x 时代,Chain 是一个个继承自 BaseChain 的子类,写起来又啰嗦又难调。从 0.1 开始,官方推了一套叫 LCEL(LangChain Expression Language)的写法,到 1.2.1 已经变成绝对主流。

LCEL 长这样:

代码语言:javascript
复制
# LCEL 用 | 把组件串起来
from langchain_core.prompts import (
ChatPromptTemplate
)
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import (
StrOutputParser
)prompt = ChatPromptTemplate.from_template(
"用一句话解释 {topic}"
)
model = ChatOpenAI(
model="gpt-4o-mini",
temperature=0
)
parser = StrOutputParser()# 这一行就是 LCEL 的灵魂
chain = prompt | model | parser# 同步调用
result = chain.invoke(
{"topic": "LCEL"}
)
print(result)

注意那个 |,它就是 LCEL 的灵魂。背后是 Python 重载了 __or__ 方法,把任何实现了 Runnable 协议的对象串成一条管线。

这套写法的好处不是「短」,而是这几点:

同步/异步/流式同源:同一条 chain,调 .invoke() 是同步、.ainvoke() 是异步、.stream() 是流式输出。零改动。

原生支持并行:用 RunnableParallel 包一下,多个分支并发执行,省一半延迟。

可观测:每个节点都被 LangSmith 自动记录,调试不用再 print。

说实话,我最开始也不信「换个写法能差这么多」,直到我用流式输出的时候——传统 0.x Chain 写流式要继承一堆 Callback,LCEL 直接 .stream(),一行解决。从那天起我就再没回去过。

三、Tool:给 LLM 一双手

第二个抽象叫 Tool。LLM 本身只会吐字符串,不会查数据库、不会发请求、更不会执行代码。Tool 就是把这些「外部能力」封成 LLM 能调的接口。

1.x 时代,定义一个 Tool 的最小写法是这样:

代码语言:javascript
复制
from langchain_core.tools import tool@tool
def search_orders(
user_id: str,
days: int = 7
) -> str:
"""按用户 ID 查最近订单。Args:
user_id: 用户唯一标识
days: 回溯天数,默认 7
"""
# 真实场景:调内部订单服务
return f"user={user_id} orders=3"# 让模型「看见」这个工具
model_with_tools = model.bind_tools(
[search_orders]
)

这个 docstring 不是写给人看的,是写给模型看的。LangChain 会把它和参数签名一起塞进 System Prompt,告诉模型「你有这么一个工具,参数是这样,啥情况下调它」。

有个坑我必须提一下:很多人写 Tool 时把 docstring 写得跟 Java 注释一样八股,结果模型死活不调用。我后来明白了——docstring 要写得像产品需求一样具体,把「什么场景下用、不用什么场景」都讲清楚,模型才会准确触发。

四、Agent:让 LLM 自己决定下一步

第三个抽象,也是最容易被神话的一个:Agent。

很多人以为 Agent 是某种黑魔法,其实它的核心就一个循环:ReAct(Reasoning + Acting)。流程长这样:

用户提问

Thought:要不要调工具?

✅ 需要 → Action:调用 Tool(带参数)

📥 Observation: 工具执行结果回到模型

🔁 回到 Thought(循环直到结束)

❌ 不需要 → 直接生成回答

这套循环本身没什么神秘的,不超过 50 行代码就能手写出来。LangChain 的价值是把它标准化了:

• 统一的 Tool 协议,任何 LLM Provider 都能用

• 自动处理消息历史,不用自己拼 messages 列表

• 出错时的重试、超时、最大循环次数兜底

• 和 LangSmith 打通,每一步 Thought / Action / Observation 都能回放

但我必须泼盆冷水:裸 Agent(AgentExecutor)已经不是 2026 年的推荐写法了。复杂工作流官方明确指向 LangGraph——这就是下一篇要讲的。这一篇你只需要知道:Agent 的本质是个循环,循环外加状态管理就是 LangGraph。

五、30 行 LCEL,一个能扛事故的 RAG

说了那么多抽象,回到我开头那次事故的反思——上代码。下面这段是真能跑的,关键是它把那次让我崩溃的「五处耦合」全拆开了:

代码语言:javascript
复制
from langchain_community.\
document_loaders import TextLoader
from langchain_text_splitters import (
RecursiveCharacterTextSplitter
)
from langchain_openai import (
OpenAIEmbeddings, ChatOpenAI
)
from langchain_community.\
vectorstores import FAISS
from langchain_core.prompts import (
ChatPromptTemplate
)
from langchain_core.runnables import (
RunnablePassthrough
)
from langchain_core.output_parsers import (
StrOutputParser
)# 1. 加载 + 切块
docs = TextLoader(
"docs/handbook.md"
).load()
splits = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
).split_documents(docs)# 2. 向量化 + 索引
vs = FAISS.from_documents(
splits,
OpenAIEmbeddings()
)
retriever = vs.as_retriever(
search_kwargs={"k": 4}
)# 3. Prompt + Model + Parser
prompt = ChatPromptTemplate.from_template(
"""根据下面的资料回答问题。
资料:{context}
问题:{question}
要求:只用资料中的信息。"""
)
model = ChatOpenAI(
model="gpt-4o-mini"
)# 4. LCEL 拼装
rag_chain = (
{
"context": retriever,
"question": RunnablePassthrough()
}
| prompt
| model
| StrOutputParser()
)print(
rag_chain.invoke(
"我们的请假流程是什么?"
)
)

核心就是最后那段 LCEL。{"context": retriever, "question": RunnablePassthrough()} 这种字典写法是 LCEL 的常用套路——字典里每个 key 都会被独立执行(并行),最后合并成一个 dict 传给下一个节点。

想换向量库?把 FAISS 改成 Milvus,其他完全不动。想加重排?在 retriever 后面再串一个 CohereRerank。想流式返回?.stream() 一行替换 .invoke()

对照我那次事故——如果当时是这套结构,重建向量库我只需要替换那一个 retriever 实例,根本不会牵动检索、Prompt、解析任何一处。

这就是「抽象」的真正价值。不是省了几行代码,是把「修改的影响范围」严格收敛在一个组件内。线上事故不是因为 bug,是因为耦合——LangChain 帮你强制解耦。

六、1.x 的分包格局,必须搞清楚

从 0.1 升到 1.2.1,LangChain 做了一件特别重要的事:把代码拆成三个独立包。这事坑过不少人——明明 pip 装了 langchain,导入还是失败。

包名

作用

什么时候装

langchain-core

Runnable / LCEL / 基础抽象

永远装,是基石

langchain

Chain / Agent / 高层封装

需要 AgentExecutor 等高层 API 时

langchain-community

第三方集成(向量库、LLM Provider)

用 FAISS、Milvus、Tavily 等时

独立包

langchain-openai / langchain-anthropic

主流 Provider 都拆出去单独维护

实战建议:新项目最小依赖装这几个就够了——langchain-corelangchain-openai(或对应 Provider)、langchain-text-splitters。能不装 community 就不装,因为它会拖一堆可选依赖进来。

顺便说一下 Python 版本:1.2.1 强制要求 Python 3.10+。如果你还在 3.9,趁早升,不然 match-case 那种新语法在源码里到处都是,根本跑不起来。

七、什么时候不该用 LangChain?

到这里你可能觉得 LangChain 是万能药,但我必须给个反面观点。我看过一篇 KM 上的 nanobot 架构分析,它整个 Agent 框架只有 4000 行代码,没用 LangChain,照样跑得很好。

那篇文章作者的判断我很认同:当你的 Agent 行为高度定制、流程极端复杂、性能要求苛刻时,LangChain 的抽象反而是负担

具体来说,这几种场景我会建议先别上 LangChain:

原型阶段:你只是想验证一个想法,几十行 OpenAI SDK 就能跑通,框架是过度工程

性能敏感的高 QPS 在线服务:LCEL 每次调用有不可忽视的开销,毫秒级延迟敏感的场景要慎重

需要极度自定义的 Agent 行为:比如自定义 ReAct 循环、特殊的状态转移、多 Agent 协作——LangGraph 比 LangChain 更适合,但它也是 LangChain 生态的一部分

反过来,这几种场景闭眼上 LangChain

• 企业内部知识库 / 客服 Bot / 文档问答(典型 RAG)

• 需要快速集成多种 LLM、多种向量库、多种检索源的项目

• 团队人手多、需要标准化接口降低协作成本的项目

• 需要 LangSmith 做可观测性的生产环境

八、下一篇:从 Agent 到 LangGraph

讲到这儿,三件套讲完了:Chain 编排、Tool 扩展、Agent 自主循环。但你应该已经感觉到一个限制——裸 Agent 的循环是隐式的。它跑完了就跑完了,中间状态、回滚、人工介入、子流程拼装……都很难做。

在播客《AI西经东译》最近一期里,Cole Medin 提了个概念叫 Harness Engineering,意思是给 Agent 套一个明确的「骨架」。LangChain 这套抽象就是 Harness 的标准件,但真正把 Harness 显式化的是 LangGraph——它把 Agent 的状态机直接画出来,每个节点、每条边都看得见。

下一篇我会用 LangGraph 重写这一篇的 RAG,加上一些有意思的东西:

• 把检索结果质量评估做成显式节点,不达标自动改写 query 重新检索

• 加一个 human-in-the-loop 节点,关键回答让人确认

• 多 Agent 并行:一个负责检索、一个负责生成、一个负责审校

arXiv 上前几天还有篇 StreamMA 的论文,讨论多 Agent 流式通信,2026 这个方向真的在快速演进——但 LangGraph 已经能把今天的需求覆盖得差不多了。

这一篇先到这里。本文涉及的所有代码都能跑。下一篇见。

—— Agent全栈工程师系列 · 第 1 / 20 篇 ——

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-06-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 陆业聪 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档