langchain 入门指南 - 让 AI 记住你说过的话

本文涉及的产品
阿里云百炼推荐规格 ADB PostgreSQL,4核16GB 100GB 1个月
简介: langchain 入门指南 - 让 AI 记住你说过的话

在我们通过 web 端的聊天界面与 AI 对话时,AI 会记住你说过的话。这样,你可以在对话中引用之前的话语,或者在之后的对话中提到之前的话语。

但是如果我们像下面这样调用 API 的时候,就会发现 AI 不会记住我们之前说过的话:

from langchain_openai import ChatOpenAI
chat = ChatOpenAI(
    model="yi-large",
    temperature=0.3,
    max_tokens=200,
    api_key='your key',
    base_url="https://api.lingyiwanwu.com/v1",
)
response = chat.invoke('今天广州天气晴朗,26~35摄氏度。')
print(response.content)
response = chat.invoke('今天广州适合穿什么?')
print(response.content)

输出:

这句话的意思是今天广州的天气非常好,晴朗无云,气温在26摄氏度到35摄氏度之间。这是一个适合户外活动的好天气,但也要注意防晒和补水,因为气温较高。
很抱歉,我无法提供实时天气信息或建议。要了解今天的广州适合穿什么,您可以查看当地的天气预报,了解当前的气温、湿度和天气状况,然后根据这些信息选择合适的衣物。
通常,广州属于亚热带季风气候,夏季炎热潮湿,冬季温和,春秋季节宜人。根据季节和天气预报,您可以选择穿短袖、长袖、薄外套或厚外套等。
别忘了查看是否需要携带雨具,因为广州的降雨量也比较丰富。

虽然我们告诉了 LLM 今天广州的天气,但是在第二次调用的时候,AI 并没有记住我们之前说过的话,所以不能依据当前的天气状况给我提供穿衣建议。

为什么 AI 不会记住我说过的话

这是因为大模型本身并不具备记忆功能。在我们每次使用大模型的 API 的时候,它都是基于训练模型时候的数据以及我们传递的信息来进行推理的。

如果让大模型记住我们说过的话,那么它需要存储的信息量会非常庞大,这样的成本是非常高昂的。

同时,如果每一次调用的时候,都在一个庞大的上下文中进行推理,那么推理的时间也会非常长,消耗的资源会非常多。

所以,大模型通常是不会记住我们说过的话的。

解决办法:我们自己记住

既然大模型记不住我们说过的话,那唯一的办法就是我们自己记住,然后下次调用的时候,将之前的话语传递给 AI。

from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage
chat = ChatOpenAI(
    model="yi-large",
    temperature=0.3,
    max_tokens=200,
    api_key='your key',
    base_url="https://api.lingyiwanwu.com/v1",
)
messages = [
    HumanMessage('今天广州天气晴朗,26~35摄氏度。'),
]
response = chat.invoke(messages)
messages.append(AIMessage(response.content))
messages.append(HumanMessage('今天广州适合穿什么?'))
print(messages)
response = chat.invoke(messages)
print(response.content)

输出:

[
  HumanMessage(content='今天广州天气晴朗,26~35摄氏度。'), 
  AIMessage(content='这句话的意思是,今天广州的天气非常好,晴朗无云,气温在26摄氏度到35摄氏度之间。这是一个非常适合户外活动的天气,既不太热也不太冷。'), 
  HumanMessage(content='今天广州适合穿什么?')
]
根据您提供的信息,今天广州的天气晴朗,气温在26到35摄氏度之间。这个温度范围适合穿着轻薄、透气的衣物。以下是一些建议:
1. 上衣:可以选择短袖T恤、薄衬衫或棉质衣物,以保持凉爽。
2. 下装:可以穿短裤、七分裤或轻薄的牛仔裤。
3. 鞋子:舒适的凉鞋、帆布鞋或运动鞋都是不错的选择。
4. 配件:如果需要外出,可以戴上一顶遮阳帽和太阳镜,以保护皮肤和眼睛不受紫外线伤害。
5. 防晒:由于天气晴朗,紫外线可能较强,建议涂抹防晒霜以保护皮肤。
请根据您的个人舒适度和活动需求来调整着装。如果您的活动包括室内外结合,可以准备一件轻薄的外套或披肩,以

输出的第一部分是我们问第二个问题的上下文,第二部分是 AI 的回答。

在这个例子中,我们将之前的对话保存在了 messages 中,然后在下一次调用的时候,将之前的对话传递给 AI。

当然,这里只是举个例子。真实使用中,我们可能会将一大段信息交给 LLM,然后让它来帮我们进行分析推理,然后可以问它问题从而得到答案。

