跳到主要内容

🤖 聊天机器人

引言

聊天机器人是 LangGraphJS 最常见和最实用的应用场景之一。作为前端开发者,你可能已经熟悉了传统的聊天界面开发,但智能聊天机器人需要处理更复杂的对话逻辑、上下文管理和动态响应生成。

本节将从前端开发者的角度,展示如何使用 LangGraphJS 构建不同复杂度的聊天机器人:

  • 基础聊天机器人:简单的问答交互
  • 记忆型聊天机器人:支持多轮对话的上下文记忆
  • 智能助手:具备工具调用能力的高级聊天机器人
  • 流式聊天机器人:实时响应的用户体验优化

核心概念

对话状态管理

在传统的前端聊天应用中,我们通常只需要管理消息列表。但在智能聊天机器人中,状态管理更加复杂:

消息类型

LangGraphJS 中的消息系统支持多种消息类型:

  • HumanMessage:用户输入
  • AIMessage:AI 回复
  • SystemMessage:系统指令
  • ToolMessage:工具调用结果

基础聊天机器人

让我们从最简单的聊天机器人开始:

基础聊天机器人:

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

// 初始化 OpenAI 模型
const model = new ChatOpenAI({
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.7,
});

// 聊天节点:处理用户输入并生成回复
async function chatbotNode(state: typeof MessagesAnnotation.State) {
// 调用 LLM 生成回复
const response = await model.invoke(state.messages);

// 返回状态更新,添加 AI 回复到消息历史
return { messages: [response] };
}

// 构建聊天机器人图
const workflow = new StateGraph(MessagesAnnotation)
.addNode('chatbot', chatbotNode)
.addEdge(START, 'chatbot')
.addEdge('chatbot', END);

// 编译图
const app = workflow.compile();

// 使用示例
async function runBasicChatbot() {
console.log('=== 基础聊天机器人示例 ===');

// 单轮对话示例
const result1 = await app.invoke({
messages: [new HumanMessage('你好!你是谁?')],
});

console.log('用户:', '你好!你是谁?');
console.log('AI:', result1.messages[result1.messages.length - 1].content);

// 另一个独立的对话
const result2 = await app.invoke({
messages: [new HumanMessage('今天天气怎么样?')],
});

console.log('\n用户:', '今天天气怎么样?');
console.log('AI:', result2.messages[result2.messages.length - 1].content);

console.log('\n注意:每次调用都是独立的,没有记忆功能');
}

// 如果直接运行此文件
if (require.main === module) {
runBasicChatbot().catch(console.error);
}

export { app as basicChatbot, runBasicChatbot };

这个基础版本展示了 LangGraphJS 聊天机器人的核心结构:

  1. 状态定义:使用 MessagesAnnotation 管理对话历史
  2. 聊天节点:处理用户输入并生成回复
  3. 图构建:连接输入、处理和输出节点

记忆型聊天机器人

为了支持多轮对话,我们需要添加持久化存储:

记忆型聊天机器人:

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

// 初始化 OpenAI 模型
const model = new ChatOpenAI({
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.7,
});

// 聊天节点:处理用户输入并生成回复
async function chatbotNode(state: typeof MessagesAnnotation.State) {
// 调用 LLM 生成回复,LLM 会自动考虑整个对话历史
const response = await model.invoke(state.messages);

// 返回状态更新,添加 AI 回复到消息历史
return { messages: [response] };
}

// 创建检查点保存器(内存存储)
const checkpointer = new MemorySaver();

// 构建带记忆的聊天机器人图
const workflow = new StateGraph(MessagesAnnotation)
.addNode('chatbot', chatbotNode)
.addEdge(START, 'chatbot')
.addEdge('chatbot', END);

// 编译图,添加检查点功能
const app = workflow.compile({ checkpointer });

