跳转至主要内容

构建聊天机器人

先决条件

本指南假定您熟悉以下概念

本指南需要 langgraph >= 0.2.28

注意

本教程之前使用 RunnableWithMessageHistory 构建了一个聊天机器人。您可以在 v0.2 文档中访问本教程的这个版本。

LangGraph 实现提供了许多优于 RunnableWithMessageHistory 的优势,包括持久化应用程序状态的任意组件的能力(而不仅仅是消息)。

概述

我们将介绍如何设计和实现由 LLM 驱动的聊天机器人的示例。这个聊天机器人将能够进行对话并记住之前的交互。

请注意,我们构建的这个聊天机器人将仅使用语言模型进行对话。您可能正在寻找以下几个其他相关概念

  • 对话式 RAG:通过外部数据源启用聊天机器人体验
  • 代理:构建可以采取行动的聊天机器人

本教程将涵盖基础知识,这对这两个更高级的主题将有所帮助,但如果您选择,可以随意直接跳到那里。

设置

Jupyter Notebook

本指南(以及文档中的大多数其他指南)使用 Jupyter notebooks,并假定读者也是如此。Jupyter notebooks 非常适合学习如何使用 LLM 系统,因为通常情况下事情可能会出错(意外输出、API 宕机等),并且在交互式环境中学习指南是更好地理解它们的好方法。

本教程和其他教程最好在 Jupyter notebook 中运行。有关如何安装的说明,请参阅此处

安装

对于本教程,我们将需要 @langchain/corelanggraph

yarn add @langchain/core @langchain/langgraph uuid

有关更多详细信息,请参阅我们的安装指南

LangSmith

您使用 LangChain 构建的许多应用程序将包含多个步骤,其中包含多次 LLM 调用。随着这些应用程序变得越来越复杂,能够检查您的链或代理内部究竟发生了什么是至关重要的。最好的方法是使用 LangSmith

在上面的链接注册后,请确保设置您的环境变量以开始记录追踪

process.env.LANGSMITH_TRACING = "true";
process.env.LANGSMITH_API_KEY = "...";

快速入门

首先,让我们学习如何单独使用语言模型。LangChain 支持许多不同的语言模型,您可以互换使用它们 - 选择您想要使用的模型!

选择您的聊天模型

安装依赖项

提示

请参阅 本节,了解有关安装集成包的常规说明.

yarn 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
});

让我们首先直接使用模型。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 输入。我们的应用程序现在有两个参数 - 输入 messageslanguage。我们应该更新应用程序的状态以反映这一点

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 中创建聊天机器人的基础知识,您可能对以下更高级的教程感兴趣

  • 对话式 RAG:通过外部数据源启用聊天机器人体验
  • 代理:构建可以采取行动的聊天机器人

如果您想深入了解细节,以下是一些值得查看的内容


此页是否对您有所帮助?


您也可以留下详细的反馈 在 GitHub 上.