对话内容记忆的抽象

上一个例子中,我们是每一次请求和响应的内容都保存在了 messages 中,然后传递给 AI。

这样操作可能会比较麻烦,因为消息历史会逐渐增长,直到达到 LLM 的最大上下文长度。

这个时候,我们就需要删除一部分历史消息,从而保证 LLM 可以正常处理我们的请求。

除了最大上下文限制的原因,太长的上下文也会带来大量的 token 消耗,这样会增加我们的成本。

因此,我们非常需要定期对历史消息进行处理,删除一部分意义不大的历史消息,或者删除那些最久远的消息,只保留最近的消息。

这跟人类的记忆一样,我们对近期发生的事情记忆深刻,而对很久远的事情记忆模糊。

ConversationBufferWindowMemory

为了解决这个问题,langchain 提供了一些处理历史消息的工具。

比如适合我上面说的这个场景的 RunnableWithMessageHistory,它可以记住指定次数的消息,然后在超过指定次数的时候,删除最早的消息。

from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import AIMessage
from langchain_core.runnables import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
from langchain.memory import ConversationBufferWindowMemory
llm = ChatOpenAI(
    model="yi-large",
    temperature=0.3,
    max_tokens=200,
    api_key='your key',
    base_url="https://api.lingyiwanwu.com/v1",
)
store = {}  # memory is maintained outside the chain
def get_session_history(session_id: str) -> InMemoryChatMessageHistory:
    if session_id not in store:
        store[session_id] = InMemoryChatMessageHistory()
        return store[session_id]
    memory = ConversationBufferWindowMemory(
        chat_memory=store[session_id],
        k=10,
        return_messages=True,
    )
    assert len(memory.memory_variables) == 1
    key = memory.memory_variables[0]
    messages = memory.load_memory_variables({})[key]
    store[session_id] = InMemoryChatMessageHistory(messages=messages)
    return store[session_id]
chain = RunnableWithMessageHistory(llm, get_session_history)
conf = {"configurable": {"session_id": "1"}}
response = chain.invoke('今天广州天气晴朗,26~35摄氏度。', config=conf) # type: AIMessage
print(f"response 1: {response.content}")
response = chain.invoke('今天广州适合穿什么?', config=conf) # type: AIMessage
print(f"response 2: {response.content}")

输出:

response 1: 这句话的意思是今天广州的天气非常好,晴朗无云,气温在26摄氏度到35摄氏度之间。这个温度范围对于夏天来说是比较舒适的,适合户外活动。
response 2: 根据您提供的信息,今天广州的天气晴朗,气温在26到35摄氏度之间。这个温度范围适合穿着轻薄、透气的衣物。以下是一些建议:
1. 上衣:可以选择短袖T恤、薄衬衫或棉质衣物,避免穿得过多导致出汗后衣服湿透。
2. 下装:可以穿短裤、七分裤或轻薄的牛仔裤。如果是在室内或者空调环境中,可以考虑带一件长裤以防着凉。
3. 鞋子:舒适的凉鞋、帆布鞋或运动鞋都是不错的选择。
4. 配件:可以戴一顶遮阳帽和太阳镜来保护皮肤和眼睛免受紫外线伤害。如果需要长时间在户外,可以考虑涂抹防晒霜。
5. 携带物品:由于气温较高,建议随身携带水瓶以保持水分,同时可以携带

相比之下,现在我们并不需要每次都手动保存历史消息,而是交给 ConversationBufferWindowMemory 来处理。

这样,我们就可以更加专注于对话的内容,而不用担心历史消息的处理。

在上面这个例子中,我们指定了 k=10,也就是说,只保存最近的 20 条消息,

超过 20 条的消息之后会删除最早的消息(这是因为在底层实现中,会使用 k * 2,而不是 k)。

我们可以指定 k=1 来验证一下():

from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import AIMessage
from langchain_core.runnables import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
from langchain.memory import ConversationBufferWindowMemory
llm = ChatOpenAI(
    model="yi-large",
    temperature=0.3,
    max_tokens=200,
    api_key='your key',
    base_url="https://api.lingyiwanwu.com/v1",
)
store = {}  # memory is maintained outside the chain
def get_session_history(session_id: str) -> InMemoryChatMessageHistory:
    if session_id not in store:
        store[session_id] = InMemoryChatMessageHistory()
        return store[session_id]
    memory = ConversationBufferWindowMemory(
        chat_memory=store[session_id],
        k=1,
        return_messages=True,
    )
    assert len(memory.memory_variables) == 1
    key = memory.memory_variables[0]
    # 返回最新的 k * 2 条消息
    messages = memory.load_memory_variables({})[key]
    store[session_id] = InMemoryChatMessageHistory(messages=messages)
    return store[session_id]