// 使用示例
async function runMemoryChatbot() {
console.log('=== 记忆型聊天机器人示例 ===');

// 定义线程配置,用于标识对话会话
const threadConfig = { configurable: { thread_id: 'conversation-1' } };

// 第一轮对话
console.log('\n--- 第一轮对话 ---');
const result1 = await app.invoke(
{
messages: [new HumanMessage('你好!我叫张三,我是一名前端开发者。')],
},
threadConfig
);

console.log('用户:', '你好!我叫张三,我是一名前端开发者。');
console.log('AI:', result1.messages[result1.messages.length - 1].content);

// 第二轮对话 - 测试记忆功能
console.log('\n--- 第二轮对话 ---');
const result2 = await app.invoke(
{
messages: [new HumanMessage('你还记得我的名字吗?我是做什么工作的?')],
},
threadConfig
);

console.log('用户:', '你还记得我的名字吗?我是做什么工作的?');
console.log('AI:', result2.messages[result2.messages.length - 1].content);

// 第三轮对话 - 继续测试上下文
console.log('\n--- 第三轮对话 ---');
const result3 = await app.invoke(
{
messages: [
new HumanMessage('我想学习 LangGraphjs,你能给我一些建议吗?'),
],
},
threadConfig
);

console.log('用户:', '我想学习 React,你能给我一些建议吗?');
console.log('AI:', result3.messages[result3.messages.length - 1].content);

// 演示不同线程的独立性
console.log('\n--- 新的对话线程 ---');
const newThreadConfig = { configurable: { thread_id: 'conversation-2' } };

const result4 = await app.invoke(
{
messages: [new HumanMessage('你知道我的名字吗?')],
},
newThreadConfig
);

console.log('用户:', '你知道我的名字吗?');
console.log('AI:', result4.messages[result4.messages.length - 1].content);
console.log('注意:新线程中 AI 不记得之前的对话');
}

// 获取对话历史的辅助函数
async function getConversationHistory(threadId: string) {
const config = { configurable: { thread_id: threadId } };
const state = await app.getState(config);
return state.values.messages || [];
}

// 清除对话历史的辅助函数
async function clearConversationHistory(threadId: string) {
const config = { configurable: { thread_id: threadId } };
// 通过更新状态为空消息数组来清除历史
await app.updateState(config, { messages: [] });
}

// 如果直接运行此文件
if (require.main === module) {
runMemoryChatbot().catch(console.error);
}

export {
app as memoryChatbot,
runMemoryChatbot,
getConversationHistory,
clearConversationHistory,
};

关键改进:

  • 检查点机制:使用 MemorySaver 保存对话状态
  • 线程管理:通过 thread_id 区分不同的对话会话
  • 上下文延续:自动加载历史对话记录

智能助手(带工具调用)

真正的智能助手需要能够执行实际操作:

智能助手:

import '../../utils/loadEnv';
import {
StateGraph,
MessagesAnnotation,
START,
END,
} from '@langchain/langgraph';
import { MemorySaver } from '@langchain/langgraph';
import { ToolNode } from '@langchain/langgraph/prebuilt';
import { ChatOpenAI } from '@langchain/openai';
import { HumanMessage } from '@langchain/core/messages';
import { tool } from '@langchain/core/tools';
import { z } from 'zod';

// 定义工具:天气查询
const weatherTool = tool(
async ({ location }) => {
// 模拟天气 API 调用
const weatherData = {
北京: '晴天,温度 25°C,湿度 60%',
上海: '多云,温度 22°C,湿度 70%',
广州: '小雨,温度 28°C,湿度 85%',
深圳: '晴天,温度 30°C,湿度 65%',
};

const weather = weatherData[location as keyof typeof weatherData];
return weather || `抱歉,暂时无法获取 ${location} 的天气信息`;
},
{
name: 'get_weather',
description: '获取指定城市的天气信息',
schema: z.object({
location: z.string().describe('城市名称'),
}),
}
);

// 定义工具:计算器
const calculatorTool = tool(
async ({ expression }) => {
try {
// 简单的数学表达式计算(生产环境中应使用更安全的计算库)
const result = eval(expression);
return `计算结果:${expression} = ${result}`;
} catch (error) {
return `计算错误:无法计算表达式 "${expression}"`;
}
},
{
name: 'calculator',
description: '执行数学计算',
schema: z.object({
expression: z.string().describe('数学表达式,如 "2 + 3 * 4"'),
}),
}
);

