LangChain的History的使用#
History是LangChain提供了一个保存对话历史的功能,LangChain的社区有很多的实现,他们都是基于BaseChatMessageHistory
子类来拓展的,LangChain在langchain_core
包中只实现了InMemoryChatMessageHistory
。
社区提供的如下:
LangChain中memory和History的主要区别是:
Memory是用于在Chain的执行过程中存储和加载状态信息的抽象接口。
History是用于存储和管理对话历史消息的抽象接口。
memory是短时间的记忆,History是长时间的记忆。
InMemoryChatMessageHistory#
在内存中记录的对话历史
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
import langchain
langchain.debug=True
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个ai助手,基于下面的对话历史来回答问题"),
("placeholder", "{chat_history}"),
("human", "{input}"),
]
)
history = InMemoryChatMessageHistory()
chain = prompt | ChatOpenAI() | StrOutputParser()
wrapped_chain = RunnableWithMessageHistory(chain, lambda x: history)
res = wrapped_chain.invoke(
{"input": "你是谁"},
config={"configurable": {"session_id": "42"}})
wrapped_chain.invoke({"input": "我刚才问你什么了?"},config={"configurable": {"session_id": "42"}})
history.messages
RedisChatMessageHistory#
数据存放在redis中,需要引入redis,需要在本地先启动redis
RedisChatMessageHistory
在构建的时候需要链接信息,默认是redis://localhost:6379/0
,key的前缀是message_store
,存储的数据类型是list
from langchain_community.chat_message_histories import RedisChatMessageHistory
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
import langchain
langchain.debug=True
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个ai助手,基于下面的对话历史来回答问题"),
("placeholder", "{chat_history}"),
("human", "{input}"),
]
)
history = RedisChatMessageHistory(session_id="42")
history.clear()
history.messages
chain = prompt | ChatOpenAI() | StrOutputParser()
wrapped_chain = RunnableWithMessageHistory(chain, lambda x: history)
res = wrapped_chain.invoke(
{"input": "你是谁"},
config={"configurable": {"session_id": "42"}})
history.messages
wrapped_chain.invoke({"input": "我刚才问你什么了?"},config={"configurable": {"session_id": "42"}})
history.messages
history.redis_client.type(history.key)
与此类似的有MongoDBChatMessageHistory
,ElasticsearchChatMessageHistory
,SQLChatMessageHistory
。
SQLChatMessageHistory
是将数据存储在sql数据库中。
Memory和History同时使用#
Memory中使用History#
Memory中底层实现就是History,可以在构造Memory的时候指定chat_Memory,默认是InMemoryChatMessageHistory
from langchain.memory import ConversationBufferWindowMemory
from langchain.chains import ConversationChain
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
template = """
你是一个ai助手,叫做小李,你需要根据对话历史来回答用户问题.
Chat history: {history}
Question: {input}
"""
prompt = ChatPromptTemplate.from_template(template)
history = RedisChatMessageHistory(session_id="42")
history.clear()
memory = ConversationBufferWindowMemory(chat_memory=history,k=2)
chain = ConversationChain(
llm=ChatOpenAI(),
memory=memory,
prompt=prompt,
)
chain.invoke({"input":"你是谁?"})
chain.invoke({"input":"你知道1+1等于?"})
redis中看数据
key存在了,数据已经存在了,在进件多轮对话看redis中结果
chain.invoke({"input":"你知道1+2等于?"})
chain.invoke({"input":"你知道1+3于?"})
chain.invoke({"input":"我问你的第一个问题是什么?"})
redis中数据如下:
怎么看起来不是预期的,预期是保存4条对话记录,也就是2轮对话。但是redis中全部都保存起来了。从上面的执行过程中可以看到,确实传递给llm的是最后两轮对话,是预期的。翻找代码发现
Memory中对对话历史做处理是不影响History的,History会记录完整的对话历史,Memory会对History中的message做加工 ,具体可以看
langchain.memory.chat_memory.BaseChatMemory.save_context
。
上面的例子是Memory中套入了History,并且使用的是老的LangChain的写法,不是LCEL。
LCEL使用的是RunnableWithMessageHistory
来包装chain
,他需要返回的是一个BaseChatMessageHistory
的子类,看起来Memory这一套并不适用,需要自己在包装一下,在返回History的时候使用Memory来处理一下。
History中使用Memory(使用LCEL)#
不兼容,需要手动做兼容 原因如下
在Memory的实现中,是通过History的
add_message
的方式来完成的。这个接口的入参就一个message
,但是Memory对外暴漏的接口是save_context
,每次都要求传递两个参数,input,output
,这里就不能兼容,需要有一种方式来判断本次添加的message是那种,在我的代码中我是通过switch
来做的。需要处理数据首次加载的问题,先从Memory中获取数据,如果没有,在从History中获取,最后数据回灌到History。
下面的代码没有跑动
import json
import logging
from typing import List, Optional
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.messages import (
BaseMessage,
message_to_dict,
messages_from_dict,
)
from langchain_community.utilities.redis import get_client
logger = logging.getLogger(__name__)
class RedisConversationBufferHistory(RedisChatMessageHistory):
"""Chat message history stored in a Redis database. use ConversationBufferMemory"""
def __init__(
self,
**kwargs
):
super().__init__(**kwargs)
self.memory = ConversationBufferWindowMemory(k=2)
self.switch = True
def change_switch(self):
self.switch = !self.switch
def is_open_switch(self):
return self.switch
@property
def messages(self) -> List[BaseMessage]:
return self.memory.buffer_as_messages
def add_message(self, message: BaseMessage) -> None:
"""Append the message to the record in Redis"""
if len(message.content) > 0:
self.redis_client.lpush(self.key, json.dumps(message_to_dict(message)))
if self.ttl:
self.redis_client.expire(self.key, self.ttl)
if self.is_open_switch():
self.memory.save_context(message,"")
else:
self.memory.save_context("",message)
def clear(self) -> None:
super().clear()
self.memory.clear()
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
import langchain
langchain.debug=True
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个ai助手,基于下面的对话历史来回答问题"),
("placeholder", "{chat_history}"),
("human", "{input}"),
]
)
chain = prompt | ChatOpenAI() | StrOutputParser()
history = RedisConversationBufferHistory(session_id="43")
history.clear()
wrapped_chain = RunnableWithMessageHistory(chain,lambda x: history)
res = wrapped_chain.invoke(
{"input": "你是谁"},
config={"configurable": {"session_id": "43"}})
res = wrapped_chain.invoke(
{"input": "hello"},
config={"configurable": {"session_id": "42"}})
end end end