常见用例

聊天机器人

构建具备记忆、工具调用和流式响应能力的智能聊天机器人

📚 学习目标

学完这篇文章后,你将能够:

  • 使用 MemorySaver 实现多轮对话记忆
  • 构建具备工具调用能力的智能助手
  • 实现流式响应以优化用户体验

前置知识

在开始学习之前,建议先阅读:

你需要了解:

  • MessagesAnnotation 的基本用法

1 基础聊天机器人

最简单的聊天机器人只需要一个节点:接收消息,调用 LLM,返回回复。

import { StateGraph, START, END } from '@langchain/langgraph';
import { MessagesAnnotation } from '@langchain/langgraph';
import { ChatOpenAI } from '@langchain/openai';
import { HumanMessage } from '@langchain/core/messages';

const model = new ChatOpenAI({ model: 'gpt-4o-mini', temperature: 0.2 });

// 最小聊天节点:把 messages 交给模型,返回新消息
const chatbotNode = async (state: typeof MessagesAnnotation.State) => {
  const response = await model.invoke(state.messages);
  return { messages: [response] };
};

export const chatApp = new StateGraph(MessagesAnnotation)
  .addNode('chatbot', chatbotNode)
  .addEdge(START, 'chatbot')
  .addEdge('chatbot', END)
  .compile();

// 调用示例
await chatApp.invoke({ messages: [new HumanMessage('你好,介绍一下你自己')] });

代码解析

  1. MessagesAnnotation 提供了 messages 的合并规则(把新消息追加到历史里)。
  2. 节点只返回“增量更新”:{ messages: [response] }
  3. 这是一条最短链路:用户 -> 模型 -> 输出。

2 从 Demo 到“能用”:你需要补齐哪些能力?

一个产品级聊天机器人至少会遇到这些问题:

能力为什么需要你会用到的章节
记忆(thread_id)多轮对话不能每次从头来持久化
工具调用查天气/查资料/查数据库工具调用
流式输出打字机效果 + 降低感知延迟流式处理
错误恢复工具失败、超时、重试错误处理

3 添加记忆 (Memory)

为了让机器人记住之前的对话,我们需要:

  1. 使用 MemorySaver 作为 checkpointer。
  2. 在调用时传入 thread_id
import { MemorySaver } from '@langchain/langgraph';
import { HumanMessage } from '@langchain/core/messages';

const checkpointer = new MemorySaver();
// 👇 编译时传入 checkpointer
const app = workflow.compile({ checkpointer });

// 👇 调用时传入 thread_id
const config = { configurable: { thread_id: "user-123" } };
await app.invoke({ messages: [new HumanMessage("Hi!")] }, config);

// 同一 thread_id 再次调用:历史会自动加载并合并
await app.invoke({ messages: [new HumanMessage('我刚才说了什么?')] }, config);

📝 提醒

thread_id 是“会话主键”。建议你在真实产品里用更稳定的格式,例如 userId:conversationId


4 智能助手 (工具调用)

现代聊天机器人通常需要调用工具(如搜索、查天气)。

架构图

关键代码

利用 bindToolsToolNode 可以快速实现。

下面是一段“可复制”的最小 ReAct Loop(省略真实 API 调用,重点看结构):

import { StateGraph, START, END } from '@langchain/langgraph';
import { MessagesAnnotation } from '@langchain/langgraph';
import { ToolNode } from '@langchain/langgraph/prebuilt';
import { tool } from '@langchain/core/tools';
import { z } from 'zod';

const weatherTool = tool(
  async ({ city }) => `Weather in ${city}: Sunny`,
  {
    name: 'get_weather',
    description: 'Get current weather for a city',
    schema: z.object({ city: z.string() }),
  }
);

const tools = [weatherTool];
const modelWithTools = model.bindTools(tools);
const toolNode = new ToolNode(tools);

const agentNode = async (state: typeof MessagesAnnotation.State) => {
  const response = await modelWithTools.invoke(state.messages);
  return { messages: [response] };
};

const shouldContinue = (state: typeof MessagesAnnotation.State) => {
  const lastMessage = state.messages[state.messages.length - 1];
  // 简化写法:只要最后一条消息包含 tool_calls,就继续走 tools
  if ((lastMessage as { tool_calls?: unknown[] })?.tool_calls?.length) return 'tools';
  return END;
};

export const reactChat = new StateGraph(MessagesAnnotation)
  .addNode('agent', agentNode)
  .addNode('tools', toolNode)
  .addEdge(START, 'agent')
  .addConditionalEdges('agent', shouldContinue)
  .addEdge('tools', 'agent')
  .compile();

💡 提示

循环边存在时,调用时加 recursionLimit(例如 10-20),避免模型陷入坏循环。


5 流式响应 (Streaming)

LangGraph 支持流式输出,这对于聊天体验至关重要。

import { HumanMessage } from '@langchain/core/messages';