// 定义工具:时间查询
const timeTool = tool(
async ({ timezone = 'Asia/Shanghai' }) => {
const now = new Date();
const timeString = now.toLocaleString('zh-CN', {
timeZone: timezone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
return `当前时间(${timezone}):${timeString}`;
},
{
name: 'get_time',
description: '获取当前时间',
schema: z.object({
timezone: z.string().optional().describe('时区,默认为 Asia/Shanghai'),
}),
}
);

// 工具列表
const tools = [weatherTool, calculatorTool, timeTool];

// 初始化带工具的 OpenAI 模型
const model = new ChatOpenAI({
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.7,
}).bindTools(tools);

// 助手节点:处理用户输入并决定是否使用工具
async function assistantNode(state: typeof MessagesAnnotation.State) {
const response = await model.invoke(state.messages);
return { messages: [response] };
}

// 工具节点:执行工具调用
const toolNode = new ToolNode(tools);

// 路由函数:决定下一步是使用工具还是结束
function shouldContinue(state: typeof MessagesAnnotation.State) {
const lastMessage = state.messages[state.messages.length - 1];

// 检查最后一条消息是否包含工具调用
if (
'tool_calls' in lastMessage &&
lastMessage.tool_calls &&
Array.isArray(lastMessage.tool_calls) &&
lastMessage.tool_calls.length > 0
) {
return 'tools';
}

return END;
}

// 创建检查点保存器
const checkpointer = new MemorySaver();

// 构建智能助手图
const workflow = new StateGraph(MessagesAnnotation)
.addNode('assistant', assistantNode)
.addNode('tools', toolNode)
.addEdge(START, 'assistant')
.addConditionalEdges('assistant', shouldContinue)
.addEdge('tools', 'assistant');

// 编译图
const app = workflow.compile({ checkpointer });

// 使用示例
async function runAssistantChatbot() {
console.log('=== 智能助手聊天机器人示例 ===');

const threadConfig = { configurable: { thread_id: 'assistant-demo' } };

// 测试天气查询
console.log('\n--- 测试天气查询 ---');
const weatherResult = await app.invoke(
{
messages: [new HumanMessage('北京今天天气怎么样?')],
},
threadConfig
);

console.log('用户:', '北京今天天气怎么样?');
console.log(
'AI:',
weatherResult.messages[weatherResult.messages.length - 1].content
);

// 测试计算功能
console.log('\n--- 测试计算功能 ---');
const calcResult = await app.invoke(
{
messages: [new HumanMessage('帮我计算 15 * 8 + 32')],
},
threadConfig
);

console.log('用户:', '帮我计算 15 * 8 + 32');
console.log(
'AI:',
calcResult.messages[calcResult.messages.length - 1].content
);

// 测试时间查询
console.log('\n--- 测试时间查询 ---');
const timeResult = await app.invoke(
{
messages: [new HumanMessage('现在几点了?')],
},
threadConfig
);

console.log('用户:', '现在几点了?');
console.log(
'AI:',
timeResult.messages[timeResult.messages.length - 1].content
);

// 测试复合查询
console.log('\n--- 测试复合查询 ---');
const complexResult = await app.invoke(
{
messages: [new HumanMessage('告诉我现在的时间,然后查一下上海的天气')],
},
threadConfig
);

console.log('用户:', '告诉我现在的时间,然后查一下上海的天气');
console.log(
'AI:',
complexResult.messages[complexResult.messages.length - 1].content
);

// 测试普通对话(不需要工具)
console.log('\n--- 测试普通对话 ---');
const chatResult = await app.invoke(
{
messages: [new HumanMessage('你好,你能做什么?')],
},
threadConfig
);

console.log('用户:', '你好,你能做什么?');
console.log(
'AI:',
chatResult.messages[chatResult.messages.length - 1].content
);
}

// 获取可用工具列表的辅助函数
function getAvailableTools() {
return tools.map((tool) => ({
name: tool.name,
description: tool.description,
schema: tool.schema,
}));
}

// 如果直接运行此文件
if (require.main === module) {
runAssistantChatbot().catch(console.error);
}

export {
app as assistantChatbot,
runAssistantChatbot,
getAvailableTools,
tools,
};

智能助手的特点:

  • 工具集成:可以调用外部 API 和服务
  • 条件路由:根据需要决定是否使用工具
  • 结果整合:将工具执行结果融入对话

流式聊天机器人

为了提供更好的用户体验,我们可以实现流式响应:

流式聊天机器人:

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

// 初始化 OpenAI 模型
const model = new ChatOpenAI({
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.7,
streaming: true, // 启用流式响应
});

// 聊天节点:处理用户输入并生成回复
async function chatbotNode(state: typeof MessagesAnnotation.State) {
const response = await model.invoke(state.messages);
return { messages: [response] };
}

// 创建检查点保存器
const checkpointer = new MemorySaver();

// 构建流式聊天机器人图
const workflow = new StateGraph(MessagesAnnotation)
.addNode('chatbot', chatbotNode)
.addEdge(START, 'chatbot')
.addEdge('chatbot', END);

// 编译图
const app = workflow.compile({ checkpointer });

// 流式响应示例
async function runStreamingChatbot() {
console.log('=== 流式聊天机器人示例 ===');

const threadConfig = { configurable: { thread_id: 'streaming-demo' } };

console.log('\n--- 流式响应演示 ---');
console.log('用户: 请详细介绍一下 React 的核心概念');
console.log('AI: ', { newline: false });

// 使用 streamEvents 获取流式响应
for await (const event of app.streamEvents(
{
messages: [new HumanMessage('请详细介绍一下 React 的核心概念')],
},
{ version: 'v2', ...threadConfig }
)) {
// 过滤 LLM 流式输出事件
if (event.event === 'on_chat_model_stream') {
const chunk = event.data?.chunk;
if (chunk?.content) {
process.stdout.write(chunk.content);
}
}
}

console.log('\n\n--- 另一个流式响应 ---');
console.log('用户: 能给我一些学习建议吗?');
console.log('AI: ', { newline: false });

for await (const event of app.streamEvents(
{
messages: [new HumanMessage('能给我一些学习建议吗?')],
},
{ version: 'v2', ...threadConfig }
)) {
if (event.event === 'on_chat_model_stream') {
const chunk = event.data?.chunk;
if (chunk?.content) {
process.stdout.write(chunk.content);
}
}
}

console.log('\n');
}

// 流式状态更新示例
async function runStreamingStates() {
console.log('\n=== 流式状态更新示例 ===');

const threadConfig = { configurable: { thread_id: 'state-streaming' } };

console.log('\n--- 监听状态变化 ---');

// 使用 stream 方法获取每步的状态更新
const stream = await app.stream(
{
messages: [new HumanMessage('你好,请介绍一下自己')],
},
{ streamMode: 'updates', ...threadConfig }
);

for await (const chunk of stream) {
console.log('状态更新:', JSON.stringify(chunk, null, 2));
}
}

// 自定义流式处理器
class StreamingHandler {
private buffer: string = '';
private onToken?: (token: string) => void;
private onComplete?: (fullResponse: string) => void;

constructor(options: {
onToken?: (token: string) => void;
onComplete?: (fullResponse: string) => void;
}) {
this.onToken = options.onToken;
this.onComplete = options.onComplete;
}

async handleStream(
graph: typeof app,
input: { messages: any[] },
config: any
) {
this.buffer = '';

for await (const event of graph.streamEvents(input, {
version: 'v2',
...config,
})) {
if (event.event === 'on_chat_model_stream') {
const chunk = event.data?.chunk;
if (chunk?.content) {
this.buffer += chunk.content;
this.onToken?.(chunk.content);
}
}
}

this.onComplete?.(this.buffer);
return this.buffer;
}
}

// 使用自定义流式处理器的示例
async function runCustomStreamingHandler() {
console.log('\n=== 自定义流式处理器示例 ===');

const threadConfig = { configurable: { thread_id: 'custom-streaming' } };

const handler = new StreamingHandler({
onToken: (token) => {
process.stdout.write(token);
},
onComplete: (fullResponse) => {
console.log(`\n\n[完整响应长度: ${fullResponse.length} 字符]`);
},
});

console.log('\n用户: 请解释一下什么是状态管理');
console.log('AI: ');

await handler.handleStream(
app,
{
messages: [new HumanMessage('请解释一下什么是状态管理')],
},
threadConfig
);
}

// 批量流式处理示例
async function runBatchStreaming() {
console.log('\n=== 批量流式处理示例 ===');

const questions = ['什么是组件?', '什么是 Props?', '什么是 State?'];

for (let i = 0; i < questions.length; i++) {
const threadConfig = { configurable: { thread_id: `batch-${i}` } };

console.log(`\n--- 问题 ${i + 1}: ${questions[i]} ---`);
console.log('AI: ');

for await (const event of app.streamEvents(
{
messages: [new HumanMessage(questions[i])],
},
{ version: 'v2', ...threadConfig }
)) {
if (event.event === 'on_chat_model_stream') {
const chunk = event.data?.chunk;
if (chunk?.content) {
process.stdout.write(chunk.content);
}
}
}

console.log('\n');
}
}

// 如果直接运行此文件
if (require.main === module) {
async function main() {
await runStreamingChatbot();
await runStreamingStates();
await runCustomStreamingHandler();
await runBatchStreaming();
}

main().catch(console.error);
}

export {
app as streamingChatbot,
runStreamingChatbot,
runStreamingStates,
StreamingHandler,
runCustomStreamingHandler,
runBatchStreaming,
};

React 集成示例

在实际的前端应用中,你需要将聊天机器人集成到 React 组件中:

React 聊天组件:

import React, { useState, useRef, useEffect } from 'react';
import { HumanMessage } from '@langchain/core/messages';
import { memoryChatbot } from './memory-chatbot';
import { assistantChatbot } from './assistant-chatbot';

// 消息类型定义
interface Message {
id: string;
role: 'user' | 'assistant';
content: string;
timestamp: Date;
isStreaming?: boolean;
}

// 聊天机器人类型
type ChatbotType = 'basic' | 'assistant';

// 组件属性
interface ChatComponentProps {
type?: ChatbotType;
threadId?: string;
className?: string;
placeholder?: string;
maxHeight?: string;
}

export const ChatComponent: React.FC<ChatComponentProps> = ({
type = 'basic',
threadId = 'default-thread',
className = '',
placeholder = '输入您的消息...',
maxHeight = '500px',
}) => {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);

// 选择聊天机器人
const chatbot = type === 'assistant' ? assistantChatbot : memoryChatbot;

// 滚动到底部
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};

useEffect(() => {
scrollToBottom();
}, [messages]);

// 发送消息
const sendMessage = async () => {
if (!input.trim() || isLoading) return;

const userMessage: Message = {
id: Date.now().toString(),
role: 'user',
content: input.trim(),
timestamp: new Date(),
};

setMessages((prev) => [...prev, userMessage]);
setInput('');
setIsLoading(true);
setError(null);

try {
const config = { configurable: { thread_id: threadId } };

// 调用聊天机器人
const result = await chatbot.invoke(
{
messages: [new HumanMessage(userMessage.content)],
},
config
);

const assistantMessage: Message = {
id: (Date.now() + 1).toString(),
role: 'assistant',
content: result.messages[result.messages.length - 1].content as string,
timestamp: new Date(),
};

setMessages((prev) => [...prev, assistantMessage]);
} catch (err) {
console.error('聊天错误:', err);
setError('抱歉,发生了错误。请稍后再试。');
} finally {
setIsLoading(false);
}
};

// 流式发送消息(简化版本,暂时使用普通调用)
const sendStreamingMessage = async () => {
// 暂时使用普通发送,避免类型问题
await sendMessage();
};

// 处理键盘事件
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
};

