
最近做 AI 辅助工具的时候团队里聊得最多、也踩坑最多的一个词就是 context-mode。这个词翻译成中文就是“上下文模式”但真正动手做过的朋友应该清楚它远不是往提示词里塞几段代码那么简单。项目推进到一定阶段后你会发现模型能力反而不再是瓶颈真正的瓶颈变成了“模型到底能不能看到它该看的东西”。我这个项目本质上就是给一个 AI 代码助手增加了一套 context-mode。核心目标很明确让 AI 在回答问题时能够自动感知当前项目的文件结构、最近改动、历史讨论甚至用户当下的操作意图从而给出贴合真实场景的答案而不是一本正经地胡说八道。内容适合两类人看一类是正在做 AI 应用、AI 编程助手或知识库问答系统的开发者另一类是重度使用 AI 工具、天天抱怨“AI 答非所问”的普通用户。很多人把问题归结为模型笨其实大部分时候是上下文压根没喂对。这篇就把 context-mode 的概念、设计思路、核心实现和踩过的坑一次讲透你看完基本能照着思路改造自己的项目。1. 项目概述context-mode 到底解决什么问题1.1 从一个典型的“翻车现场”说起先讲一个我真实遇到的场景。同事用 AI 助手重构一个 Python 模块模型给出的建议从语法层面看毫无问题但一落地就报错。原因很典型这个模块依赖项目里一个自定义的消息队列封装而 AI 完全不知道有这个封装存在于是自己“合理”地假设了一个标准接口。这就是所谓“答非所问”的本质。模型不是不会写代码而是它看到的只有你粘贴的那一小段内容没有项目背景、没有依赖关系、没有历史决策记录。你在 IDE 里看到的上下文是极其丰富的——左侧目录树、打开的标签页、Git 改动、最近搜索记录这些信息在你脑子里构成了对问题的完整理解。但对模型来说它就是个“失忆的专家”每一轮对话都像第一次见面。context-mode 要解决的正是这个信息不对称的问题把散落在项目里、会话里、操作行为里的上下文按照需求和优先级组织起来在合适的时机交给模型。1.2 context-mode 的准确定位我比较认可的定义是context-mode 是一种让系统能够自动采集、组织、筛选并注入上下文的工作模式核心目标是让模型的输出基于真实场景而不是基于统计概率的合理猜测。它不是一个单纯的功能点而是一套处理流程典型包含四个环节采集从文件系统、版本控制、对话历史、编辑器状态等源头获取原始信息组织对原始信息做结构化处理按主题和时间线归拢筛选根据当前问题计算相关性决定哪些信息进入模型视野注入按约定的格式把筛选后的上下文拼装进提示词这四个环节缺一不可。很多早期方案只做了“采集”和“注入”把所有内容一股脑塞给模型看起来简单粗暴但实际效果往往很差——上下文塞得越多模型越分不清主次响应速度和成本也水涨船高。只有把“组织”和“筛选”做到位context-mode 才真正有工程价值。1.3 适合谁来参考如果你是应用开发者这篇里的分层设计、token 预算分配、裁剪策略可以直接用到你的 AI 应用里。如果你是工具的普通用户读完你会明白为什么同一个模型在别人手里那么好用、在你手里却总是不靠谱——差异往往就在于你有没有给它足够有效的上下文或者说你用的工具够不够聪明地帮你组织上下文。2. 核心思路拆解context-mode 的设计逻辑2.1 上下文不是越多越好刚开始做 context-mode 时团队最容易犯的一个错误就是“贪多”。总担心模型看不到关键信息于是把整个项目的文件列表、全部 Git 历史、所有对话记录都往里面塞。实测下来效果反而更差因为上下文里充满了噪声。用生活里的场景来类比面试官让你评估一个候选人给你一摞 500 份简历其中 480 份都不相关你要花大量时间才能找到关键的那 20 份。大模型同样面临这个问题它的注意力是有限的信息量超过阈值之后关键信号会被淹没在无关内容里。所以我在设计时定了一个原则相关性 数量。一个 token 的有效价值取决于它和当前问题的关联强度而不是它本身的信息含量。与其给模型 10 万个 token 的项目快照不如精心挑选 5000 个 token 的高相关片段。2.2 上下文的分层模型既然不能全塞那就得给上下文分层次管理。我参考了经典的操作系统内存管理思路把上下文分成三个层级全局层项目的稳定信息包括 README、技术栈说明、编码规范、业务背景、常用组件清单。这一层更新频率最低可能一周才变一次但决定了模型对项目的基本认知会话层当前对话内产生的动态信息包括用户最近几轮提问、AI 之前的回答、用户对答案的修正。这一层的时效性极强每一轮对话都在变化局部层与当前操作直接相关的即时信息包括当前打开的文件、光标附近的代码、最近一次 Git diff、选中的代码片段。这一层是模型做具体决策时的“近距离视野”三个层级各有各的作用缺了任何一层都会出现明显问题。缺全局层模型会写出风格不一致、无视项目规范的代码缺会话层模型会反复犯同一个错误你刚纠正过的方向它扭头就忘缺局部层模型不理解你当下的具体操作只能泛泛而谈。2.3 为什么“拼 prompt”式的老方案不行有人可能会问这跟手动把相关文件复制粘贴给模型有什么区别区别就在于“模式”二字。手动拼 prompt 有三大痛点一是时效性差文件改了你还得重新复制二是主观性强你觉得自己理解了但可能漏掉了真正关键的信息三是不可扩展一次两次可以天天做根本不现实。context-mode 的价值在于它把“理解上下文”这个本来要人来做的事情自动化了。系统自己监控文件变更、自己跟踪 Git diff、自己维护对话摘要、自己判断哪些文件相关。人只需要专注于发起问题剩下的让模式去处理。3. 实操落地从零实现一套 context-mode3.1 整体架构与模块划分我实现这套 context-mode 时用的是 Python核心分五个模块各司其职模块职责对应层级collector采集文件、Git 状态、对话记录等原始数据全部normalizer将采集到的原始数据统一成结构化对象全部scorer计算每个上下文片段与当前问题的相关度筛选budgeter按 token 预算分配各层级容量筛选injector将最终选中的上下文渲染进提示词注入模块之间用纯数据接口通信互不依赖。比如 collector 只负责产出统一的 ContextItem 数据对象不关心后续怎么筛选scorer 只负责对 ContextItem 打分不关心数据从哪来。这样每个模块都可以单独替换、单独测试出了问题也好定位。3.2 采集层实现要点采集层是整个 pipeline 的地基。我的 collector 核心逻辑如下from pathlib import Path import os SUPPORTED_SUFFIX {.py, .js, .ts, .md, .yaml, .json, .sql} def collect_project_files(root: str, max_size: int 100_000) - list[dict]: 扫描项目文件返回符合条件的文件列表。 max_size 单位是字节超过 100KB 的文件默认跳过 避免把压缩文件或构建产物误认为源码上下文。 items [] for path in Path(root).rglob(*): # 跳过隐藏目录、虚拟环境、构建产物 if any(part.startswith(.) for part in path.parts): continue if not path.is_file(): continue if path.suffix not in SUPPORTED_SUFFIX: continue size path.stat().st_size if size max_size: continue items.append({ path: str(path.relative_to(root)), size: size, modified: path.stat().st_mtime, }) return items这里有几个细节值得说。第一是文件大小过滤很多人会忽略构建产物问题比如 node_modules、dist、target 这些目录一旦扫进去直接把 token 预算打穿。第二是不要用os.walk硬遍历全盘用Path.rglob配合黑白名单更可控。第三是记录修改时间后续做增量采集和缓存失效判断都要靠它。Git diff 的采集同样重要。我封装了一个函数只取当前未提交的改动不碰历史记录git diff --stat git diff -- *.py *.js *.ts之所以限定后缀是因为有时候文件里的锁文件、生成文件会产生大量无意义 diff把它们过滤掉能显著降低噪声。3.3 相关性打分怎么判断“该看什么”采集到上下文后最核心的一步是计算相关性。我的 scorer 综合考虑三个维度关键词重合度、位置邻近度、操作时间接近度。def score_item(item: dict, query: str, current_file: str) - float: score 0.0 # 1. 文件名与查询词的字符重合度 query_terms set(query.lower().split()) name item[path].lower() overlap sum(1 for term in query_terms if term in name) score overlap * 2.0 # 2. 当前打开文件路径重合度 cur_parts set(Path(current_file).parts) item_parts set(Path(item[path]).parts) common_parts len(cur_parts item_parts) score common_parts * 1.5 # 3. 修改时间接近度越新越高 import time age_hours (time.time() - item[modified]) / 3600 if age_hours 1: score 3.0 elif age_hours 24: score 1.0 return score这个打分函数看着简单但我实际跑下来的效果非常好。原因是它抓住了三个最核心的信号语义相关性、空间邻近性、时间新鲜度。做复杂的关键词嵌入也未必比这三板斧强多少尤其是在没有大量标注数据的情况下。需要提醒的是打分函数的权重得根据不同场景调整。如果是做代码生成修改时间接近度的权重应该提高因为用户大概率在改最近动过的文件如果是做文档问答关键词重合度权重应该提高因为答案往往藏在特定的文档章节里。3.4 注入格式与渲染上下文筛选完最后要解决的是“怎么把内容呈现给模型”。我的做法是给上下文加标签和层级标记让模型明确知道每段信息的类型def render_context(items: list[dict], tier: str) - str: blocks [] for item in items: header f[{tier}] {item[path]} blocks.append(f{header}\n\n{item[content]}\n) return \n\n.join(blocks)渲染后的提示词结构大概是这样的[global] 项目说明 项目基于 FastAPI 构建遵循分层架构... [local] src/services/order_service.py def create_order(...): ... [local] git diff (未提交) def cancel_order(...):在 prompt 的最前面我加了一段说明性文字告诉模型这些是经过筛选的项目上下文优先级从高到低排列如果上下文与当前问题无关可以忽略。加了这句话之后模型的“被干扰”情况明显减少——它学会了主动忽略不相关的上下文而不是被迫逐字处理。4. 核心参数与调优实践4.1 关键参数一览context-mode 在实际运行中涉及不少参数我整理了一张常用参数表每个参数都标注了我在项目里的经验值参数含义经验值说明max_context_tokens上下文最大 token 数8000根据模型窗口自适应tier_global_ratio全局层占比30%给稳定信息留足空间tier_session_ratio会话层占比40%对话历史是核心tier_local_ratio局部层占比30%实时操作信息max_file_size单文件最大大小100KB超限直接跳过top_k_files局部层最多选文件数5防止碎片化summary_threshold会话摘要触发轮数6超过则压缩历史参数之间其实有联动关系。比如top_k_files设得太大每个文件的内容就可能很浅模型看到的信息都是“只言片语”反而不如集中看两三个完整文件。我最终把局部层的文件数限制在 5 个以内保证每个文件都能获得完整展示。4.2 参数计算过程示例我拿一个实际项目举例。某次对话中项目文件扫描出 30 个符合条件的源文件加上 Git diff 和最近 10 轮对话全部攒起来预计要 2 万 token但模型窗口只有 16K还得给生成结果留空间所以我设定 max_context_tokens 为 8000。分配过程是这样的total_budget 8000 # 按比例分配 tier_budgets { global: int(total_budget * 0.30), # 2400 session: int(total_budget * 0.40), # 3200 local: int(total_budget * 0.30), # 2400 }然后对每一层分别做贪心选择按打分从高到低拿文件每拿一个文件先估算它的 token 数——我用的粗估方式是字符数除以 3中文场景偏保守可以除以 2。直到该层预算用完或者文件已经全部选完。实际分配的时候会面临一个权衡是让 3 个文件完整展示还是让 5 个文件各展示一段我的经验是前者。因为一个文件如果只保留了三分之一它内部的函数依赖关系、类结构你是看不全的对模型来说等于看悬疑小说只看到了线索没看到上下文。完整性优先于覆盖度这是我在调参过程中最重要的心得。4.3 会话摘要的兜底策略当对话轮数太多会话层 40% 的预算放不下完整历史时就需要摘要兜底。我用的策略是分段摘要每 3 轮对话生成一次小结新对话继续用完整历史旧对话统一替换成摘要块。def summarize_history(conversation: list[dict], summary_model) - str: 把超过 summary_threshold 的历史对话压缩为摘要。 if len(conversation) 6: return format_history(conversation) old_turns conversation[:-6] recent_turns conversation[-6:] summary_text summary_model.summarize(old_turns) return f[摘要] {summary_text}\n\n{format_history(recent_turns)}这个做法的好处是近期的完整上下文保留久远的历史以压缩形态存在兼顾准确性和容量。但需要注意摘要本身也会损失信息所以我会要求摘要模型保留“用户明确提出的修改要求”和“已经确认的结论”这两类信息丢失了后续会非常被动。5. 常见问题与排查实录5.1 问题速查表做 context-mode 的过程中踩了不少坑我整理了一份问题速查表都是实际遇到并解决的现象根因解决方案响应超时上下文注入过多单次请求 token 超限检查 max_context_tokens降低局部层文件数重要文件总被裁剪打分权重偏差关键词过于宽泛提高位置邻近度权重或手动 pin 文件历史摘要失真摘要模型丢失关键约束信息摘要时明确要求保留“用户明确要求”多用户上下文串了全局层缓存未按用户维度隔离给缓存 key 加 user_id 维度模型重复询问已有信息会话层预算被全局层挤压调整 tier 比例给会话层更多空间采集了不相关文件扫描白名单过宽加强后缀过滤增加文件路径关键词排除这里面最坑的是“多用户上下文串了”。出现这个问题的原因是会话缓存设计的时候只拿了 project_id 当缓存 key忽略了用户维度。结果两个用户同时操作同一个项目后一个用户的问题里带着前一个用户的操作上下文AI 给出的答案是“混合风格”对谁都不对。修复方案很粗暴——缓存 key 加上 user_id同时把采集器里的本地临时文件路径也按用户隔离。5.2 排查思路分享遇到 context-mode 效果不理想时我的排查顺序基本固定先看注入的上下文内容对不对再看上下文是否出现在提示词的正确位置最后才怀疑模型能力。具体操作上我会开一个 debug 模式把最终发送给模型的提示词完整打印到日志里。这一步能解决 80% 的问题。你打印出来一看就明白要么是上下文压根没采到要么是采到了但排序不对要么是注入格式出了问题标签没配对导致模型把代码块当成了自然语言。另一个很实用的技巧是“消融测试”——每次只保留一个层级的上下文看模型输出的差异。如果去掉局部层之后模型回答明显变差说明局部层信息是关键如果去掉全局层模型仍然能答对说明全局层暂时可以压缩。消融测试能帮你精确定位“哪一层的信息在起作用”而不是凭感觉调参。5.3 性能优化记录context-mode 的采集和打分是有性能开销的尤其是大型项目里频繁触发文件扫描会拖慢整体响应。我用三层缓存来解决文件元信息缓存10 分钟过期避免每次请求都重新扫目录文件内容缓存按文件 mtime 判断文件没变直接用缓存内容打分结果缓存相同 query 在 5 分钟内直接复用实测优化后上下文构建耗时从平均 1.8 秒降到了 0.2 秒左右。这组缓存带来的收益非常明显也让 context-mode 从“影响体验的负担”变成了“感受不到存在的基础设施”。6. 几点个人经验与后续扩展最后分享几条在项目里沉淀下来的个人经验这些不是教科书上写的而是实操中一点点试出来的。第一context-mode 的“上下文构建”要追求确定性而不是追求智能。早期我尝试过让 LLM 来自动决定哪些文件重要、哪些文件可以丢弃效果非常不稳定——同一个问题隔了几分钟问模型选文件的逻辑都不一样。后来改成规则 打分函数的方式虽然看起来“笨”但至少每一次的结果都是可预期、可debug的。智能留给模型本身工程部分还是要回归确定性和可控性。第二上下文可视化比任何参数文档都有用。我给 context-mode 加了一个简单的运行时面板显示当前请求注入了哪些文件、每个文件消耗多少 token、哪些文件被裁剪掉了。这个面板上线后团队调试效率提升了不止一倍。很多问题你看参数表看不出来但一看面板就明白——原来全局层的 README 每次都占了 4000 token整整吃掉了一半预算。第三不要一开始就追求“全自动”。我在项目早期把 context-mode 设计成完全自动采集结果用户的控制感很差。后来加了一个手动 pin 功能允许用户把某个文件固定在局部层最顶端所有自动打分都不覆盖它。这个小改动极大提升了工具的可用性。用户信任的是自己能干预的系统不是黑盒。后续再扩展的话我想做两件事一是把打分函数换成小模型embedding加规则回调在保持确定性底线的同时增强语义理解二是把 context-mode 做成跨 IDE 的通用层让同一套上下文策略能服务编辑器、CLI、网页端多个入口。这些方向目前已经在实验阶段进展顺利的话会再单独写一篇分享。做 context-mode 这个项目的最大感受是AI 工具的上限由模型决定但下限往往由上下文管理决定。同一个模型有没有一套好用的 context-mode用起来完全是两个体验。这也是为什么我觉得这个方向值得认真做——它不是炫技而是真正解决用户在真实场景里天天碰到的痛点。