Skip to content

LangChain 整体总结:AI Agent 第一阶段学习完成

LangChain 部分学完了,我们来整体总结一下。

为什么要用 LangChain

市面上有很多大模型,它们的 API 格式整体分为三类:OpenAI、Anthropic(Claude)、Google Gemini。国产大模型的 API 都兼容 OpenAI 格式。

举个例子,比如 system 消息怎么传。

OpenAI 格式是这样,放在 messages 数组里:

json
{
  "model": "gpt-3.5-turbo",
  "messages": [
    {"role": "system", "content": "你是代码助手"},
    {"role": "user", "content": "你好"}
  ]
}

Anthropic 格式是这样,放在单独的 system 字段:

json
{
  "model": "claude-4.5-opus",
  "system": "你是一个代码助手",
  "messages": [
    {
      "role": "user",
      "content": [{"type": "text", "text": "分析这段代码"}]
    }
  ]
}

Gemini 的格式是这样,放在 system_instruction 字段:

json
{
  "contents": [{
    "role": "user",
    "parts": [{ "text": "解释下这段代码" }]
  }],
  "system_instruction": { // 系统指令又是另一种写法
    "parts": [{ "text": "你是一个代码专家" }]
  }
}

类似这样的差异挺多,如果直接和具体大模型耦合,那你的代码就没法切换其他模型了。所以需要一个统一的写法,然后适配不同的大模型

你如果用 LangChain,就是这样:所有大模型的 API 都实现 BaseChatModel,这样调用的时候 API 一样,细节由 ChatXxx 去实现。它们在不同的包里:

  • https://www.npmjs.com/package/@langchain/google-genai
  • https://www.npmjs.com/package/@langchain/deepseek
  • https://www.npmjs.com/package/@langchain/anthropic

也可能是在 @langchain/community 包。

有同学说,不对啊,不是说国产大模型都支持 OpenAI 格式么?之前我们也是直接用 ChatOpenAI 调用的 qwen-plus 之类的模型,为啥还有单独的 ChatModel?——是的,虽然这些模型兼容 OpenAI 格式,但是每个大模型都有一些自己独有的细节,如果想用全部特性,还是要用专门的 ChatModel 类。

所以,为什么要用 LangChain?它可以用统一的 ChatModel API 来调用各种大模型,屏蔽了底层差异。很多公司的项目里就用到了各种大模型,基于 LangChain 可以做到切换各种大模型,代码不变。

所以,我们是基于 LangChain 的 API 来学习大模型的特性,而不是直接学某个大模型的特定 API。

通过 BaseChatModel 屏蔽了大模型底层差异后,再就是对输入、输出做控制——这就用到了 PromptTemplate 和 OutputParser 的 API。

输入控制:Prompt Template

我们基于 prompt 来调用大模型。prompt 可能会很复杂,而且会长期迭代,这就需要组件化管理,用的时候组合,而且 prompt 里还需要加少量案例(Few Shot)。所以 LangChain 提供了 PromptTemplate 的 API:

  • 通过 ChatPromptTemplate 创建 prompt 模板,其中的占位符用的时候传入;如果是对话记录,是通过 MessagesPlaceholder 传入
  • 多个 PromptTemplate 可以用 PipelinePromptTemplate 组合:指定多个 pipelinePrompts,然后指定最终的 finalPrompt,这样就是多个 PromptTemplate 合成一个
  • 有时还需要加入一些示例,用 FewShotPromptTemplate:指定模板和填入的值,生成的就是带少量示例(Few Shot)的 prompt
  • 而且还可以根据长度、语义来做示例选择(LengthBasedExampleSelector / SemanticSimilarityExampleSelector)

这些就是 Prompt Template 的核心 API 了。

输出控制:OutputParser 与 withStructuredOutput

然后是输出部分,也就是 OutputParser:我们希望大模型按照我们指定的格式输出,比如某个 json 结构。

