掘金 人工智能 前天 10:56
三、(基础)使用 LangChain 构建一个有上下文记忆简单聊天-2
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入探讨了如何使用LangChain构建具有上下文记忆功能的简单聊天程序,并有效地管理对话历史和控制上下文长度。通过trim_messages助手限制消息大小,确保模型在有限的上下文窗口内保持记忆。文章还介绍了如何在链中使用修剪器,以及如何将对话历史包装到上下文会话中,并实现了流式处理,提升用户体验。

💬 使用`trim_messages`助手管理对话历史,限制传入消息大小,防止消息列表无限增长。

🔗 在LangChain链中使用`trim_messages`,确保模型在有限的上下文窗口内处理信息,避免超出上下文窗口。

🔄 将对话历史包装到上下文会话中,通过`RunnableWithMessageHistory`实现,维护和更新对话历史记录。

🚀 实现了流式处理,允许用户实时看到模型响应的进度,提升用户体验。

前言

    上一章讲解了如何使用 LangChain 构建一个有上下文记忆简单聊天程序,并且增加了提示词模版,但是我们还是没有很好的体现和管理对话历史,控制上下文长度等。本章将继续讲解如何使用LangChain 构建一个有上下文记忆简单聊天程序并管理对话历史,控制上下文长度。

准备工作

1. 管理对话历史

    调用大模型聊天时,一个重要的概念是如何管理对话历史。如果不加以管理,消息列表将无限增长,并可能溢出大型语言模型的上下文窗口。因此,添加一个限制传入消息大小的步骤是很重要的。LangChain 提供了一些内置的助手来 管理消息列表。在这种情况下,我们将使用 trim_messages 助手来减少我们发送给模型的消息数量。修剪器允许我们指定希望保留的令牌数量,以及其他参数,例如是否希望始终保留系统消息以及是否允许部分消息:
from langchain_core.messages import SystemMessage, trim_messages, HumanMessage, AIMessagefrom langchain_core.messages.utils import count_tokens_approximatelyfrom langchain_openai import ChatOpenAI# model = ChatOpenAI(model="GLM-4-Flash-250414", api_key="申请的key", base_url="https://open.bigmodel.cn/api/paas/v4/")trimmer = trim_messages(    max_tokens=65,    strategy="last",    # 国产大模型一般不兼容 langchain 的 get_num_tokens_from_messages() token获取接口,也就是token_counter=model,    # 可以使用 count_tokens_approximately(预估token数)、len(每一条一个token)等方法    # token_counter=model,    # token_counter=len,    token_counter=count_tokens_approximately,    include_system=True,    allow_partial=False,    start_on="human",)# 模拟从数据库加载的对话数据messages = [    SystemMessage(content="你是个好助手"),    HumanMessage(content="你好!我是小明"),    AIMessage(content="你好,小明!很高兴见到你。请问有什么我可以帮助你的吗?"),    HumanMessage(content="我喜欢香草冰淇淋"),    AIMessage(content="很好"),    HumanMessage(content="2 + 2等于多少"),    AIMessage(content="4"),    HumanMessage(content="谢谢"),    AIMessage(content="不客气!"),    HumanMessage(content="你开心吗?"),    AIMessage(content="是的!"),]print(trimmer.invoke(messages))

可以看到 count_tokens_approximately 截断后的对话数据,最开始的2条信息已经被截断

[SystemMessage(content='你是个好助手', additional_kwargs={}, response_metadata={}),HumanMessage(content='我喜欢香草冰淇淋', additional_kwargs={}, response_metadata={}),AIMessage(content='很好', additional_kwargs={}, response_metadata={}),HumanMessage(content='2 + 2等于多少', additional_kwargs={}, response_metadata={}),AIMessage(content='4', additional_kwargs={}, response_metadata={}),HumanMessage(content='谢谢', additional_kwargs={}, response_metadata={}),AIMessage(content='不客气!', additional_kwargs={}, response_metadata={}),HumanMessage(content='玩得开心吗?', additional_kwargs={}, response_metadata={}),AIMessage(content='是的!', additional_kwargs={}, response_metadata={})]

2. 在链中使用

    要在我们的链中使用它,我们只需在将 messages 输入传递给提示之前运行修剪器。现在如果我们尝试询问模型我们的名字,它将不知道,因为我们修剪了聊天历史的那部分:
