前言
基于大模型的原理,大模型在非训练模式下是不会把输入存储起来的,所以一般情况下使用的大模型都是没有记忆能力的。大模型的调用是无状态的,就像HTTP请求一样,每次针对大模型的调用都是一次独立的事件,那么像chatgpt、qwen、kimi这些大模型网站,是怎么实现多次交互的,并且看起来大模型能记住所有的对话的呢?
其实原理非常简单,就是每次和大模型对话时,都会把所有的对话历史都发给大模型,这样大模型就可以根据历史对话来回答问题,看起来大模型就像是有了记忆能力。
每次都维护所有的对话历史太麻烦了吧!那么在LangGraph中怎么使用记忆能力呢?见下面的例子
我们使用checkpointer来存储状态,这里的checkpointer使用的实现是MemorySaver,一个基于内存的存储,程序结束后就不存在了。
单会话例子
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, MessagesState, START, END
from dotenv import load_dotenv
from langgraph.checkpoint.memory import MemorySaver
import os
# 1. 加载环境变量
load_dotenv()
base_url = os.getenv("BASE_URL")
openai_api_key = os.getenv("OPENAI_API_KEY")
model_name = "qwen-max"
# 2. 定义模型
model = ChatOpenAI(model=model_name, base_url=base_url, api_key=openai_api_key)
# 3. 调用模型的函数
def call_llm(state: MessagesState):
messages = state["messages"]
response = model.invoke(messages)
return {"messages": [response]}
# 4. 定义工作流
workflow = StateGraph(MessagesState)
workflow.add_node("call_llm", call_llm)
workflow.add_edge(START, "call_llm")
workflow.add_edge("call_llm", END)
# 5.消息存储使用内存
checkpointer = MemorySaver()
# 6.定义带存储的图
app_with_memory = workflow.compile(checkpointer=checkpointer)
def interact_with_agent_with_memory():
# 自定义交互标识
thread_id = "session_1"
while True:
user_input = input("You: ")
if user_input.lower() in ["exit", "quit", "q"]:
print("结束对话")
break
input_message = {"messages": [("human", user_input)]}
# 请求调用的配置
config = {"configurable": {"thread_id": thread_id}}
for chunk in app_with_memory.stream(
input_message, config=config, stream_mode="values"
):
chunk["messages"][-1].pretty_print()
interact_with_agent_with_memory()
演示结果
You: 我的名字是什么
================================ Human Message =================================
我的名字是什么
================================== Ai Message ==================================
您好!您并没有告诉我您的名字,所以我无法知道您的名字是什么。如果您愿意,可以告诉我您的名字,我很乐意用您的名字来称呼您。
You: 我叫小明
================================ Human Message =================================
我叫小明
================================== Ai Message ==================================
你好,小明!很高兴认识你。有什么我可以帮助你的吗?
You: 我的名字是什么
================================ Human Message =================================
我的名字是什么
================================== Ai Message ==================================
你的名字是小明。有什么我可以帮助小明的吗?
You: exit
结束对话
上面的例子可以看到,大模型似乎有了记忆能力。我们为了展示,把调用大模型的消息全部打印出来看一下,修改一个方法如下
# 3. 调用模型的函数
def call_llm(state: MessagesState):
messages = state["messages"]
response = model.invoke(messages)
print("对话内容 messages: ", messages)
return {"messages": [response]}
输出结果为
You: 我的名字是什么
================================ Human Message =================================
我的名字是什么
对话内容 messages: [HumanMessage(content='我的名字是什么', additional_kwargs={}, response_metadata={}, id='cb3ea43b-2036-439b-b1b0-137db0d3c084')]
================================== Ai Message ==================================
您好!您并没有告诉我您的名字,所以我无法知道您的名字是什么。如果您愿意,可以告诉我您的名字,我会很乐意用它来称呼您。
You: 我叫小明
================================ Human Message =================================
我叫小明
对话内容 messages: [HumanMessage(content='我的名字是什么', additional_kwargs={}, response_metadata={}, id='cb3ea43b-2036-439b-b1b0-137db0d3c084'), AIMessage(content='您好
!您并没有告诉我您的名字,所以我无法知道您的名字是什么。如果您愿意,可以告诉我您的名字,我会很乐意用它来称呼您。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 32, 'prompt_tokens': 11, 'total_tokens': 43, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_name': 'qwen-max', 'system_fingerprint': None, 'id': 'chatcmpl-47ecbe6c-aa4d-975d-8678-3c008b3692d0', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--16c82f36-e2e6-4a5a-b6f4-e51a70b0cd7f-0', usage_metadata={'input_tokens': 11, 'output_tokens': 32, 'total_tokens': 43, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}), HumanMessage(content='我叫小明', additional_kwargs={}, response_metadata={}, id='1a3457ba-9aaa-45ef-9218-0f2a5c2302e8')]
================================== Ai Message ==================================
你好,小明!很高兴认识你。有什么我可以帮助你的吗?
You: 我的名字是什么
================================ Human Message =================================
我的名字是什么
对话内容 messages: [HumanMessage(content='我的名字是什么', additional_kwargs={}, response_metadata={}, id='cb3ea43b-2036-439b-b1b0-137db0d3c084'), AIMessage(content='您好
!您并没有告诉我您的名字,所以我无法知道您的名字是什么。如果您愿意,可以告诉我您的名字,我会很乐意用它来称呼您。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 32, 'prompt_tokens': 11, 'total_tokens': 43, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_name': 'qwen-max', 'system_fingerprint': None, 'id': 'chatcmpl-47ecbe6c-aa4d-975d-8678-3c008b3692d0', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--16c82f36-e2e6-4a5a-b6f4-e51a70b0cd7f-0', usage_metadata={'input_tokens': 11, 'output_tokens': 32, 'total_tokens': 43, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}), HumanMessage(content='我叫小明', additional_kwargs={}, response_metadata={}, id='1a3457ba-9aaa-45ef-9218-0f2a5c2302e8'), AIMessage(content='你好,小明!很高兴认识你。有什么我可以帮助你的吗?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 57, 'total_tokens': 72, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_name': 'qwen-max', 'system_fingerprint': None, 'id': 'chatcmpl-a8ceed67-78fe-937e-bd8a-dc2b0c07b36a', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--ad9dcc91-4d7e-429a-b338-27556b162568-0', usage_metadata={'input_tokens': 57, 'output_tokens': 15, 'total_tokens': 72, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}), HumanMessage(content='我的名字是什么', additional_kwargs={}, response_metadata={}, id='f440d4ad-881f-455b-8080-b221645bcd21')]
================================== Ai Message ==================================
你的名字是小明。有什么其他事情需要我帮忙的吗?
You: exit
结束对话
可以看到,调用大模型时,我们每次都会把之前的对话记录都发送给大模型,这样大模型就有了记忆能力!
多会话例子
这里关键的配置是config中的 thread_id属性,不同的thread_id 意味着不同的记忆,也就是会话。下面来看一个多个会话的例子
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, MessagesState, START, END
from dotenv import load_dotenv
from langgraph.checkpoint.memory import MemorySaver
import os
# 1. 加载环境变量
load_dotenv()
base_url = os.getenv("BASE_URL")
openai_api_key = os.getenv("OPENAI_API_KEY")
model_name = "qwen-max"
# 2. 定义模型
model = ChatOpenAI(model=model_name, base_url=base_url, api_key=openai_api_key)
def call_llm(state: MessagesState):
messages = state["messages"]
response = model.invoke(messages)
return {"messages": [response]}
# 3. 定义图
workflow = StateGraph(MessagesState)
workflow.add_node("call_llm", call_llm)
workflow.add_edge(START, "call_llm")
workflow.add_edge("call_llm", END)
# 4. 编译带存储的图
checkpointer = MemorySaver()
app_with_memory = workflow.compile(checkpointer=checkpointer)
def interact_with_agent_across_sessions():
while True:
# 5. 设置会话id
thread_id = input("输入会话ID: ")
if thread_id.lower() in ["exit", "quit"]:
print("结束对话.")
break
if thread_id.lower() == "new":
thread_id = f"session_{os.urandom(4).hex()}" # Generate a unique session ID
while True:
# 6. 在某个会话下进行交互
user_input = input("You: ")
if user_input.lower() in ["exit", "quit", "end session"]:
print(f"结束会话 {thread_id}.")
break
input_message = {"messages": [("human", user_input)]}
config = {"configurable": {"thread_id": thread_id}}
for chunk in app_with_memory.stream(
input_message, config=config, stream_mode="values"
):
chunk["messages"][-1].pretty_print()
interact_with_agent_across_sessions()
演示结果为
输入会话ID: 1
You: 我是小明
================================ Human Message =================================
我是小明
================================== Ai Message ==================================
你好,小明!很高兴见到你。有什么我可以帮助你的吗?如果你有任何问题或需要讨论某个话题,请随时告诉我。
You: 我是谁啊
================================ Human Message =================================
我是谁啊
================================== Ai Message ==================================
你是小明。如果你有任何其他问题或需要进一步的帮助,请告诉我!
You: exit
结束会话 1.
输入会话ID: 2
You: 我是小红
================================ Human Message =================================
我是小红
================================== Ai Message ==================================
你好,小红!很高兴见到你。有什么我可以帮助你的吗?
You: 我是谁啊
================================ Human Message =================================
我是谁啊
================================== Ai Message ==================================
你刚刚告诉我你是小红。如果你是在问其他方面的身份或角色,可以给我更多的信息,我会尽力帮助你!
You: exit
结束会话 2.
输入会话ID: 1
You: 我是谁啊
================================ Human Message =================================
我是谁啊
================================== Ai Message ==================================
你是小明。如果你还有其他问题或需要帮助的地方,请告诉我!
You: exit
结束会话 1.
输入会话ID: exit
结束对话.
上面例子中,我们使用了两个不同的thread_id,一个是1,一个是2,两个会话展示了两个不同的记忆,大模型在不同的会话中区分了我们的身份。