这依赖两种机制:tool_call、json schema。大模型训练的时候就强制 tool_call、json schema 只能输出符合格式的 json,所以能保证返回的一定是符合格式要求的。

如果都不支持,也可以用 OutputParser,也就是在 prompt 里带上格式要求,然后按照这个格式解析。

但不用自己区分用哪种方式,直接调用 model.withStructuredOutput 就可以了,LangChain 会根据调用的模型来选择用哪种(优先 tool call)。

我们做了一个智能录入数据的例子:AI 应用里这个功能很常见。一般用 model.withStructuredOutput 就可以了,但在一些场景下,还是需要 OutputParser 的:

  • 流式打印:比如我们做的流式版 mini cursor,就是用 JsonOutputToolsParser 解析了流式的内容。之前流式返回的内容,参数片段在 tool_call_chunks 里;用了 JsonOutputToolsParser 会解析成 json 格式。这时候的片段信息不完整(比如少了括号、少了引号),如果自己解析 json 还是挺麻烦的,就可以直接用这个 OutputParser
  • 非 json 格式:比如 XML

类似这种 OutputParser 我们也学了一些:

  • StringOutputParser:从各种格式里取出内容,返回字符串
  • StructuredOutputParser:按照某种 JSON 格式返回内容并解析成对象
  • XMLOutputParser:按照 xml 格式返回内容并解析成对象
  • JsonOutputToolsParser:解析 tool_call 的信息,支持流式

在大模型的输出控制方面,model.withStructuredOutput 加上 OutputParser 就够用了。

Tool 与 MCP

输出格式控制用到了 tool_call,这是我们最先学的特性:

  1. 定义 tool,加一下 name、description、参数 schema
  2. 然后 model.bindTools 绑定到大模型
  3. 只要描述写的清楚,那大模型就会在需要调用 tool 的时候返回 tool_calls 信息,并且按照你指定的 schema 来填充参数
  4. 这样我们根据 tool_calls 去调用工具,然后把结果封装成 ToolMessage 也放入 messages 数组
  5. 之后继续循环调用,直到没有新的 tool_call,循环结束

当然,不是所有的 tool 都要自己写,有很多 MCP(可跨进程调用的 tool)可以直接复用。如果 MCP Server 跑在本地进程,就是用 stdio 进程通信,否则就是 http 通信。比如高德 MCP 是用了 http 通信,而 Chrome DevTools 的 MCP 用了 stdio 本地进程通信。

在 Cursor 等编辑器里配置好 MCP Server 后就可以看到 MCP 提供的所有的 tools。代码里是用 @langchain/mcp-adapters 这个包来和 MCP Server 通信:client.getTools() 拿到所有 tool,然后绑定到大模型就好了,其余的和自己定义的 tool 没区别。

Memory

依然是这个循环:如果调用次数少,没啥问题,把所有对话放到 messages 数组;但是如果聊的多了,这样可能会超过大模型上下文限制,就需要做一些 memory 的处理。比如你用 Cursor、Claude Code 的时候,token 到了上限就会触发总结。

当然,messages 数组的写法太原始,一般用 ChatMessageHistory 的 API:它可以把 messages 存到内存、Redis、文件、数据库等。

memory 的管理策略也有三种:

  • 截断:去掉之前的一些 message
  • 总结:调用大模型对之前的 messages 生成摘要
  • 检索:基于向量数据库根据 query 检索之前聊的内容来继续聊

长时记忆基本都是要用向量数据库检索的。

RAG

检索涉及到 RAG,这基本也是 Agent 必备的功能。

把一段内容向量化,在坐标空间内就可以通过夹角来判断相似度——也就是余弦相似度。当然实际上向量的维度很大,比如 1024。基于 Milvus 之类的向量数据库,可以快速根据向量的余弦相似度,检索出相关文档。

