
去年下半年开始身边越来越多朋友在讨论同一个问题AI代理明明接入了能力很强的大模型为什么用起来总有种“它是鱼的记忆”的即视感上一轮刚交代清楚的需求这一轮它就凭空失忆明明给足了资料它却拎不清哪些才是当前任务真正要用的。后来我仔细复盘了自己做的几个代理项目发现绝大多数问题都不在模型本身而在于我们根本没把“上下文”当成一个需要认真治理的工程对象。上下文不是什么玄乎的东西。它就是你喂给模型的全部信息是模型做出判断的全部依据。开发普通软件时你绝不会放任全局变量无限增长、不做声明周期管理代码里到处都是野引用早晚出事。但到了AI代理这里很多人反而松懈了——一轮会话下来历史消息无脑追加工具返回的原始数据原样堆砌系统提示词越写越长直到某天模型开始胡说八道还在纳闷“我给了它这么多信息怎么还会错”这篇文章想聊的就是如何用“开发生命周期”的思维去治理AI代理的上下文。上下文从产生、组装、注入、更新到清理每个环节都需要有明确的策略和机制。如果你是正在用Claude、GPT等模型API构建代理的开发者或者在Cursor这类AI编程工具里经常被上下文问题困扰又或者只是好奇“上下文超限到底会发生什么”这篇内容应该能帮你少踩不少坑。1. 为什么AI代理上下文需要一个生命周期1.1 你管理的是模型思考的边界先看一个容易被忽视的事实上下文窗口就是模型“当下能感知到的全部世界”。窗口之外的信息哪怕你再怎么觉得“它应该知道”对它来说就是不存在的。你在后台配置了一堆工具函数、塞了一份120页的产品文档、保留了完整的多轮对话记录可当这些内容的总长度逼近模型上下文窗口上限时真正送给模型推理的往往只是被截断或稀释后的残片。上下文一旦超出限制不同的模型服务商处理方式不同。有的会直接报错提示你减少输入有的会从最前面悄悄裁剪历史消息还有的会把过长的内容以某种方式摘要后塞进后续请求。无论哪种结果都很糟糕要么请求直接失败要么模型在关键信息被裁剪后开始“一本正经地胡说八道”。这就是很多人遇到的幻觉问题的真正来源之一——不是模型变笨了是你给它的上下文已经残缺了。把上下文管理比作内存管理其实特别贴切。一个长期运行的程序如果每次申请内存都不释放最终一定会OOM。AI代理的上下文窗口就是一块固定大小的内存而每一次对话历史、每一段工具返回、每一条系统提示都在占用这块内存。上过操作系统课的人都明白内存碎片化之后系统性能会急剧下降。上下文也一样当无关信息占据大量空间时模型必须费力地在“噪声”中识别真正重要的内容回答质量自然会下降。1.2 应该说不是所有信息都值得进入上下文大多数代理设计失败的共同点就是把“上下文工程”做成了“上下文堆积”。我见过不少项目系统提示词里写了一大堆业务规则对话记录不做任何裁剪就无限追加工具调用返回的JSON结果原样塞进消息列表。结果就是上下文越来越臃肿Token费用越来越高模型回答越来越飘。你需要建立的第一认知是上下文质量远比数量重要。宁可精准地给模型三五百个Token的高密度信息也不要稀里糊涂地塞进去两三万个Token的杂烩。这就像带人去图书馆找一本书你告诉他“书架第三层、从左边数第七本、红色封面”就够不需要把整个图书馆的导览图都背给他听。所以上下文工程的一个核心原则就是有意识地控制哪些信息进入上下文以及以什么形式、什么顺序进入。这需要你像治理代码依赖一样治理上下文依赖——明确每个Agent任务真正需要哪些数据剔除无关内容对长文本做结构化提炼对历史交互做压缩摘要。这套治理动作本质上就是上下文的生命周期管理。2. 上下文生命周期的四个关键阶段2.1 构建阶段从多源信息到结构化上下文上下文生命周期的第一步是“构建”。这个阶段解决的核心问题是当前任务需要什么信息信息从哪里来以及用什么样的结构组织这些信息。以一个典型的企业知识库问答Agent为例。用户提问“公司今年的差旅报销政策是什么”背后至少涉及三类信息系统提示词里固定的业务规则、用户当前提问的意图、知识库中与报销政策相关的文档片段。如果你直接把整本员工手册塞进上下文模型也能回答但浪费了大量Token还可能在政策更新后引用到过期版本。更合理的做法是先做一次检索把员工手册中与差旅报销相关的几个章节抽出来再做一次裁剪移除表格格式、页眉页脚保留关键条款最后组装成结构化的上下文块。这个过程和你在代码里先读取配置、再加载对应模块、最后把依赖注入构造函数本质上是同一回事。这里有个容易被忽略的细节上下文构建的顺序会影响模型的理解效果。通常系统指令要放在最前面因为它定义了模型的角色和任务边界随后是用户当前的需求再往后才是检索到的参考材料。如果顺序颠倒模型可能把系统指令误当作普通对话内容行为模式就混乱了。这已经是被不少团队的实验验证过的经验。2.2 路由阶段把上下文送到该去的地方管理过微服务的人都知道“服务路由”的重要性——不同的请求要打到不同的后端实例上。AI代理同样需要“上下文路由”在同一个Agent内部可能有多个子任务、多个工具函数、多个知识域你不能总是把全量上下文一次性推给模型。举个例子。你做了一个内容创作Agent它既能写产品文案又能写技术文档还能做竞品调研。如果不管用户说什么都把三套指令和两套资料库全量塞进上下文模型的表现一定会很糟——过多的任务定义会互相干扰模型甚至可能把写技术文档的严谨语气用在了趣味文案上。正确的做法是先让一个轻量的分类模型或者一个廉价的规则判断器识别用户意图然后只加载对应技能所需的提示词和知识片段再拼接用户当前输入最后才发送给主模型执行。这个过程就是上下文的路由。不少成熟的Agent框架比如LangChain里的RouterChain一类组件干的就是这件事。路由阶段的另一个要点是控制多工具调用的上下文污染。有些Agent在一个任务里要连续调用多个工具如果把前一个工具的完整原始输出一字不改地塞给下一个工具很容易超出上下文限制。我习惯给每个工具定义统一的输出Schema返回前先做字段裁剪和格式整理只保留对后续步骤有意义的那几个字段。相当于微服务之间的接口约定——你返回给我的应该是我真正消费得动的数据。2.3 维护阶段让上下文跟上对话节奏多轮对话是上下文生命周期管理中最容易失控的场景。每轮用户的提问都会基于上一轮模型的回答每轮工具调用都可能改变业务状态。上下文如果只是简单“追加”体积会不断膨胀而且早期信息可能已经过时。维护阶段要做的是持续跟踪上下文中关键信息的变化。比如你做一个多步骤的数据分析Agent用户先上传了一份CSV然后要求“先做数据清洗再统计缺失值最后画分布图”。每一步执行完后任务状态都发生了变化。你需要在上下文中动态更新哪些文件已经处理过了、当前处于哪个步骤、下一步的输入是什么。一个实用的技巧是引入“状态摘要”。每完成一个子任务就把这一步的结论浓缩成几十个Token的摘要覆盖掉原始的冗长输出。比如原始工具返回了一个500行的JSON表格经过摘要后变成“数据清洗完成删除12条重复记录修复38处缺失值无异常编码”。这样下一轮模型做判断时既能获得足够的信息又不用面对原始的海量数据。这种“摘要化”手段是控制上下文体积增长最有效的策略之一。维护阶段还涉及上下文版本管理的概念。你可能会调整系统提示词、替换参考文档或者改变工具配置。每次调整后上下文的语义都发生了变化。如果调试时发现模型表现异常你最好能回溯到上一版上下文的状态去对比。这跟代码版本回滚是同一个道理。我以前用自己实现的ContextManager时给每次上下文快照加过一个版本号调试效率提升非常明显。2.4 清理阶段上下文超限不是灾难而是信号很多开发者一看到“context length exceeded”就紧张觉得是自己没处理好。我的看法正好相反上下文超限是一个重要的健康信号它提醒你“当前的任务状态已经积累了太多过时的信息该做一次清理了”。清理阶段的策略有好几层从轻到重分别是丢弃、截断、摘要、外部化。丢弃最粗暴直接删掉最早的用户消息适合早期寒暄截断相对精细按照重要程度逐条淘汰比如优先保留最近的对话和系统指令摘要就是我们前面提到的方式将早期对话整体压缩为一段概述外部化则是把详细的历史记录存到数据库或向量检索里需要时再按关键词捞回上下文。选择一个怎样的清理策略取决于你的业务对历史信息的敏感度。如果只是写代码、做转换这类无状态任务丢几条旧消息影响不大但如果是法律咨询、医疗问诊这类需要完整追溯历史的场景你就必须做摘要甚至要保留可查询的外部存档。清理动作不能等到超限报错才做而应该设置一个“水位线”。比方说当已用Token达到窗口大小的70%时就触发一次清理流程。提前清理既不会影响当前任务的连贯性又让模型始终有充裕的上下文空间处理最新输入。这跟磁盘告警阈值是一个道理——你不可能等到磁盘写满了再去做迁移。3. 实操为你的AI代理搭建上下文生命周期管理3.1 一个可运行的上下文管理器骨架光讲理论不落地等于白说。下面我用FastAPI配合一个大语言模型API的方式演示一个简单的上下文管理器是怎么工作的。它做的事情不复杂但足够勾勒出生命周期管理的基本轮廓# context_lifecycle.py import time import json from dataclasses import dataclass, field from typing import List, Optional, Callable from enum import Enum class ContextStage(Enum): CREATED created ROUTED routed MAINTAINED maintained ARCHIVED archived dataclass class ContextUnit: role: str content: str stage: ContextStage ContextStage.CREATED token_count: int 0 created_at: float field(default_factorytime.time) class ContextManager: 一个简单的上下生命周期管理器 def __init__(self, max_tokens: int 8000, watermark: float 0.7): self.units: List[ContextUnit] [] self.max_tokens max_tokens self.watermark watermark self.summarizer_history: List[str] [] def append(self, role: str, content: str): 追加一条上下文并触发水位线检查 unit ContextUnit(rolerole, contentcontent) self.units.append(unit) self._check_watermark() def _total_tokens(self) - int: # 生产环境可以用tiktoken计算这里按字符数粗略估算 return sum(len(u.content) // 2 for u in self.units) def _check_watermark(self): 当上下文使用量超过水位线触发压缩 if self._total_tokens() self.max_tokens * self.watermark: self._compress_early_units() def _compress_early_units(self): 将最早的几条用户/助手消息压缩成一段摘要 early_units self.units[:-4] # 保留最近4条 if len(early_units) 3: return compressed self._summarize(early_units) self.units [ContextUnit( rolesystem, contentf[历史对话摘要] {compressed}, stageContextStage.ARCHIVED, token_countlen(compressed) // 2 )] self.units[-4:] def _summarize(self, units: List[ContextUnit]) - str: 调用模型对早期消息做总结 # 这里留给你对接具体的模型API return [这里由模型根据早前对话生成概述] def snapshot(self) - List[dict]: 输出当前可用于API请求的消息列表 return [{role: u.role, content: u.content} for u in self.units]这个类做的事情就是维护一个消息列表当总长度超过水位线时把最靠前的历史消息压缩成一条系统摘要。核心逻辑很少但已经把生命周期管理的构建、维护、清理三个环节串起来了。用一个FastAPI接口来演示它的使用方式# main.py from fastapi import FastAPI, HTTPException from context_lifecycle import ContextManager from pydantic import BaseModel app FastAPI() class ChatRequest(BaseModel): user_message: str class ChatSession: def __init__(self): self.ctx ContextManager(max_tokens8000, watermark0.7) self.ctx.append(system, 你是可靠的技术助手回答简洁且准确。) sessions: dict[str, ChatSession] {} app.post(/chat/{session_id}) async def chat(session_id: str, req: ChatRequest): if session_id not in sessions: sessions[session_id] ChatSession() session sessions[session_id] user_content req.user_message.strip() if not user_content: raise HTTPException(status_code400, detailRequest body cannot be empty) session.ctx.append(user, user_content) # 这里替换为你实际使用的模型API调用 # response call_openai_style_api(session.ctx.snapshot(), temperaturetemperature) response_content 模型返回的示例回答 session.ctx.append(assistant, response_content) return {reply: response_content}你可能注意到我没有把真实的模型API调用放进来因为代码的重点是生命周期管理的骨架而不是某一家模型的对接细节。你只要把那段注释替换成你自己的API调用即可。3.2 上下文超限时的自动摘要与降级策略刚才的ContextManager里预留了_summarize方法。这一步是上下文生命周期管理中的重头戏值得单独展开讲。一个常见的摘要策略是“分层摘要”。想象你在跟模型长谈三小时如果要把三小时的对话全文塞进上下文早就爆了。但你可以每半小时生成一次摘要最后再对摘要做二次摘要形成金字塔结构。建模时你只需要携带最近一轮的完整对话和最顶层的摘要即可。实现上我建议在调用摘要模型时使用一个独立、便宜的模型。这里注意几个要点摘要模型也要有一个明确的指令比如“提取对话中所有已确认的需求、技术决策、用户偏好和未完成事项”而不是让它笼统地“总结一下对话”——否则摘要会丢关键信息。分层摘要之外另一个实用的降级策略是“关键信息抽取”。不是所有历史内容都值得被摘要有些信息本来就该被直接丢弃。比如用户问候的客套话、和当前任务无关的闲聊、重复表达同样意思的冗余发言。在一次对话中我先让摘要模型判断“哪些内容与当前任务目标有关”只对有关部分生成摘要。这个过程本质上是把上下文的清理和路由结合了起来。还有一个容易被忽视的坑摘要本身的Token消耗。每次触发压缩摘要模型会产生一个输入和输出。如果对话频率很高频繁地做摘要会产生可观的额外成本。所以我给压缩动作设置了一个最小间隔——比如60秒内最多触发一次避免短时间大量消息涌入时反复做无意义的压缩。外部化存储也值得一提。上下文生命周期到了“归档”阶段并不意味着信息彻底消失。我会把完整的原始消息记录同步写入数据库同时也把摘要存一份带时间戳的版本。后续如果用户问“我们之前讨论过的那个功能点是什么”Agent就可以通过检索去数据库捞取当时的完整记录再作为新增的上下文块注入当前对话。这样有限的上下文窗口留给了当前任务历史知识则放进了无限的外部存储。3.3 在IDE、本地模型和复杂代理中的延伸应用上下文生命周期管理不只适用于后端API调用。现在很多人每天都在用的AI编程助手比如Cursor这类工具底层也在做类似的事情。你打开一个项目它需要把哪些文件当作上下文给你的AI对话窗口这其实就是“构建阶段”的决策。你有没有手动指定过文件引用有没有用过“Codebase索引”这些动作本质上都是在构建和路由上下文。结合搜索词里的“ai代理助手加本地模型”再补充一句本地模型因为参数规模小上下文窗口往往更紧张生命周期管理的价值反而更大。云端模型动辄支持几十万Token的上下文你可以稍稍偷懒但本地模型通常只有4K、8K、16K的场景如果不在上下文构建和清理上下功夫几乎必然会被截断问题困扰。对于复杂的、多步骤的Agent我还会做“上下文数据流图”的分解。我会先画出整个代理执行过程中的信息流向什么数据从哪个工具来经过什么转换最后进入模型的哪一次调用里。画完这张图哪些环节会堆积冗余数据哪些环节存在上下文越权访问都能看得一清二楚。这跟调试代码时的调用链追踪是类似思维。4. 常见问题与排查技巧实录4.1 幻觉问题先查上下文再查参数我处理过的绝大多数“AI幻觉”案例刨根问底之后问题都出在上下文。要么是相关材料根本没有进入上下文模型只能靠“印象”去编要么是无关信息太多把模型真正要用的关键信息挤压甚至截断了。排查幻觉问题时我通常按这个顺序走一遍先打印出每一次调用模型的完整请求数据确认系统提示、历史消息、检索资料实际送进去的内容是什么然后手动判断这些内容里是否有直接回答当前问题的信息如果信息确实在再看是否被截断、位置是不是太靠后。绝大多数幻觉案例在前两步就能定位方向。温度参数的影响我也顺带提一句。温度控制的是模型生成时的随机性。很多人习惯把所有任务都调成0.7这是不对的。需要确定性输出的任务比如信息抽取、代码生成、结构化输出我通常把温度调到0~0.3只有创意写作、头脑风暴类任务才调高到0.8以上。但请注意温度调低可以减少“随机编造”却无法弥补上下文缺失导致的“无据可依”。4.2 上下文超限的报错与应对不同模型服务商的报错信息不一样。有的提示“maximum context length exceeded”有的返回4xx错误码还有一些SDK会直接抛出异常。遇到这类报错第一反应应该是查看“发送出去的请求”到底有多少Token而不是急着去调参数。如果发现确实是单次请求就超限那就要优化上下文输入检查系统提示词是否过于冗长参考文档是否有更精简的版本历史消息是否需要压缩。如果请求本身没有超限但经常在多次调用后失败那大概率是会话管理端没有做水位线检查下一次请求还在增长终于在某一步突破了上限。我自己的做法是在调用API封装层统一拦截这一类错误收到超限信号时自动触发一次上下文压缩逻辑压缩完重试一次。这个兜底机制极大减少了线上代理的崩溃率。但需要提醒的是兜底机制只能救急不能替代前面的水位线管理——如果每次都靠超限后再压缩体验和成本都不可控。4.3 上下文与执行环境两个容易混淆的概念热词里面提到的“执行上下文”和“js的this指向”其实跟我们要讨论的AI代理上下文不是一回事但在排查问题时经常被混在一起。从编程语言的视角看执行上下文关心的是当前代码运行时可以访问到的变量和函数AI代理的上下文关心的则是模型生成回复时可以看到的信息集合。前者是程序语义层面的概念后者是模型输入层面的数据。不过我们可以借鉴一些编程实践来管理代理上下文。比如很多语言强调“变量作用域要尽量小”代理上下文设计也可以有类似思路某个工具函数执行时只需要把它真正用到的数据片段传入而不要让它看到全部用户对话。这样的隔离设计不仅减少了上下文体积还降低了敏感信息泄露的风险。另外如果你在FastAPI这类异步框架里做代理开发注意HTTP请求本身也有一个上下文对象。你需要小心区分哪些是FastAPI的请求上下文比如当前用户、请求头哪些是你要组装给模型看的对话上下文。把它们混在一个数据结构里代码会越来越难维护。4.4 一个速查表不同场景的上下文管理推荐配置最后分享一张我平时常用的配置参考表覆盖几个典型场景。这里的数值不是金科玉律仅供参考。核心是让你有个起点再根据实际效果微调。场景系统提示词建议历史保留策略水位线触发阈值建议温度代码生成/补全简洁3~5条规则保留最近5~10轮60%0~0.2信息抽取/结构化输出明确输出格式要求保留最近2~3轮即可50%0~0.1技术问答/知识库RAG定义角色和答案风格保留最近5轮70%0~0.4创意写作给出主题和调性参考保留较多历史尽早摘要75%0.8~1.0多步骤数据分析代理定义步骤和工具边界每个子任务结束立即摘要65%0~0.3每次调整完上下文策略我都建议做一轮回归测试准备固定的10~20条任务样本分别记录回答的准确率、上下文Token消耗、平均响应延迟。不要依赖感觉做决策用数据说话。如果你懒得自己搭评测集也可以在项目里临时建一个简单的脚本把每次调用的请求和响应都存成日志之后抽样分析。我自己踩过不少坑试过调大温度值想“提升创造力”结果只是增加了噪声试过把所有历史对话一股脑都塞进去结果成本翻倍、准确率反而下降也试过把摘要做得太激进结果模型丢失了用户曾经明确表达过的偏好。后来我总结出来上下文管理这件事没有一劳永逸的银弹它就是一套需要持续观察、持续调优的工程实践。最后再分享一个小技巧每次修改完上下文管理逻辑后我会专门跑一轮“长时间对话压力测试”。让Agent连续处理多个相关任务人为制造上下文增长观察它在不同阶段的表现是否稳定。通过这种方式可以在项目上线前提前暴露很多生命周期管理上的薄弱环节。这套做法算是真正把“上下文开发”当成一个严肃的软件工程问题了而不是靠临时抓瞎去救火。