chain = RunnableWithMessageHistory(llm, get_session_history)
conf = {"configurable": {"session_id": "1"}}
response = chain.invoke('今天广州天气晴朗,26~35摄氏度。', config=conf) # type: AIMessage
print(f"response 1: {response.content}")
response = chain.invoke('这是一条无用的消息,请你忽略。', config=conf) # type: AIMessage
print(f"response 2: {response.content}")
response = chain.invoke('今天广州适合穿什么?', config=conf) # type: AIMessage
print(f"response 3: {response.content}")

输出:

response 1: 这句话的意思是,今天广州的天气非常好,晴朗无云,气温在26摄氏度到35摄氏度之间。这是一个非常适合户外活动的天气,既不太热也不太冷。
response 2: 好的,我会忽略这条消息。如果您有其他问题或需要帮助,请随时告诉我!
response 3: 很抱歉,我无法提供实时数据或当前的天气情况。........<一大堆>

因为我们使用了 k=1,所以当我们交谈了三次之后,第一次发送的内容就会被删除了。

所以当我们问第三个问题的时候,AI 并没有记住我们之前说过的话。

本文例子用到的一些类的介绍

InMemoryChatMessageHistory

没有特殊功能,只有一个 messages 属性,用于保存消息,是 list 类型。

ConversationBufferWindowMemory

  • 它有一个 chat_memory 属性,用于保存历史消息。
  • 当我们从它的实例中获取消息的时候(调用它的 load_memory_variables 方法的时候),它只会返回最近的 k * 2 条历史消息。

ConversationSummaryBufferMemory

除了 ConversationBufferWindowMemorylangchain 还提供了 ConversationSummaryBufferMemory,它会将历史消息进行摘要(当超过了指定长度的时候),然后保存摘要:

def prune(self) -> None:
    """Prune buffer if it exceeds max token limit"""
    buffer = self.chat_memory.messages
    curr_buffer_length = self.llm.get_num_tokens_from_messages(buffer)
    if curr_buffer_length > self.max_token_limit:
        pruned_memory = []
        while curr_buffer_length > self.max_token_limit:
            pruned_memory.append(buffer.pop(0))
            curr_buffer_length = self.llm.get_num_tokens_from_messages(buffer)
        self.moving_summary_buffer = self.predict_new_summary(
            pruned_memory, self.moving_summary_buffer
        )

prune 方法会在超过指定长度的时候,将历史消息进行摘要,然后保存摘要。

优缺点

优点:

  • 控制了缓存内容大小
  • 尽量记忆了对话的内容

缺点:

  • 在缓存内容超出限制后,为控制缓存的大小,会持续通过大模型来总结较早的内容。
  • 相应延迟增加很多
  • 成本增加

总结

在使用 LLM 的时候,我们需要注意到 LLM 并不会记住我们之前说过的话。

但是我们可以自行保存历史消息,然后在下一次调用的时候,将之前的消息传递给 AI。

为了方便处理历史消息,langchain 提供了 ConversationBufferWindowMemory 这个工具,可以帮助我们保存历史消息,并在超过指定数量的时候删除最早的消息。