RAG 的流程是这样的:

  1. 内容存入向量数据库:各种来源的内容,通过 loader 加载,用 Splitter 分割后再用嵌入模型向量化,存到 Milvus 之类的向量数据库
  2. 检索:根据 query 向量化之后去做余弦相似度匹配,检索出相关文档,让大模型生成回答

比如我们做的电子书阅读助手:检索了 5 个片段,然后给大模型基于这些语义相关的片段来生成回答。这就是 RAG 的流程。

当然,我们是直接用的 @zilliz/milvus2-sdk-node 这个 Milvus 的包。实际上 LangChain 有一层封装,在 @langchain/community 包下——用这层封装是更好的,就像前面讲 ChatModel 一样,它也是屏蔽了底层差异。调用 similaritySearchVectorWithScore 做相似度检索。

LCEL:把组件串成流水线

至此,我们 LangChain 的各个组件就都过了一遍:ChatModel、PromptTemplate、OutputParser、Tool、MCP、Memory、RAG

但如果硬编码的方式组合这些组件不好管理,每个人写法都不一样;而且如果你想加一下监测某个组件输入输出、执行耗时、token 消耗等逻辑,也得硬编码。所以 LangChain 提供了一种声明式的编码方式:LCEL

LCEL 就是:每个组件都实现了 Runnable 接口(比如 ChatModel、OutputParser、PromptTemplate 等),并且提供了一系列 Runnable 的 API 可以连接不同的组件,组装出一条 chain 之后统一执行。调用方式有 invoke(同步调用)、stream(流式)、batch(批量调用)三种。

再回到刚才那个问题:有了声明式的 chain 之后,再加耗时、token 消耗、输入输出的日志等,怎么做呢?——只要加一个 callbacks 回调就可以了,所有的节点就动态加上了这段逻辑。这就是声明式写法的好处。

后面我们会学 LangSmith,它是用来做 chain 执行的监测的。它是怎么监测每个节点的情况的呢?就是基于 Runnable 的 callbacks。

也就是说,通过 LCEL 的写法,把各个组件用声明式的方式连接起来,可以动态加一些逻辑。而且每个节点自带了重试、备选方案、配置等功能,开箱即用。

这种写法要学一些 API:

  • RunnableSequence:顺序执行
  • RunnableLambda:把函数包装成 Runnable
  • RunnableMap:并行执行多个 chain,结果放在对象属性上
  • RunnableBranch:if else 逻辑
  • RouterRunnable:switch case 逻辑,根据 key 决定执行哪个 chain
  • RunnableEach:循环数组每个元素来调用 chain
  • RunnablePassthrough:拿到原始输入
  • RunnablePick:取输入对象的某些属性返回
  • RunnableWithMessageHistory:给 chain 加上 memory

刚开始可能不大习惯,但是多练习写几个 chain 就会了。写法上可以简化:函数会自动转成 RunnableLambda、对象会自动转成 RunnableMap。

基本都是这三步:

  1. 分析流程,拆分原子步骤
  2. 根据步骤之间的关系,选择对应 Runnable API
  3. 统一调用(invoke、stream、batch)

总之,有了 LCEL 后,LangChain 就不再只是工具集,而是一个工业化流水线——每个节点都自带一些功能,还可以给每个节点动态加一些逻辑。

总结

  • LangChain 通过 ChatModel 屏蔽了各种大模型的差异,可以用同样的 API 来写代码,可以切换大模型
  • 我们过了一遍各种组件:ChatModel、PromptTemplate、Tool & MCP、OutputParser、Memory、RAG
  • 然后是 LCEL 组合各种组件,编排 chain,它可以给节点动态增删逻辑,而且还内置了一些功能

学完组件可以说 LangChain 是工具集,学完 LCEL 就可以说 LangChain 是工业流水线了。把这两方面都掌握好,LangChain 就学的差不多了。

学到这里的整个知识体系,可以用一张图来概括:

LangChain 知识体系总览

学到这,AI Agent 学习整体告一段落,你也可以自己总结回顾下了。

基于 VitePress 构建 · 专注前端与 AI 实战