当 AI 应用开发,就像搭积木一样简单,你还愿意手写底层的代码逻辑吗?
为什么需要 LangChain?
从手写代码的痛苦说起
不知道大家是否有过这样的经历:比如我想做一个 AI 对话应用,但结果却是写了大量重复的胶水代码:
javascript
const const API_URL = process.env.DEEPSEEK_API_URL || 'https://api.deepseek.com/v1/chat/completions'
const API_KEY = process.env.DEEPSEEK_API_KEY
// 每次都要写 fetch 调用 DeepSeek API
const response = await fetch(API_URL, {
method: 'POST',
headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ model: 'deepseek-chat', messages })
});
// 手动管理对话历史,处理 token 限制
messages.push({ role: 'user', content: input });
if (messages.length > 20) messages = messages.slice(-15);
// 实现 Function Calling 要写大量样板代码
while (assistant.tool_calls) {
for (const toolCall of assistant.tool_calls) {
// 解析参数、执行工具、返回结果...
}
}
核心问题:LLM 应用有大量重复的"胶水代码"
上述代码咋一看没什么问题,但是:
- 每个项目都要重写一遍
- 容易出错(忘记处理边界情况)
- 难以维护(改动一处影响全局)
LangChain 的承诺
标准化 LLM 应用的构建方式,让开发者专注于业务逻辑!
LangChain 相当于把 AI 应用开发变成了"搭积木",我们想做什么,搭积木就行:
javascript
const chain = prompt.pipe(model).pipe(outputParser);
const result = await chain.invoke({ input: "你好" });
LangChain 核心概念速览
六大核心组件
| 组件 | 作用 | 一句话解释 |
|---|---|---|
| Models | 统一LLM接口 | 换模型就像换插头 |
| Prompts | 提示词管理与模板 | 动态生成提示词 |
| Chains | 串联多个步骤 | 流水线作业 |
| Memory | 对话历史管理 | 让AI有记忆 |
| Tools | 外部能力封装 | AI的手和脚 |
| Agents | 自主决策执行工具 | 让AI自己决定用哪个工具 |
概念关系图
graph TD
Entry[应用入口] --> Agent{Agent需要
调用工具时?} Agent -->|需要工具| Tools[Tool 1] Agent -->|需要工具| Tools2[Tool 2] Agent -->|需要工具| Tools3[Tool 3] Agent -->|不需要工具| Chain[Chain
不需要工具时的简单对话] Tools --> Output[输出结果] Tools2 --> Output Tools3 --> Output Chain --> ChainProcess[Prompts → Models → Memory] ChainProcess --> Output
调用工具时?} Agent -->|需要工具| Tools[Tool 1] Agent -->|需要工具| Tools2[Tool 2] Agent -->|需要工具| Tools3[Tool 3] Agent -->|不需要工具| Chain[Chain
不需要工具时的简单对话] Tools --> Output[输出结果] Tools2 --> Output Tools3 --> Output Chain --> ChainProcess[Prompts → Models → Memory] ChainProcess --> Output
手写代码 vs LangChain
代码量对比
| 功能 | 手写代码 | LangChain | 代码减少 |
|---|---|---|---|
| 单次问答 | ~30 行 | ~10 行 | 67% |
| 带记忆对话 | ~80 行 | ~20 行 | 75% |
| Function Calling | ~150 行 | ~40 行 | 73% |
| ReAct Agent | ~200 行 | ~50 行 | 75% |
复杂度对比
| 维度 | 手写代码 | LangChain |
|---|---|---|
| 学习成本 | 低(只需懂HTTP) | 高(需理解框架概念) |
| 实现速度 | 慢(重复造轮子) | 快(组件组合) |
| 调试难度 | 低(代码可控) | 高(需理解框架内部) |
| 可维护性 | 依赖代码质量 | 框架保证 |
| 扩展性 | 自己设计 | 插件式扩展 |
LangChain 核心概念深入
Models:统一的模型接口
LangChain 封装了主流 LLM 的统一接口,切换模型就像换插头一样简单:
typescript
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage } from "@langchain/core/messages";
import dotenv from "dotenv";
dotenv.config();
// DeepSeek 配置(兼容 OpenAI 接口)
const deepseekModel = new ChatOpenAI({
model: "deepseek-chat",
temperature: 0.7,
apiKey: process.env.DEEPSEEK_API_KEY,
configuration: {
baseURL: 'https://api.deepseek.com/v1'
}
});
// 也可以使用其他模型
// const openaiModel = new ChatOpenAI({ model: "gpt-4", apiKey: process.env.OPENAI_API_KEY });
// const claudeModel = new ChatAnthropic({ model: "claude-3-opus-20240229", apiKey: process.env.ANTHROPIC_API_KEY });
// 统一调用方式
const messages = [new HumanMessage("你好,请用一句话介绍 DeepSeek")];
const response = await deepseekModel.invoke(messages);
console.log(response.content);
Prompts:提示词管理
基础模板
typescript
const template = PromptTemplate.fromTemplate(
"你是一个{role}专家。请回答: {question}"
);
const formatted = await template.format({
role: "AI应用开发",
question: "什么是 LangChain?"
});
多消息模板(用于聊天模型)
typescript
const chatPrompt = ChatPromptTemplate.fromMessages([
["system", "你是一个{role}助手,请用{language}回答"],
["human", "{input}"],
]);
const formattedChatPrompt = await chatPrompt.formatMessages({
role: "编程",
language: "中文",
input: "如何使用 DeepSeek API?"
});
带占位符的模板(用于记忆)
javascript
const promptWithMemory = ChatPromptTemplate.fromMessages([
["system", "你是一个友好的 AI 助手,由深度求索公司开发"],
new MessagesPlaceholder("chat_history"), // 记忆会动态插入到这里
["human", "{input}"]
]);
Chains:流程编排
LangChain 表达式语言 (LCEL, LangChain Expression Language) 是最推荐的方式,它用 | 管道符将组件串联起来:
typescript
import { ChatOpenAI } from "@langchain/openai";
import { PromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
import dotenv from 'dotenv';
dotenv.config();
// 配置 DeepSeek 模型
const model = new ChatOpenAI({
model: "deepseek-chat",
temperature: 0.7,
apiKey: process.env.DEEPSEEK_API_KEY,
configuration: {
baseURL: 'https://api.deepseek.com/v1'
}
});
const prompt = PromptTemplate.fromTemplate("给我讲一个关于{topic}的笑话,要简短有趣");
// 使用 LCEL 语法构建链
const chain = prompt.pipe(model).pipe(new StringOutputParser());
// 执行链
const result = await chain.invoke({ topic: "程序员" });
console.log(result);
// 我们也可以串联更复杂的操作
const complexChain = prompt
.pipe(model)
.pipe(new StringOutputParser())
.pipe((text) => `🤖 AI 说:${text}`); // 自定义处理
const upperResult = await complexChain.invoke({ topic: "AI" });
console.log(upperResult);
Tools:工具封装
工具是 Agent 的"手脚",LangChain 提供了多种定义工具的方式:
1. 简单工具:只有字符串输入
typescript
const searchTool = new DynamicTool({
name: "search",
description: "搜索互联网信息,输入搜索关键词",
func: async (query: string) => {
// 这里可以调用真实的搜索 API
console.log(`🔍 搜索: ${query}`);
return `关于"${query}"的搜索结果:找到 1000 条相关信息...`;
}
});
2. 结构化工具:带参数验证
typescript
const weatherTool = new DynamicStructuredTool({
name: "get_weather",
description: "获取指定城市的天气信息,支持摄氏度和华氏度",
schema: z.object({
city: z.string().describe("城市名称,如:北京、上海、深圳"),
unit: z.enum(["celsius", "fahrenheit"]).optional().default("celsius").describe("温度单位")
}),
func: async ({ city, unit = "celsius" }) => {
console.log(`🌤️ 查询天气: ${city}, 单位: ${unit}`);
// 模拟天气数据
const weatherData: Record<string, any> = {
"北京": { temp: 22, condition: "晴" },
"上海": { temp: 18, condition: "雨" },
"深圳": { temp: 25, condition: "阴" }
};
const data = weatherData[city] || { temp: 20, condition: "多云" };
const temp = unit === "celsius" ? data.temp : (data.temp * 9/5 + 32);
const unitSymbol = unit === "celsius" ? "°C" : "°F";
return `${city}今天${data.condition},温度${temp}${unitSymbol}`;
}
});
const result = await weatherTool.func({ city: "北京", unit: "celsius" });
console.log(result); // 输出: 北京今天晴,温度22°C
3. 计算器工具
typescript
const calculatorTool = new DynamicStructuredTool({
name: "calculator",
description: "计算数学表达式,支持加减乘除和括号",
schema: z.object({
expression: z.string().describe("数学表达式,如:2+3*4 或 (10-5)/2")
}),
func: async ({ expression }) => {
console.log(`🧮 计算: ${expression}`);
try {
// 注意:实际生产环境应该使用安全的表达式求值库,如 math.js
const result = eval(expression);
return `${expression} = ${result}`;
} catch (error) {
return `计算错误: ${expression},请检查表达式格式`;
}
}
});
const result = await calculatorTool.func({ expression: "(10+5)*2" });
console.log(result); // 输出: (10+5)*2 = 30
4. 自定义工具类
typescript
import { Tool } from "@langchain/core/tools";
class CurrentTimeTool extends Tool {
name = "get_current_time";
description = "获取当前时间,输入时区(可选),返回当前日期和时间";
async _call(input: string): Promise<string> {
const timezone = input || "Asia/Shanghai";
const now = new Date();
return `当前时间 (${timezone}): ${now.toLocaleString('zh-CN', { timeZone: timezone })}`;
}
}
const timeTool = new CurrentTimeTool();
const result = await timeTool.invoke("Asia/Shanghai");
console.log(result); // 输出: 当前时间 (Asia/Shanghai): 2026/3/31 7:19:16
Memory:记忆管理
javascript
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, AIMessage, BaseMessage } from "@langchain/core/messages";
import { RunnableWithMessageHistory } from "@langchain/core/runnables";
import { InMemoryChatMessageHistory } from "@langchain/core/chat_history";
import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
import dotenv from 'dotenv';
dotenv.config();
// 配置 DeepSeek 模型
const model = new ChatOpenAI({
model: "deepseek-chat",
temperature: 0.7,
apiKey: process.env.DEEPSEEK_API_KEY,
configuration: {
baseURL: 'https://api.deepseek.com/v1'
}
});
// ============ 1. 缓冲记忆:保存最近 N 条消息 ============
class BufferMemoryV2 {
private chatHistory: InMemoryChatMessageHistory;
private maxMessages: number;
constructor(maxMessages: number = 10) {
this.chatHistory = new InMemoryChatMessageHistory();
this.maxMessages = maxMessages;
}
async addMessage(message: BaseMessage) {
await this.chatHistory.addMessage(message);
// 限制消息数量
const messages = await this.chatHistory.getMessages();
if (messages.length > this.maxMessages) {
// 移除最早的消息(每次移除2条,保持成对)
const toRemove = messages.slice(0, messages.length - this.maxMessages);
for (const msg of toRemove) {
// 注意:InMemoryChatMessageHistory 没有单独删除方法
// 这里简化处理,实际使用中需要重新创建历史
}
}
}
async getMessages() {
return await this.chatHistory.getMessages();
}
async clear() {
await this.chatHistory.clear();
}
}
// ============ 2. 总结记忆:自动总结历史,节省 token ============
class ConversationSummaryMemoryV2 {
private llm: ChatOpenAI;
private summary: string = "";
private messageHistory: InMemoryChatMessageHistory;
private maxTokenLimit: number;
constructor(llm: ChatOpenAI, maxTokenLimit: number = 2000) {
this.llm = llm;
this.maxTokenLimit = maxTokenLimit;
this.messageHistory = new InMemoryChatMessageHistory();
}
async addMessage(message: BaseMessage) {
await this.messageHistory.addMessage(message);
// 简单检查是否需要总结(实际应用中需要计算 token)
const messages = await this.messageHistory.getMessages();
if (messages.length > 5) { // 超过5条消息时进行总结
await this.summarize();
}
}
private async summarize() {
const messages = await this.messageHistory.getMessages();
const conversationText = messages
.map(m => `${m instanceof HumanMessage ? "Human" : "AI"}: ${m.content}`)
.join("\n");
const prompt = `请总结以下对话的关键信息,保持简洁:
${conversationText}
总结:`;
const response = await this.llm.invoke([new HumanMessage(prompt)]);
this.summary = response.content as string;
await this.messageHistory.clear(); // 清空历史,只保留总结
}
async loadMemoryVariables() {
const messages = await this.messageHistory.getMessages();
const historyText = messages
.map(m => `${m instanceof HumanMessage ? "Human" : "AI"}: ${m.content}`)
.join("\n");
const fullHistory = this.summary
? `[历史总结]: ${this.summary}\n[最新对话]: ${historyText}`
: historyText;
return { history: fullHistory };
}
}
// ============ 3. 主程序:带缓冲记忆的对话 ============
// 创建消息历史存储
const messageHistories: Record<string, InMemoryChatMessageHistory> = {};
// 创建带历史记录的链
const prompt = ChatPromptTemplate.fromMessages([
["system", "你是一个友好的AI助手。"],
new MessagesPlaceholder("history"),
["human", "{input}"],
]);
const chain = prompt.pipe(model);
const chainWithHistory = new RunnableWithMessageHistory({
runnable: chain,
getMessageHistory: async (sessionId: string) => {
if (!messageHistories[sessionId]) {
messageHistories[sessionId] = new InMemoryChatMessageHistory();
}
return messageHistories[sessionId];
},
inputMessagesKey: "input",
historyMessagesKey: "history",
});
// 多轮对话测试
async function runConversation() {
const sessionId = "user-123";
// 第一轮对话
const result1 = await chainWithHistory.invoke(
{ input: "你好,我叫小明" },
{ configurable: { sessionId } }
);
console.log("AI:", result1.content);
// 第二轮对话
const result2 = await chainWithHistory.invoke(
{ input: "我是一名程序员" },
{ configurable: { sessionId } }
);
console.log("AI:", result2.content);
// 第三轮对话 - AI 会记住前面的信息
const result3 = await chainWithHistory.invoke(
{ input: "我叫什么名字?做什么工作?" },
{ configurable: { sessionId } }
);
console.log("AI:", result3.content);
// 查看记忆内容
const history = messageHistories[sessionId];
const messages = await history?.getMessages();
console.log("\n=== 记忆内容 ===");
messages?.forEach((msg, idx) => {
const role = msg instanceof HumanMessage ? "用户" : "AI";
console.log(`${idx + 1}. ${role}: ${msg.content}`);
});
}
runConversation();
Agents:智能体
typescript
import { ChatOpenAI } from "@langchain/openai";
import { DynamicTool } from "@langchain/core/tools";
import { AgentExecutor, createToolCallingAgent } from "@langchain/classic/agents";
import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
import dotenv from 'dotenv';
dotenv.config();
// 配置 DeepSeek 模型
const model = new ChatOpenAI({
model: "deepseek-chat",
temperature: 0.7,
apiKey: process.env.DEEPSEEK_API_KEY,
configuration: {
baseURL: 'https://api.deepseek.com/v1'
}
});
// 1. 定义工具
// 天气工具
const weatherTool = new DynamicTool({
name: "get_weather",
description: "获取指定城市的天气信息。输入城市名称,返回天气情况。",
func: async (city: string) => {
const weatherData: Record<string, string> = {
"北京": "晴天,25°C,微风",
"上海": "多云,28°C,湿度60%",
"广州": "雷阵雨,30°C,注意带伞"
};
return weatherData[city] || `${city}的天气:晴转多云,温度适中`;
}
});
// 计算器工具
const calculatorTool = new DynamicTool({
name: "calculator",
description: "计算数学表达式。输入数学表达式如 '23 * 45',返回计算结果。",
func: async (expression: string) => {
try {
const result = eval(expression);
return `${expression} = ${result}`;
} catch (error: any) {
return `计算错误:${error.message}`;
}
}
});
// 2. 定义提示模板
// 注意:不能自定义 system 提示,必须使用 MessagesPlaceholder("agent_scratchpad")
const prompt = ChatPromptTemplate.fromMessages([
["system", "You are a helpful assistant"], // 必须是英文基础提示(兼容工具调用)
new MessagesPlaceholder("chat_history"),
["human", "{input}"],
new MessagesPlaceholder("agent_scratchpad"), // 必须保留,不能修改
]);
// 3. 创建 Agent
const agent = await createToolCallingAgent({
llm: model,
tools: [weatherTool as any, calculatorTool],
prompt,
});
// 4. 创建 Agent 执行器
const executor = new AgentExecutor({
agent,
tools: [weatherTool as any, calculatorTool],
maxIterations: 5,
verbose: true,
returnIntermediateSteps: true,
});
// 5. 执行
const result = await executor.invoke({
input: "北京天气怎么样?然后帮我算一下 23*45",
chat_history: [] // 传入 chat_history,用于支持聊天历史
});
console.log("\n最终答案:", result.output);
实战:用 LangChain 重写对话应用
基础对话应用
typescript
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import dotenv from "dotenv";
dotenv.config();
async function basicChat() {
try {
// 创建模型
const model = new ChatOpenAI({
model: "deepseek-chat",
temperature: 0.7,
apiKey: process.env.DEEPSEEK_API_KEY,
configuration: {
baseURL: "https://api.deepseek.com/v1"
}
});
// 创建提示模板
const prompt = ChatPromptTemplate.fromMessages([
["system", "你是一个友好的AI助手,用中文回答问题。"],
["human", "{input}"]
]);
// 构建并执行链
const chain = prompt.pipe(model);
const result = await chain.invoke({
input: "你好,请介绍一下自己"
});
console.log("AI:", result.content);
} catch (error) {
console.error("发生错误:", error);
}
}
basicChat();
带记忆的对话应用
typescript
// src/chat-with-memory.ts
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, AIMessage, BaseMessage } from "@langchain/core/messages";
import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
import dotenv from "dotenv";
dotenv.config();
// 自定义 BufferMemory 类
class BufferMemory {
private messages: BaseMessage[] = [];
private maxMessages: number = 20;
constructor(options?: { maxMessages?: number }) {
this.maxMessages = options?.maxMessages || 20;
}
async addUserMessage(content: string) {
this.messages.push(new HumanMessage(content));
this.trimMessages();
}
async addAIMessage(content: string) {
this.messages.push(new AIMessage(content));
this.trimMessages();
}
private trimMessages() {
if (this.messages.length > this.maxMessages) {
this.messages = this.messages.slice(-this.maxMessages);
}
}
async getHistory() {
return this.messages;
}
async clear() {
this.messages = [];
}
}
async function chatWithMemory() {
// 配置模型
const model = new ChatOpenAI({
model: "deepseek-chat",
temperature: 0.7,
apiKey: process.env.DEEPSEEK_API_KEY,
configuration: {
baseURL: "https://api.deepseek.com/v1"
}
});
// 创建记忆实例
const memory = new BufferMemory({ maxMessages: 10 });
// 创建提示模板
const prompt = ChatPromptTemplate.fromMessages([
["system", "你是一个友好的AI助手,用中文回答问题。"],
new MessagesPlaceholder("history"),
["human", "{input}"]
]);
// 创建对话函数
async function chat(input: string): Promise<string> {
// 获取历史消息
const history = await memory.getHistory();
// 构建输入
const formattedPrompt = await prompt.formatMessages({
input,
history
});
// 调用模型
const response = await model.invoke(formattedPrompt);
const responseText = response.content as string;
// 保存到记忆
await memory.addUserMessage(input);
await memory.addAIMessage(responseText);
return responseText;
}
// 多轮对话
const response1 = await chat("我叫张三");
console.log("AI:", response1);
const response2 = await chat("我叫什么名字?");
console.log("AI:", response2); // 会记得名字
// 查看历史
const history = await memory.getHistory();
console.log("\n=== 对话历史 ===");
history.forEach((msg, idx) => {
const role = msg instanceof HumanMessage ? "用户" : "AI";
console.log(`${idx + 1}. ${role}: ${msg.content}`);
});
}
chatWithMemory();
LangChain 调试技巧
开启 Debug 模式
typescript
// 在入口文件添加
process.env.LANGCHAIN_DEBUG = "true";
使用回调处理器
typescript
// 自定义回调
class MyCallbackHandler extends BaseCallbackHandler {
name = "my_callback";
async handleLLMStart(llm: Serialized, prompts: string[]) {
console.log(`🚀 LLM调用开始,提示词长度: ${prompts[0].length}`);
}
async handleLLMEnd(output: LLMResult) {
console.log(`✅ LLM调用完成,输出长度: ${output.generations[0][0].text.length}`);
}
async handleToolStart(tool: Serialized, input: string) {
console.log(`🔧 工具调用开始: ${tool.name}(${input})`);
}
}
LangSmith 追踪(推荐)
bash
# 设置环境变量
export LANGCHAIN_TRACING_V2=true
export LANGCHAIN_API_KEY=lsv2_sk_xxxxx
export LANGCHAIN_PROJECT=my-ai-project
# 运行应用,所有调用会自动上传到LangSmith平台
选择建议
什么时候用手写代码?
| 场景 | 推荐度 | 原因 |
|---|---|---|
| 学习原理、理解内部机制 | ✅ 推荐 | 手写能更好理解底层 |
| 简单场景(1-2次API调用) | ⚠️ 可选 | 框架可能过度设计 |
| 对代码体积有严格要求 | ✅ 推荐 | 框架会增加依赖体积 |
| 需要完全控制每一行代码 | ✅ 推荐 | 框架有抽象层 |
什么时候用 LangChain?
| 场景 | 推荐度 | 原因 |
|---|---|---|
| 多轮对话 + 记忆管理 | ✅ 强烈推荐 | Memory组件非常方便 |
| 多工具 Agent 系统 | ✅ 强烈推荐 | 省去大量循环代码 |
| RAG 应用(文档+检索) | ✅ 强烈推荐 | 内置检索器、向量存储 |
| 生产级应用(需可观测性) | ✅ 强烈推荐 | LangSmith追踪、回调 |
| 快速原型开发 | ✅ 强烈推荐 | 组件组合,快速迭代 |
学习路线建议
- 第1阶段:手写代码理解原理
- 手写 DeepSeek API 调用
- 手写 Function Calling 循环
- 手写 ReAct Agent
- 理解底层工作机制
- 第2阶段:LangChain 替代手写逻辑
- 用 Models 替代手写 API 调用
- 用 Chains 简化工作流
- 用 Memory 管理对话历史
- 用 Agents 处理工具调用
- 第3阶段:深入 LangChain 源码
- 理解 LCEL 实现原理
- 自定义 CallbackHandler
- 实现自定义工具和链
- 贡献开源代码
- 第4阶段:根据需求混合使用
- 简单场景用手写
- 复杂场景用 LangChain
- 关键路径自己控制
结语
你尝试过用LangChain开发什么应用?遇到过哪些坑?欢迎在评论区分享你的经验!
对于文章中错误的地方或有任何疑问,欢迎在评论区留言讨论!