from langchain_core.messages import SystemMessage, trim_messages, HumanMessage, AIMessagefrom langchain_openai import ChatOpenAIfrom langchain_core.messages.utils import count_tokens_approximatelyfrom operator import itemgetterfrom langchain_core.runnables import RunnablePassthroughfrom langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholdermodel = ChatOpenAI(model="GLM-4-Flash-250414", api_key="申请的key", base_url="https://open.bigmodel.cn/api/paas/v4/")trimmer = trim_messages(    max_tokens=65,    strategy="last",    # 国产大模型一般不兼容 langchain 的 get_num_tokens_from_messages() token获取接口,也就是token_counter=model,    # 可以使用 count_tokens_approximately(预估token数)、len(每一条一个token)等方法    # token_counter=model,    # token_counter=len,    token_counter=count_tokens_approximately,    include_system=True,    allow_partial=False,    start_on="human",)prompt = ChatPromptTemplate.from_messages(    [        (            "system",            "你是个乐于助人的助手。尽你所能用{language}回答所有问题",        ),        MessagesPlaceholder(variable_name="messages"),    ])# 模拟从数据库加载的对话数据messages = [    SystemMessage(content="你是个好助手"),    HumanMessage(content="你好!我是小明"),    AIMessage(content="你好,小明!很高兴见到你。请问有什么我可以帮助你的吗?"),    HumanMessage(content="我喜欢香草冰淇淋"),    AIMessage(content="很好"),    HumanMessage(content="2 + 2等于多少"),    AIMessage(content="4"),    HumanMessage(content="谢谢"),    AIMessage(content="不客气!"),    HumanMessage(content="你开心吗?"),    AIMessage(content="是的!"),]chain = (    RunnablePassthrough.assign(messages=itemgetter("messages") | trimmer)    | prompt    | model)response = chain.invoke(    {        "messages": messages + [HumanMessage(content="我叫什么名字?")],        "language": "中文",    })print(response.content)

模型的回答,可以看到它不知道我的名字

您没有告诉我您的名字,如果您愿意,可以告诉我您的名字。

我们换个在聊天历史的问题

response = chain.invoke(    {        "messages": messages + [HumanMessage(content="我问了什么数学题?")],        "language": "中文",    })print(response.content)

模型的回答,可以看到它是知道我们问的什么的

你问的是“2 + 2 等于多少”。

3. 包装入上下文会话中

from langchain_core.messages import SystemMessage, trim_messages, HumanMessage, AIMessagefrom langchain_openai import ChatOpenAIfrom langchain_core.messages.utils import count_tokens_approximatelyfrom operator import itemgetterfrom langchain_core.runnables import RunnablePassthroughfrom langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholderfrom langchain_core.runnables.history import RunnableWithMessageHistoryfrom langchain_core.chat_history import BaseChatMessageHistory, InMemoryChatMessageHistorymodel = ChatOpenAI(model="GLM-4-Flash-250414", api_key="申请的key", base_url="https://open.bigmodel.cn/api/paas/v4/")trimmer = trim_messages(    max_tokens=65,    strategy="last",    # 国产大模型一般不兼容 langchain 的 get_num_tokens_from_messages() token获取接口,也就是token_counter=model,    # 可以使用 count_tokens_approximately(预估token数)、len(每一条一个token)等方法    # token_counter=model,    # token_counter=len,    token_counter=count_tokens_approximately,    include_system=True,    allow_partial=False,    start_on="human",)prompt = ChatPromptTemplate.from_messages(    [        (            "system",            "你是个乐于助人的助手。尽你所能用{language}回答所有问题",        ),        MessagesPlaceholder(variable_name="messages"),    ])messages = [    SystemMessage(content="你是个好助手"),    HumanMessage(content="你好!我是小明"),    AIMessage(content="你好,小明!很高兴见到你。请问有什么我可以帮助你的吗?"),    HumanMessage(content="我喜欢香草冰淇淋"),    AIMessage(content="很好"),    HumanMessage(content="2 + 2等于多少"),    AIMessage(content="4"),    HumanMessage(content="谢谢"),    AIMessage(content="不客气!"),    HumanMessage(content="你开心吗?"),    AIMessage(content="是的!"),]def add_messages(model_message):    """    模拟自动添加消息到记录操作,加入链中    :param model_message:    :return:    """    messages.append(model_message)    return model_messagechain = (    RunnablePassthrough.assign(messages=itemgetter("messages") | trimmer)    | prompt    | model    | add_messages)store = {}def get_session_history(session_id: str) -> BaseChatMessageHistory:    if session_id not in store:        store[session_id] = InMemoryChatMessageHistory()    return store[session_id]with_message_history = RunnableWithMessageHistory(    chain,    get_session_history,    input_messages_key="messages",)config = {"configurable": {"session_id": "aaaaaaa"}}# 将加载的历史并入到 config 的 session_id 中messages.append(HumanMessage(content="我叫什么名字?"))response = with_message_history.invoke(    {        "messages": messages,        "language": "中文",    },    config=config,)print(response.content)# 当问了新问题,历史消息会多出两条messages.append(HumanMessage(content="我刚问了什么数学题?"))response = with_message_history.invoke(    {        "messages": messages,        "language": "中文",    },    config= config)print(response.content)

当问了新问题,历史消息会多出两条,我们问了第一个问题 “我叫什么名字?” 会进行新的截断,当我们再次问 “我刚问了什么数学题?”时,模型将不知道我们的问题,这就体现出模型上下文是有一定长度的,超出后将不再有记忆。

对不起,我不能确定你的名字。你希望我记住你的名字吗?很抱歉,由于我是一个人工智能助手,我无法记住你之前的提问或对话。每次与我交互时,我都会尽力根据当前的信息来回答你的问题。如果你需要帮助,请再次告诉我你的数学问题。

当我们再次查看截断的对话数据时

print(trimmer.invoke(messages))

可以看到历史对话被我们新问的问题截断

[SystemMessage(content='你是个好助手', additional_kwargs={}, response_metadata={}),HumanMessage(content='我叫什么名字?', additional_kwargs={}, response_metadata={}),AIMessage(content='对不起,我不能确定你的名字。你希望我记住你的名字吗?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 68, 'total_tokens': 85, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_name': 'GLM-4-Flash-250414', 'system_fingerprint': None, 'id': '20250630163449f5eea44259c541d5', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--af679e07-301f-4d10-bf1b-893695fd0965-0', usage_metadata={'input_tokens': 68, 'output_tokens': 17, 'total_tokens': 85, 'input_token_details': {}, 'output_token_details': {}}),HumanMessage(content='我刚问了什么数学题?', additional_kwargs={}, response_metadata={}),AIMessage(content='很抱歉,由于我是一个人工智能助手,我无法记住你之前的提问或对话。每次与我交互时,我都会尽力根据当前的信息来回答你的问题。如果你需要帮助,请再次告诉我你的数学问题。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 45, 'prompt_tokens': 73, 'total_tokens': 118, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_name': 'GLM-4-Flash-250414', 'system_fingerprint': None, 'id': '20250630163449887e83e423c54d8f', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--a7900a73-bf27-4716-ab9b-505eb1d57188-0', usage_metadata={'input_tokens': 73, 'output_tokens': 45, 'total_tokens': 118, 'input_token_details': {}, 'output_token_details': {}})]

当我们将 max_tokens 设置变大后所有的记忆都会有效,我们刚才问的问题也会有正确答案

max_tokens=100,
你叫小明。你问的是“2 + 2 等于多少”。

4. 流式处理

大型语言模型有时可能需要一段时间才能响应,因此为了改善用户体验,大多数应用程序所做的一件事是随着每个令牌的生成流回。这样用户就可以看到进度。

from langchain_core.messages import HumanMessagefrom langchain_openai import ChatOpenAIfrom operator import itemgetterfrom langchain_core.runnables import RunnablePassthroughfrom langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholderfrom langchain_core.runnables.history import RunnableWithMessageHistoryfrom langchain_core.chat_history import BaseChatMessageHistory, InMemoryChatMessageHistorymodel = ChatOpenAI(model="GLM-4-Flash-250414", api_key="申请的key", base_url="https://open.bigmodel.cn/api/paas/v4/")prompt = ChatPromptTemplate.from_messages(    [        (            "system",            "你是个乐于助人的助手。尽你所能用{language}回答所有问题",        ),        MessagesPlaceholder(variable_name="messages"),    ])chain = (    RunnablePassthrough.assign(messages=itemgetter("messages"))    | prompt    | model)store = {}def get_session_history(session_id: str) -> BaseChatMessageHistory:    if session_id not in store:        store[session_id] = InMemoryChatMessageHistory()    return store[session_id]with_message_history = RunnableWithMessageHistory(    chain,    get_session_history,    input_messages_key="messages",)config = {"configurable": {"session_id": "aaaaaaa"}}# 流式处理for r in with_message_history.stream(    {        "messages": [HumanMessage(content="你好!我是小明。给我讲个笑话")],        "language": "中语",    },    config=config,):    print(r.content, end="|")

模型的回答

你好|,|小明|!|很高兴|为你|讲|笑话|。|这里|有一个|:有一天|,|小明|在|数学|课上|睡觉|,|老师|发现了|,|就|问他|:“|小明|,|你知道|你现在|在|做什么|吗|?”|小明|迷|迷糊|糊|地|回答|:“|我在|做梦|。”|老师|笑着说|:“|那|你的|数学|梦|做得|怎么样|?|”希望|这个|笑话|能|让你|开心|!|还有|其他|想|听的|笑话|吗|?||

本章讲解了如何使用 LangChain 构建一个有上下文记忆简单聊天程序第二部分,管理对话历史,控制上下文长度等。下篇我们将讲解如何调用向量模型、使用向量数据库等,为建立我们自己的知识库做准备

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

LangChain 对话历史 上下文管理 流式处理
相关文章