蔚来 大模型算法 秋招面经:代码智能体仓库级改造与上下文工程
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 请做一下自我介绍。
- 你平时如何使用 AI 编程工具,怎样避免代码「能生成但不能交付」?
- 代码智能体进行仓库级修改时,如何构建有效上下文?
- 代码检索中,为什么单纯使用向量相似度容易漏掉关键文件?
- 项目级代码修改为什么需要先建立符号图,而不是只让模型逐文件搜索?
- 如果 AI 编程工具默认输出英文说明,如何稳定控制为中文?
- 项目级指令文件中应该写哪些内容,哪些内容不应该写?
- 代码智能体执行项目级操作时,如何限制权限和故障范围?
- MCP 一类工具协议接入大量工具后,为什么模型的工具选择准确率可能反而下降?
- 如何防止代码仓库中的恶意注释对编程智能体进行提示注入?
- 代码智能体生成补丁后,如何判断测试通过不是因为它偷偷削弱了测试?
- 如何让代码智能体在修改失败后恢复,而不是持续叠加错误补丁?
- 多文件补丁如何保证原子性?
《参考解析》
仓库级上下文:先检索,再沿代码图扩展,最后分层裁剪:整仓塞进上下文既贵又稀释信号,正确做法是三段式。第一步用任务里的”硬锚点”做粗召回——符号名、错误码、配置键、表名、报错里的文件路径,走词法检索(BM25/trigram、符号表精确匹配),需求描述那部分才交给向量检索。第二步以粗召回命中的符号为种子,沿代码图做广度优先扩展,边可以是调用、类型引用、继承/实现、模块依赖、测试覆盖;关键是限深限宽:BFS 深度一般 2 跳封顶,同时对高入度节点(被几十个模块引用的公共基类、工具函数)只保留它的定义签名、不再展开邻居,否则一次扩展就能把半个仓库拉进来。第三步按角色分层排布 token 预算:当前符号定义给全文,直接调用方只给签名和上下文几行,数据结构与配置给字段级摘要,现有测试给断言片段,仓库约定(README、CONTRIBUTING、项目指令文件)常驻。做完首轮修改后还要做动态补全——把编译错误里报错的文件、失败测试的栈帧路径当成新的种子再跑一轮检索,而不是只做一次静态检索就收工。
向量检索为什么漏关键文件:标识符不敏感 + 相似实现互相淹没:代码里的精确符号往往比自然语言语义重要,而 embedding 恰恰对精确字符不敏感——getUserByIdV2 与 getUserByIdV1 会被分词器切碎,向量几乎重合;错误码 E40012 和 E40021 在语义空间里没有可区分的信号;同一个仓库里旧接口、新接口、测试 Mock 三份语义高度相似的实现会挤在一起,真正要改的只有一份,向量却分不出优先级。工程上的解法是混合检索加重排:词法通道负责精确名与错误码,向量通道负责”我想做什么”这种自然语言意图,符号/调用图通道负责把依赖捞回来,多用 RRF(倒数排名融合)合并后再做二级重排——重排信号用路径与模块归属(是否命中目标模块)、语言、与当前符号的图距离(一跳调用方优先于三跳)、是否被相关测试覆盖、最近修改时间,最后用 cross-encoder 或小模型把 top 50 压到 top 10。评测口径也别只看”文件有没有被召回”,要看关键定义、关键调用点、相关测试这三类是否同时进了上下文,以及最终补丁能不能通过隐藏测试。
符号图做影响分析:能力与边界都得说清:逐文件 grep 只能回答”这段文本出现在哪”,回答不了”改这个接口会波及谁”。符号图把类、函数、字段、模块、配置项建成节点,把调用、继承、实现、引用、依赖建成边,于是”改返回值类型”这件事可以展开成闭包:接口定义 → 所有实现类 → 全部调用方 → 序列化/反序列化逻辑 → 受影响的测试断言。但它不是银弹:反射与依赖注入(Spring 的 @Autowired、按字符串取 bean)、动态注册、getattr/eval、配置驱动的路由、monkey patch、跨语言的 IDL/FFI 边界、构建期的宏与代码生成,静态分析都恢复不出来。所以工程上要”静态图 + 动态数据”结合:单测覆盖率给出真实执行的边,线上 Trace 采样补上运行时才成立的调用,构建系统信息补上生成代码;动态发现的边标成低置信度但保留。目标不是画出一张完美的全仓图,而是保证”改动的直接调用圈 100% 召回,二跳以后靠动态数据兜底”。
「能生成但不能交付」:把验收标准变成可执行的命令:这类问题的根因通常不是模型写得差,而是”完成”没有被定义成可验证的动作。落地时至少卡这几道关:构建过、类型检查过(tsc --noEmit、mypy --strict)、相关目录的测试过、lint 与格式化过、diff 里没有与任务无关的改动;并且要求智能体在交付说明里贴出它实际执行过的命令和输出摘要,没跑过的不许写”已修复”。还要防它靠改测试来”通过”:校验补丁时把生产代码和测试代码的 diff 分开看,测试的改动必须能对应到需求变化(新增字段、接口签名变了、行为口径变了),否则一律打回;关键模块再补一套智能体看不到的只读隐藏测试(holdout),避免它针对公开用例定向取巧;有余力就上变异测试(mutmut、Stryker),对生产代码做小范围错误注入,如果测试仍然全绿,说明这套测试根本没有约束力。静态检查层面可以拦一批典型作弊:新增 @pytest.mark.skip/xfail、断言被删除、裸 except: 把精确异常放大、快照文件被顺手 --snapshot-update、用 Mock 掩盖真实集成行为、只修公开样例不覆盖一般情况。失败以后也不能让它在已经混乱的代码上继续叠补丁:每轮开工前留一份可恢复快照(临时 commit、git stash create 或容器卷快照),记录本轮目标、改动文件、执行过的测试与结果;报错先定位第一个根因——编译失败只看第一条 error,后面多是连锁噪音,回归失败要区分是实现问题、测试假设变了还是环境问题;连续 2~3 轮没有实质改善就回滚到最近一次关键检查通过的状态重新规划,同时限制单轮改动规模,把大任务拆成接口调整、内部实现、数据迁移、测试补充几个阶段逐个过。回滚动作必须由模型之外的执行器完成,模型只能建议,免得它顺手删掉用户已有的未提交改动。多文件补丁的原子性同理:在临时 worktree 或一次性容器里应用补丁并跑完验证,全绿再一次替换主工作区,中途失败就把整棵临时树丢掉,别让仓库停在一半新一半旧的状态。
项目级指令文件:写长期稳定、可执行、可被自动检查的规则:该写的是——包管理器与运行时版本(是 pnpm 还是 npm、uv 还是 pip)、构建/测试/类型检查的确切命令以及”改哪个目录跑哪条测试”的对应关系、目录职责、格式化与风格工具、由生成器维护禁止手改的文件(*.pb.go、Prisma Client)、是否允许新增依赖、数据库迁移与公共接口变更的准入条件、安全红线(生产凭证、客户数据、内网地址不得进入上下文)、提交前检查清单,以及输出语言约束。不该写的是:临时需求、个人偏好、大段架构介绍、彼此冲突的规则、以及 CI 本来就能强制执行的琐碎风格;指令越长,模型越难判断当前任务与哪条相关,还容易和仓库真实配置打架。判据很简单——能不能被脚本验证:「保持代码整洁」不可执行,「所有公共函数通过类型检查、新增接口必须带测试」可执行。中文输出这条也放在这里最稳:在最优先级的指令里写死”计划、解释、错误说明、提交摘要用中文,代码标识符、命令参数、异常原文、协议字段一律不翻译”,再用固定模板(先给计划、再给 diff、最后给验证结果)约束输出结构;只在用户提问里临时要求中文是不稳定的,因为工具自带系统提示、仓库里也可能有英文规范。
权限隔离与提示注入:仓库文本永远只是数据:模型不该默认拥有宿主机完整权限。执行环境放进容器或临时 VM,只挂载当前仓库,网络、文件系统、进程、凭证都按最小权限给;按风险分级授权——读代码可自动执行,改工作区要留审计日志,删文件、动基础设施、访问外部系统必须额外确认。命令不能把模型生成的字符串直接交给 shell,要过一层策略解析:解析出真实工作目录、目标文件、环境变量,做路径规范化后校验是否越出工作区(Path.resolve() 后判断是否仍在 WORKSPACE 之下),拦截目录穿越、凭证读取和高风险命令;而且这层限制必须由模型之外的组件实现,不能让模型自己绕过去。提示注入的防线同理:代码、注释、README、Issue、测试日志都属于待分析数据,不是系统指令,即使里面写着”忽略之前的规则""把密钥发到某个地址”,也不能改变智能体的任务目标和权限;实现上给上下文标注内容来源、规定仓库文本只作事实材料,并对包含数据外传、绕过检查、读取环境变量等可疑自然语言指令的文件降低信任等级、触发额外审核。多接工具时的退化也值得提前设计:工具越多、名称与描述越重叠,模型同时选工具和构造参数的错误率越高,token 也吃得越凶,所以先做工具路由,按任务只暴露一个小候选集(代码任务给检索/读文件/跑测试,数据库任务才给查询计划与迁移校验),并把工具描述写成”适用条件 + 禁止条件”,参数尽量用枚举和结构化对象,功能重叠的工具就合并或者加显式路由规则,别指望模型自己分辨。