做了这么久的 LLM 开发,也用了不少工具,打算开启一个新系列:快学 llm 开发。既写给自己,也写给自己的团队,算是对这段时间的总结和回顾。
作为系列第一篇,打算聊聊毁誉参半的 langchain。因为团队主要用 TypeScript,本文就以 langchain.js 为主要对象。由于 langchain 本身的工程化相当好,所以其实本文也适用于 langchain python 版本。
但你若熟悉 python 或是对于切换语言也没心理负担,我个人推荐 python 版本。因为就我个人观察,python 版本的更新速度更快一些,比如:用于 agent long-term memory 的 LangMem SDK 目前就只有 python 版本。
由于 langchain 已经足够出名,因此我也就直接跳过其背景介绍,直切主题。
我该不该用 LangChain ?
树大招风,LangChain 自然也不能幸免于难。我最近看到的一个段子就是:“LangChain 毁了我的婚姻”,见下图:
不止于 LangChain,我还看到过另一个版本,只是主角换成了 Linux。
就这个问题本身来讲,其实没有什么对错,只有适合不适合。只是,我建议在做出决定之前,先了解几个关于 LangChain 的事实。
首先,LangChain 并非孤立的框架,它是一个生态系统,这一点从其网站上的架构图可以看出来。而很多人口中的 LangChain 其实只是冰山一角。并且,由于 LLM 本身就是一个快速发展的领域,还有很多工具其实并未在此列出,典型如:LangMem SDK 和 langchain-mcp-adapters。
其次,LangChain 本身也非常活跃,但凡有新的 idea 和技术范式,它要么会第一时间加入到框架中,要么会以模版工程的形式供开发者快速使用。在其 GitHub 上,你可以找到丰富的开发模版,有的甚至是当前最新的技术实现,如 evaluation、deep report 和 artifacts。
最后,由于是最早流行的 LLM 开发框架,很多第三方本身就会对 LangChain 提供支持,比如:CoinBase 的 AgentKit。
关于这个问题,我的选择是:Yes,我的理由是:
- 我现在的项目需要工程化的支撑,而 LangChain 提供了从开发到运维的全套方案。
- 我需要在不同的 LLM 之间进行切换,而 LangChain 提供了统一的接口。
- 我需要实现 Agent,而 LangChain 提供了 Agent 的实现,即 LangGraph。
- LangChain 本身很成熟,对于工程性的一些琐事提供了很好的支持,比如加载各类格式的文档数据。
- LangChain 很活跃,会第一时间加入我想要的特性,比如:mcp adapters 和 artifacts 等等。
- LangChain 有成熟的生态,很多第三方工具都已经支持。
顺便提一句,LangChain 其实并不是我的第一选择。在我真正意义上的第一个 LLM 项目中,我用的是 Vercel AI SDK。
LangChain 和 Vercel AI SDK 的区别
由于两者经常被拿来比较,而且也有不少开发者也都宣称转移到 Vercel AI SDK 后轻松了不少。作为两者都在用且从 Vercel AI SDK 转过来的开发者,我简单说一下两者的区别。
- Vercel AI SDK 是孤立的框架,专注于 Chat 和 Chat 相关的 UI。
- LangChain 是一个生态系统,提供了完整的从开发到运维的闭环。
并且,虽然 Vercel AI SDK 号称也能实现 Agent,但与 LangGraph 相比实在是小巫见大巫。虽然也有基于它的第三方 Agent 开发框架,但我个人倾向于采用出于同一个开发组织的方案。
LangChain 中的关键概念
很多开发者刚开始接触 LangChain 时,第一感觉就是:懵逼。原因无外乎两点:
- LLM 属于新兴领域,若没有机器学习相关的背景,初次接触时会感到陌生。
- 早年的 LangChain 文档对于开发者并不友好,加上其广泛的触及面,会让新进者感到无从下手。(注:现在的文档有了极大改善,有很多教程,可直接上手。)
对此,我建议从其解决的问题角度入手,由简入繁,逐步深入。
LangChain 解决的无外乎是 LLM 调用的问题
所谓的 LLM 调用就是:Prompt 进,处理结果出。而由于 LLM 本身有不同的 provider,自然它需要有一个统一的接口,于是就有了 Chat Model。
由此,得出两个概念:Prompt 和 Chat Model。
其中,为了避免重复写大段的 Prompt,于是又有了 Prompt Template 。至于如何书写 Prompt,包括 Few-shot prompting,Zero-shot prompting 和 Chain-of-thought prompting 等等,网上已经有很多资料,这里就不再赘述。
既然是 LLM 调用,那就必然会有输入和输出
在输入端有:
- Document Loader:用于加载不同格式的文档数据。
- Text Splitter:用于将长文本分割成短文本,以便于 LLM 处理。
在输出端有:
- Output Parser:用于解析 LLM 的输出结果,将其转换为期望的格式。
对于输出时机,还有所谓的 Streaming,即边生成边输出,这样可以提高用户体验。
模型也有自身的能力
有的模型可以能够输出 json,即 Structured Output;有的则能“使用”外部工具,即 Tool 和 Tool Calling。
对话是由消息组成的
一个完整对话由多种消息组成,典型的消息类型有:
- System Message:系统消息,用于设置对话的上下文和规则。
- Human Message:人类用户的消息。
- AI Message:AI 模型的消息。
- Tool Message:外部工具调用结果。
一个典型的对话消息序列如下:
[System Message]
[Human Message]
[AI Message]
[Human Message]
[AI Message]
...
它们也构成了对话历史。
Chain 其实是任务流水线
对于初学者,chain 一词可能是最难以观词望意的概念。但看过文档之后,你会发现它其实就是一个简单的任务流水线。你可用用它来搭建简单的工作流,它甚至都提供了一个叫 LCEL(LangChain Expression Language) 的 DSL。
但因为现在已经有了 LangGraph,所以我个人推荐直接忽略它。
Agent 不过是一组技术概念的组合
关于 Agent,我特别喜欢 Agno 文档中的一幅图:
LangGraph 提供了对于 Agent 的直接支持,在它的实现里,Agent 是一组状态图 + Memory + Tool 的组合。同时,它提供了若干预定义的 Agent 实现,如:
createReactAgent({
llm: chatModel,
tools: [...],
prompt: systemPrompt,
});
并且提供了典型的多 Agent 协作模式的实现,如:Supervisor
和 Swarm
。
除了 Model
和 Tool
,上图中其他两个概念:
- Memory:用于存储 Agent 的状态和上下文信息。
- Knowledge:用于存储 Agent 可能用到的信息。
简而言之,Memory 跟 Agent 内部状态相关,而 Knowledge 则是 Agent 可能用到的外部信息,一内一外
向量是 LLM 开发中搜索的基础
传统开发中,通常是将数据规范化之后存于数据库,然后再通过 SQL 进行搜索。在 LLM 中,其实也有类似的过程,只不过其所谓的规范化是指将数据转换为向量。
这是因为机器学习本身需要利用向量来进行计算和预测,原始的文本和图像等必须经过数值化处理才能被其理解。而这个过程就是向量化,也就是所谓的 Embedding,向量化过程本身也需要用到对应的 LLM 模型。
用于保存向量数据的数据库称为 Vector DB,典型的如:PGVector 和 Pinecone 等等。
用到向量的典型场景:
- Agent 长期记忆搜索
- RAG 应用
- 外部知识库搜索
至此,相信你应该已经对于 LangChain 的关键概念有了一个大致的了解。
LangChain 开发经验谈
因为目前的 LangChain 的文档已经提供了足够多的指南和说明,这里我就不再一一讲解如何入门和使用了,只是谈谈我个人的一些经验,以供参考。
模板为先
作为成熟的框架,LangChain 提供了丰富的模版和示例。你可以直接使用它们来快速搭建自己的应用。而大多数时候,我们的开发并不特殊,因此我建议在理清需求之后,先直接去它的 GitHub 上浏览现场的模版和示例,看看是否可以直接使用。
这样至少可以省了工程搭建和配置的时间。同时,你会发现一些高级的场景或用例,LangChain 其实已经有了现成的实现,如 :Deep Report 和 Canvas 等等。你拿过来直接在上面改改就行了。
提醒:注意工程的最后提交时间,确保它不过时且用到了最新的版本。
集成 Vercel AI SDK 简化 UI 层的开发
LangChain 并没有提供现成的 UI 组件,此时你可以考虑集成 Vercel AI SDK。利用它的 useChat
和 LangChainAdapter
可以很方便的实现 Chat UI。
此外,在 UI 层面,还可以考虑使用 assistant-ui
和 CopilotKit
。
注意控制会话的长度
由于 LLM 的上下文窗口有限,因此在使用 LangChain 时,必须注意控制会话的长度。你当然可以自己手工管理,如控制传入的消息数组长度,但 LangChain 已经提供了现成的方案:trimMessages
。
LangChain.js 的 PDF Loader 有点过时
这主要在于其依赖已经有很长时间没有更新。其症状就是某些 PDF 加载之后无法读出其中的内容,好在最近 MuPDF
提供了 js 版本,建议直接使用它来实现自己的 PDF Loader。我在自己的 tsw-cli 中就是用的它的 python 版本:pymupdf4llm
。
大多数时候,使用 RecursiveCharacterTextSplitter 就行了
由于目前采用 MD 作为 LLM 输入渐成主流,因此我建议直接使用 RecursiveCharacterTextSplitter
,它特别适合有格式的文档,包括代码文件。
使用 RecordManager 进行增量更新
向量化本身是一个耗时的过程,若每次都重新向量化,势必会浪费大量的时间和资源。LangChain 提供了 RecordManager
来帮助你进行增量更新,可查阅相关的指南。
有时,可以将异常以消息对话的方式进行处理
有时,你可能想将对话过程中发生的异常消息以更人性化的方式反馈给用户,那么可以采用下面类似的代码实现:
try {
const session = await auth.api.getSession({
headers: request.headers,
});
if (!session) {
throw new Error("请登录后继续未完的对话。");
}
const body = await request.json();
const messages = body.messages;
if (!messages) {
throw new Error("请赐言一二。");
}
const streamEvents = await query(convertVercelMessagesToLangChain(messages));
return LangChainAdapter.toDataStreamResponse(streamEvents);
} catch (error) {
console.error("Error in agent route:", error);
if (error instanceof Error) {
const stream = new ReadableStream({
start(controller) {
controller.enqueue(error.message);
controller.close();
},
});
return LangChainAdapter.toDataStreamResponse(stream);
}
return new Response("Internal Server Error", {
status: 500,
statusText: "Internal Server Error",
});
}
结语
LangChain.js 是一个成熟的 LLM 开发框架,提供了丰富的模版和示例,适合快速搭建 LLM 应用。除此之外,因为拥有成熟的生态和活跃的社区,并对工程环境提供了很好的支持,因此我个人建议:除非你的项目就是简单的套壳应用,一般来说都可以考虑使用 LangChain.js,尤其是你在构建 RAG 类型应用时。
本文包含付费内容,需要会员权限才能查看完整内容。