const stream = await reactChat.stream(
  { messages: [new HumanMessage('用 200 字讲一个故事')] },
  { streamMode: 'messages' }
);

for await (const [msg] of stream) {
  // msg 通常是 chunk 类型(用于实现打字机效果)
  process.stdout.write(String(msg.content));
}

当你需要“更底层的事件”(例如工具开始/结束、模型 token 流)时,使用 streamEvents(见 流式处理)。


6 对话风格与系统提示

如果你希望机器人保持统一口吻(例如“专业顾问”或“冷静客服”),可以在消息中添加系统提示:

import { SystemMessage } from '@langchain/core/messages';

const systemPrompt = new SystemMessage(
  '你是专业客服,回答必须简洁、礼貌,并在最后给出下一步建议。'
);

await chatApp.invoke({
  messages: [systemPrompt, new HumanMessage('我想退货怎么办?')],
});

💡 说明

系统提示越清晰,回复风格越稳定。避免过长的系统提示,以免挤占上下文窗口。

7 工具失败与降级

当工具调用失败时,建议做降级处理,避免对话中断:

const safeToolNode = async (state: typeof MessagesAnnotation.State) => {
  try {
    return await toolNode.invoke(state);
  } catch (error) {
    return {
      messages: [
        {
          role: 'tool',
          content: `工具失败:${(error as Error).message}`,
        },
      ],
    };
  }
};

⚠️ 注意

建议将“工具失败”反馈给 LLM,让它尝试替代方案或向用户解释限制。

8 控制上下文长度

长期对话会导致上下文过长,可以使用“摘要”或“截断策略”:

const summarizeHistory = async (messages: BaseMessage[]) => {
  const summary = await model.invoke([
    new SystemMessage('请将以下对话总结为 5 条要点'),
    ...messages,
  ]);
  return String(summary.content);
};

ℹ️ 说明

将摘要写回状态后,可以在下一轮对话中替代冗长历史。


9 会话管理与多用户

真实应用中,通常需要维护多个并发会话:

const makeThreadId = (userId: string, conversationId: string) =>
  `${userId}:${conversationId}`;

const config = {
  configurable: {
    thread_id: makeThreadId('user-42', 'conv-001'),
  },
};

await app.invoke({ messages: [new HumanMessage('你好')] }, config);

💡 建议

把 thread_id 持久化到数据库,避免前端刷新导致会话丢失。

10 简单的前端集成思路

如果你在前端做聊天 UI,可以把“发送/接收”拆成两层:

  1. 前端只负责维护消息列表
  2. 后端负责调用 LangGraph,并返回增量消息
// 前端伪代码
const onSend = async (text: string) => {
  appendMessage({ role: 'user', content: text });
  const resp = await fetch('/api/chat', { method: 'POST', body: JSON.stringify({ text }) });
  const data = await resp.json();
  appendMessage({ role: 'assistant', content: data.reply });
};

ℹ️ 说明

流式输出时可用 SSE/WebSocket,把 token 逐步追加到 UI。


11 安全边界与输入校验

聊天机器人通常需要做输入校验与工具白名单控制:

  • 限制工具调用范围:只注册必要工具
  • 过滤敏感输入:避免提示注入
  • 日志审计:记录关键决策路径
const safeTools = [weatherTool, searchTool];
const modelWithTools = model.bindTools(safeTools);

⚠️ 注意

不要把“危险操作”暴露给模型(如文件删除、支付等)。


💡 练习题

  1. 实战题:构建一个“算命大师”聊天机器人。它应该:

    • 记住用户的名字和生日(使用 Memory)。
    • 有一个 calculate_fortune 工具,根据生日计算运势。
    • 语气始终神秘兮兮的。
    点击查看答案

    使用 MemorySaver + thread_id 维护会话,工具调用逻辑可复用第 4 节中的 ReAct Loop。

  2. 操作题:为聊天机器人添加统一风格(系统提示)。

    点击查看答案

    在每次调用时加入 SystemMessage 作为第一条消息。

  3. 思考题:工具调用失败时,应该如何向用户解释?

    点击查看答案

    可以提示“工具暂不可用”,并给出替代方案或让用户换个问题。

  4. 操作题:实现一个“摘要节点”,把历史对话压缩为 5 条要点。

    点击查看答案

    复用模型生成摘要,然后将摘要写回状态替代历史 messages。

  5. 思考题:流式输出比普通输出体验好在哪里?

    点击查看答案

    能显著降低用户等待焦虑,提升响应的“即时感”。


✅ 总结

本章要点

  • MemorySaver + thread_id 是实现记忆的关键。
  • 工具调用让聊天机器人变成了真正的“助手”。
  • 流式输出能显著提升长文本生成的体验。

下一步:如何让机器人基于文档回答问题?学习RAG 系统

登录以继续阅读

解锁完整文档、代码示例及更多高级功能。

立即登录

On this page