快速上手
从零构建一个能算术的智能体
工具 + 状态 + 图 = 完整的 AI 智能体光说不练
前三章学了概念和模式,但还没有真正跑过一行代码。是时候动手了。
前几章
- 知道了节点、边、状态这些概念
- 看了六种工作流模式的示意图
- 但没有写过完整的可运行代码
这一章
- 从 npm init 开始,搭一个完整项目
- 定义工具、状态、节点、边
- 跑起来一个能做加减乘除的智能体
开始之前
你需要有
- Node.js 18+ 和 npm
- 一个 DeepSeek API Key
- 读过前三章(至少了解节点、边、状态的概念)
学完后你应该能
- 用 tool() 定义自定义工具
- 用 StateSchema 定义图状态
- 组装节点和边,编译成可运行的图
- 理解 MessagesValue 和 ReducedValue 的区别
项目初始化
三步创建项目并安装依赖
# 创建项目目录
$ mkdir calculator-agent && cd calculator-agent
$ npm init -y
# 安装核心依赖
$ npm install @langchain/langgraph @langchain/core @langchain/deepseek zod
# 安装 TypeScript(如果还没装)
$ npm install -D typescript tsx
@langchain/langgraph
图编排框架:StateGraph、StateSchema、MessagesValue 等
@langchain/deepseek
DeepSeek 模型集成:ChatDeepSeek
zod
参数校验:为工具定义输入 Schema
export DEEPSEEK_API_KEY=sk-...,或者创建 .env 文件。别把 Key 硬编码在代码里。
定义工具
用 tool() + zod 创建三个数学工具
import { ChatDeepSeek } from "@langchain/deepseek";
import { tool } from "@langchain/core/tools";
import * as z from "zod";
const model = new ChatDeepSeek({
model: "deepseek-chat",
temperature: 0,
});
// 加法工具
const add = tool(({ a, b }) => a + b, {
name: "add",
description: "Add two numbers",
schema: z.object({
a: z.number().describe("First number"),
b: z.number().describe("Second number"),
}),
});
// 乘法工具
const multiply = tool(({ a, b }) => a * b, {
name: "multiply",
description: "Multiply two numbers",
schema: z.object({
a: z.number().describe("First number"),
b: z.number().describe("Second number"),
}),
});
// 除法工具
const divide = tool(({ a, b }) => a / b, {
name: "divide",
description: "Divide two numbers",
schema: z.object({
a: z.number().describe("First number"),
b: z.number().describe("Second number"),
}),
});
// 按名称索引工具,绑定到模型
const toolsByName = { [add.name]: add, [multiply.name]: multiply, [divide.name]: divide };
const tools = Object.values(toolsByName);
const modelWithTools = model.bindTools(tools);
AuthenticationError或Could not find API key→ 确认已设置DEEPSEEK_API_KEY环境变量:$ export DEEPSEEK_API_KEY=sk-...NotFoundError: model "xxx" not found→ 检查模型名称拼写是否正确,或尝试换一个可用模型
定义状态
MessagesValue 追踪对话,ReducedValue 追踪调用次数

图:状态在节点间的流转
【AI 生图提示词】请生成一张中文技术示意图,风格简洁、配色柔和、适合教学: - 画面主体:一个横向流程图,从左到右展示 4 个节点 - 节点 1:"START"(灰色圆角矩形) - 节点 2:"llmCall"(蓝色圆角矩形)——标注:"读取 messages,调用模型,追加 AI 回复" - 菱形判断:"有 tool_calls?"——是→节点 3,否→节点 4 - 节点 3:"toolNode"(绿色圆角矩形)——标注:"执行工具,追加 ToolMessage" - 节点 4:"END"(灰色圆角矩形) - 节点 3 有一条回边回到节点 2,标注"循环" - 底部一条横线,下方展示 State 结构:"messages: MessagesValue(自动追加)| llmCalls: ReducedValue(累加计数)" - 各元素关系:节点之间是线性串联(START→llmCall→判断),判断有条件分支(有tool_calls→toolNode,无→END),toolNode 回到 llmCall 形成循环 - 关键标注:每个节点旁标注它在 State 上的读写操作,回边标注"循环",底部 State 标注"所有节点共享" - 配色建议:浅灰背景,节点用柔和蓝/绿色,判断用橙色,回边用红色虚线 - 画面比例:16:9 - 避免:过度装饰、3D 效果
import {
StateGraph, StateSchema, MessagesValue, ReducedValue,
GraphNode, ConditionalEdgeRouter, START, END,
} from "@langchain/langgraph";
const MessagesState = new StateSchema({
// 消息历史 — 内置追加 reducer,新消息自动拼到末尾
messages: MessagesValue,
// LLM 调用计数 — 自定义 reducer,每次 +1
llmCalls: new ReducedValue(
z.number().default(0),
{ reducer: (x, y) => x + y }
),
});
MessagesValue
内置的消息追加器。节点返回 { messages: [newMsg] },新消息自动拼到已有消息列表末尾。
ReducedValue
自定义 reducer 的字段。这里每次返回 { llmCalls: 1 },reducer 把它加到已有值上。
模型节点
调用 LLM,把回复追加到消息历史
import { SystemMessage } from "@langchain/core/messages";
const llmCall: GraphNode<typeof MessagesState> = async (state) => {
const response = await modelWithTools.invoke([
new SystemMessage(
"You are a helpful assistant tasked with performing arithmetic on a set of inputs."
),
...state.messages,
]);
return {
messages: [response], // MessagesValue 自动追加
llmCalls: 1, // ReducedValue 累加 +1
};
};
工具节点
执行模型请求的工具调用,把结果追加到消息历史
import { AIMessage, ToolMessage } from "@langchain/core/messages";
const toolNode: GraphNode<typeof MessagesState> = async (state) => {
const lastMessage = state.messages.at(-1);
// 安全检查:最后一条必须是 AI 消息
if (lastMessage == null || !AIMessage.isInstance(lastMessage)) {
return { messages: [] };
}
// 逐个执行工具调用
const result: ToolMessage[] = [];
for (const toolCall of lastMessage.tool_calls ?? []) {
const tool = toolsByName[toolCall.name];
const observation = await tool.invoke(toolCall);
result.push(observation);
}
return { messages: result };
};
结束判断
条件边:模型还要调工具吗?
const shouldContinue: ConditionalEdgeRouter<
typeof MessagesState, "toolNode"
> = (state) => {
const lastMessage = state.messages.at(-1);
// 不是 AI 消息 → 结束
if (!lastMessage || !AIMessage.isInstance(lastMessage)) {
return END;
}
// AI 请求了工具调用 → 去执行工具
if (lastMessage.tool_calls?.length) {
return "toolNode";
}
// AI 没有请求工具 → 回复用户,结束
return END;
};
组装智能体
把所有零件拼成一个完整的图
const agent = new StateGraph(MessagesState)
.addNode("llmCall", llmCall)
.addNode("toolNode", toolNode)
.addEdge(START, "llmCall")
.addConditionalEdges("llmCall", shouldContinue, ["toolNode", END])
.addEdge("toolNode", "llmCall")
.compile();

