最近买了 《LangGraph 实战》这本书,有点学废了,尝试跟着官方文档重新学习了下 LangChain。
最近 LangChain LangGraph 都发布了 1.0 版本,他们也获得了大额融资,应该会有一定的稳定性。openai 之类本身的请求都非常简单,核心是 /completions,但是衍生的概念非常多,比如 human-in-loop,agent,memory。目前我们大部分的应用简单接口就能满足,后续,准备构建更加复杂的应用。
LangChain 现在是基于 LangGraph 构建的。LangChain 底层是 LangGraph。LangChain 一般用于线性场景,应用比较简单。LangGraph 基于图的概念构建,较为复杂,有多种状态以及概念。
本篇,贴一些我测试过的 demo ,给自己学习做个记录。
import { HumanMessage, SystemMessage } from "langchain";
import { ChatAlibabaTongyi } from "@langchain/community/chat_models/alibaba_tongyi";
const model = new ChatAlibabaTongyi({
model: "qwen-plus",
alibabaApiKey: "",
});
const systemMsg = new SystemMessage("You are a helpful assistant.");
const humanMsg = new HumanMessage("你是谁啊?");
const messages = [systemMsg, humanMsg];
const response = await model.invoke(messages); // Returns AIMessage
console.log(response.content);
// 使用自定义 BooleanOutputParser 示例
import { HumanMessage, SystemMessage } from "langchain";
import { ChatAlibabaTongyi } from "@langchain/community/chat_models/alibaba_tongyi";
import { BooleanOutputParser } from "./BooleanOutputParser";
// 创建布尔解析器
const booleanParser = new BooleanOutputParser();
// 创建模型链
const model = new ChatAlibabaTongyi({
model: "qwen-plus",
alibabaApiKey: "",
}).pipe(booleanParser);
// 示例 1:判断问题
const systemMsg1 = new SystemMessage(
`你是一个助手。${booleanParser.getFormatInstructions()}`
);
const humanMsg1 = new HumanMessage("1 + 1 是否等于 2?");
console.log("问题 1: 1 + 1 是否等于 2?");
const response1 = await model.invoke([systemMsg1, humanMsg1]);
console.log("回答:", response1); // 应该输出: true
console.log("类型:", typeof response1); // 输出: boolean
console.log("---");
// 示例 2:判断问题
const systemMsg2 = new SystemMessage(
`你是一个助手。${booleanParser.getFormatInstructions()}`
);
const humanMsg2 = new HumanMessage("地球是平的吗?");
console.log("问题 2: 地球是平的吗?");
const response2 = await model.invoke([systemMsg2, humanMsg2]);
console.log("回答:", response2); // 应该输出: false
console.log("类型:", typeof response2); // 输出: boolean
// 使用 StringOutputParser + 手动转换的简单方案
import { HumanMessage, SystemMessage } from "langchain";
import { ChatAlibabaTongyi } from "@langchain/community/chat_models/alibaba_tongyi";
import { StringOutputParser } from "@langchain/core/output_parsers";
// 创建模型链(使用 StringOutputParser)
const model = new ChatAlibabaTongyi({
model: "qwen-plus",
alibabaApiKey: "",
}).pipe(new StringOutputParser());
// 辅助函数:将字符串转换为布尔值
function parseBoolean(text: string): boolean {
const trimmed = text.trim().toLowerCase();
const trueValues = ["yes", "true", "1", "是", "对", "正确"];
const falseValues = ["no", "false", "0", "否", "错", "错误"];
if (trueValues.includes(trimmed)) {
return true;
}
if (falseValues.includes(trimmed)) {
return false;
}
throw new Error(`无法解析 "${text}" 为布尔值`);
}
// 示例使用
const systemMsg = new SystemMessage(
"你是一个助手。请只用 yes 或 no 来回答问题。"
);
const humanMsg = new HumanMessage("1 + 1 是否等于 2?");
const response = await model.invoke([systemMsg, humanMsg]);
console.log("模型原始回答:", response); // 字符串: "yes" 或 "no"
const booleanResult = parseBoolean(response);
console.log("解析后的布尔值:", booleanResult); // 布尔值: true 或 false
console.log("类型:", typeof booleanResult); // "boolean"
// 链使用
import { HumanMessage, SystemMessage } from "langchain";
import { ChatAlibabaTongyi } from "@langchain/community/chat_models/alibaba_tongyi";
// 修复输出解析器的导入路径
import { StringOutputParser } from "@langchain/core/output_parsers";
const model = new ChatAlibabaTongyi({
model: "qwen-plus",
alibabaApiKey: "",
})
// 这里使用了管道,就可以直接获取 content 内容了
.pipe(new StringOutputParser());
const systemMsg = new SystemMessage("You are a helpful assistant.");
const humanMsg = new HumanMessage("你是谁啊?");
const messages = [systemMsg, humanMsg];
const response = await model.invoke(messages); // Returns string (因为使用了 StrOutputParser)
console.log(response); // response 已经是字符串了,不需要 .content
// 复合链使用 - 新闻生成 + 结构化提取
// 注意,这里最终提取出了 JSON 结构的数据
import { ChatAlibabaTongyi } from "@langchain/community/chat_models/alibaba_tongyi";
import { StringOutputParser, StructuredOutputParser } from "@langchain/core/output_parsers";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { RunnableLambda } from "@langchain/core/runnables";
// 初始化模型
const model = new ChatAlibabaTongyi({
model: "qwen-plus",
alibabaApiKey: "",
});
// 第一步:根据标题生成新闻正文
const newsGenPrompt = ChatPromptTemplate.fromMessages([
["system", "请根据以下新闻标题撰写一段简短的新闻内容(100字以内):\n\n标题:{title}"],
]);
// 第一个子链:生成新闻内容
const newsChain = newsGenPrompt.pipe(model).pipe(new StringOutputParser());
// 方式1:普通函数(之前的写法)
const logNewsContent = (news: string) => {
console.log("📰 生成的新闻内容:");
console.log("─".repeat(50));
console.log(news);
console.log("─".repeat(50));
console.log();
return news; // 返回原内容,继续传递给下一个链
};
// 方式2:使用 RunnableLambda 包装(更规范的写法)
const logNewsContentRunnable = RunnableLambda.from((news: string) => {
console.log("📰 生成的新闻内容(RunnableLambda版本):");
console.log("─".repeat(50));
console.log(news);
console.log("─".repeat(50));
console.log();
return news;
});
// 第二步:从正文中提取结构化字段
const parser = StructuredOutputParser.fromNamesAndDescriptions({
time: "事件发生的时间",
location: "事件发生的地点",
event: "发生的具体事件",
});
const summaryPrompt = ChatPromptTemplate.fromMessages([
["system", "请从下面这段新闻内容中提取关键信息,并返回结构化JSON格式:\n\n{news}\n\n{format_instructions}"],
]);
// 第二个子链:提取结构化信息
const summaryChain = summaryPrompt
.pipe(model)
.pipe(new StringOutputParser())
.pipe(parser);
// 组合成一个复合 Chain(加入自定义组件)
// 可以选择使用普通函数或 RunnableLambda
const fullChain = newsChain
.pipe(logNewsContentRunnable) // 使用 RunnableLambda 版本(推荐)
// .pipe(logNewsContent) // 或者使用普通函数版本(也可以)
.pipe((news) => {
return summaryChain.invoke({
news: news,
format_instructions: parser.getFormatInstructions(),
});
});
// 调用复合链
console.log("开始执行复合链...\n");
const result = await fullChain.invoke({ title: "苹果公司在加州发布新款AI芯片" });
console.log("提取的结构化信息:");
console.log(JSON.stringify(result, null, 2));
// 多轮对话示例 - 流式输出版本
import { ChatAlibabaTongyi } from "@langchain/community/chat_models/alibaba_tongyi";
import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
import { HumanMessage, AIMessage } from "@langchain/core/messages";
import * as readline from "readline";
// 初始化模型
const model = new ChatAlibabaTongyi({
model: "qwen-plus",
alibabaApiKey: "",
});
const parser = new StringOutputParser();
// 创建提示词模板(包含历史消息占位符)
const prompt = ChatPromptTemplate.fromMessages([
["system", "你叫成龙,是著名演员。"],
new MessagesPlaceholder("messages"), // 历史消息占位符
]);
// 创建链
const chain = prompt.pipe(model).pipe(parser);
// 初始化历史消息列表
const messagesList: (HumanMessage | AIMessage)[] = [];
// 创建命令行交互接口
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
console.log("🔹 多轮对话已开启(流式输出),输入 exit 或 quit 结束对话\n");
// 递归函数处理用户输入
const chat = () => {
rl.question("你:", async (userQuery) => {
// 检查是否退出
if (userQuery.toLowerCase() === "exit" || userQuery.toLowerCase() === "quit") {
console.log("\n👋 再见!");
rl.close();
return;
}
// 1) 追加用户消息
messagesList.push(new HumanMessage(userQuery));
try {
// 2) 使用流式调用模型
process.stdout.write("小智:");
let fullResponse = "";
const stream = await chain.stream({ messages: messagesList });
// 逐块输出 流式输出
for await (const chunk of stream) {
process.stdout.write(chunk); // 流式打印,不换行
fullResponse += chunk;
}
console.log("\n"); // 输出完成后换行
// 3) 追加 AI 回复到历史
messagesList.push(new AIMessage(fullResponse));
// 4) 仅保留最近 50 条消息(防止上下文过长)
if (messagesList.length > 50) {
messagesList.splice(0, messagesList.length - 50);
}
} catch (error) {
console.error("\n❌ 错误:", error);
}
// 继续下一轮对话
chat();
});
};
// 开始对话
chat();
// LangChain 浏览器自动化 - TypeScript 版本
// 使用 Playwright 进行浏览器自动化操作
// 对应 Python 版本的 PlayWrightBrowserToolkit
// 注意:LangChain JS/TS 版本中没有内置的 PlaywrightBrowserToolkit
// 我们需要手动创建自定义工具来实现浏览器自动化功能
import { ChatOpenAI } from "@langchain/openai";
import { createAgent, tool } from "langchain";
import { chromium } from "playwright";
import { z } from "zod";
async function main() {
// 1. 初始化 Playwright 浏览器
const browser = await chromium.launch({
headless: false, // 设置为 false 可以看到浏览器操作过程
});
const context = await browser.newContext();
const page = await context.newPage();
// 2. 创建自定义浏览器工具(模拟 Python 的 PlayWrightBrowserToolkit)
// 工具1:导航到指定 URL
const navigateTool = tool(
async ({ url }: { url: string }) => {
try {
await page.goto(url, { waitUntil: "networkidle" });
return `成功导航到: ${url}`;
} catch (error: any) {
return `导航失败: ${error.message}`;
}
},
{
name: "navigate",
description: "导航到指定的 URL 地址",
schema: z.object({
url: z.string().describe("要访问的网页 URL"),
}),
}
);
// 工具2:获取页面内容
const getPageContentTool = tool(
async () => {
try {
// 提取文本内容
const text = await page.evaluate(() => {
// 移除 script 和 style 标签
const scripts = document.querySelectorAll("script, style");
scripts.forEach((el) => el.remove());
return document.body.innerText;
});
return text.slice(0, 5000); // 限制返回内容长度
} catch (error: any) {
return `获取页面内容失败: ${error.message}`;
}
},
{
name: "get_page_content",
description: "获取当前页面的文本内容",
schema: z.object({}),
}
);
// 工具3:点击元素
const clickTool = tool(
async ({ selector }: { selector: string }) => {
try {
await page.click(selector);
return `成功点击元素: ${selector}`;
} catch (error: any) {
return `点击失败: ${error.message}`;
}
},
{
name: "click",
description: "点击页面上的元素",
schema: z.object({
selector: z.string().describe("CSS 选择器"),
}),
}
);
// 工具4:填写表单
const fillTool = tool(
async ({ selector, text }: { selector: string; text: string }) => {
try {
await page.fill(selector, text);
return `成功填写: ${selector}`;
} catch (error: any) {
return `填写失败: ${error.message}`;
}
},
{
name: "fill",
description: "在输入框中填写文本",
schema: z.object({
selector: z.string().describe("CSS 选择器"),
text: z.string().describe("要填写的文本"),
}),
}
);
// 工具5:截图
const screenshotTool = tool(
async ({ path }: { path: string }) => {
try {
await page.screenshot({ path });
return `截图已保存到: ${path}`;
} catch (error: any) {
return `截图失败: ${error.message}`;
}
},
{
name: "screenshot",
description: "对当前页面进行截图",
schema: z.object({
path: z.string().describe("截图保存路径"),
}),
}
);
// 工具列表
const tools = [
navigateTool,
getPageContentTool,
clickTool,
fillTool,
screenshotTool,
];
// 3. 初始化模型(使用通义千问)
const model = new ChatOpenAI({
model: "qwen-plus",
apiKey: "",
configuration: {
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
},
temperature: 0,
});
// 4. 创建 Agent(对应 Python 的 create_openai_tools_agent)
const agent = createAgent({
model,
tools,
systemPrompt: `你是一个浏览器自动化助手。你可以使用以下工具来完成任务:
- navigate: 访问指定的网页
- get_page_content: 获取当前页面的文本内容
- click: 点击页面元素
- fill: 填写表单
- screenshot: 截图
请根据用户的指令,合理使用这些工具来完成任务。`,
});
try {
// 5. 定义任务
const command = {
messages: [
{
role: "user",
content:
"访问这个网站 https://www.microsoft.com/en-us/microsoft-365/blog/2025/01/16/copilot-is-now-included-in-microsoft-365-personal-and-family/?culture=zh-cn&country=cn 并帮我总结一下这个网站的内容",
},
],
};
// 6. 执行任务
console.log("开始执行任务...");
const response = await agent.invoke(command);
console.log("\n执行结果:");
console.log(response);
} catch (error) {
console.error("执行出错:", error);
} finally {
// 7. 清理资源
await browser.close();
}
}
// 执行主函数
main().catch(console.error);
import { MultiServerMCPClient } from "@langchain/mcp-adapters";
import { createAgent } from "langchain";
import { ChatOpenAI } from "@langchain/openai";
const client = new MultiServerMCPClient({
playwright: {
command: "npx",
args: ["@playwright/mcp@latest"],
transport: "stdio",
},
});
const model = new ChatOpenAI({
model: "qwen-plus",
apiKey: "",
configuration: {
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
},
temperature: 0,
});
const tools = await client.getTools();
console.log("工具个数:", tools.length);
console.log("\n所有工具列表:");
tools.forEach((tool, index) => {
console.log(`${index + 1}. ${tool.name} - ${tool.description}`);
});
const agent = createAgent({
model,
tools,
});
const response = await agent.invoke({
messages: [{ role: "user", content: "打开这个页面,并总结其内容,https://jscoder.com/posts/20251019195842.html" }],
});
console.log(response);
// 清理资源并退出
await client.close();
process.exit(0);
import { StateGraph, START, END, Annotation } from "@langchain/langgraph";
// 定义 State 的类型注解
const StateAnnotation = Annotation.Root({
x: Annotation<number>(),
});
// 创建一个 StateGraph
const builder = new StateGraph(StateAnnotation);
// 定义加法节点
function addition(state: typeof StateAnnotation.State) {
console.log(`加法节点收到的初始值:`, state);
return { x: state.x + 1 };
}
// 定义减法节点
function subtraction(state: typeof StateAnnotation.State) {
console.log(`减法节点收到的初始值:`, state);
return { x: state.x - 2 };
}
// 向图中添加两个节点
builder.addNode("addition", addition);
builder.addNode("subtraction", subtraction);
// 构建节点之间的边
builder.addEdge(START, "addition");
builder.addEdge("addition", "subtraction");
builder.addEdge("subtraction", END);
console.log("节点:", builder.nodes);
console.log("边:", builder.edges);
// 编译图
const graph = builder.compile();
// 打印图的 ASCII 表示
console.log(graph.getGraph().drawMermaid());
// 定义初始状态
const initialState = { x: 10 };
// 执行图
async function runGraph() {
const result = await graph.invoke(initialState);
console.log("最后的结果:", result);
}
runGraph();
import { StateGraph, Annotation, START } from "@langchain/langgraph";
import { BaseMessage, HumanMessage, AIMessage } from "@langchain/core/messages";
import { ChatOpenAI } from "@langchain/openai";
// 定义 State 类型,使用 Annotation 和 add_messages
const StateAnnotation = Annotation.Root({
messages: Annotation<BaseMessage[]>({
// add_messages 是一个 reducer,用于合并消息列表
reducer: (left: BaseMessage[], right: BaseMessage[]) => {
return left.concat(right);
},
default: () => [],
}),
});
// 创建图构建器
const graph_builder = new StateGraph(StateAnnotation);
const model = new ChatOpenAI({
model: "qwen-plus",
apiKey: "",
configuration: {
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
},
temperature: 0,
});
// 定义 chatbot 节点函数
async function chatbot(
state: typeof StateAnnotation.State
): Promise<Partial<typeof StateAnnotation.State>> {
const response = await model.invoke(state.messages);
return { messages: [response] };
}
// 添加节点
graph_builder.addNode("chatbot", chatbot);
// 添加边
graph_builder.addEdge(START, "chatbot");
// 编译图
const graph = graph_builder.compile();
// 测试多轮对话
async function runChatbot() {
const messages_list: BaseMessage[] = [
new HumanMessage("你好,我叫大模型真好玩,好久不见。"),
new AIMessage("你好呀!我是苍老师,一名女演员。很高兴认识你!"),
new HumanMessage("请问,你还记得我叫什么名字么?"),
];
const final_state = await graph.invoke({ messages: messages_list });
console.log("\n=== 对话历史 ===");
final_state.messages.forEach((msg, index) => {
const role = msg.type === "human" ? "用户" : "AI";
console.log(`\n${index + 1}. [${role}]: ${msg.content}`);
});
}
runChatbot();
import { StateGraph, Annotation, START, END } from "@langchain/langgraph";
import { BaseMessage, SystemMessage, HumanMessage } from "@langchain/core/messages";
import { ChatOpenAI } from "@langchain/openai";
import { tool } from "@langchain/core/tools";
import { ToolNode } from "@langchain/langgraph/prebuilt";
import { z } from "zod";
// 定义 AgentState
const AgentStateAnnotation = Annotation.Root({
messages: Annotation<BaseMessage[]>({
reducer: (left: BaseMessage[], right: BaseMessage[]) => {
return left.concat(right);
},
default: () => [],
}),
});
// 定义天气查询工具
const WeatherQuerySchema = z.object({
loc: z.string().describe("城市名称"),
});
const get_weather = tool(
async ({ loc }: { loc: string }) => {
/**
* 查询即时天气函数
* @param loc - 必要参数,字符串类型,用于表示查询天气的具体城市名称
* @returns 心知天气 API 查询即时天气的结果
*/
const url = "https://api.seniverse.com/v3/weather/now.json";
const params = new URLSearchParams({
key:"", // 从环境变量读取
location: loc,
language: "zh-Hans",
unit: "c",
});
try {
const response = await fetch(`${url}?${params}`);
const data = await response.json();
return JSON.stringify(data.results[0].now);
} catch (error) {
return `查询天气失败: ${error}`;
}
},
{
name: "get_weather",
description: "查询即时天气函数,用于获取指定城市的当前天气信息",
schema: WeatherQuerySchema,
}
);
// 初始化模型(使用通义千问)
const model = new ChatOpenAI({
model: "qwen-plus",
apiKey: "",
configuration: {
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
},
temperature: 0,
});
// 绑定工具到模型
const tools = [get_weather];
const model_with_tools = model.bindTools(tools);
// 定义 call_model 节点
async function call_model(
state: typeof AgentStateAnnotation.State
): Promise<Partial<typeof AgentStateAnnotation.State>> {
const system_prompt = new SystemMessage(
"你是一个AI助手,可以依据用户提问产生回答,你还具备调用天气函数的能力"
);
const response = await model_with_tools.invoke([system_prompt, ...state.messages]);
return { messages: [response] };
}
// 创建工具节点
const tool_node = new ToolNode(tools);
// 定义条件判断函数
function should_continue(
state: typeof AgentStateAnnotation.State
): "continue" | "end" {
const messages = state.messages;
const last_message = messages[messages.length - 1];
console.log("last_message", last_message);
// 检查最后一条消息是否包含工具调用
if ("tool_calls" in last_message && last_message.tool_calls && last_message.tool_calls.length > 0) {
return "continue";
} else {
return "end";
}
}
// 构建图
const graph_builder = new StateGraph(AgentStateAnnotation);
// 添加节点
graph_builder.addNode("agent", call_model);
graph_builder.addNode("tools", tool_node);
// 添加边
graph_builder.addEdge(START, "agent");
graph_builder.addEdge("tools", "agent");
// 添加条件边
graph_builder.addConditionalEdges("agent", should_continue, {
continue: "tools",
end: END,
});
// 编译图
const graph = graph_builder.compile();
// 执行测试
async function runReACT() {
console.log("=== ReACT 图执行开始 ===\n");
const final_state = await graph.invoke({
messages: [new HumanMessage("请问上海天气如何?")],
});
console.log("\n=== 完整对话历史 ===");
final_state.messages.forEach((msg, index) => {
console.log(`\n${index + 1}. [${msg._getType()}]:`);
if (msg._getType() === "human") {
console.log(` 内容: ${msg.content}`);
} else if (msg._getType() === "ai") {
console.log(` 内容: ${msg.content || "(调用工具)"}`);
if ("tool_calls" in msg && msg.tool_calls && msg.tool_calls.length > 0) {
console.log(` 工具调用:`, JSON.stringify(msg.tool_calls, null, 2));
}
} else if (msg._getType() === "tool") {
console.log(` 工具返回: ${msg.content}`);
}
});
console.log("\n=== ReACT 图执行结束 ===");
}
runReACT();