跳转到主要内容

如何添加消息历史

先决条件

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

注意

本指南之前介绍了 RunnableWithMessageHistory 抽象。您可以在 v0.2 文档中访问本指南的此版本。

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

在构建聊天机器人时,将对话状态传入和传出链至关重要。LangGraph 实现了内置的持久层,允许链状态自动持久化在内存中,或 SQLite、Postgres 或 Redis 等外部后端。详细信息可以在 LangGraph 持久性文档中找到。

在本指南中,我们将演示如何通过将任意 LangChain runnables 包装在最小的 LangGraph 应用程序中来添加持久性。这使我们能够持久化消息历史记录和链状态的其他元素,从而简化多轮应用程序的开发。它还支持多线程,使单个应用程序能够与多个用户分别交互。

设置

yarn add @langchain/core @langchain/langgraph

让我们也设置一个聊天模型,我们将在下面的示例中使用它。

选择你的聊天模型

安装依赖项

提示

请参阅 此部分,了解有关安装集成包的一般说明.

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

示例:消息输入

聊天模型 添加记忆提供了一个简单的示例。聊天模型接受消息列表作为输入并输出消息。LangGraph 包括一个内置的 MessagesState,我们可以将其用于此目的。

下面,我们:1. 将图状态定义为消息列表;2. 向图中添加一个调用聊天模型的节点;3. 使用内存检查点编译器编译图,以在运行之间存储消息。

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);
// Update message history with response:
return { messages: response };
};

// Define a new graph
const workflow = new StateGraph(MessagesAnnotation)
// Define the (single) node in the graph
.addNode("model", callModel)
.addEdge(START, "model")
.addEdge("model", END);

// Add memory
const memory = new MemorySaver();
const app = workflow.compile({ checkpointer: memory });

当我们运行应用程序时,我们传入一个配置对象,该对象指定 thread_id。此 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-ABTqCeKnMQmG9IH8dNF5vPjsgXtcM",
"content": "Hi Bob! How can I assist you today?",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"completionTokens": 10,
"promptTokens": 12,
"totalTokens": 22
},
"finish_reason": "stop",
"system_fingerprint": "fp_e375328146"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 12,
"output_tokens": 10,
"total_tokens": 22
}
}
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-ABTqD5jrJXeKCpvoIDp47fvgw2OPn",
"content": "Your name is Bob. How can I help you today, Bob?",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"completionTokens": 14,
"promptTokens": 34,
"totalTokens": 48
},
"finish_reason": "stop",
"system_fingerprint": "fp_e375328146"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 34,
"output_tokens": 14,
"total_tokens": 48
}
}

请注意,不同线程的状态是分开的。如果我们向具有新 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-ABTqDkctxwmXjeGOZpK6Km8jdCqdl",
"content": "I'm sorry, but I don't have access to personal information about users. How can I assist you today?",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"completionTokens": 21,
"promptTokens": 11,
"totalTokens": 32
},
"finish_reason": "stop",
"system_fingerprint": "fp_52a7f40b0b"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 11,
"output_tokens": 21,
"total_tokens": 32
}
}

示例:对象输入

LangChain runnables 通常通过单个对象参数中的单独键接受多个输入。一个常见的例子是具有多个参数的提示模板。

之前的 runnable 是聊天模型,这里我们将提示模板和聊天模型链接在一起。

import {
ChatPromptTemplate,
MessagesPlaceholder,
} from "@langchain/core/prompts";

const prompt = ChatPromptTemplate.fromMessages([
["system", "Answer in {language}."],
new MessagesPlaceholder("messages"),
]);

const runnable = prompt.pipe(llm);

对于这种情况,我们定义图状态以包含这些参数(除了消息历史记录)。然后,我们以与之前相同的方式定义单节点图。

请注意,在下面的状态中:- 对 messages 列表的更新将附加消息;- 对 language 字符串的更新将覆盖该字符串。

import {
START,
END,
StateGraph,
MemorySaver,
MessagesAnnotation,
Annotation,
} from "@langchain/langgraph";

// Define the State
const GraphAnnotation = Annotation.Root({
language: Annotation<string>(),
// Spread `MessagesAnnotation` into the state to add the `messages` field.
...MessagesAnnotation.spec,
});

// Define the function that calls the model
const callModel2 = async (state: typeof GraphAnnotation.State) => {
const response = await runnable.invoke(state);
// Update message history with response:
return { messages: [response] };
};

const workflow2 = new StateGraph(GraphAnnotation)
.addNode("model", callModel2)
.addEdge(START, "model")
.addEdge("model", END);

const app2 = workflow2.compile({ checkpointer: new MemorySaver() });
const config3 = { configurable: { thread_id: uuidv4() } };
const input4 = {
messages: [
{
role: "user",
content: "What's my name?",
},
],
language: "Spanish",
};
const output4 = await app2.invoke(input4, config3);
console.log(output4.messages[output4.messages.length - 1]);
AIMessage {
"id": "chatcmpl-ABTqFnCASRB5UhZ7XAbbf5T0Bva4U",
"content": "Lo siento, pero no tengo suficiente información para saber tu nombre. ¿Cómo te llamas?",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"completionTokens": 19,
"promptTokens": 19,
"totalTokens": 38
},
"finish_reason": "stop",
"system_fingerprint": "fp_e375328146"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 19,
"output_tokens": 19,
"total_tokens": 38
}
}

管理消息历史记录

消息历史记录(和应用程序状态的其他元素)可以通过 .getState 访问

const state = (await app2.getState(config3)).values;

console.log(`Language: ${state.language}`);
console.log(state.messages);
Language: Spanish
[
HumanMessage {
"content": "What's my name?",
"additional_kwargs": {},
"response_metadata": {}
},
AIMessage {
"id": "chatcmpl-ABTqFnCASRB5UhZ7XAbbf5T0Bva4U",
"content": "Lo siento, pero no tengo suficiente información para saber tu nombre. ¿Cómo te llamas?",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"completionTokens": 19,
"promptTokens": 19,
"totalTokens": 38
},
"finish_reason": "stop",
"system_fingerprint": "fp_e375328146"
},
"tool_calls": [],
"invalid_tool_calls": []
}
]

我们还可以通过 .updateState 更新状态。例如,我们可以手动附加新消息

const _ = await app2.updateState(config3, {
messages: [{ role: "user", content: "test" }],
});
const state2 = (await app2.getState(config3)).values;

console.log(`Language: ${state2.language}`);
console.log(state2.messages);
Language: Spanish
[
HumanMessage {
"content": "What's my name?",
"additional_kwargs": {},
"response_metadata": {}
},
AIMessage {
"id": "chatcmpl-ABTqFnCASRB5UhZ7XAbbf5T0Bva4U",
"content": "Lo siento, pero no tengo suficiente información para saber tu nombre. ¿Cómo te llamas?",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"completionTokens": 19,
"promptTokens": 19,
"totalTokens": 38
},
"finish_reason": "stop",
"system_fingerprint": "fp_e375328146"
},
"tool_calls": [],
"invalid_tool_calls": []
},
HumanMessage {
"content": "test",
"additional_kwargs": {},
"response_metadata": {}
}
]

有关管理状态的详细信息,包括删除消息,请参阅 LangGraph 文档


此页是否对您有所帮助?


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