ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

大语言模型对话历史截断策略:解决上下文窗口限制的工程实践

大语言模型对话历史截断策略:解决上下文窗口限制的工程实践 1. 从一次“对话失忆”的故障说起那天下午我正调试一个基于大语言模型的智能客服原型。测试流程很顺利用户连续问了几个关于产品功能的问题AI都对答如流。但当用户在第15轮对话时突然回溯到第3轮的一个细节并追问“你刚才说的那个高级功能的配置项具体路径是什么” 屏幕另一端的AI助手却给出了一个令人啼笑皆非的回答“抱歉我还没有理解您提到的‘高级功能’具体指什么您可以再详细描述一下吗”问题显而易见AI“失忆”了。它忘记了对话历史中早期的关键信息。这并非模型本身智力不足而是我们喂给它的“记忆”——也就是对话上下文——因为长度限制被无情地截断了。早期的重要信息被丢弃导致模型只能基于最近几轮对话进行回应仿佛一个健忘症患者。这个场景几乎是所有AI应用开发者都会遇到的经典难题如何优雅地处理不断增长的对话历史使其始终适配模型有限的上下文窗口这就是“根据Token长度截断历史对话”要解决的核心问题。它不是一个炫酷的新功能而是AI应用走向实用化、工程化过程中必须夯实的底层基础。无论是构建聊天机器人、代码助手还是数据分析Agent只要涉及多轮交互你就绕不开它。处理不好轻则用户体验割裂重则逻辑错误频发。本文将从一个实践者的角度拆解这个问题的来龙去脉并分享一套经过实战检验的、可复用的截断策略与代码实现。2. 理解问题的根源Token、上下文窗口与成本三角在动手写代码之前我们必须彻底理解为什么要做截断以及背后的约束是什么。这关乎三个核心概念Token、上下文窗口和成本它们构成了一个相互制约的“三角关系”。2.1 Token大模型世界的“单词”首先Token不是字符也不是单词。它是大语言模型LLM处理文本的基本单位。对于英文一个Token可能是一个短单词如“cat”也可能是一个长单词的一部分如“unbelievable”可能被拆成“un”, “bel”, “ieve”, “able”。对于中文一个汉字通常就是一个Token但复杂的词汇或专有名词也可能被拆分。为什么是Token因为模型在训练时就是基于Token序列来学习语言规律的。当我们向模型发送一段提示Prompt时API如OpenAI、Claude、DeepSeek会首先用其专属的分词器Tokenizer将文本转换成Token ID序列。模型处理的是这些ID输出的也是ID最后再解码回文本。注意不同模型的分词器不同。用GPT-4的分词器去数Claude消息的Token数结果会不准确可能导致截断计算错误。务必使用对应模型官方提供的工具库如OpenAI的tiktoken Anthropic可能提供相应SDK进行计数。2.2 上下文窗口模型的“工作记忆”上限上下文窗口Context Window是指模型单次处理所能接受的最大Token数量。这个限制是硬性的由模型架构和训练方式决定。输入限制你发送给模型的全部内容系统指令、历史对话、用户当前问题等的Token总数不能超过此上限。输出限制模型生成回复的Token数也受此窗口约束通常还有一个更小的max_tokens参数来控制单次生成的长度。常见的上下文窗口大小GPT-3.5-turbo: 16K tokensGPT-4/4-turbo: 128K tokensClaude 3 Opus/Sonnet: 200K tokens一些开源模型如Llama 3: 8K或128K不等这个窗口就是模型的“短期工作记忆”。窗口越大模型能“记住”和参考的信息就越多但同时也带来了更高的计算成本和延迟。2.3 成本三角长度、质量与开销的权衡这里就引出了第三个维度成本。成本分为两类计算成本API费用绝大多数按Token计费的云API对输入Input和输出Output分别收费。输入Token通常比输出Token便宜但总量巨大时依然可观。一个128K上下文满负荷的请求其输入费用可能数十倍于一个简短问答。性能成本延迟与稳定性处理超长上下文需要更多的GPU内存和计算时间导致API响应变慢。对于需要实时交互的应用这是不可接受的。此外过长的上下文有时反而会干扰模型使其在无关信息中“迷失”降低回答质量。于是我们面临一个三角权衡想要更长的记忆更多历史- 需要更大的上下文窗口 -导致更高的成本和更慢的响应。想要更低的成本和更快的响应- 必须限制上下文长度 -可能导致记忆丢失截断。“根据Token长度截断历史对话”的本质就是在这个三角中寻找一个动态平衡点在有限的预算和性能要求下尽可能保留对当前问答最有价值的历史信息。3. 设计截断策略不只是“掐头去尾”最朴素的截断方式是“先进先出”FIFO当总Token数超限时从最旧的历史消息开始删除。这简单粗暴但往往效果很差因为它假设最早的信息最不重要。在实际对话中开场白、核心任务定义、关键约束条件往往出现在最前面。一个健壮的截断策略需要更精细的思考。我们可以将其分解为几个核心决策点。3.1 策略一优先级保留——什么信息绝对不能丢不是所有历史消息都平等。我们需要给消息定义优先级系统指令System Prompt这是对话的“宪法”定义了AI的角色、行为规范和回答格式。它必须全程保留且通常放在上下文最开头。即使需要极端截断也应优先压缩其他部分尽力保留系统指令的核心。关键用户设定例如用户说“请用Python代码回答”、“请用表格形式总结”、“忽略所有关于X的信息”。这类设定是后续对话的“游戏规则”优先级极高。本轮用户问题Latest User Query这是触发本次模型调用的直接原因必须完整保留且通常紧接在系统指令之后。历史对话轮次History Turns这是需要被动态管理的主体。其中距离当前问题较近的轮次通常相关性更高。一个实用的做法是定义“保留区”和“可压缩区”。保留区包括系统指令和当前用户问题永远不动。可压缩区就是历史对话在这里面进行腾挪。3.2 策略二智能压缩——如何让有限的空间承载更多信息直接删除是最后的手段。在此之前我们可以尝试“压缩”总结摘要Summarization这是最有效的方法之一。当历史对话积累到一定长度时可以调用模型自身或一个更小、更快的摘要模型将早期的多轮对话总结成一段精炼的文字。例如将前10轮关于“需求讨论”的对话总结为“用户想要开发一个带有用户登录和数据分析面板的Web应用技术栈倾向React和Python Flask预算中等。” 然后用这段总结替换掉原始的长篇历史。这样核心信息得以保留但Token占用大幅减少。关键信息提取不总结整个对话而是提取出其中的关键实体、数字、决策点以结构化的方式如JSON保存。例如从需求讨论中提取{“功能”: [“登录”, “数据面板”], “技术栈”: “ReactFlask”, “预算”: “中等”}。忽略无关细节模型可以学习忽略那些与当前问题明显无关的历史分支。但这需要更复杂的逻辑来判断相关性。在我的项目中我通常采用“滑动窗口摘要锚点”的混合策略。为历史对话维护一个固定大小的滑动窗口例如最近5轮窗口内的对话保持原样以保证连贯性。当更早的历史需要被纳入考虑时不是直接载入原文而是载入之前为那段历史生成的“摘要锚点”。这样既能维持超长记忆的幻觉又严格控制了Token消耗。3.3 策略三动态计算——何时触发截断或压缩截断不是一次性的而是一个持续的过程。我们需要一个“监控器”预计算与检查在每次准备向模型API发送请求前计算当前组装好的完整Prompt系统指令 压缩后的历史摘要 原始历史窗口 当前问题的Token总数。设定安全阈值不要等到达到上下文窗口极限如128K才行动。应该设定一个安全阈值例如窗口上限的80%102.4K。一旦预计算长度超过此阈值就触发压缩或截断流程。分层触发机制Level 1轻度超限例如超过阈值的10%。优先尝试删除或压缩优先级最低的历史轮次如一些简单的寒暄或确认。Level 2中度超限超过阈值的30%。触发对最早一片历史对话的自动摘要并用摘要替换原文。Level 3严重超限超过阈值的50%。采取更激进的手段如只保留系统指令、最近2-3轮对话和当前问题并提示用户“由于对话过长部分早期信息已被总结如需细节请再次询问”。这个动态机制确保了应用在各种对话长度下都能稳定运行同时给用户平滑的体验。4. 实战代码实现一个可复用的对话管理器理论说完了我们来看代码。我将展示一个Python类ConversationManager的核心部分它实现了上述的混合策略。我们使用OpenAI的tiktoken进行Token计数。import tiktoken from typing import List, Dict, Any, Optional from dataclasses import dataclass, field from enum import Enum class MessageRole(Enum): SYSTEM system USER user ASSISTANT assistant dataclass class Message: role: MessageRole content: str token_count: int 0 # 缓存Token数避免重复计算 is_summary: bool False # 标记是否为摘要消息 summary_of_ids: List[str] field(default_factorylist) # 此摘要代表了哪些原始消息的ID class ConversationManager: def __init__(self, model_name: str gpt-4, system_prompt: str , max_context_tokens: int 128000, safety_threshold: float 0.8): 初始化对话管理器。 Args: model_name: 使用的模型名称用于选择正确的分词器。 system_prompt: 系统指令。 max_context_tokens: 模型上下文窗口大小。 safety_threshold: 触发管理的安全阈值比例0-1。 self.model_name model_name try: self.encoder tiktoken.encoding_for_model(model_name) except KeyError: # 如果模型未在tiktoken中注册使用cl100k_baseGPT-4/3.5-turbo的编码器作为后备 self.encoder tiktoken.get_encoding(cl100k_base) print(fWarning: Model {model_name} not found in tiktoken, using cl100k_base as fallback.) self.system_message Message(roleMessageRole.SYSTEM, contentsystem_prompt) self._update_message_token_count(self.system_message) self.history: List[Message] [] # 存储所有历史消息包括原始和摘要 self.max_context_tokens max_context_tokens self.safety_threshold_tokens int(max_context_tokens * safety_threshold) self.safety_threshold safety_threshold # 滑动窗口配置 self.raw_history_window_size 5 # 保留最近N轮原始对话 self.summary_interval 10 # 每累积10轮原始对话考虑生成一个摘要 def _update_message_token_count(self, message: Message): 计算并更新单条消息的Token数包含角色标记等开销。 # 一个近似的计算角色名 内容 一些结构开销 text_to_encode f{message.role.value}: {message.content} message.token_count len(self.encoder.encode(text_to_encode)) def add_user_message(self, content: str): 添加用户消息到历史并触发管理。 msg Message(roleMessageRole.USER, contentcontent) self._update_message_token_count(msg) self.history.append(msg) self._manage_context() def add_assistant_message(self, content: str): 添加助手消息到历史并触发管理。 msg Message(roleMessageRole.ASSISTANT, contentcontent) self._update_message_token_count(msg) self.history.append(msg) self._manage_context() def _calculate_current_prompt_tokens(self, new_user_query: str) - int: 模拟计算如果现在发送请求整个Prompt的Token数。 包括系统消息 历史含摘要 新的用户问题。 total self.system_message.token_count for msg in self.history: total msg.token_count # 加上即将发送的新问题 new_msg Message(roleMessageRole.USER, contentnew_user_query) self._update_message_token_count(new_msg) total new_msg.token_count return total def _manage_context(self): 核心管理函数检查Token使用情况并执行相应的压缩或截断策略。 # 1. 首先永远保证系统消息在首位如果历史中有系统消息调整位置 # 这里简化处理假设系统消息只在初始化时存在且不被移动。 # 2. 计算当前历史不含即将发送的新问题的总Token数 current_history_tokens sum(msg.token_count for msg in self.history) total_if_now current_history_tokens self.system_message.token_count # 3. 检查是否超过安全阈值 if total_if_now self.safety_threshold_tokens: return # 处于安全范围无需操作 print(fContext management triggered. Current tokens: {total_if_now}, Threshold: {self.safety_threshold_tokens}) # 4. 分级处理策略 excess_ratio (total_if_now - self.safety_threshold_tokens) / self.safety_threshold_tokens if excess_ratio 0.2: # Level 1: 轻度超限删除最早的、非关键的原始消息非摘要 self._remove_earliest_non_critical() elif excess_ratio 0.5: # Level 2: 中度超限尝试生成摘要 self._summarize_old_messages() else: # Level 3: 严重超限激进截断只保留核心 self._aggressive_truncation() def _remove_earliest_non_critical(self): 策略L1删除最早的非关键原始消息。 # 关键消息系统消息、摘要消息、最近N轮原始消息 indices_to_keep set() # 保留最近的原始消息窗口 raw_messages [i for i, msg in enumerate(self.history) if not msg.is_summary] keep_raw_indices raw_messages[-self.raw_history_window_size:] if len(raw_messages) self.raw_history_window_size else raw_messages for idx in keep_raw_indices: indices_to_keep.add(idx) # 保留所有摘要消息它们已经是压缩后的精华 for idx, msg in enumerate(self.history): if msg.is_summary: indices_to_keep.add(idx) # 重建历史只保留需要保留的索引 new_history [] for idx, msg in enumerate(self.history): if idx in indices_to_keep: new_history.append(msg) else: print(fRemoving non-critical message: {msg.content[:50]}...) self.history new_history def _summarize_old_messages(self): 策略L2对最早的一批原始消息生成摘要。 # 找出最早的一片连续的、未被摘要覆盖的原始消息 candidate_indices [] for idx, msg in enumerate(self.history): if not msg.is_summary: candidate_indices.append(idx) else: # 遇到摘要消息如果已经积累了一些候选可以考虑对它们生成摘要 if len(candidate_indices) self.summary_interval: break if len(candidate_indices) 5: # 至少5条才值得摘要 return # 模拟摘要过程在实际应用中这里需要调用一个摘要函数可能是另一个LLM调用 old_messages [self.history[i] for i in candidate_indices] summary_text self._call_summarization_api(old_messages) # 这是一个需要你实现的函数 # 创建摘要消息 summary_msg Message( roleMessageRole.USER, # 或者用一个自定义角色如“summary” contentf[Summary of previous conversation]: {summary_text}, is_summaryTrue, summary_of_ids[str(i) for i in candidate_indices] # 记录摘要了哪些消息 ) self._update_message_token_count(summary_msg) # 用摘要消息替换原来的那片消息 # 先删除旧消息 for idx in sorted(candidate_indices, reverseTrue): del self.history[idx] # 在删除的位置插入摘要 insert_position candidate_indices[0] if candidate_indices else 0 self.history.insert(insert_position, summary_msg) print(fCreated summary for {len(old_messages)} old messages, saving approximately {sum(m.token_count for m in old_messages) - summary_msg.token_count} tokens.) def _aggressive_truncation(self): 策略L3激进截断只保留系统消息和极少数最新历史。 print(Performing aggressive truncation.) # 只保留系统消息已在别处、最近的1轮用户和助手对话如果存在、以及所有摘要消息因为它们是高密度信息 recent_raw [] # 从后往前找最近的一对用户/助手消息 for msg in reversed(self.history): if msg.role in (MessageRole.USER, MessageRole.ASSISTANT) and not msg.is_summary: recent_raw.append(msg) if len(recent_raw) 2: # 找一轮交互用户助手 break # 保留所有摘要消息和最近找到的原始消息 new_history [] for msg in self.history: if msg.is_summary: new_history.append(msg) # 添加最近找到的原始消息按原始顺序但这里我们简化处理按找到的反序添加回去 for msg in reversed(recent_raw): if msg not in new_history: # 需要重新插入到合适位置以保持时序这里简化直接追加 new_history.append(msg) self.history new_history def _call_summarization_api(self, messages: List[Message]) - str: 模拟调用摘要API。 在实际实现中这里应该 1. 将messages中的内容拼接成一段连贯文本。 2. 调用一个LLM可以是同一个大模型也可以是一个更小、更快的专用摘要模型生成摘要。 3. 返回摘要文本。 为了示例我们返回一个模拟摘要。 # 拼接内容 content .join([f{msg.role.value}: {msg.content} for msg in messages]) # 模拟摘要取前200个字符加上“...已总结” # 真实场景下这里应该是一个真实的API调用例如 # response openai.ChatCompletion.create(modelgpt-3.5-turbo, messages[{role: user, content: f请用一段话总结以下对话的核心内容\n\n{content}}]) # return response.choices[0].message.content simulated_summary content[:200] ... (summarized) return simulated_summary def get_messages_for_api(self, new_user_query: str) - List[Dict[str, str]]: 准备发送给LLM API的消息列表。 这是最终被调用的方法它会在发送前进行最终的长度检查和管理。 # 最终检查并管理上下文传入新问题模拟计算 projected_tokens self._calculate_current_prompt_tokens(new_user_query) if projected_tokens self.max_context_tokens: # 如果加入新问题后仍然超标执行更激进的管理 print(fWarning: Projected tokens ({projected_tokens}) exceed max context ({self.max_context_tokens}). Forcing aggressive management.) self._aggressive_truncation() # 重新计算 projected_tokens self._calculate_current_prompt_tokens(new_user_query) if projected_tokens self.max_context_tokens: # 如果还是超标只能极端截断丢弃所有历史只保留系统和当前问题 print(Emergency: Truncating all history, keeping only system and latest query.) self.history [] # 构建API所需的消息格式 messages [{role: self.system_message.role.value, content: self.system_message.content}] for msg in self.history: messages.append({role: msg.role.value, content: msg.content}) messages.append({role: user, content: new_user_query}) return messages # 使用示例 if __name__ __main__: manager ConversationManager( system_prompt你是一个有帮助的AI助手。, max_context_tokens4096, # 示例用一个小窗口 safety_threshold0.7 ) # 模拟多轮对话 for i in range(20): manager.add_user_message(f用户第{i}轮问题内容可能很长用于测试上下文管理。 * 5) manager.add_assistant_message(f助手第{i}轮回复。 * 3) # 准备第21轮请求 final_messages manager.get_messages_for_api(这是最新的问题) print(f最终发送的消息数量{len(final_messages)}) # 这里可以将 final_messages 发送给 OpenAI API这个ConversationManager类提供了一个基础框架。它维护消息历史自动计算Token并在历史过长时根据分级策略进行管理。关键点在于缓存Token计数避免每次检查都重新编码所有消息提升性能。分级策略根据超出安全阈值的比例触发不同强度的管理动作。摘要集成_summarize_old_messages方法预留了接入真实摘要API的接口。最终检查在get_messages_for_api中做最终把关确保请求不会因超长而失败。5. 高级议题与边界情况处理实现基础截断只是第一步。在实际生产环境中还有更多细节需要考虑。5.1 不同模型的分词器差异与适配如前所述不同模型的分词器不同。tiktoken主要支持OpenAI系列模型。如果你使用Claude、Gemini或开源模型需要找到对应的分词工具。Anthropic Claude虽然没有官方公开的Python分词器但你可以通过其API的“计数”功能来估算或者使用社区项目如anthropic-tokenizer注意非官方。Google Gemini可以使用google-generativeai库中的count_tokens方法。开源模型Llama, Mistral等通常使用Hugging Face的transformers库中的AutoTokenizer。一个健壮的管理器应该能适配多种模型。你可以抽象一个TokenCounter接口from abc import ABC, abstractmethod class TokenCounter(ABC): abstractmethod def count(self, text: str) - int: pass class OpenAITokenCounter(TokenCounter): def __init__(self, model_name: str): self.encoder tiktoken.encoding_for_model(model_name) def count(self, text: str) - int: return len(self.encoder.encode(text)) class HuggingFaceTokenCounter(TokenCounter): def __init__(self, tokenizer_name: str): from transformers import AutoTokenizer self.tokenizer AutoTokenizer.from_pretrained(tokenizer_name) def count(self, text: str) - int: return len(self.tokenizer.encode(text))然后在ConversationManager初始化时传入对应的TokenCounter实例。这样切换模型后端时只需更换计数器即可。5.2 函数调用Function Calling与工具使用历史的管理当AI应用涉及函数调用OpenAI的tools/function calling Anthropic的tools时历史管理变得更复杂。因为函数调用的请求和结果也是对话历史的一部分并且通常以结构化JSON格式嵌入。这些JSON内容也会消耗大量Token。管理策略需要特别考虑压缩工具调用结果函数返回的数据可能非常冗长如数据库查询结果。可以考虑只保留关键字段或者对结果进行摘要。例如一个返回了20条用户记录的查询可以总结为“找到了20条符合条件的数据主要分布在A、B、C三个类别”。保留工具调用结构虽然可以压缩结果但工具调用的名称、参数等结构信息应尽量保留因为它们是模型理解“自己做了什么”的关键。在摘要中包含工具使用当对一段历史生成摘要时必须将其中发生的工具调用及其关键结果也概括进去否则模型会丢失这段“行动记忆”。这需要扩展我们的Message数据结构以区分纯文本消息和包含工具调用的复杂消息并在压缩逻辑中做特殊处理。5.3 长上下文模型的特殊优化如Claude 200K对于Claude 3 200K这类超大上下文窗口的模型策略可以有所不同。因为窗口足够大可能很长时间都不需要截断。此时重点可以放在成本优化而非防溢出上。延迟摘要即使Token数还在安全范围内如果发现某一段历史对话例如关于某个子话题的20轮讨论已经结束可以主动对其进行摘要替换掉原文。这能永久性地降低后续所有请求的Token消耗从而节省长期成本。重要性打分可以为每轮对话打一个“重要性”分数。分数可以根据多种信号计算是否包含用户明确指令、是否被后续对话多次引用、是否包含数字/事实等关键信息。在需要腾出空间时优先删除低分消息而不是简单地删除最旧的消息。这需要更复杂的启发式规则或甚至一个小型机器学习模型来评估。5.4 用户体验与透明沟通截断和压缩对用户来说可能是“隐形”的但有时需要让用户感知到。主动提示当进行了一次重大的历史摘要后可以在AI的回复开头或结尾温和地提示“为了保持对话的流畅我已经将我们之前关于XX话题的讨论总结了一下。如果您需要回顾任何细节请随时告诉我。” 这避免了用户突然发现AI“失忆”时的困惑。提供“回忆”功能可以实现一个命令如/recall让用户主动要求AI根据保存的摘要或元数据重新概述某个早期话题。这需要你将生成的摘要与原始话题的索引关联存储。6. 测试与验证确保你的截断策略真的有效实现完策略后必须进行 rigorous 测试。糟糕的截断策略比没有策略更可怕因为它可能 silently 丢弃关键信息。单元测试为_manage_context下的各个策略函数编写测试。模拟超长历史验证其是否按预期删除或压缩了消息并检查最终Token数。集成测试模拟真实的多轮对话流。构建一个测试脚本自动进行数十轮问答并在每一轮后检查API请求是否成功未因超长而报错。AI的回复是否仍然与早期设定的关键指令保持一致例如如果早期要求“用日语回答”后期是否还在用日语。当被问及早期讨论的细节时AI能否从摘要中提取出正确信息。压力测试输入远超上下文窗口的巨型文本例如直接粘贴一篇长论文看管理器是否能将其压缩到可接受的范围内而不导致程序崩溃或信息完全丢失。A/B测试如果适用在生产环境中可以对不同用户群采用不同的截断策略例如一组用简单的FIFO另一组用智能摘要然后比较关键指标对话任务完成率、用户满意度、平均每会话Token成本等。一个我常用的测试技巧是在历史中埋入一些“记忆测试点”。例如在第5轮对话中让用户说“我的幸运数字是42请记住它”。然后在第25轮对话时用户问“我的幸运数字是多少”。一个优秀的截断策略应该能通过摘要或其他方式保留这个信息让AI正确回答“42”。如果AI答不上来或答错说明策略有缺陷。7. 避坑指南我在实践中踩过的那些“坑”最后分享几个从真实项目教训中总结的经验希望能帮你绕开弯路。坑一Token计数不准导致API调用失败这是最常见的问题。原因包括使用了错误的分词器如前所述为GPT-4训练的计数方式不适用于Claude。忽略了消息格式的开销OpenAI的Chat API消息格式是{role: user, content: hello}。分词器对这段JSON编码后的字符串进行计数而不仅仅是对hello计数。角色名、引号、括号都占Token。上面的_update_message_token_count方法模拟了这种格式但最准确的方式是使用与目标API完全相同的预处理逻辑进行计数。有些客户端库如OpenAI Python SDK的ChatCompletion.create内部会帮你计数并报错但依赖这个不如自己提前算好。坑二过度摘要导致信息“蒸馏”失真摘要是一把双刃剑。让AI去总结它自己说过的话可能会丢失 nuance细微差别、忽略反讽或假设甚至引入错误。例如用户说“我不太喜欢方案A方案B看起来还行但成本有点高。方案C呢”。一个粗糙的摘要可能变成“用户讨论了方案A、B、C”完全丢失了情感倾向和关键顾虑成本。建议摘要提示词Prompt至关重要。不要简单地说“总结以下对话”而要给出更具体的指令“请提取以下对话中用户明确陈述的需求、约束条件、偏好喜欢/不喜欢以及提出的问题忽略寒暄和过程性对话。用客观、简洁的列表形式输出。”坑三截断破坏了对话的连贯性即使保留了最重要的信息如果对话的“流”被打断体验也会变差。比如用户正在一步步地调试代码AI在每一步给出反馈。如果你在中间截断只保留了最后的错误信息和AI的回复模型就看不到调试的完整思路可能无法给出连贯的建议。建议对于这种强序列依赖的对话调试、教学、分步指导尽量以“会话块”为单位进行保留或丢弃。识别出一个相对独立的话题单元例如“解决登录错误”这个话题下的所有轮次要么全部保留要么整体摘要/丢弃。这比按固定轮数滑动窗口更合理。坑四没有为“长对话”设计数据存储本文主要讨论内存中的上下文管理。但对于真正长期的对话跨越多次会话如客服工单你需要将历史持久化到数据库。这时摘要的价值就更大了。你不可能每次都将完整的、长达数百轮的历史从数据库加载到内存。你应该存储两种数据原始消息记录存储在廉价的长期存储中如对象存储按会话ID和时序索引。对话摘要快照定期例如每50轮生成的摘要存储在快速数据库中。当用户重新打开会话时首先加载最近的摘要快照和最近的原始消息快速重建上下文而不是从头加载所有历史。处理长对话上下文就像在有限的船舱内为一场漫长的航行打包行李。你需要决定什么是必需品系统指令、核心规则什么是近期要用的最近几轮对话什么可以压缩成旅行指南历史摘要以及什么可以暂时留在岸上早期细节需要时可查询。没有完美的方案只有最适合你当前应用场景的权衡。
返回列表