在LLM上下文窗口的限制下有效管理对话历史,是构建复杂有状态应用程序的基本挑战。虽然简单的缓冲区足以应对简短的交流,但处理长时间交互或大型背景文档的生产系统需要更高级的技巧。未能有效管理上下文窗口会导致回复不连贯、重要信息丢失,最终导致糟糕的用户体验。主要问题是容量有限。LLM在单次推理请求中只能处理有限数量的令牌(其上下文窗口)。随着对话或相关数据的增加,您必须决定哪些信息最重要并保留在该窗口中。本节研究智能地做出这些决定的策略。摘要技术摘要技术是将交互历史的较旧部分浓缩成更短的形式,从而在上下文窗口中为新消息腾出空间。LangChain为此提供了内置机制:ConversationSummaryMemory: 这种方式维护整个对话的持续摘要。每次交流后,历史记录(包括之前的摘要和新消息)会发送给LLM,LLM生成一个更新的、整合的摘要。虽然这确保了对话的要点得以保留,但它在每一步都会产生LLM调用以进行摘要,增加了延迟和成本。上下文的质量完全取决于LLM生成良好摘要的能力。ConversationSummaryBufferMemory: 一种实用的混合方法,此记忆模块精确地保留了最近交互的缓冲区,同时维护了旧交流的摘要。它使用一个max_token_limit参数。交互被添加到缓冲区,直到令牌限制被超出。此时,缓冲区中最旧的消息会被摘要(使用LLM调用)并合并到现有的摘要部分,从而在缓冲区中创建空间。这平衡了对近期细节的需求与对过去内容进行浓缩的必要性,通常在成本、延迟和信息保留之间提供一个很好的折衷方案。仔细考量这些权衡。摘要技术保留了长期上下文,但会引入计算开销,并可能根据摘要质量导致信息丢失。对于对延迟敏感的应用程序,可以考虑后台摘要任务等策略。滑动窗口缓冲区最简单的方法是只保留对话中最近的部分。ConversationBufferWindowMemory: 这种记忆类型跟踪最近的k次交互(用户输入和AI响应对)。当发生新的交互并且历史记录超过k轮时,最旧的交互会被丢弃。# 示例:配置一个用于保留最近3轮对话的滑动窗口记忆 from langchain.memory import ConversationBufferWindowMemory # 保留最近 k=3 次交互(输入/输出对) window_memory = ConversationBufferWindowMemory(k=3)这种方法计算开销低,并且易于实现。其主要缺点是上下文的突然丢失。早于k次交互的信息会被完全遗忘,无论其对当前轮次的潜在关联性如何。这适用于上下文主要在本地且长期依赖性最小的应用程序。令牌限制约束许多记忆模块,特别是基于缓冲区的模块,允许您指定一个max_token_limit。这相当于对发送给LLM的上下文中所包含的令牌数量设置了一个硬性上限。LangChain的记忆类在内部使用此限制来决定何时修剪消息、触发摘要(如ConversationSummaryBufferMemory中),或者简单地截断历史记录。设置适当的令牌限制对于以下方面很重要:防止错误: 避免超出LLM的最大上下文大小。成本控制: 直接管理每次API调用处理的令牌数量。性能: 较短的上下文通常会带来更快的LLM响应时间。此限制通常与其他策略(如摘要或窗口化)协同使用。从记忆存储中选择性检索与线性存储对话历史不同,向量存储支持的记忆改变了方法。它不是试图将可能庞大的历史记录塞进一个小窗口,而是将整个历史记录(或相关文档)外部存储,通常在向量数据库中。VectorStoreRetrieverMemory: 当应用程序需要上下文时,此记忆模块使用检索器(通常基于语义相似性搜索)根据当前输入或查询,从向量存储中查找最相关的片段。只有这些相关的片段,可能结合最新的消息,才会被注入到LLM提示中。这种方法有效地将总历史大小与LLM的上下文窗口解耦。如果与当前话题相关,应用程序可以从长时间对话的早期“记住”信息。其有效性取决于检索机制的质量——它在需要时呈现正确的过去信息的能力。这与第4章讨论的RAG技术密切相关。对话压缩与摘要不同,压缩旨在减少历史记录的令牌数量,同时保留最重要的信息和关系,通常使用LLM来完成此任务。基于实体的记忆: 像ConversationKGMemory这样的系统试图从对话中构建知识图谱,提取重要实体及其关系。提供给LLM的上下文可能是最近消息和从该图谱中综合的相关事实的混合。自定义压缩逻辑: 更高级的实现可能涉及一个专用的LangChain链(LLMChain),其目的是获取当前历史记录并输出一个为下游任务特别设计的压缩、精炼版本,可能专注于特定类型的信息(例如,用户偏好、已识别的目标)。压缩寻求比基本摘要更高的信息保真度,但通常会带来更高的计算成本和实现复杂度。策略组合生产应用程序通常受益于组合这些策略。一个配置可能包括:一个ConversationBufferWindowMemory,用于保存最近几轮对话,以提供即时上下文。一个ConversationSummaryMemory或类似机制,用于浓缩早于窗口的交互。一个VectorStoreRetrieverMemory,通过语义搜索提供对完整长期历史或相关文档的访问。在将最终上下文传递给主LLM之前,对其应用严格的令牌限制。digraph G { rankdir=TB; node [shape=box, style=rounded, fontname="Arial", fontsize=10, margin=0.2]; edge [fontname="Arial", fontsize=9]; subgraph cluster_strategies { label = "上下文管理选项"; bgcolor="#e9ecef"; summarize [label="摘要\n(例如,ConversationSummaryBufferMemory)", fillcolor="#b2f2bb", style=filled]; window [label="滑动窗口\n(例如,ConversationBufferWindowMemory)", fillcolor="#a5d8ff", style=filled]; retrieve [label="选择性检索\n(例如,VectorStoreRetrieverMemory)", fillcolor="#ffec99", style=filled]; compress [label="压缩\n(例如,自定义链,KG记忆)", fillcolor="#eebefa", style=filled]; } history [label="完整对话历史 / 文档存储", shape=cylinder, style=filled, fillcolor="#ced4da"]; llm_prompt [label="LLM提示上下文\n(有限令牌)", shape=note, style=filled, fillcolor="#ffc9c9"]; history -> summarize [label=" 浓缩旧内容 "]; history -> window [label=" 保留最近内容 "]; history -> retrieve [label=" 搜索相关内容 "]; history -> compress [label=" 提炼信息 "]; summarize -> llm_prompt [label=" 摘要 + 最近内容 "]; window -> llm_prompt [label=" 仅最近内容 "]; retrieve -> llm_prompt [label=" 检索内容 + 最近内容 "]; compress -> llm_prompt [label=" 压缩内容 + 最近内容 "]; }不同的策略处理完整历史记录,以将相关信息适配到有限的LLM提示上下文中。方法选择选择正确的策略组合需要分析您应用程序的需求:交互风格: 是简短的问答、长时间运行的聊天机器人,还是执行任务的代理?长时间交互更适合摘要或检索。信息需求: LLM需要精确地逐字回忆最近的消息,还是过去交互的要点就足够了?延迟要求: 简单窗口化最快;摘要/压缩会显著增加LLM调用延迟。检索延迟取决于向量存储的性能。成本预算: 用于摘要/压缩的LLM调用会增加直接成本。向量存储有基础设施成本。窗口化通常是最便宜的。保真度与成本: 通过摘要或窗口化造成的潜在信息丢失量,与更复杂方法的成本相比,可接受的程度是多少?没有单一的“最佳”策略。有效的上下文窗口管理通常涉及根据观察到的性能和成本指标进行实验和调整,使用LangSmith(第5章讨论)等评估工具来衡量不同记忆配置对应用程序质量的影响。监控每次交互的令牌数量以及记忆操作引入的延迟对于生产系统很重要。