// 清除对话
const clearChat = () => {
setMessages([]);
setError(null);
};

// 重试最后一条消息
const retryLastMessage = () => {
if (messages.length < 2) return;

const lastUserMessage = messages
.slice()
.reverse()
.find((msg) => msg.role === 'user');

if (lastUserMessage) {
setInput(lastUserMessage.content);
// 移除最后的用户消息和AI回复
setMessages((prev) => prev.slice(0, -2));
}
};

return (
<div className={`chat-component ${className}`}>
{/* 聊天头部 */}
<div className='chat-header'>
<h3>{type === 'assistant' ? '🤖 智能助手' : '💬 聊天机器人'}</h3>
<div className='chat-controls'>
<button
onClick={clearChat}
className='clear-btn'
disabled={isLoading}
>
清除
</button>
{error && (
<button
onClick={retryLastMessage}
className='retry-btn'
disabled={isLoading}
>
重试
</button>
)}
</div>
</div>

{/* 消息列表 */}
<div
className='messages-container'
style={{ maxHeight, overflowY: 'auto' }}
>
{messages.length === 0 && (
<div className='empty-state'>
<p>开始对话吧!👋</p>
{type === 'assistant' && (
<p className='hint'>我可以帮您查询天气、进行计算、获取时间等。</p>
)}
</div>
)}

{messages.map((message) => (
<div key={message.id} className={`message ${message.role}`}>
<div className='message-content'>
{message.content}
{message.isStreaming && (
<span className='streaming-indicator'></span>
)}
</div>
<div className='message-time'>
{message.timestamp.toLocaleTimeString()}
</div>
</div>
))}

{isLoading && !messages.some((m) => m.isStreaming) && (
<div className='message assistant'>
<div className='message-content'>
<div className='typing-indicator'>
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
)}

<div ref={messagesEndRef} />
</div>

{/* 错误提示 */}
{error && <div className='error-message'>⚠️ {error}</div>}

{/* 输入区域 */}
<div className='input-container'>
<input
ref={inputRef}
type='text'
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={handleKeyPress}
placeholder={placeholder}
disabled={isLoading}
className='message-input'
/>
<div className='input-buttons'>
<button
onClick={sendMessage}
disabled={!input.trim() || isLoading}
className='send-btn'
>
发送
</button>
<button
onClick={sendStreamingMessage}
disabled={!input.trim() || isLoading}
className='stream-btn'
title='流式响应'
>
📡
</button>
</div>
</div>

{/* 样式 */}
<style jsx>{`
.chat-component {
display: flex;
flex-direction: column;
border: 1px solid #e1e5e9;
border-radius: 8px;
background: white;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
border-bottom: 1px solid #e1e5e9;
background: #f8f9fa;
}

.chat-header h3 {
margin: 0;
color: #2d3748;
}

.chat-controls {
display: flex;
gap: 8px;
}

.clear-btn,
.retry-btn {
padding: 4px 12px;
border: 1px solid #cbd5e0;
border-radius: 4px;
background: white;
color: #4a5568;
cursor: pointer;
font-size: 12px;
}

.clear-btn:hover,
.retry-btn:hover {
background: #f7fafc;
}

.clear-btn:disabled,
.retry-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}

.messages-container {
flex: 1;
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
}

.empty-state {
text-align: center;
color: #718096;
padding: 40px 20px;
}

.empty-state .hint {
font-size: 14px;
margin-top: 8px;
}

.message {
display: flex;
flex-direction: column;
max-width: 80%;
}

.message.user {
align-self: flex-end;
}

.message.assistant {
align-self: flex-start;
}

.message-content {
padding: 12px 16px;
border-radius: 18px;
word-wrap: break-word;
white-space: pre-wrap;
}

.message.user .message-content {
background: #3182ce;
color: white;
}

.message.assistant .message-content {
background: #f7fafc;
color: #2d3748;
border: 1px solid #e2e8f0;
}

.message-time {
font-size: 11px;
color: #a0aec0;
margin-top: 4px;
padding: 0 4px;
}

.message.user .message-time {
text-align: right;
}

.streaming-indicator {
animation: blink 1s infinite;
}

@keyframes blink {
0%,
50% {
opacity: 1;
}
51%,
100% {
opacity: 0;
}
}

.typing-indicator {
display: flex;
gap: 4px;
align-items: center;
}

.typing-indicator span {
width: 8px;
height: 8px;
border-radius: 50%;
background: #cbd5e0;
animation: typing 1.4s infinite ease-in-out;
}

.typing-indicator span:nth-child(1) {
animation-delay: -0.32s;
}

.typing-indicator span:nth-child(2) {
animation-delay: -0.16s;
}

@keyframes typing {
0%,
80%,
100% {
transform: scale(0);
}
40% {
transform: scale(1);
}
}

.error-message {
padding: 12px 16px;
background: #fed7d7;
color: #c53030;
border-top: 1px solid #e1e5e9;
font-size: 14px;
}

.input-container {
display: flex;
padding: 16px;
border-top: 1px solid #e1e5e9;
gap: 8px;
}

.message-input {
flex: 1;
padding: 12px 16px;
border: 1px solid #e2e8f0;
border-radius: 24px;
outline: none;
font-size: 14px;
}

.message-input:focus {
border-color: #3182ce;
box-shadow: 0 0 0 3px rgba(49, 130, 206, 0.1);
}

.message-input:disabled {
background: #f7fafc;
color: #a0aec0;
}

.input-buttons {
display: flex;
gap: 8px;
}

.send-btn,
.stream-btn {
padding: 12px 20px;
border: none;
border-radius: 24px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
}

.send-btn {
background: #3182ce;
color: white;
}

.send-btn:hover:not(:disabled) {
background: #2c5aa0;
}

.stream-btn {
background: #38a169;
color: white;
min-width: 48px;
}

.stream-btn:hover:not(:disabled) {
background: #2f855a;
}

.send-btn:disabled,
.stream-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
`}</style>
</div>
);
};

