1. 上下文管理到底在管什么第一次听到“上下文管理”这个词很多人会下意识觉得这是架构师才需要操心的事跟日常写业务代码没关系。但实际情况恰恰相反——你写的每一个函数、每一次接口调用、每一轮对话交互背后都藏着一套上下文流转的逻辑。管得好系统跑得稳、响应快、成本低管得不好轻则答非所问重则内存溢出、请求超时、账单爆炸。我先把话说清楚上下文管理指的是在一个系统处理任务的过程中对“当前任务所依赖的全部信息”进行收集、裁剪、组织、传递和回收的一整套机制。这些信息可能包括对话历史、用户状态、工具调用结果、检索到的文档片段、系统提示词等等。它要解决的问题很朴素——在正确的时间把正确的信息以正确的体量送到正确的位置。为什么现在这个话题突然热起来了因为大模型应用的爆发让上下文从“几个变量”变成了“几万到几十万token的文本流”。以前写个Web服务上下文就是session里存个用户ID现在做一个智能助手上下文可能是几十轮对话加上一堆知识库检索结果。体量变了复杂度就指数级上升。你不可能把所有东西一股脑塞进去那样既贵又慢还容易让模型“分心”。所以上下文管理从一个边缘话题变成了核心工程问题。这篇文章适合谁看如果你正在做AI应用开发、对话系统、RAG检索增强生成、Agent工具编排或者任何需要维护“有状态交互”的系统那这篇内容就是给你写的。我会从设计思路讲到实操细节从参数计算讲到踩坑排查尽量把这件事说透。即使你目前只写传统的后端服务里面关于状态裁剪和传递的思路也一样有参考价值。2. 整体设计思路与方案选型2.1 为什么不能“全量塞入”先算一笔账。假设你用的是某个主流大模型上下文窗口是128K token。听起来很大对吧但实际算一下一轮对话平均消耗500 token128K大概能装256轮。可问题是你还要留空间给系统提示词、工具定义、检索结果、模型输出。真正能分给历史对话的可能只有一半不到。而且token越多推理成本越高——很多API是按输入token计费的你每次把全部历史传进去费用是线性增长的。更关键的是模型对上下文的注意力不是均匀分布的。业界有个被反复验证的现象叫“中间遗忘”模型对上下文开头和结尾的信息记得比较牢中间部分容易被忽略。你把100轮对话全塞进去模型反而可能漏掉第37轮里用户说的关键偏好。所以全量塞入不仅贵效果还未必好。我试过一个极端案例某客服机器人把用户过去三个月的所有对话记录都拼进上下文结果模型在回答“我的订单到哪了”时居然引用了一个月前另一笔订单的物流信息。这就是上下文污染——无关信息干扰了当前任务的判断。2.2 三种主流策略的取舍目前业界处理上下文大致有三条路线各有各的适用场景。第一条是滑动窗口。只保留最近N轮对话超出部分直接丢弃。实现最简单成本可控适合闲聊类、实时性要求高的场景。缺点是会丢失早期的重要信息比如用户在开头说的“我对花生过敏”聊到第50轮推荐餐厅时模型就忘了。第二条是摘要压缩。把历史对话定期用模型总结成一段简短摘要替换掉原始对话。这样能保留长期记忆token占用也小。但摘要本身有信息损耗而且每次摘要都要调一次模型增加延迟和成本。适合长周期任务比如持续几天的项目协作助手。第三条是检索增强。把所有历史存到向量数据库每轮根据当前问题检索最相关的片段拼进上下文。这样理论上记忆无限且只取相关的。缺点是需要额外的检索基础设施检索质量直接影响效果而且检索本身也有延迟。实际工程中这三种策略往往是组合使用的。我目前比较推荐的架构是近期对话用滑动窗口保留原文中期对话做摘要压缩远期记忆走检索。这样兼顾了实时性、连贯性和长期记忆。下面这张表可以帮你快速判断该选哪种策略实现复杂度token成本信息保留度适用场景滑动窗口低低仅近期闲聊、实时问答摘要压缩中中中长期长周期任务、项目助手检索增强高中高理论上无限知识密集、个性化服务组合策略高可控高生产级AI应用2.3 上下文的分层模型我在实际项目中总结出一个分层思路把上下文按“生命周期”和“重要性”分成四层每层用不同的管理策略。第一层是系统层包括系统提示词、角色设定、工具定义。这部分基本不变每轮都带上但要控制篇幅。我见过有人把系统提示词写了三千字结果每轮都在烧钱。建议系统提示词控制在500 token以内工具定义按需加载。第二层是会话层就是当前这轮对话的短期历史。用滑动窗口管理保留最近10到20轮。这层是模型理解当前意图的主要依据要保证质量。第三层是记忆层跨会话的长期信息。比如用户的偏好、历史决策、重要事实。这层用摘要或结构化存储按需注入。第四层是知识层外部检索结果、文档片段。这层用检索增强每轮动态获取用完即弃。分层的好处是每层可以独立优化。比如系统层可以缓存会话层可以裁剪记忆层可以定期整理知识层可以调检索参数。混在一起管理就会很乱。3. 核心细节解析与实操要点3.1 token预算的分配方法上下文管理的核心约束是token预算。你得先知道总共能用多少再决定怎么分。假设模型窗口是128K我的分配方案是这样的系统提示词与工具定义预留8K模型输出预留预留16K因为输出也要占窗口检索结果预留32K历史对话剩余约72K然后根据平均每轮对话的token数反推能保留多少轮。如果一轮平均800 token72K大概能放90轮。但实际我不会放满会留20%的缓冲所以实际保留约70轮。超过部分触发摘要或丢弃。这个计算不是一次性的要在代码里动态调整。比如检索结果这次只用了10K那历史对话就可以多放一些。我通常写一个预算管理器每轮开始前根据各部分的实际占用重新分配。注意不同模型的token计算方式不一样中文和英文的token比例也不同。中文大致是1个汉字对应1到2个token英文是1个单词对应1到1.5个token。做预算时要用实际模型的分词器算别凭感觉估。3.2 对话历史的裁剪策略裁剪不是简单地砍掉最老的。我试过几种策略效果差异很大。最粗暴的是FIFO先进先出砍最老的。问题是用户最早说的关键信息可能就没了。改进版是保留首尾。保留第一轮对话通常包含用户的核心诉求和最近N轮中间砍掉。这比纯FIFO好但中间可能有重要转折。我目前用的是重要性加权。给每轮对话打个分评分维度包括是否包含用户明确偏好、是否包含决策结论、是否包含实体信息人名、时间、数字。评分低的先砍。评分可以用规则做也可以用小模型做。规则版大概长这样def score_message(msg): score 0 if msg.role user: score 1 if any(kw in msg.content for kw in [记住, 重要, 偏好, 不要, 必须]): score 3 if re.search(r\d{4}-\d{2}-\d{2}|\d元|\d个, msg.content): score 2 if len(msg.content) 200: score 1 return score然后按分数从低到高砍直到满足预算。这套规则不完美但比FIFO强不少。实测下来关键信息保留率从60%提升到了85%左右。3.3 摘要压缩的触发时机与写法摘要压缩不能太频繁否则延迟高也不能太稀疏否则上下文爆了才压缩就来不及。我的做法是设置一个水位线当历史对话token占用超过预算的80%时触发压缩。压缩时把最老的一半对话拿出来让模型总结成一段话替换掉原文。摘要的提示词很关键。我试过几种写法最后稳定用的是这个结构请将以下对话压缩为一段不超过200字的摘要必须保留 1. 用户明确表达的偏好和禁忌 2. 已达成的结论和决策 3. 涉及的具体实体人名、时间、数字、地点 4. 未解决的问题 不要包含寒暄和重复内容。这样压出来的摘要信息密度高后续模型能快速抓住重点。我对比过用这个提示词比让模型“自由总结”的关键信息保留率高30%以上。实操心得摘要不要只做一次。长期任务里摘要本身也会越积越多需要做“摘要的摘要”。我一般设置两级一级摘要保留最近三次压缩的结果二级摘要把更早的合并。这样层级清晰不会无限膨胀。3.4 检索增强的注入方式检索增强听起来简单——搜到相关片段拼进去就行。但实际有几个坑。第一个坑是检索粒度。按整篇文档检索可能一篇里只有一段相关其他都是噪音。按段落检索又可能丢失上下文。我目前用“段落加标题”的方式每个片段带上所属文档的标题和前后各一段作为上下文。这样既精准又不失语境。第二个坑是注入位置。检索结果放在系统提示词后面、历史对话前面还是放在最后我试过几种发现放在历史对话之后、用户当前问题之前效果最好。因为模型对靠近末尾的内容注意力更强检索结果紧挨着问题相关性判断更准。第三个坑是数量控制。检索太多片段会稀释注意力。我一般控制在3到5个片段每个片段不超过500 token。如果检索结果相关性分数低于阈值宁可不注入也别硬塞。3.5 工具调用结果的上下文处理Agent类应用里工具调用结果往往很长。比如调一次搜索API返回几千字调一次数据库返回一堆JSON。这些结果如果全塞进上下文很快就爆了。我的处理原则是工具结果只保留模型决策所需的最小信息。具体做法是在工具返回后先用一个轻量模型或规则做一次“结果提炼”提取关键字段和结论原始结果存到外部存储上下文里只放提炼后的摘要和引用ID。如果模型后续需要细节再通过引用ID去取。这样上下文占用能降低70%以上。代价是多了一次提炼调用但相比省下的token成本和提升的响应速度这笔账是划算的。4. 实操过程与核心环节实现4.1 搭建上下文管理器的完整流程下面我以一个对话系统为例走一遍完整的实现流程。假设你用Python框架不限核心逻辑是通用的。第一步定义上下文的数据结构。我用一个类来管理包含四个队列system_prompt、history、memory、retrieval。每个队列有独立的token计数和预算上限。class ContextManager: def __init__(self, max_tokens128000): self.max_tokens max_tokens self.system_prompt self.history [] # list of {role, content, score} self.memory [] # list of summary strings self.retrieval [] # list of retrieved chunks self.budget { system: 8000, output: 16000, retrieval: 32000, history: 72000 }第二步实现token计数。用模型对应的分词器别自己估。如果拿不到分词器用tiktoken之类的通用库近似也行但要留10%的误差缓冲。import tiktoken enc tiktoken.get_encoding(cl100k_base) def count_tokens(text): return len(enc.encode(text))第三步实现历史裁剪。每轮对话结束后把新消息加入history然后检查是否超预算。超了就按重要性评分裁剪。def trim_history(self): total sum(count_tokens(m[content]) for m in self.history) if total self.budget[history]: return # 按分数升序排列砍低分的 self.history.sort(keylambda m: m[score]) while total self.budget[history] and self.history: removed self.history.pop(0) total - count_tokens(removed[content]) # 恢复时间顺序 self.history.sort(keylambda m: m[timestamp])第四步实现摘要压缩。当history被砍掉的内容积累到一定量触发摘要。把砍掉的内容送给模型总结结果存入memory。def compress(self, removed_messages): text \n.join([f{m[role]}: {m[content]} for m in removed_messages]) prompt f请将以下对话压缩为不超过200字的摘要保留偏好、结论、实体和未解决问题\n{text} summary call_llm(prompt) self.memory.append(summary) # memory也要控制总量超过就做二级摘要 if sum(count_tokens(s) for s in self.memory) 8000: self.merge_memory()第五步组装最终上下文。每轮请求前按顺序拼接system_prompt memory retrieval history 当前问题。拼接时再次检查总token超了就继续裁。def build_context(self, current_query): parts [self.system_prompt] parts.extend(self.memory) parts.extend(self.retrieval) parts.extend([f{m[role]}: {m[content]} for m in self.history]) parts.append(fuser: {current_query}) full \n.join(parts) # 最终检查 while count_tokens(full) self.max_tokens - self.budget[output]: # 优先砍retrieval再砍history if self.retrieval: self.retrieval.pop() elif self.history: self.history.pop(0) else: break full self.rebuild(parts) return full这套流程跑下来一个完整的上下文管理器就成型了。代码不复杂关键是每一步的阈值和策略要调好。4.2 参数调优的实操记录上面代码里的数字不是拍脑袋定的是我在实际项目中反复调出来的。分享几个关键参数的调整过程。history预算从50%调到56%。一开始我给history分50%的预算发现长对话里模型经常“失忆”。后来分析发现检索结果实际用量往往低于预留的32K所以把history提到56%检索降到25%。调整后长对话的连贯性明显改善。摘要触发水位从90%降到80%。90%触发时经常出现“压缩完还是超”的情况因为压缩本身也要时间期间新消息又进来了。降到80%后有足够缓冲压缩从容多了。检索片段数从8个降到4个。一开始觉得检索越多越好结果模型被噪音干扰回答质量反而下降。降到4个后相关性判断更准。这个数字跟你的检索质量有关检索准的话可以少放检索糙的话放多了也没用。输出预留从8K提到16K。这个坑我踩得比较惨。有次模型生成长报告输出到一半被截断了因为输出预留不够。后来统一提到16K再没出过截断问题。4.3 一个完整的对话轮次演示光看代码可能还不够直观我模拟一轮完整对话把上下文的变化过程展示出来。假设用户第一轮说“帮我规划一个三天的北京行程我不吃辣预算3000元。”这轮结束后history里有一条消息评分很高包含偏好“不吃辣”、实体“北京”“三天”“3000元”。第二轮用户说“第一天想去故宫。”history加一条评分中等。此时总token还远没到预算不触发任何裁剪。对话进行到第30轮history累计token接近预算的80%。触发压缩把前15轮拿出来总结。摘要里保留了“不吃辣”“预算3000”“三天”“故宫”等关键信息原文从history移除摘要进memory。第31轮用户问“第二天推荐吃什么”组装上下文时system_prompt memory含摘要 最近15轮history 当前问题。模型看到摘要里的“不吃辣”推荐了不辣的餐厅。如果没有摘要这个偏好可能在第30轮时已经被裁掉了。这个演示说明了一个核心点上下文管理不是等爆了再处理而是持续在后台运行。每一轮都在做预算检查、评分、裁剪、压缩。用户感知不到但效果差异巨大。5. 常见问题与排查技巧实录5.1 模型“失忆”的排查思路模型突然忘记之前说过的信息是最常见的问题。排查时按这个顺序走先查history是否被裁掉了。打印当前history的所有消息看关键信息还在不在。如果不在说明裁剪策略有问题要么提高预算要么改进评分规则。再查摘要是否丢失了信息。如果关键信息在摘要环节丢的检查摘要提示词是否覆盖了该类型信息。比如用户说“我对海鲜过敏”摘要提示词里如果没有“禁忌”这一项就可能被漏掉。最后查注入位置。有时候信息在上下文里但模型没注意到。试试把关键信息挪到更靠近末尾的位置或者用特殊标记如【重要】标出来。我整理了一个速查表现象可能原因排查方法解决方向忘记早期偏好history被裁剪打印history检查提高预算或改进评分忘记中期结论摘要丢失检查摘要内容优化摘要提示词忽略已注入信息位置太靠前调整注入顺序挪到靠近末尾混淆不同会话记忆层污染检查memory隔离按会话分区存储回答与检索矛盾检索噪音检查相关性分数提高阈值或减少片段5.2 token超限的紧急处理有时候线上突然报token超限请求直接失败。这时候需要一套紧急处理流程。第一步定位超限来源。打印各部分token占用system多少、memory多少、retrieval多少、history多少。通常超限都是某一部分异常膨胀比如检索返回了一个超长文档或者history里混进了一条巨长的工具结果。第二步紧急裁剪。如果retrieval超了直接砍到只剩最高分的2个片段。如果history超了按评分砍掉最低的30%。如果system超了检查是不是工具定义重复加载了。第三步加兜底逻辑。在build_context的最后加一个硬截断如果拼完还是超直接从末尾截断到预算内。虽然粗暴但能保证请求不失败。当然截断后要记日志事后分析原因。注意硬截断可能截掉当前问题导致模型答非所问。所以硬截断只能作为最后手段且截断后要在日志里标记方便排查。5.3 成本失控的优化经验上下文管理做不好成本会失控。我见过一个案例某应用每轮请求平均消耗50K token按调用量算下来每月成本高得离谱。后来做了几项优化成本降了60%。优化一系统提示词瘦身。原来系统提示词写了2000字精简到500字每轮省1500 token。精简方法是把不常用的工具定义改成按需加载把冗长的角色描述改成要点式。优化二检索结果去重。检索返回的片段经常有重复内容去重后平均减少30%的检索token。优化三历史摘要替代原文。把超过10轮的对话全部摘要化history token占用降低50%以上。优化四缓存系统提示词。很多模型API支持提示词缓存系统提示词不变的部分可以缓存费用大幅降低。这个要查你用的API是否支持。优化五按需检索。不是每轮都检索而是判断当前问题是否需要外部知识。简单寒暄、确认类问题不触发检索省下检索token。这几项加起来效果很显著。关键是每项都不复杂但要有意识去做。5.4 多轮对话中的角色混淆问题多轮对话里模型有时候会把用户说的话当成自己说的或者把工具返回的内容当成用户输入。这是上下文拼接格式不清晰导致的。我的解决方法是严格标记每段内容的来源。用户消息用[用户]开头助手消息用[助手]开头工具结果用[工具结果]开头检索内容用[参考资料]开头。这样模型能清楚区分。另外history里不要只存content要存role。拼接时按role加标记。我试过不加标记直接拼角色混淆率大概5%加了标记后降到1%以下。还有一个细节工具结果和检索内容要明确告诉模型“这是外部信息不是用户说的”。我通常在检索内容前加一句“以下是从知识库检索到的参考资料请基于此回答但不要直接复述”效果不错。5.5 上下文管理的测试方法上下文管理逻辑复杂不测试很容易出bug。我一般做三类测试。单元测试针对裁剪、摘要、组装每个函数单独测。构造超长history验证裁剪后token在预算内构造含关键信息的对话验证摘要后关键信息还在。集成测试模拟完整的多轮对话跑50轮每轮检查上下文组装结果。重点看关键信息是否在预期轮次后仍然存在。压力测试构造极端情况比如单轮输入10K token、检索返回20个片段、history有200轮。验证系统不崩溃、不超限、响应时间可接受。测试用例要覆盖边界空history、空检索、超长单条消息、全是低分消息等。这些边界往往就是线上故障的来源。6. 进阶技巧与长期维护6.1 上下文质量的监控指标上线后不能不管要持续监控。我关注这几个指标平均上下文token占用反映成本趋势突然升高要排查。裁剪触发频率太频繁说明预算不够或对话太长。摘要压缩比压缩后token除以压缩前反映摘要效率。关键信息保留率抽样检查看重要信息是否还在。首token延迟上下文越长延迟越高反映用户体验。这些指标可以打到监控面板上设置告警阈值。比如裁剪频率超过每10轮一次就该考虑调预算了。6.2 上下文管理的演进方向这套东西不是一成不变的。随着模型窗口变大、成本下降策略也要调整。比如窗口从128K涨到1M滑动窗口就可以放更多轮摘要频率可以降低。但核心思路不变在预算约束下最大化信息价值。另外结构化记忆是个值得投入的方向。与其让模型自由总结不如用结构化格式存记忆比如JSON格式的偏好列表、事实列表。这样检索和注入都更精准也不容易丢失信息。我目前在一些项目里试点效果比纯文本摘要好。还有一个方向是自适应预算。根据当前问题的复杂度动态调整各部分预算。简单问题少给检索复杂问题多给历史。这需要先做问题分类再映射到预算配置。实现起来有点复杂但收益明显。6.3 我踩过的几个坑最后分享几个我实际踩过的坑帮你省点时间。坑一用字符数估token。早期图省事用字符数除以2估token结果中文场景下严重低估频繁超限。后来老老实实用分词器再没出过问题。坑二摘要提示词太笼统。一开始写“请总结以下对话”结果模型总结得又长又水关键信息还丢了。后来改成明确列出要保留的信息类型质量立马上来了。坑三忘记给输出留预算。有次模型生成长文输出到一半被截断用户看到半截回答。后来固定预留16K给输出问题解决。坑四检索结果不设上限。有次检索返回了30个片段上下文直接爆了。后来加了硬上限最多5个片段每个不超过500 token。坑五memory无限增长。摘要越积越多最后memory本身就成了负担。后来加了二级摘要memory总量控制在8K以内。这些坑都不难解决但不知道的话就会反复踩。希望你看完能直接绕过。这套上下文管理的思路和实现我在几个项目里跑了一年多整体稳定。核心就一句话把上下文当成有限资源来经营每一token都要花在刀刃上。具体策略可以根据你的场景调整但预算意识、分层思维、持续监控这三条是通用的。