相关实践学习
阿里云百炼xAnalyticDB PostgreSQL构建AIGC应用
通过该实验体验在阿里云百炼中构建企业专属知识库构建及应用全流程。同时体验使用ADB-PG向量检索引擎提供专属安全存储,保障企业数据隐私安全。
AnalyticDB PostgreSQL 企业智能数据中台:一站式管理数据服务资产
企业在数据仓库之上可构建丰富的数据服务用以支持数据应用及业务场景;ADB PG推出全新企业智能数据平台,用以帮助用户一站式的管理企业数据服务资产,包括创建, 管理,探索, 监控等; 助力企业在现有平台之上快速构建起数据服务资产体系
目录
相关文章
|
2月前
|
前端开发 机器人 API
前端大模型入门(一):用 js+langchain 构建基于 LLM 的应用
本文介绍了大语言模型(LLM)的HTTP API流式调用机制及其在前端的实现方法。通过流式调用,服务器可以逐步发送生成的文本内容,前端则实时处理并展示这些数据块,从而提升用户体验和实时性。文章详细讲解了如何使用`fetch`发起流式请求、处理响应流数据、逐步更新界面、处理中断和错误,以及优化用户交互。流式调用特别适用于聊天机器人、搜索建议等应用场景,能够显著减少用户的等待时间,增强交互性。
509 2
|
2月前
|
存储 人工智能 搜索推荐
解锁AI新境界:LangChain+RAG实战秘籍,让你的企业决策更智能,引领商业未来新潮流!
【10月更文挑战第4天】本文通过详细的实战演练,指导读者如何在LangChain框架中集成检索增强生成(RAG)技术,以提升大型语言模型的准确性与可靠性。RAG通过整合外部知识源,已在生成式AI领域展现出巨大潜力。文中提供了从数据加载到创建检索器的完整步骤,并探讨了RAG在企业问答系统、决策支持及客户服务中的应用。通过构建知识库、选择合适的嵌入模型及持续优化系统,企业可以充分利用现有数据,实现高效的商业落地。
104 6
|
2月前
|
人工智能 JSON Java
【极速入门版】编程小白也能轻松上手Comate AI编程插件
【极速入门版】编程小白也能轻松上手Comate AI编程插件
34 0
|
25天前
|
人工智能 自然语言处理 机器人
手把手带你搭建一个语音对话机器人,5分钟定制个人AI小助手(新手入门篇)
本文介绍了如何从零开始搭建一个语音对话机器人,涵盖自动语音识别(ASR)、自然语言处理(NLP)和文本到语音合成(TTS)三大核心模块。通过使用开源工具如FunASR、LLaMA3-8B和ChatTTS,以及FastAPI和Gradio等技术,详细指导读者轻松实现个人AI小助手的构建,适合技术新手快速上手。
172 1
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
探索AI的奥秘:机器学习入门指南
【10月更文挑战第30天】本篇文章是一份初学者友好的机器学习入门指南,旨在帮助读者理解并开始实践机器学习。我们将介绍机器学习的基本概念,包括监督学习、无监督学习和强化学习等。我们还将提供一些实用的代码示例,以帮助读者更好地理解和应用这些概念。无论你是编程新手,还是有一定经验的开发者,这篇文章都将为你提供一个清晰的机器学习入门路径。
38 2
|
2月前
|
人工智能 API 决策智能
swarm Agent框架入门指南:构建与编排多智能体系统的利器 | AI应用开发
Swarm是OpenAI在2024年10月12日宣布开源的一个实验性质的多智能体编排框架。其核心目标是让智能体之间的协调和执行变得更轻量级、更容易控制和测试。Swarm框架的主要特性包括轻量化、易于使用和高度可定制性,非常适合处理大量独立的功能和指令。【10月更文挑战第15天】
340 6
|
2月前
|
存储 人工智能 Java
Neo4j从入门到精通:打造高效知识图谱数据库 | AI应用开发
在大数据和人工智能时代,知识图谱作为一种高效的数据表示和查询方式,逐渐受到广泛关注。本文从入门到精通,详细介绍知识图谱及其存储工具Neo4j,涵盖知识图谱的介绍、Neo4j的特点、安装步骤、使用方法(创建、查询)及Cypher查询语言的详细讲解。通过本文,读者将全面了解如何利用Neo4j处理复杂关系数据。【10月更文挑战第14天】
152 6
|
2月前
|
机器学习/深度学习 人工智能 开发框架
解锁AI新纪元:LangChain保姆级RAG实战,助你抢占大模型发展趋势红利,共赴智能未来之旅!
【10月更文挑战第4天】本文详细介绍检索增强生成(RAG)技术的发展趋势及其在大型语言模型(LLM)中的应用优势,如知识丰富性、上下文理解和可解释性。通过LangChain框架进行实战演练,演示从知识库加载、文档分割、向量化到构建检索器的全过程,并提供示例代码。掌握RAG技术有助于企业在问答系统、文本生成等领域把握大模型的红利期,应对检索效率和模型融合等挑战。
204 14
|
3月前
|
人工智能 搜索推荐 数据挖掘
让 AI 回答更精准 ◎ 来学学这些Prompt入门小技巧
这篇文章介绍了如何通过有效的提示词来提升向AI提问的质量,使其回答更加精准,并提供了实用的指导原则和案例分析。
让 AI 回答更精准 ◎ 来学学这些Prompt入门小技巧
|
2月前
|
人工智能 前端开发 JavaScript
前端大模型入门(二):掌握langchain的核心Runnable接口
Langchain.js 是 Langchain 框架的 JavaScript 版本,专为前端和后端 JavaScript 环境设计。最新 v0.3 版本引入了强大的 Runnable 接口,支持灵活的执行方式和异步操作,方便与不同模型和逻辑集成。本文将详细介绍 Runnable 接口,并通过实现自定义 Runnable 来帮助前端人员快速上手。