构建聊天机器人
本教程之前使用 RunnableWithMessageHistory 构建了一个聊天机器人。您可以在 v0.2 文档中访问本教程的这个版本。
LangGraph 实现提供了许多优于 RunnableWithMessageHistory
的优势,包括持久化应用程序状态的任意组件的能力(而不仅仅是消息)。
概述
我们将介绍如何设计和实现由 LLM 驱动的聊天机器人的示例。这个聊天机器人将能够进行对话并记住之前的交互。
请注意,我们构建的这个聊天机器人将仅使用语言模型进行对话。您可能正在寻找以下几个其他相关概念
本教程将涵盖基础知识,这对这两个更高级的主题将有所帮助,但如果您选择,可以随意直接跳到那里。
设置
Jupyter Notebook
本指南(以及文档中的大多数其他指南)使用 Jupyter notebooks,并假定读者也是如此。Jupyter notebooks 非常适合学习如何使用 LLM 系统,因为通常情况下事情可能会出错(意外输出、API 宕机等),并且在交互式环境中学习指南是更好地理解它们的好方法。
本教程和其他教程最好在 Jupyter notebook 中运行。有关如何安装的说明,请参阅此处。
安装
对于本教程,我们将需要 @langchain/core
和 langgraph
- npm
- yarn
- pnpm
npm i @langchain/core @langchain/langgraph uuid
yarn add @langchain/core @langchain/langgraph uuid
pnpm add @langchain/core @langchain/langgraph uuid
有关更多详细信息,请参阅我们的安装指南。
LangSmith
您使用 LangChain 构建的许多应用程序将包含多个步骤,其中包含多次 LLM 调用。随着这些应用程序变得越来越复杂,能够检查您的链或代理内部究竟发生了什么是至关重要的。最好的方法是使用 LangSmith。
在上面的链接注册后,请确保设置您的环境变量以开始记录追踪
process.env.LANGSMITH_TRACING = "true";
process.env.LANGSMITH_API_KEY = "...";
快速入门
首先,让我们学习如何单独使用语言模型。LangChain 支持许多不同的语言模型,您可以互换使用它们 - 选择您想要使用的模型!
选择您的聊天模型
- Groq
- OpenAI
- Anthropic
- FireworksAI
- MistralAI
- VertexAI
安装依赖项
请参阅 本节,了解有关安装集成包的常规说明.
- npm
- yarn
- pnpm
npm i @langchain/groq
yarn add @langchain/groq
pnpm add @langchain/groq
添加环境变量
GROQ_API_KEY=your-api-key
实例化模型
import { ChatGroq } from "@langchain/groq";
const llm = new ChatGroq({
model: "llama-3.3-70b-versatile",
temperature: 0
});
安装依赖项
请参阅 本节,了解有关安装集成包的常规说明.
- npm
- yarn
- pnpm
npm i @langchain/openai
yarn add @langchain/openai
pnpm add @langchain/openai
添加环境变量
OPENAI_API_KEY=your-api-key
实例化模型
import { ChatOpenAI } from "@langchain/openai";
const llm = new ChatOpenAI({
model: "gpt-4o-mini",
temperature: 0
});
安装依赖项
请参阅 本节,了解有关安装集成包的常规说明.
- npm
- yarn
- pnpm
npm i @langchain/anthropic
yarn add @langchain/anthropic
pnpm add @langchain/anthropic
添加环境变量
ANTHROPIC_API_KEY=your-api-key
实例化模型
import { ChatAnthropic } from "@langchain/anthropic";
const llm = new ChatAnthropic({
model: "claude-3-5-sonnet-20240620",
temperature: 0
});
安装依赖项
请参阅 本节,了解有关安装集成包的常规说明.
- npm
- yarn
- pnpm
npm i @langchain/community
yarn add @langchain/community
pnpm add @langchain/community
添加环境变量
FIREWORKS_API_KEY=your-api-key
实例化模型
import { ChatFireworks } from "@langchain/community/chat_models/fireworks";
const llm = new ChatFireworks({
model: "accounts/fireworks/models/llama-v3p1-70b-instruct",
temperature: 0
});
安装依赖项
请参阅 本节,了解有关安装集成包的常规说明.
- npm
- yarn
- pnpm
npm i @langchain/mistralai
yarn add @langchain/mistralai
pnpm add @langchain/mistralai
添加环境变量
MISTRAL_API_KEY=your-api-key
实例化模型
import { ChatMistralAI } from "@langchain/mistralai";
const llm = new ChatMistralAI({
model: "mistral-large-latest",
temperature: 0
});
安装依赖项
请参阅 本节,了解有关安装集成包的常规说明.
- npm
- yarn
- pnpm
npm i @langchain/google-vertexai
yarn add @langchain/google-vertexai
pnpm add @langchain/google-vertexai
添加环境变量
GOOGLE_APPLICATION_CREDENTIALS=credentials.json
实例化模型
import { ChatVertexAI } from "@langchain/google-vertexai";
const llm = new ChatVertexAI({
model: "gemini-1.5-flash",
temperature: 0
});
让我们首先直接使用模型。ChatModel
是 LangChain “Runnables” 的实例,这意味着它们公开了一个用于与其交互的标准接口。要简单地调用模型,我们可以将消息列表传递给 .invoke
方法。
await llm.invoke([{ role: "user", content: "Hi im bob" }]);
AIMessage {
"id": "chatcmpl-AekDrrCyaBauLYHuVv3dkacxW2G1J",
"content": "Hi Bob! How can I help you today?",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"promptTokens": 10,
"completionTokens": 10,
"totalTokens": 20
},
"finish_reason": "stop",
"usage": {
"prompt_tokens": 10,
"completion_tokens": 10,
"total_tokens": 20,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"system_fingerprint": "fp_6fc10e10eb"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"output_tokens": 10,
"input_tokens": 10,
"total_tokens": 20,
"input_token_details": {
"audio": 0,
"cache_read": 0
},
"output_token_details": {
"audio": 0,
"reasoning": 0
}
}
}
模型本身没有任何状态概念。例如,如果您提出后续问题
await llm.invoke([{ role: "user", content: "Whats my name" }]);
AIMessage {
"id": "chatcmpl-AekDuOk1LjOdBVLtuCvuHjAs5aoad",
"content": "I'm sorry, but I don't have access to personal information about users unless you've shared it with me in this conversation. How can I assist you today?",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"promptTokens": 10,
"completionTokens": 30,
"totalTokens": 40
},
"finish_reason": "stop",
"usage": {
"prompt_tokens": 10,
"completion_tokens": 30,
"total_tokens": 40,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"system_fingerprint": "fp_6fc10e10eb"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"output_tokens": 30,
"input_tokens": 10,
"total_tokens": 40,
"input_token_details": {
"audio": 0,
"cache_read": 0
},
"output_token_details": {
"audio": 0,
"reasoning": 0
}
}
}
让我们看一下 LangSmith 追踪示例
我们可以看到它没有将之前的对话轮次纳入上下文,也无法回答问题。这造成了糟糕的聊天机器人体验!
为了解决这个问题,我们需要将整个对话历史记录传递到模型中。让我们看看当我们这样做时会发生什么
await llm.invoke([
{ role: "user", content: "Hi! I'm Bob" },
{ role: "assistant", content: "Hello Bob! How can I assist you today?" },
{ role: "user", content: "What's my name?" },
]);
AIMessage {
"id": "chatcmpl-AekDyJdj6y9IREyNIf3tkKGRKhN1Z",
"content": "Your name is Bob! How can I help you today, Bob?",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"promptTokens": 33,
"completionTokens": 14,
"totalTokens": 47
},
"finish_reason": "stop",
"usage": {
"prompt_tokens": 33,
"completion_tokens": 14,
"total_tokens": 47,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"system_fingerprint": "fp_6fc10e10eb"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"output_tokens": 14,
"input_tokens": 33,
"total_tokens": 47,
"input_token_details": {
"audio": 0,
"cache_read": 0
},
"output_token_details": {
"audio": 0,
"reasoning": 0
}
}
}
现在我们可以看到我们得到了很好的回应!
这是聊天机器人进行对话互动能力的基本思想。那么我们如何最好地实现这一点呢?
消息持久化
LangGraph 实现了内置的持久化层,使其成为支持多轮对话的聊天应用的理想选择。
将我们的聊天模型包装在最小的 LangGraph 应用程序中,可以让我们自动持久化消息历史记录,从而简化多轮应用程序的开发。
LangGraph 配备了一个简单的内存检查点,我们将在下面使用它。
import {
START,
END,
MessagesAnnotation,
StateGraph,
MemorySaver,
} from "@langchain/langgraph";
// Define the function that calls the model
const callModel = async (state: typeof MessagesAnnotation.State) => {
const response = await llm.invoke(state.messages);
return { messages: response };
};
// Define a new graph
const workflow = new StateGraph(MessagesAnnotation)
// Define the node and edge
.addNode("model", callModel)
.addEdge(START, "model")
.addEdge("model", END);
// Add memory
const memory = new MemorySaver();
const app = workflow.compile({ checkpointer: memory });
我们现在需要创建一个 config
,每次都将其传递给 runnable。此配置包含不直接属于输入的信息,但仍然有用。在这种情况下,我们想要包含一个 thread_id
。它应该看起来像
import { v4 as uuidv4 } from "uuid";
const config = { configurable: { thread_id: uuidv4() } };
这使我们能够使用单个应用程序支持多个对话线程,这是应用程序具有多个用户时的常见需求。
然后我们可以调用应用程序
const input = [
{
role: "user",
content: "Hi! I'm Bob.",
},
];
const output = await app.invoke({ messages: input }, config);
// The output contains all messages in the state.
// This will log the last message in the conversation.
console.log(output.messages[output.messages.length - 1]);
AIMessage {
"id": "chatcmpl-AekEFPclmrO7YfAe7J0zUAanS4ifx",
"content": "Hi Bob! How can I assist you today?",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"promptTokens": 12,
"completionTokens": 10,
"totalTokens": 22
},
"finish_reason": "stop",
"usage": {
"prompt_tokens": 12,
"completion_tokens": 10,
"total_tokens": 22,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"system_fingerprint": "fp_6fc10e10eb"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"output_tokens": 10,
"input_tokens": 12,
"total_tokens": 22,
"input_token_details": {
"audio": 0,
"cache_read": 0
},
"output_token_details": {
"audio": 0,
"reasoning": 0
}
}
}
const input2 = [
{
role: "user",
content: "What's my name?",
},
];
const output2 = await app.invoke({ messages: input2 }, config);
console.log(output2.messages[output2.messages.length - 1]);
AIMessage {
"id": "chatcmpl-AekEJgCfLodGCcuLgLQdJevH7CpCJ",
"content": "Your name is Bob! How can I help you today, Bob?",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"promptTokens": 34,
"completionTokens": 14,
"totalTokens": 48
},
"finish_reason": "stop",
"usage": {
"prompt_tokens": 34,
"completion_tokens": 14,
"total_tokens": 48,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"system_fingerprint": "fp_6fc10e10eb"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"output_tokens": 14,
"input_tokens": 34,
"total_tokens": 48,
"input_token_details": {
"audio": 0,
"cache_read": 0
},
"output_token_details": {
"audio": 0,
"reasoning": 0
}
}
}
太棒了!我们的聊天机器人现在记住了关于我们的事情。如果我们更改配置以引用不同的 thread_id
,我们可以看到它重新开始对话。
const config2 = { configurable: { thread_id: uuidv4() } };
const input3 = [
{
role: "user",
content: "What's my name?",
},
];
const output3 = await app.invoke({ messages: input3 }, config2);
console.log(output3.messages[output3.messages.length - 1]);
AIMessage {
"id": "chatcmpl-AekELvPXLtjOKgLN63mQzZwvyo12J",
"content": "I'm sorry, but I don't have access to personal information about individuals unless you share it with me. How can I assist you today?",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"promptTokens": 11,
"completionTokens": 27,
"totalTokens": 38
},
"finish_reason": "stop",
"usage": {
"prompt_tokens": 11,
"completion_tokens": 27,
"total_tokens": 38,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"system_fingerprint": "fp_39a40c96a0"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"output_tokens": 27,
"input_tokens": 11,
"total_tokens": 38,
"input_token_details": {
"audio": 0,
"cache_read": 0
},
"output_token_details": {
"audio": 0,
"reasoning": 0
}
}
}
但是,我们始终可以返回到原始对话(因为我们将其持久化在数据库中)
const output4 = await app.invoke({ messages: input2 }, config);
console.log(output4.messages[output4.messages.length - 1]);
AIMessage {
"id": "chatcmpl-AekEQ8Z5JmYquSfzPsCWv1BDTKZSh",
"content": "Your name is Bob. Is there something specific you would like to talk about?",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"promptTokens": 60,
"completionTokens": 16,
"totalTokens": 76
},
"finish_reason": "stop",
"usage": {
"prompt_tokens": 60,
"completion_tokens": 16,
"total_tokens": 76,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"system_fingerprint": "fp_39a40c96a0"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"output_tokens": 16,
"input_tokens": 60,
"total_tokens": 76,
"input_token_details": {
"audio": 0,
"cache_read": 0
},
"output_token_details": {
"audio": 0,
"reasoning": 0
}
}
}
这就是我们如何支持聊天机器人与许多用户进行对话的方式!
现在,我们所做的只是在模型周围添加一个简单的持久化层。我们可以通过添加提示模板来使其更复杂和个性化。
提示模板
提示模板有助于将原始用户信息转换为 LLM 可以使用的格式。在这种情况下,原始用户输入只是一条消息,我们将其传递给 LLM。现在让我们使其稍微复杂一些。首先,让我们添加一个带有某些自定义指令的系统消息(但仍然将消息作为输入)。接下来,我们将添加除了消息之外的更多输入。
为了添加系统消息,我们将创建一个 ChatPromptTemplate
。我们将利用 MessagesPlaceholder
来传递所有消息。
import { ChatPromptTemplate } from "@langchain/core/prompts";
const promptTemplate = ChatPromptTemplate.fromMessages([
[
"system",
"You talk like a pirate. Answer all questions to the best of your ability.",
],
["placeholder", "{messages}"],
]);
我们现在可以更新我们的应用程序以合并此模板
import {
START,
END,
MessagesAnnotation,
StateGraph,
MemorySaver,
} from "@langchain/langgraph";
// Define the function that calls the model
const callModel2 = async (state: typeof MessagesAnnotation.State) => {
const prompt = await promptTemplate.invoke(state);
const response = await llm.invoke(prompt);
// Update message history with response:
return { messages: [response] };
};
// Define a new graph
const workflow2 = new StateGraph(MessagesAnnotation)
// Define the (single) node in the graph
.addNode("model", callModel2)
.addEdge(START, "model")
.addEdge("model", END);
// Add memory
const app2 = workflow2.compile({ checkpointer: new MemorySaver() });
我们以相同的方式调用应用程序
const config3 = { configurable: { thread_id: uuidv4() } };
const input4 = [
{
role: "user",
content: "Hi! I'm Jim.",
},
];
const output5 = await app2.invoke({ messages: input4 }, config3);
console.log(output5.messages[output5.messages.length - 1]);
AIMessage {
"id": "chatcmpl-AekEYAQVqh9OFZRGdzGiPz33WPf1v",
"content": "Ahoy, Jim! A pleasure to meet ye, matey! What be on yer mind this fine day?",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"promptTokens": 32,
"completionTokens": 23,
"totalTokens": 55
},
"finish_reason": "stop",
"usage": {
"prompt_tokens": 32,
"completion_tokens": 23,
"total_tokens": 55,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"system_fingerprint": "fp_39a40c96a0"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"output_tokens": 23,
"input_tokens": 32,
"total_tokens": 55,
"input_token_details": {
"audio": 0,
"cache_read": 0
},
"output_token_details": {
"audio": 0,
"reasoning": 0
}
}
}
const input5 = [
{
role: "user",
content: "What is my name?",
},
];
const output6 = await app2.invoke({ messages: input5 }, config3);
console.log(output6.messages[output6.messages.length - 1]);
AIMessage {
"id": "chatcmpl-AekEbrpFI3K8BxemHZ5fG4xF2tT8x",
"content": "Ye be callin' yerself Jim, if I heard ye right, savvy? What else can I do fer ye, me hearty?",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"promptTokens": 68,
"completionTokens": 29,
"totalTokens": 97
},
"finish_reason": "stop",
"usage": {
"prompt_tokens": 68,
"completion_tokens": 29,
"total_tokens": 97,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"system_fingerprint": "fp_6fc10e10eb"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"output_tokens": 29,
"input_tokens": 68,
"total_tokens": 97,
"input_token_details": {
"audio": 0,
"cache_read": 0
},
"output_token_details": {
"audio": 0,
"reasoning": 0
}
}
}
太棒了!现在让我们的提示稍微复杂一些。让我们假设提示模板现在看起来像这样
const promptTemplate2 = ChatPromptTemplate.fromMessages([
[
"system",
"You are a helpful assistant. Answer all questions to the best of your ability in {language}.",
],
["placeholder", "{messages}"],
]);
请注意,我们已向提示添加了一个新的 language
输入。我们的应用程序现在有两个参数 - 输入 messages
和 language
。我们应该更新应用程序的状态以反映这一点
import {
START,
END,
StateGraph,
MemorySaver,
MessagesAnnotation,
Annotation,
} from "@langchain/langgraph";
// Define the State
const GraphAnnotation = Annotation.Root({
...MessagesAnnotation.spec,
language: Annotation<string>(),
});
// Define the function that calls the model
const callModel3 = async (state: typeof GraphAnnotation.State) => {
const prompt = await promptTemplate2.invoke(state);
const response = await llm.invoke(prompt);
return { messages: [response] };
};
const workflow3 = new StateGraph(GraphAnnotation)
.addNode("model", callModel3)
.addEdge(START, "model")
.addEdge("model", END);
const app3 = workflow3.compile({ checkpointer: new MemorySaver() });
const config4 = { configurable: { thread_id: uuidv4() } };
const input6 = {
messages: [
{
role: "user",
content: "Hi im bob",
},
],
language: "Spanish",
};
const output7 = await app3.invoke(input6, config4);
console.log(output7.messages[output7.messages.length - 1]);
AIMessage {
"id": "chatcmpl-AekF4R7ioefFo6PmOYo3YuCbGpROq",
"content": "¡Hola, Bob! ¿Cómo puedo ayudarte hoy?",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"promptTokens": 32,
"completionTokens": 11,
"totalTokens": 43
},
"finish_reason": "stop",
"usage": {
"prompt_tokens": 32,
"completion_tokens": 11,
"total_tokens": 43,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"system_fingerprint": "fp_39a40c96a0"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"output_tokens": 11,
"input_tokens": 32,
"total_tokens": 43,
"input_token_details": {
"audio": 0,
"cache_read": 0
},
"output_token_details": {
"audio": 0,
"reasoning": 0
}
}
}
请注意,整个状态都是持久化的,因此如果不需要更改,我们可以省略像 language
这样的参数
const input7 = {
messages: [
{
role: "user",
content: "What is my name?",
},
],
};
const output8 = await app3.invoke(input7, config4);
console.log(output8.messages[output8.messages.length - 1]);
AIMessage {
"id": "chatcmpl-AekF8yN7H81ITccWlBzSahmduP69T",
"content": "Tu nombre es Bob. ¿En qué puedo ayudarte, Bob?",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"promptTokens": 56,
"completionTokens": 13,
"totalTokens": 69
},
"finish_reason": "stop",
"usage": {
"prompt_tokens": 56,
"completion_tokens": 13,
"total_tokens": 69,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"system_fingerprint": "fp_6fc10e10eb"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"output_tokens": 13,
"input_tokens": 56,
"total_tokens": 69,
"input_token_details": {
"audio": 0,
"cache_read": 0
},
"output_token_details": {
"audio": 0,
"reasoning": 0
}
}
}
为了帮助您了解内部发生的事情,请查看此 LangSmith 追踪。
管理对话历史记录
构建聊天机器人时,需要理解的一个重要概念是如何管理对话历史记录。如果管理不当,消息列表将无限增长,并可能溢出 LLM 的上下文窗口。因此,添加一个限制您传递的消息大小的步骤非常重要。
重要的是,您需要在提示模板之前执行此操作,但在从消息历史记录加载先前的消息之后执行此操作。
我们可以通过在提示之前添加一个简单的步骤来修改 messages
键,然后将新的链包装在 Message History 类中来实现此目的。
LangChain 配备了一些内置助手,用于管理消息列表。在这种情况下,我们将使用 trimMessages 助手来减少我们发送给模型的消息数量。修剪器允许我们指定我们想要保留多少令牌,以及其他参数,例如我们是否想要始终保留系统消息以及是否允许部分消息
import {
SystemMessage,
HumanMessage,
AIMessage,
trimMessages,
} from "@langchain/core/messages";
const trimmer = trimMessages({
maxTokens: 10,
strategy: "last",
tokenCounter: (msgs) => msgs.length,
includeSystem: true,
allowPartial: false,
startOn: "human",
});
const messages = [
new SystemMessage("you're a good assistant"),
new HumanMessage("hi! I'm bob"),
new AIMessage("hi!"),
new HumanMessage("I like vanilla ice cream"),
new AIMessage("nice"),
new HumanMessage("whats 2 + 2"),
new AIMessage("4"),
new HumanMessage("thanks"),
new AIMessage("no problem!"),
new HumanMessage("having fun?"),
new AIMessage("yes!"),
];
await trimmer.invoke(messages);
[
SystemMessage {
"content": "you're a good assistant",
"additional_kwargs": {},
"response_metadata": {}
},
HumanMessage {
"content": "I like vanilla ice cream",
"additional_kwargs": {},
"response_metadata": {}
},
AIMessage {
"content": "nice",
"additional_kwargs": {},
"response_metadata": {},
"tool_calls": [],
"invalid_tool_calls": []
},
HumanMessage {
"content": "whats 2 + 2",
"additional_kwargs": {},
"response_metadata": {}
},
AIMessage {
"content": "4",
"additional_kwargs": {},
"response_metadata": {},
"tool_calls": [],
"invalid_tool_calls": []
},
HumanMessage {
"content": "thanks",
"additional_kwargs": {},
"response_metadata": {}
},
AIMessage {
"content": "no problem!",
"additional_kwargs": {},
"response_metadata": {},
"tool_calls": [],
"invalid_tool_calls": []
},
HumanMessage {
"content": "having fun?",
"additional_kwargs": {},
"response_metadata": {}
},
AIMessage {
"content": "yes!",
"additional_kwargs": {},
"response_metadata": {},
"tool_calls": [],
"invalid_tool_calls": []
}
]
要在我们的链中使用它,我们只需要在将 messages
输入传递给提示之前运行修剪器。
const callModel4 = async (state: typeof GraphAnnotation.State) => {
const trimmedMessage = await trimmer.invoke(state.messages);
const prompt = await promptTemplate2.invoke({
messages: trimmedMessage,
language: state.language,
});
const response = await llm.invoke(prompt);
return { messages: [response] };
};
const workflow4 = new StateGraph(GraphAnnotation)
.addNode("model", callModel4)
.addEdge(START, "model")
.addEdge("model", END);
const app4 = workflow4.compile({ checkpointer: new MemorySaver() });
现在,如果我们尝试询问模型我们的名字,它将不知道,因为我们修剪了聊天记录的该部分
const config5 = { configurable: { thread_id: uuidv4() } };
const input8 = {
messages: [...messages, new HumanMessage("What is my name?")],
language: "English",
};
const output9 = await app4.invoke(input8, config5);
console.log(output9.messages[output9.messages.length - 1]);
AIMessage {
"id": "chatcmpl-AekHyVN7f0Pnuyc2RHVL8CxKmFfMQ",
"content": "I don't know your name. You haven't shared it yet!",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"promptTokens": 97,
"completionTokens": 12,
"totalTokens": 109
},
"finish_reason": "stop",
"usage": {
"prompt_tokens": 97,
"completion_tokens": 12,
"total_tokens": 109,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"system_fingerprint": "fp_6fc10e10eb"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"output_tokens": 12,
"input_tokens": 97,
"total_tokens": 109,
"input_token_details": {
"audio": 0,
"cache_read": 0
},
"output_token_details": {
"audio": 0,
"reasoning": 0
}
}
}
但是,如果我们询问最后几条消息中的信息,它会记住
const config6 = { configurable: { thread_id: uuidv4() } };
const input9 = {
messages: [...messages, new HumanMessage("What math problem did I ask?")],
language: "English",
};
const output10 = await app4.invoke(input9, config6);
console.log(output10.messages[output10.messages.length - 1]);
AIMessage {
"id": "chatcmpl-AekI1jwlErzHuZ3BhAxr97Ct818Pp",
"content": "You asked what 2 + 2 equals.",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"promptTokens": 99,
"completionTokens": 10,
"totalTokens": 109
},
"finish_reason": "stop",
"usage": {
"prompt_tokens": 99,
"completion_tokens": 10,
"total_tokens": 109,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"system_fingerprint": "fp_6fc10e10eb"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"output_tokens": 10,
"input_tokens": 99,
"total_tokens": 109,
"input_token_details": {
"audio": 0,
"cache_read": 0
},
"output_token_details": {
"audio": 0,
"reasoning": 0
}
}
}
如果您查看 LangSmith,您可以在 LangSmith 追踪中准确查看幕后发生的事情。
后续步骤
现在您已经了解了如何在 LangChain 中创建聊天机器人的基础知识,您可能对以下更高级的教程感兴趣
如果您想深入了解细节,以下是一些值得查看的内容
- 流式传输:流式传输对于聊天应用程序至关重要
- 如何添加消息历史记录:深入了解与消息历史记录相关的所有内容
- 如何管理大型消息历史记录:管理大型聊天历史记录的更多技术
- LangGraph 主要文档:有关使用 LangGraph 构建的更多详细信息