// 使用示例组件
export const ChatExample: React.FC = () => {
const [chatType, setChatType] = useState<ChatbotType>('basic');

return (
<div style={{ padding: '20px', maxWidth: '800px', margin: '0 auto' }}>
<div style={{ marginBottom: '20px' }}>
<label style={{ marginRight: '10px' }}>聊天机器人类型:</label>
<select
value={chatType}
onChange={(e) => setChatType(e.target.value as ChatbotType)}
style={{
padding: '8px',
borderRadius: '4px',
border: '1px solid #ccc',
}}
>
<option value='basic'>基础聊天机器人</option>
<option value='assistant'>智能助手</option>
</select>
</div>

<ChatComponent
type={chatType}
threadId={`demo-${chatType}`}
placeholder={
chatType === 'assistant'
? '试试问我天气、时间或让我帮你计算...'
: '输入您的消息...'
}
/>
</div>
);
};

架构模式

简单聊天架构

智能助手架构

最佳实践

1. 状态设计

状态最小化原则

只在状态中保存必要的信息,避免存储可以重新计算的数据:

// ✅ 好的做法
const StateAnnotation = Annotation.Root({
messages: Annotation<BaseMessage[]>({
reducer: messagesStateReducer,
default: () => [],
}),
userId: Annotation<string>(),
});

