LLM应用成本优化:5大上下文管理设计模式解析与实战
1. 项目概述从“烧钱”的Token到精明的上下文管理最近在拆解一些开源LLM应用框架的源码发现一个挺有意思的现象很多项目在功能实现上做得花里胡哨但在最核心的“钱袋子”问题上——也就是Token消耗——却显得相当粗放。这让我想起了早期做云计算成本优化时大家也是先堆功能最后才想起来看账单。现在的大模型应用开发尤其是那些需要处理长对话、多轮交互的场景Token消耗就是实打实的“云账单”。你写的每一段提示词、模型返回的每一个字都在按量计费。“OpenCode 源码拆解二Token 怎么省——上下文管理的 5 个设计模式”这个标题一下就戳中了当前LLM应用工程化的痛点。它不是一个泛泛而谈的“性能优化”话题而是直指一个可量化、可优化、且直接影响运营成本的核心技术环节上下文Context管理。所谓上下文简单说就是你在调用大模型API时连同你的问题Prompt一起发送过去的那一串历史对话、系统指令、参考文档等所有文本信息的总和。这个“总和”的长度直接决定了你这次API调用要花多少钱以及模型是否能“记住”足够多的背景信息来给出准确回答。省Token不是简单地截断文本。粗暴地砍掉后半段历史对话可能会导致模型患上“失忆症”忘记关键的约定或前提而无脑地保留所有内容账单又会让你肉疼。这其中的平衡艺术就是上下文管理要解决的核心问题。OpenCode这类框架的源码恰恰是观察一线开发者如何用设计模式系统化解决这个问题的绝佳样本。这五个模式不是纸上谈兵的理论而是从真实项目迭代中沉淀下来的、经过实战检验的工程方案。接下来我们就深入代码层面看看这些模式是如何工作的以及在实际项目中该如何选择和运用。2. 核心需求解析为什么上下文管理是LLM应用的“成本阀门”在深入设计模式之前我们必须先搞清楚为什么上下文管理如此重要以至于需要专门的设计模式来应对。这背后是三个相互交织的刚性需求成本控制、性能保障和效果维持。2.1 成本控制的直接压力大模型API的计费绝大多数是基于Token数量。无论是输入Prompt Tokens还是输出Completion Tokens都明码标价。以一个处理客户服务对话的场景为例如果每次用户提问你都把完整的、可能长达几十轮的对话历史全部塞给模型那么单次调用的Token数会像滚雪球一样增长。假设平均每轮对话消耗200个Token50轮历史就是1万个Token。按某主流API的输入价格估算这1万个Token可能价值几美分。看起来不多但乘以日均十万次的对话量月度成本就会飙升到一个非常可观的数字。因此上下文管理的首要目标就是在满足业务需求的前提下尽可能减少每次API调用所携带的冗余Token。2.2 模型上下文窗口的硬性限制所有大模型都有一个“上下文窗口”Context Window限制比如4K、8K、16K、32K甚至128K Token。你发送的整个Prompt用户问题管理好的上下文长度不能超过这个限制。超过部分会被直接截断或导致API调用失败。随着对话进行历史上下文很容易触及这个上限。此时你必须做出抉择丢掉哪些信息保留哪些信息这个抉择策略的好坏直接影响了模型输出的连贯性和准确性。一个糟糕的策略可能让模型在对话中途“忘记”了用户的核心诉求。2.3 维持对话效果与逻辑连贯性成本和省空间不是唯一目标。我们最终要的是模型给出高质量的回答。如果为了省Token把一段关键的用户指令例如“请用表格形式总结”或一个重要的前提假设例如“我们正在讨论的是2023年的数据”从上下文中删除了那么模型的输出就会偏离预期。因此上下文管理的另一个核心需求是智能地保留对当前生成任务最关键的信息识别并剔除冗余、过时或无关的内容。这三个需求彼此制衡构成了一个典型的工程优化问题。而设计模式的价值就在于提供一套经过验证的、可复用的框架和策略来系统地解决这个问题而不是每次都在业务代码里写一堆临时起意的if-else逻辑。3. 设计模式一滑动窗口模式这是最直观、也最常用的上下文管理策略在很多开源框架的ConversationBufferWindowMemory或类似组件中都能看到其身影。3.1 模式原理与实现机制滑动窗口模式的思想非常简单它只保留最近发生的K轮对话或K个交互单元。就像一个固定长度的队列新的对话内容从一端进入当队列满时最老的对话内容从另一端被挤出。这里的“一轮对话”通常指一个“用户输入-助手输出”对。在OpenCode或类似框架的源码中你可能会看到这样一个核心的数据结构和方法class SlidingWindowContextManager: def __init__(self, window_size: int 10): self.window_size window_size # 窗口大小即保留的对话轮数 self.memory [] # 用于存储对话历史的列表每个元素是一轮对话 def add_interaction(self, human_input: str, ai_output: str): 添加一轮新的交互 self.memory.append({human: human_input, ai: ai_output}) # 如果超出窗口大小则移除最旧的一轮 if len(self.memory) self.window_size: self.memory.pop(0) def get_context_string(self) - str: 将当前窗口内的历史格式化为字符串作为上下文 context_parts [] for interaction in self.memory: context_parts.append(fUser: {interaction[human]}) context_parts.append(fAssistant: {interaction[ai]}) return \n.join(context_parts)3.2 适用场景与参数调优滑动窗口模式特别适用于主题聚焦、短期记忆为主的对话场景。例如客服机器人用户通常围绕当前的一个问题展开多轮追问5-10轮的历史足以覆盖整个问题解决过程。命令行工具助手用户输入一系列命令助手依次执行并给出结果通常只需要记住上一条命令的上下文。简单的多轮问答问答围绕一个实体或概念历史轮次有限。window_size窗口大小是这个模式最关键的超参数。设置太小模型容易“遗忘”设置太大则浪费Token且可能引入早期无关信息。一个实用的调优方法是基于业务数据分析统计你的应用场景中绝大多数有效对话的轮次分布。将window_size设置为能满足80%-90%对话需求的轮数。例如分析日志发现95%的对话在12轮内结束那么设置window_size12就是一个平衡点。3.3 实操心得与避坑指南注意滑动窗口模式的一个经典陷阱是“关键信息丢失”。假设用户在对话开始时说“我的订单号是123456请帮我查询状态。” 几轮关于物流的问答后窗口滑动这句包含核心订单号的话被挤出了窗口。当用户再次问“那现在到哪了”时模型因为丢失了订单号将无法回答。避坑技巧1关键信息锚定对于滑动窗口模式一个有效的补丁是“关键信息提取与锚定”。在添加交互到内存时可以同步运行一个简单的命名实体识别NER或关键词提取将如订单号、身份证号、产品SKU等关键实体单独存储在一个“持久化记忆区”。在组装最终上下文时无论窗口如何滑动都把这些关键信息固定在Prompt的开头如系统指令之后。这相当于给模型一个“便签”提醒它最重要的背景。避坑技巧2动态窗口调整不要死板地使用固定窗口。可以根据对话的“活跃度”动态调整。例如检测到用户发送了“重新开始”或开启了一个全新话题通过句子向量相似度骤降判断可以主动清空或重置窗口避免旧话题的干扰。在一些开源实现中这体现为clear()方法或在特定触发词下的自动调用。4. 设计模式二摘要压缩模式当对话历史很长且信息具有累积性时简单丢弃旧的滑动窗口就不合适了。摘要压缩模式应运而生。它的核心思想是不保留原始的长篇历史而是定期或按需将过去的多轮对话总结成一段精炼的摘要然后用这个摘要来代表那部分历史。4.1 模式的工作流程摘要压缩模式通常是一个周期性或触发式的过程积累阶段正常记录对话历史。触发条件当历史记录达到一定长度如Token数或轮数或用户显式要求“总结一下之前说的”时触发压缩。摘要生成调用一个大模型可以是同一个主模型也可以是一个更小、更便宜的摘要专用模型将待压缩的历史文本作为输入生成一段简洁、包含核心事实和结论的摘要。替换与存储用新生成的摘要替换掉被压缩的那部分原始历史记录。这个摘要成为一个新的、浓缩的“记忆单元”加入到后续的上下文中。在源码中你可能会看到一个独立的ConversationSummaryBufferMemory类它内部维护着原始对话列表和一个摘要字符串。4.2 摘要生成的技术选型与成本考量摘要生成本身也需要消耗Token和调用API这引入了新的成本。因此技术选型至关重要使用主模型如GPT-4进行摘要优点是摘要质量高能很好地理解上下文和保留关键细节。缺点是成本高且用“大炮打蚊子”。使用专用摘要模型如BART、T5等小型微调模型优点是成本极低甚至可以在本地部署延迟可控。缺点是可能无法完美理解某些专业领域对话的细微之处。使用更便宜的大模型API如Claude Haiku, GPT-3.5-Turbo这是一个很好的折中方案。用低成本模型处理摘要任务释放出来的主模型上下文窗口用于处理更复杂的推理。一个高级的实现是“分层摘要”对非常长的对话先进行多次局部摘要再对局部摘要进行全局摘要形成一种树状结构这在处理超长文档如整本书时很常见。4.3 实操中的挑战与解决方案挑战1摘要的信息损耗与失真模型生成的摘要可能遗漏你认为重要的细节或者无意中改变了原意。例如将“用户对A方案不满意但可以接受B方案”错误地摘要为“用户接受了B方案”。解决方案给摘要模型更明确的指令Prompt Engineering。例如“请生成一个客观的事实性摘要必须包含以下关键实体[订单号 日期 核心诉求]。避免做出任何推断或总结性陈述。” 同时可以在摘要后以注释形式保留绝对不可丢失的信息如“原始对话中包含订单号123456”。挑战2摘要的“上下文污染”当用摘要替代原始文本后如果后续对话需要基于原始文本的某个细微措辞进行推理摘要可能无法提供足够的信息粒度。解决方案采用“摘要索引”的混合模式。即生成摘要的同时为被压缩的原始文本块建立一个简单的索引如包含的关键词列表、实体列表。当后续对话中检测到这些关键词时可以触发一个“回忆”机制临时将被压缩的原始文本或相关片段重新加载到上下文中。这类似于计算机系统中的“缓存-主存-磁盘”层次结构。5. 设计模式三向量检索模式这是目前处理超长上下文和知识库问答中最炙手可热的模式。它完全摒弃了按时间顺序保留所有历史的思路转而采用“按需取用”的策略。5.1 核心思想记忆的外化与动态检索向量检索模式将对话历史或外部知识文档拆分成一个个小的文本片段Chunks然后使用嵌入模型Embedding Model将这些片段转换为高维向量Vector并存储到向量数据库如Chroma, Pinecone, Weaviate中。当需要构造当前对话的上下文时它并不直接拼接历史记录而是将用户当前的问题Query也转换为向量。在向量数据库中进行相似度搜索如余弦相似度找出与当前问题最相关的若干个历史片段。只将这些检索到的、最相关的片段作为上下文提供给大模型。5.2 技术栈拆解与源码窥探在OpenCode这类框架中这个模式通常由几个松耦合的组件构成文本分割器Text Splitter负责将长文本按语义、长度等规则切分成块。常见的有按字符数分割、按句子分割、按递归语义分割等。嵌入模型Embedding Model如OpenAI的text-embedding-3-small或开源的BGE-M3、Snowflake Arctic Embed。它的质量直接决定了检索的准确性。向量数据库Vector Database提供高效的向量存储、索引和相似度搜索能力。检索器Retriever封装了“将Query向量化 - 搜索 - 返回文本片段”的逻辑。源码中你会看到类似这样的流程编排# 初始化组件 embedder OpenAIEmbedding(modeltext-embedding-3-small) vector_store Chroma(persist_directory./chroma_db) retriever VectorStoreRetriever(vectorstorevector_store, embedderembedder, k5) # 返回最相关的5个片段 # 处理用户新消息时 def build_context(current_query: str, full_history: List[Dialog]): # 1. 将本轮之前的对话历史存入向量库通常异步进行 # for dialog in full_history[:-1]: # 排除当前轮 # chunks splitter.split_text(dialog.combined_text) # vector_store.add_texts(chunks) # 2. 检索与当前问题相关的历史片段 relevant_chunks retriever.get_relevant_documents(current_query) # 3. 组装上下文系统指令 检索到的片段 当前问题 context fSystem: You are a helpful assistant. Some relevant past conversation:\n for chunk in relevant_chunks: context f- {chunk.page_content}\n context f\nHuman: {current_query} return context5.3 优势、劣势与最佳实践优势极致节省Token上下文长度只等于“系统指令 检索到的几个片段 当前问题”与总对话历史长度无关。突破上下文窗口限制理论上可以关联海量的历史或知识库数据。答案相关性高直接提供与问题最相关的背景减少无关信息干扰。劣势无法感知时间顺序和对话流检索到的片段是孤立的模型可能无法理解“先发生了什么后发生了什么”这种逻辑。存在信息遗漏风险如果检索算法没找到关键片段那部分记忆就彻底丢失了。架构复杂延迟增加引入了嵌入模型和向量数据库调用增加了系统复杂性和响应延迟。最佳实践混合使用不要完全取代滑动窗口。可以结合使用保留最近2-3轮对话滑动窗口以保证对话流连贯性同时用向量检索从更早的历史中寻找相关信息。优化分块策略分块大小和重叠度是关键参数。太小则信息碎片化太大则可能包含无关信息。通常256-512个Token的块大小配合50-100个Token的重叠是一个不错的起点。元数据过滤在存储向量时为每个块附加元数据如session_id,turn_number,speaker等。检索时不仅可以基于语义相似度还可以用元数据过滤如“只检索当前会话的历史”提高精度。6. 设计模式四结构化记忆模式如果说前三种模式主要处理的是非结构化的自然语言历史那么结构化记忆模式则试图将对话中的关键信息提取出来以结构化的形式如字典、列表、图数据库进行存储和查询。这更像是在为对话建立一个动态的“知识图谱”或“事实数据库”。6.1 从非结构化到结构化的转变这个模式的核心在于一个“信息提取”步骤。在每一轮对话后或定期系统会使用大模型的功能调用Function Calling或提示词工程从对话文本中提取出结构化的信息。例如在一段关于旅行计划的对话中模型可以被引导提取出{ trip: { destination: 北京, dates: {start: 2024-10-01, end: 2024-10-05}, travelers: [{name: 张三, preference: 历史文化}], booked_items: [ {type: flight, airline: 国航, confirmation: CA1234}, {type: hotel, name: 王府酒店, check_in: 2024-10-01} ] } }6.2 实现方式基于大模型的信息提取在源码实现中这通常通过以下方式完成定义模式Schema明确你要从对话中提取哪些信息以及它们的类型和关系。这类似于数据库的表结构设计。设计提取提示词编写一个专门的Prompt指导大模型从最新的对话内容中根据定义的模式识别并填充信息。增量更新将提取出的新信息与已有的结构化记忆进行合并或更新。这需要处理冲突如同一个航班号信息被更新、去重等逻辑。# 伪代码示例 class StructuredMemoryManager: def __init__(self): self.memory_graph {} # 或用真正的图数据库 def update_memory(self, dialog_text: str): # 调用大模型进行信息提取 extraction_prompt f 请从以下对话中提取结构化信息。 对话{dialog_text} 请按照以下JSON格式输出只输出JSON {self.schema_definition} extracted_data llm_call(extraction_prompt) # 将提取的数据合并到现有记忆图中 self._merge_into_graph(extracted_data) def get_context_for_query(self, query: str) - str: # 根据当前查询从结构化记忆中查询相关信息 relevant_facts self._query_graph(query) # 将结构化的数据转换回自然语言描述作为上下文 return self._format_facts_to_text(relevant_facts)6.3 适用场景与局限性适用场景任务导向型对话如订餐、预约、购物、旅行规划等对话目标明确需要跟踪多个实体物品、时间、人员及其属性。信息收集与核实如客服场景中需要收集用户的问题详情、联系方式、订单号等。需要复杂推理和关联查询的场景结构化数据便于进行逻辑查询比如“找出所有未付款的订单中金额大于100元的”。局限性提取可能不准确大模型的信息提取并非100%可靠可能存在错误或遗漏。无法保留对话的“风味”和细节结构化存储丢失了原始的语言表达、情感色彩和细微差别。模式设计需要前瞻性你必须事先想好需要提取哪些信息如果后期需要新的信息类型可能需要重新处理历史数据。在实际应用中结构化记忆模式很少单独使用而是作为其他模式如向量检索的补充。例如用向量检索保留对话的原始文本和语境同时用结构化记忆来精准跟踪关键事实和状态两者结合既能省Token又能保证关键信息的准确性和可查询性。7. 设计模式五优先级与衰减模式这个模式引入了一个更精细、更动态的管理策略。它不再平等地看待每一段历史信息而是为它们赋予不同的“重要性分数”或“优先级”并根据时间或相关性进行“衰减”。最终在构造上下文时优先选择分数高、衰减慢的信息。7.1 核心概念记忆的价值评估优先级与衰减模式的核心是建立一个评估体系。这个体系可以基于多种信号显式重要性用户明确指出的重要信息如“记住我的订单号是123456”。隐式重要性通过模型判断例如包含关键实体人名、地点、数字、回答用户直接提问的语句、达成共识的结论等。时间衰减信息越旧其重要性分数随时间逐渐降低除非被再次提及或引用。相关性衰减与当前对话主题相关性越低的信息其分数也越低。7.2 一个简单的实现方案我们可以为每一段对话内容或信息单元附加一个元数据包含分数和最后访问时间class MemoryItem: def __init__(self, content: str, initial_score: float 1.0): self.content content self.score initial_score # 重要性分数 self.last_accessed time.time() # 最后被检索或提及的时间 self.access_count 0 # 被访问次数 class PriorityDecayMemoryManager: def __init__(self, decay_rate: float 0.1, relevance_boost: float 2.0): self.memory_items: List[MemoryItem] [] self.decay_rate decay_rate # 每日衰减率 self.relevance_boost relevance_boost # 相关时分数倍增系数 def add_memory(self, content: str, is_important: bool False): initial_score 5.0 if is_important else 1.0 # 显式重要信息高分 self.memory_items.append(MemoryItem(content, initial_score)) def _apply_decay_and_boost(self, current_query_embedding): 定期或按需执行衰减旧分数并根据当前查询提升相关项分数 now time.time() for item in self.memory_items: # 时间衰减 days_passed (now - item.last_accessed) / (60*60*24) item.score * (1 - self.decay_rate) ** days_passed # 相关性提升简化版计算与当前查询的相似度 # 实际中会用嵌入向量计算余弦相似度 # similarity calculate_similarity(item.embedding, current_query_embedding) # if similarity threshold: # item.score * self.relevance_boost # item.last_accessed now # 相关即“激活” def get_top_k_context(self, k: int, current_query: str) - str: 获取分数最高的k个记忆项作为上下文 self._apply_decay_and_boost(current_query) # 先更新分数 sorted_items sorted(self.memory_items, keylambda x: x.score, reverseTrue) top_k_items sorted_items[:k] context \n.join([item.content for item in top_k_items]) return context7.3 模式的优势与调参经验优势动态自适应上下文内容能根据对话的进展动态调整始终保留最相关、最重要的信息。模拟人类记忆更贴近人类“记住重要的忘记无关的”记忆方式。灵活可控通过调整衰减率、初始分数、提升系数等参数可以精细地控制记忆的保留策略。调参经验衰减率decay_rate这是最重要的参数。设置得太高如0.5信息遗忘太快设置得太低如0.01又起不到筛选作用。建议从0.1即每天重要性衰减10%开始根据业务对话的平均时长和频率进行调整。对于短平快的对话衰减率可以更高。相关性提升系数relevance_boost当一段历史信息被再次提及时应该大幅提升其分数防止重要信息因暂时未被提及而被过早遗忘。这个系数可以设置得比较大比如2.0到5.0。分数归一化为了防止分数无限膨胀或萎缩到零需要定期或按需对所有记忆项的分数进行归一化处理比如使用Softmax函数将其转换为总和为1的概率分布。这个模式是五种模式中最复杂、但也最智能的一种。它通常需要与其他模式结合例如用向量检索来计算相关性用优先级队列来管理最终入选上下文的项目。在OpenCode这类追求工程优雅的框架中你可能会看到一个名为ConversationScoreBasedMemory或PriorityMemory的类它封装了这套评分和衰减逻辑。8. 模式组合与实战策略没有银弹只有组合拳在真实项目中几乎不会单独使用某一种模式。一个健壮、高效的上下文管理系统往往是多种模式的组合。如何组合取决于你的具体应用场景、成本预算和对效果的要求。8.1 典型组合策略分析滑动窗口 向量检索最常用组合策略使用一个较小的滑动窗口如最近3-5轮来保证对话的即时连贯性。同时将所有历史对话包括窗口内的都存入向量数据库。上下文组装最终上下文 系统指令 滑动窗口内容 向量检索的相关片段 当前问题。优点兼顾了短期对话流的自然性和长期记忆的关联性成本可控效果均衡。这是目前很多生产级聊天机器人的标配。摘要压缩 结构化记忆用于任务追踪策略定期对过去一段时间的对话进行摘要以节省Token。同时用一个独立的结构化记忆模块来精确跟踪任务状态、实体属性等关键信息。上下文组装最终上下文 系统指令 最新摘要 结构化记忆查询结果 当前问题。优点非常适合预订、导购、信息登记等强流程性的任务能精准跟踪状态同时通过摘要保留一定的对话背景。优先级衰减 向量检索智能记忆管理策略用优先级衰减模式为向量数据库中的每一个记忆片段Chunk动态管理一个“重要性分数”。在进行检索时不仅考虑语义相似度还将这个分数作为加权因子。上下文组装检索时综合得分 语义相似度 * 重要性分数按综合得分返回Top-K片段。优点实现了真正意义上的“智能记忆”让系统能主动“记住”重要的并“忘记”无关的即使它们语义相关。8.2 选择模式的决策框架面对一个具体项目你可以通过回答下面几个问题来选择合适的模式或组合决策维度问题倾向的模式对话长度对话通常很长20轮吗是 → 优先考虑摘要压缩、向量检索信息关联性当前问题是否需要关联很久以前或分散的信息是 →向量检索是核心逻辑连贯性对话的先后顺序和逻辑递进是否至关重要是 →滑动窗口必须保留可搭配摘要关键事实追踪是否需要精确记住用户提供的数字、选项、选择等是 → 引入结构化记忆成本敏感性对Token成本是否极度敏感是 →向量检索、摘要压缩优先严格控制窗口大小架构复杂度团队能否接受维护向量数据库、嵌入模型等额外组件否 → 从简单的滑动窗口摘要开始8.3 在OpenCode中寻找实现痕迹当你阅读像OpenCode这样的框架源码时可以重点关注memory、context、retriever等包或模块。通常每种模式会对应一个或多个类。例如ConversationBufferWindowMemory-滑动窗口模式ConversationSummaryBufferMemory-摘要压缩模式VectorStoreRetrieverMemory或ConversationVectorStoreMemory-向量检索模式可能通过EntityMemory或自定义的BaseMemory子类来实现结构化记忆。优先级衰减模式可能不那么显式但其思想可能融入在ConversationTokenBufferMemory基于Token数管理或某些自定义的记忆评分逻辑中。看源码时不仅要看它们“是什么”类的定义更要看它们“怎么用”在Chain或Agent中如何被调用和组合这能给你最直接的工程启示。9. 性能评估与监控如何知道你的策略真的在“省”设计并实现了上下文管理策略后如何量化其效果不能光凭感觉必须建立监控指标。9.1 核心监控指标平均每次调用的Prompt Token数这是最直接的成本指标。你的优化目标就是在保证效果的前提下让这个数字持续下降或保持在一个较低的水平。上下文利用率(使用的Prompt Token数 / 模型上下文窗口大小) * 100%。这个指标帮助你了解是否浪费了宝贵的上下文空间。理想情况是保持在一个较高的利用率但不要经常触及上限导致截断。信息检索准确率/召回率如果用了向量检索从历史中检索到的片段有多少是真正相关的有多少相关片段被漏掉了这需要人工或利用一些自动化方法如用模型判断进行抽样评估。对话连贯性评分可以通过让另一个大模型作为裁判对优化前后的多轮对话进行评估判断模型回复是否出现了上下文断裂、遗忘关键信息等问题。用户满意度CSAT或任务完成率最终一切优化都要服务于用户体验和业务目标。如果省了Token却导致用户问题解决不了那就是本末倒置。9.2 建立一个简单的评估管道在你的开发或测试环境中可以构建一个评估流程# 伪代码评估不同记忆策略的效果 def evaluate_memory_strategy(strategy, test_dialogs): total_tokens 0 total_calls 0 coherence_scores [] for dialog in test_dialogs: strategy.clear() for turn in dialog: # 使用策略构建上下文 context strategy.build_context(turn[query]) # 模拟API调用记录Token消耗 (这里需要模拟或调用真实API的计数) # prompt_tokens count_tokens(context turn[query]) # total_tokens prompt_tokens total_calls 1 # 获取模型回复 # response call_llm(context, turn[query]) # strategy.add_interaction(turn[query], response) # 评估连贯性简化版可后续人工评 # score evaluate_coherence(strategy.get_memory(), response) # coherence_scores.append(score) avg_tokens total_tokens / total_calls avg_coherence sum(coherence_scores) / len(coherence_scores) return {avg_prompt_tokens: avg_tokens, avg_coherence: avg_coherence}通过对比不同策略如纯滑动窗口 vs. 滑动窗口向量检索在这些指标上的表现你就能做出数据驱动的决策。9.3 持续迭代没有一劳永逸的策略对话模式、用户需求、甚至大模型本身都在变化。今天有效的策略明天可能因为模型上下文窗口扩大或价格调整而需要改变。因此上下文管理不是一个“设置好就忘”的模块而应该是一个有监控、有评估、可配置、可迭代的系统组件。定期回顾你的监控面板分析异常案例比如哪些对话的Token消耗异常高或连贯性异常低并据此调整你的模式组合和参数这才是长期保持成本与效果平衡的关键。