图:完整的智能体执行流程
【AI 生图提示词】请生成一张中文技术流程图,风格简洁、配色柔和、适合教学: - 画面主体:一个循环流程图 - 节点 1:"START"(灰色圆形) - 箭头→节点 2:"llmCall 模型调用"(蓝色圆角矩形) - 箭头→菱形判断:"有 tool_calls?"(橙色菱形) - 判断"是"→节点 3:"toolNode 工具执行"(绿色圆角矩形) - 节点 3 有一条回边回到节点 2,标注"返回结果,继续对话" - 判断"否"→节点 4:"END"(灰色圆形) - 底部标注完整调用示例:"用户: Add 3 and 4 → AI: 调用 add(3,4) → 工具: 7 → AI: The answer is 7" - 各元素关系:START→llmCall 是线性连接,llmCall 到菱形判断是条件分支(有tool_calls→toolNode→回到llmCall循环,无→END结束) - 关键标注:llmCall 标注"调用模型",toolNode 标注"执行工具并返回结果",回边标注"shouldContinue 判断",底部示例展示完整调用链 - 配色建议:浅灰背景,节点用柔和色,回边用红色虚线 - 画面比例:16:9 - 避免:过度装饰、3D 效果、杂乱的颜色
toolNode → llmCall 形成循环。工具执行完后,模型需要看到结果才能决定下一步——继续调工具,还是给用户最终答案。
运行测试
用一句话启动智能体
import { HumanMessage } from "@langchain/core/messages";
const result = await agent.invoke({
messages: [new HumanMessage("Add 3 and 4.")],
});
// 打印完整的消息历史
for (const message of result.messages) {
console.log(`[${message.type}]: ${message.text}`);
}
# 预期输出:
[human]: Add 3 and 4.
[ai]: [调用 add 工具, a=3, b=4]
[tool]: 7
[ai]: The answer is 7.
# llmCalls: 2(模型被调用了两次)
排错指南
API Key 报错
症状:AuthenticationError: Invalid API Key
修复:检查环境变量 DEEPSEEK_API_KEY 是否设置正确。
工具名找不到
症状:Tool not found: add
修复:确认 toolsByName 的 key 和工具的 name 属性一致。注意大小写。
消息类型错误
症状:lastMessage.tool_calls is not a function
修复:先检查 AIMessage.isInstance(lastMessage),不是所有消息都有 tool_calls。
无限循环
症状:智能体一直调工具,不停止。
修复:检查 shouldContinue 逻辑——确保模型不调工具时返回 END,而不是继续循环。
动手练习
给计算器智能体加上减法和乘方
- 添加
subtract工具(a - b)和power工具(a ^ b) - 把它们注册到
toolsByName并绑定到模型 - 测试
"What is 2 to the power of 10?",确认智能体能正确调用 power 工具
参考答案
// 减法工具
const subtract = tool(({ a, b }) => a - b, {
name: "subtract",
description: "Subtract b from a",
schema: z.object({
a: z.number().describe("First number"),
b: z.number().describe("Second number"),
}),
});
// 乘方工具
const power = tool(({ a, b }) => Math.pow(a, b), {
name: "power",
description: "Raise a to the power of b",
schema: z.object({
a: z.number().describe("Base"),
b: z.number().describe("Exponent"),
}),
});
// 更新工具注册
const toolsByName = {
[add.name]: add,
[multiply.name]: multiply,
[divide.name]: divide,
[subtract.name]: subtract,
[power.name]: power,
};
const modelWithTools = model.bindTools(Object.values(toolsByName));