// ❌ 避免的做法
const StateAnnotation = Annotation.Root({
messages: Annotation<BaseMessage[]>({
reducer: messagesStateReducer,
default: () => [],
}),
messageCount: Annotation<number>(), // 可以从 messages.length 计算
lastMessage: Annotation<string>(), // 可以从 messages 获取
});

2. 错误处理

const chatbotNode = async (state: typeof StateAnnotation.State) => {
try {
const response = await model.invoke(state.messages);
return { messages: [response] };
} catch (error) {
console.error('聊天机器人错误:', error);
return {
messages: [
new AIMessage({
content: '抱歉,我遇到了一些问题。请稍后再试。',
}),
],
};
}
};

3. 性能优化

流式响应优化

对于长回复,使用流式处理可以显著改善用户体验:

// 在 React 组件中处理流式响应
const handleStreamingResponse = async (input: string) => {
setIsLoading(true);
let currentResponse = '';

try {
for await (const chunk of graph.streamEvents(
{ messages: [new HumanMessage(input)] },
{ version: 'v2' }
)) {
if (chunk.event === 'on_chat_model_stream') {
currentResponse += chunk.data.chunk.content;
setMessages(prev => [
...prev.slice(0, -1),
{ role: 'assistant', content: currentResponse }
]);
}
}
} finally {
setIsLoading(false);
}
};

4. 用户体验考虑

  • 加载状态:显示"正在思考..."等提示
  • 错误恢复:提供重试机制
  • 输入验证:检查用户输入的有效性
  • 响应时间:设置合理的超时时间

小结

聊天机器人是 LangGraphJS 应用的基础,通过本节的学习,你应该掌握了:

  1. 基础架构:如何构建简单的问答机器人
  2. 状态管理:如何处理对话历史和上下文
  3. 工具集成:如何让聊天机器人具备执行能力
  4. 前端集成:如何在 React 应用中使用聊天机器人
  5. 性能优化:如何提供流畅的用户体验

接下来,我们将学习 RAG 系统,了解如何构建基于知识库的问答系统。