如果你最近在用各种 AI 助手写代码、做分析一定遇到过这种状况同一个问题换个说法答案就完全不一样或者明明把所有资料都贴上去了它却抓住一段无关紧要的配置反复分析。多数人第一反应是模型不行但我在实际项目中排查过几十个类似案例真正的原因十有八九不是模型而是上下文没管理好。context-mode——上下文模式——就是把“该让模型看到哪些信息、以什么顺序看到、以什么格式看到”这件事工程化的一套方法。这篇文章我会结合自己做 AI 应用开发的实战经历把 context-mode 的组成、实现、踩坑和不同场景的适配思路一次讲透。无论你是在写智能客服、代码助手还是文档问答工具这篇都值得收藏。1. 从“答非所问”说起context-mode 到底在解决什么问题1.1 一个真实的翻车现场前阵子我在调试一个数据清洗工具当时图省事把整个项目 20 多个源文件、几份设计文档和几轮讨论记录一股脑丢给 AI 助手希望它帮我定位一个字段解析报错。结果它盯着工具配置文件分析了大半天最后建议我改依赖版本。可我当时只想知道“date 字段为什么解析失败”它却在看整个系统的依赖树。后来我把上下文收窄到三样东西原始报错信息、date 字段所在函数的完整代码、对应的测试用例。这次一分钟不到就定位了问题——时区格式不一致。同一个模型同一天结果天差地别。差别不在模型智力而在上下文组织方式。这个案例让我意识到喂给模型什么、不喂什么本身就是一种需要认真对待的工程决策。1.2 上下文模式不是“聊天记录”而是“信息管线”很多人把 context-mode 理解成“把聊天记录完整保存再原样喂回去”这其实是最大误区。原始聊天记录是低信噪比的信息源里面有一半是寒暄、重复、跑偏内容而真正有用的是从对话、代码、文档、工具结果中提取出来的“与当前任务相关的有效事实”。context-mode 的本质是一条信息管线采集、筛选、压缩、注入。每一步都是一层决策。采集决定收什么筛选决定留什么压缩决定丢什么注入决定怎么摆。你可以把它想象成后厨配菜系统模型是灶台上的厨师上下文是提前洗好、切好、按顺序排好的食材。配菜配得好大火快炒几十秒就出锅配菜乱成一堆厨师再厉害也得手忙脚乱。1.3 为什么这个模式近两年突然这么热上下文窗口从几万 token 一路涨到百万级“装得下”已经不是瓶颈“装得好”成了新瓶颈。模型对长上下文的利用并不均匀大量实验都发现了一个现象处在长文本中间位置的信息最容易被忽略这就是常说的“lost in the middle”。窗口越大这个问题越突出因为你往里塞的东西越多关键信息被淹没的概率越高。另一个推手是 Agent 应用的爆发。Agent 的上下文里既有用户的话又有工具返回的 JSON、代码片段、网页正文、记忆检索结果全是异构数据。这时候单纯靠手动整理提示词已经不够用业界开始提 Context Engineering 这个概念本质就是用一套工程化手段管理上下文。context-mode 就是这套工程化手段的落地形态它把本来靠感觉的事变成了可设计、可复用、可调优的流程。2. 拆开 context-mode采集、筛选、压缩、注入四层设计2.1 采集层不是所有信息都有资格进上下文采集层最容易犯的错是“先全收再靠筛选兜底”。在真实系统里数据源非常多日志、代码、文档、聊天记录、数据库返回、第三方 API 响应如果不做源头控制后面的筛选层再强也扛不住海量脏数据。采集层必须坚持三个原则。第一来源标注。每一条上下文条目都要带上 source 元数据比如来自哪个文件、什么时间、什么类型、置信度多高。没有来源的信息就像没有出处的传言后续既没法追溯也没法做时效判断。第二去重与版本管理。同一个文档可能被反复读取同一段代码可能有多个版本。采集时按内容哈希去重并记录版本号只保留最新版本。第三源头控制。在采集阶段就和任务目标对照能早扔就早扔。比如任务是“修 A 接口的 bug”那么底层日志里与 A 接口无关的消息在采集阶段就可以直接丢弃不要给后续流程添堵。下面是一个最简单的采集元数据结构参考字段作用示例content上下文内容本身报错信息、代码片段source来源标识用于追溯src/utils/date_parser.py#L42timestamp产生时间用于时效判断2025-06-12T10:23:00Zkind内容类型code,log,doc,chat,toolpriority初始优先级1 到 55 最高2.2 筛选层任务相关性是唯一的保留标准采集完的信息仍然太多筛选层的任务是把“可能有用”变成“当前任务确实需要”。这里面有两套常用技术路线。轻量路线是关键词加权打分。把用户请求解析成一组关键词然后对每条上下文计算命中情况命中标题词加分命中正文词加分少一点完全不相关的直接淘汰。再加上 priority 权重和时间衰减权重最后按分数取 top_k。这套方案实现成本低适合快速验证场景。重量路线是向量检索加重排。先用 embedding 把上下文和用户请求都转成向量做语义相似度召回再用更重的 rerank 模型精排。缺点是计算成本高优点是能处理同义表达比如用户说“日期格式问题”上下文中写的是“timestamp mismatch”向量检索也能捞出来。无论用哪条路线筛选前都要先做一件看似简单但极容易被忽略的事把用户意图翻译成“需要哪些事实”的清单。修 bug 需要的是报错、相关代码路径、最近改动写方案需要的是现状、约束、历史决策。意图不同筛选标准完全不同没有统一的关键词列表能同时服务两种任务。2.3 压缩层把“长而全”变成“短而准”压缩层是 context-mode 里最考验功力的环节。压缩方式通常有三种。第一种是摘要式压缩调用模型对长文本生成摘要。这种适合解释性内容比如设计文档的背景说明、讨论记录的结论部分。第二种是结构化压缩把自然语言转成关键字段比如把一段工具返回的 JSON 只保留必要字段或者把一段配置说明转成表格。第三种是截断式压缩只保留开头、结尾或关键词所在的行适合代码报错这一类本来就有固定格式的内容。压缩有一条硬性原则不得吞掉数字、标识符、专有名词。如果摘要里丢了具体的端口号、变量名、报错码这个摘要就是不合格的。我见过太多人让模型做摘要结果端口从 8080 变成“某个端口”变量名从parse_date变成“日期解析函数”模型后面就只能靠猜。比较稳的做法是混合压缩先把实体和关键数字用正则或 NER 抽取出来单独存放再让模型只压缩叙述性文字最后把实体列表拼回去。压缩率可以做到 60% 以上同时关键信息零丢失。2.4 注入层放在哪里、用什么格式决定了模型怎么用信息经过筛选压缩之后最后一步是注入。很多人把注入想得太简单以为只要把内容拼进 prompt 就行。实际上放置位置和格式对效果影响巨大。先说位置。模型对长文本不同位置的注意力是不一样的。任务指令应该放在开头或结尾这两处是注意力最强的位置需要精细推理的关键证据比如报错信息、关键代码要尽量靠近结尾大量辅助性背景材料放在中间。如果你把最关键的证据埋在中段模型很可能直接忽略它。再说格式。代码用代码块包好结构化数据用 JSON 或表格呈现历史对话保持对话格式。格式混乱会让模型花额外精力去理解结构真正干活的算力就少了。最后是预算。我一般会把上下文预算分成四块系统指令占 20%任务目标占 10%最新用户请求占 10%检索事实占 45%历史摘要占 15%。这个比例可以根据场景调但一定要有意识分配不能让某一类内容无限膨胀。层核心职责常用技术常见误区采集统一信息源带元数据入库结构化日志、事件总线、哈希去重什么都要整库注入筛选按任务相关性缩小范围BM25、向量检索、rerank只看相似度不看任务目标压缩降长度、保关键信息LLM 摘要、正则抽取、截断压缩时丢失数字与标识符注入决定位置、格式与预算Prompt 模板、token 预算控制所有内容无差别拼接3. 手写一个最小可用的 context-mode 管线理论讲再多不如代码能跑。下面我把上面四层设计实现成一个不到 150 行的 Python 骨架你可以直接复制运行然后替换成自己的数据源和模型接口。3.1 先定义数据模型上下文条目长什么样在动手写逻辑之前先把数据结构定下来。我用 dataclass 定义ContextItem里面包含五个字段对应前面表格里的元数据。from dataclasses import dataclass from datetime import datetime import re dataclass class ContextItem: content: str source: str timestamp: datetime kind: str # code / log / doc / chat / tool priority: int 1 # 1 - 55 最高这个结构是整个管线的地基。后续的筛选、压缩、注入都依赖这些字段所以采集层一开始就要保证它们被正确填充。如果源头信息没有时间和来源宁可丢弃也不要把脏数据放进来。3.2 采集把散落的信息源拧成统一结构真实场景里采集层要对接 git diff、日志系统、向量库、文件系统等等。这里我只做一个模拟版本用三个函数分别代表从代码库、报错日志、历史讨论中采集信息返回值统一为ContextItem列表。def collect_code_files(): return [ ContextItem( contentdef parse_date(raw):\n return datetime.strptime(raw, %Y-%m-%d), sourcesrc/utils/date_parser.py, timestampdatetime(2025, 6, 12, 10, 0), kindcode, priority4, ), ContextItem( contentdate parse_date(row[date]) # ValueError here, sourcesrc/etl/pipeline.py#L37, timestampdatetime(2025, 6, 12, 10, 5), kindcode, priority5, ), ] def collect_logs(): return [ ContextItem( contentValueError: time data 2025/06/12 does not match format %Y-%m-%d, sourcelogs/app.log, timestampdatetime(2025, 6, 12, 10, 6), kindlog, priority5, ), ] def collect_history(): return [ ContextItem( content用户反馈日期字段偶尔解析失败怀疑是分隔符不一致, sourcechat/thread_1024, timestampdatetime(2025, 6, 12, 9, 30), kindchat, priority3, ), ] def collect_all(): return collect_code_files() collect_logs() collect_history()注意这里的priority已经在源头标好。报错日志和报错代码行是 5普通工具函数是 4历史讨论是 3。这个初始优先级会直接影响筛选阶段的结果所以采集时要想清楚什么信息对当前任务最重要。3.3 筛选用关键词加权把相关事实捞出来筛选层我做一个轻量实现从用户请求里拆关键词对每条上下文做匹配打分再加上优先级和时效权重。语义检索的接入点也保留等你有向量库之后只需替换打分函数即可。def score_item(item: ContextItem, keywords, now: datetime): text item.content.lower() score 0.0 for kw in keywords: if kw in text: score 1.0 elif kw in item.source.lower(): score 0.5 score item.priority * 0.8 # 时效权重超过 24 小时按小时衰减 hours (now - item.timestamp).total_seconds() / 3600 if hours 24: score * max(0.3, 1.0 - (hours - 24) * 0.01) return score def filter_items(items, keywords, top_k5): now datetime.now() ranked sorted( items, keylambda x: score_item(x, keywords, now), reverseTrue, ) return ranked[:top_k]这一步体现的筛选逻辑很朴素但够用关键词命中说明和当前问题直接相关优先级高说明是核心证据时间近说明信息还没过期。我见过很多生产环境用比这复杂得多的方案但基本逻辑都是这三条。先跑通这个版本再考虑升级向量检索不要在第一天就引入重依赖。3.4 压缩截断与实体保护压缩阶段我希望演示“实体保护”的思路先把内容里的数字、变量名、关键符号抽出来再截断最后把保留的实体信息附加回去。这对于日志和报错信息尤其重要。ENTITY_PATTERN r[A-Za-z_][A-Za-z0-9_]*\(\)|\b\d{2,5}\b|\b[A-Za-z_][A-Za-z0-9_]*\b def extract_entities(text: str): return list(set(re.findall(ENTITY_PATTERN, text))) def compress_item(item: ContextItem, max_chars300): entities extract_entities(item.content) if len(item.content) max_chars: return item head item.content[: int(max_chars * 0.6)] tail item.content[-int(max_chars * 0.4):] compressed head \n...[压缩中段]...\n tail compressed f\n[关键实体] {, .join(entities[:20])} return ContextItem( contentcompressed, sourceitem.source, timestampitem.timestamp, kinditem.kind, priorityitem.priority, )从extract_entities可以看到我把函数调用形式的标识符、两到五位的数字、普通变量名都当成需要保护的实体。真实场景里你还需要保护 IP、端口、订单号这类格式可以往正则里继续加规则。这段代码想表达的核心是压缩是“删冗余保关键”不是简单截断。3.5 注入组装成模型能直接开工的 Prompt最后一步是把处理好的上下文拼进 prompt 模板同时标注每条信息的来源。标注来源有两个作用一是让模型可以引用具体文件减少凭空编造二是当你调试结果时能回头检查哪条上下文起了作用。def build_prompt(user_query, items, instructionNone): lines [] if instruction: lines.append(f[系统指令] {instruction}) lines.append([任务目标] 请基于以下上下文完成用户请求。) lines.append([上下文开始]) for idx, item in enumerate(items, 1): lines.append(f{idx}. [{item.kind}] ({item.source})) lines.append(item.content) lines.append([上下文结束]) lines.append(f[用户请求] {user_query}) return \n.join(lines) def run_pipeline(user_query, keywords, instructionNone): items collect_all() filtered filter_items(items, keywords) compressed [compress_item(i) for i in filtered] return build_prompt(user_query, compressed, instruction) if __name__ __main__: user_query 日期字段解析失败请定位原因 keywords [date, parse, valueerror, strptime] prompt run_pipeline(user_query, keywords) print(prompt)跑一遍的输入大概是这样的结构系统指令在最前检索事实按顺序排在中间用户请求放在最后。注意关键报错内容因为优先级高、时效新被筛选排在前面而用户原始请求永远在最后让模型在输出时能直接对照最新问题。这个模板不是最优解但它是可运行的最小闭环你可以在它上面继续调。4. 实战踩坑跑进生产环境后遇到的那些问题代码骨架能用只是开始。真正把 context-mode 跑进生产环境我前前后后踩了不少坑下面五个是最典型、也最容易复现的。4.1 上下文过载窗口没满模型先“迷路”第一个坑是过度追求“全”。我当时负责的一个客服机器人检索层很给力每次能召回 50 条相关知识库条目每条都跟用户问题沾边。结果模型经常忽略最关键的那一条反而用次相关的知识回答问题。问题在于信噪比。50 条“相关但不够核心”的信息塞进去单看每一条都合理合在一起就把最核心的那几条稀释了。再加上位置效应中间部分的信息更容易被忽略。修复方法是狠下心控制数量。我先把 top_k 从 50 砍到 8再把结果交给 rerank 模型精排只保留最核心的 3 到 5 条。结果准确率反而提升了。核心教训上下文宁可精不要多信息量不等于信噪比。4.2 过期上下文污染昨天的结论带偏今天的任务第二个坑来自记忆和历史的时效。我们的工具支持多轮会话早期设计是把所有历史消息都保留并压缩进上下文。有一次上午模型分析说是“A 模块的问题”下午开发人员已经修复了 A 模块并给出新报错但因为上午的错误结论还在上下文里模型继续按 A 模块排查浪费了很多时间。这是典型的上下文污染。旧结论没有被标记为失效模型分不清哪些信息描述的是“当前状态”哪些描述的是“历史过程”。对策分两步。第一步是给上下文加失效机制当代码发生变更、任务状态更新时把相关旧条目标记为 invalid 或降低时效权重。第二步是推理前做“上下文刷新”把“当前已知事实”单独放一块把“历史过程”放另一块并明确告诉模型哪些已是过去式。模型就不会再抱着旧结论不放。4.3 多轮对话指代断裂模型不知道“那个函数”是哪个第三个坑在压缩阶段。用户第一轮说“看下get_user函数”第二轮说“在那个函数里加个判断”。上下文压缩时摘要模型把get_user改成了“用户查询函数”。第二轮模型面对“在那个函数里加判断”完全不知道“那个”指的是哪个函数。根因是我让摘要模型自由压缩了所有内容没有保护标识符。模型做摘要时天然倾向把内容泛化成更“通顺”的表达函数名、变量名、类名这种精确符号往往被牺牲。修复方案是前面代码里演示的实体保护在压缩前用正则把get_user这类标识符抽取成实体清单压缩完再把实体列表附加回去。同时多轮对话里我把“已提到的实体”单独存成一个字段每一轮都原样保留在上下文中模型就能流畅建立指代关系。4.4 摘要压缩吞掉关键数字8080 变成了“某个端口”第四个坑和 4.3 一脉相承但更隐蔽。有一次文档问答场景原始文档写着“服务默认监听 8080 端口超时时间 30 秒”。模型摘要后变成“服务默认监听某个端口超时时间为几十秒”。用户追问具体端口模型真的猜了一个数字。模型做摘要本质上是在做概率化的文本改写遇到不理解的数字时它倾向于模糊化处理。但技术场景里端口、超时、版本号、错误码恰恰是最不能模糊的信息。我后来定了一条铁律所有数字、版本号、错误码、地址走正则抽取永远不经过摘要模型摘要只处理叙述性文本。关键数字在上下文中逐字保留必要时加粗或单独成行确保它们不会出现在注意力盲区。4.5 只喂上下文不喂任务目标模型成了“知道很多但不知道干嘛”的百科全书最后这个坑不算技术问题但比技术问题更常见。很多人的 prompt 里堆了一大堆上下文却没有清楚说明“你是谁、你的任务是什么、输出什么格式”。模型就像被塞了一堆资料但没有拿到任务书的实习生只能把知识综述一遍。比如“根据以下信息回答用户问题回答请用中文”这种系统指令约等于没有。更好的做法是把角色、目标、约束、输出格式都写清楚。系统指令可以写成“你是资深后端工程师。你的任务是定位日期解析报错。限制只基于提供的代码与报错信息不猜测。输出格式先给失败原因再给出可执行的修复代码块。”任务指令要放在两个强位置prompt 开头和结尾。开头定方向结尾收束输出。上下文中间的部分才不会把目标带偏。我把上面五个坑和对应的排查思路整理成了一张表方便你对照自检症状可能原因快速验证方法修复方向信息都相关但答非所问上下文过载关键信息被稀释把 top_k 砍到 5 条再测精排 减量注入答案被旧信息带偏旧结论未失效检查上下文中是否含过时描述版本化、失效标记、时间衰减模型分不清“那个函数”标识符被摘要泛化看压缩后是否保留函数名实体抽取与列表保留端口、数量被猜错摘要模型泛化数字检查关键数字是否原样存在数字不走摘要模型输出像综述不像干活系统指令没有任务目标读一遍系统指令是否具体强化角色、目标、输出格式5. 不同场景下的 context-mode 适配思路5.1 AI 代码助手按调用链与报错组织上下文代码助手和普通问答最大的区别在于代码信息高度结构化散装文本喂进去效率极低。我的经验是不要整文件塞上下文而是抽取三样东西函数签名、调用链、报错栈。函数签名告诉模型这个函数接收什么、输出什么调用链告诉模型这个函数在哪些地方被用到报错栈直接锚定问题位置。实现上可以用 tree-sitter 这类解析器抽取 AST 中的函数声明和调用关系。关键代码片段按行号锚定比如src/etl/pipeline.py#L37这样模型修改代码时能准确找到位置。编译错误和测试输出要原样保留不要做摘要因为错误信息里哪怕少一个字符都可能导致误判。5.2 文档问答分层摘要加引用溯源文档问答场景里文档往往很长整篇注入不现实只检索片段又容易断章取义。我用的是分层摘要策略先给模型一个文档地图列出文档包含哪些章节和各章节主题再给命中章节的中层摘要最后给具体段落的原文。三层信息同时注入模型既能看到全局结构又能看到精确细节。引用溯源在这个场景是刚需。每条检索片段都要跟着来源信息回答时强制模型标注“根据文档第 X 章”。这既提升了可信度也方便用户回去核实。一旦模型开始引用不存在的章节你也能立刻从注入的上下文里定位问题——一定是来源标注环节出了差错。5.3 Agent 工具调用结构化结果与调用链并重Agent 场景里上下文的最大特点是工具返回结果非常结构化JSON、状态码、字段值。我的做法是工具返回先做一层结构化摘要只保留当前任务需要的字段减少 token 占用。但有一个例外失败信息必须原样保留不能摘要因为失败原因往往藏在细节里。同时要把调用链记录下来。模型做完一次工具调用后上下文里应该能看到“调用了哪个工具、传了什么参数、返回了什么”。没有调用链模型在多步任务里会忘记自己做到哪一步反复调用同一个工具或者跳过必要步骤。这是 Agent 掉进死循环最常见的原因之一。5.4 本地工具与 IDE 插件把隐式上下文显式化最后一个场景是本地工具和 IDE 插件。这类应用有个独特优势能拿到用户当前行为。编辑器选区、光标所在函数、最近打开的文件、终端输出这些都是高价值的隐式上下文。但隐式信息必须转成显式标签模型才知道该用哪部分。我习惯把这些信息包装成带标签的上下文块比如[当前选区]、[光标附近函数]、[最近终端输出]。带上标签之后模型能更准确地判断哪些信息描述的是用户正在操作的对象哪些只是背景资料。这个简单的改动比我预想的提升大得多。场景核心数据源推荐注入策略最该防的坑AI 代码助手函数签名、调用链、报错栈报错前置代码按行号锚定整文件灌入文档问答检索片段、目录结构地图 中层摘要 原文三段式断章取义、无引用Agent 工具调用工具返回、调用链结构化摘要 失败原文只留结果不留调用过程IDE 插件选区、光标、最近文件显式标签分组注入隐式上下文无标签最后说说我个人折腾 context-mode 最大的体会它的核心不是“塞得多”而是“决策什么该塞”。早期我总想用满上下文窗口结果窗口越大模型越容易在无关信息里打转。后来我给自己定了一个土规矩——每次注入之前先问一句如果我是模型只靠这些信息能不能立刻开始干正事不能就继续删。配合这个规矩我手里的 AI 工具从“偶尔惊艳、时常抽风”慢慢变成了“稳定可用”。如果你也要在自己的应用里加 context-mode建议从最小的四层管线开始先用关键词筛选和截断压缩跑通再逐步上向量检索和模型摘要每加一层复杂度都要回头验证响应质量。多轮迭代几次你会回来感谢曾经那个愿意删上下文的自己。