面灵AI→

蔚来 大模型算法 秋招面经:代码智能体仓库级改造与上下文工程

时间
2026-09
来源
牛客网

《面试题目》

  1. 请做一下自我介绍。
  2. 你平时如何使用 AI 编程工具,怎样避免代码「能生成但不能交付」?
  3. 代码智能体进行仓库级修改时,如何构建有效上下文?
  4. 代码检索中,为什么单纯使用向量相似度容易漏掉关键文件?
  5. 项目级代码修改为什么需要先建立符号图,而不是只让模型逐文件搜索?
  6. 如果 AI 编程工具默认输出英文说明,如何稳定控制为中文?
  7. 项目级指令文件中应该写哪些内容,哪些内容不应该写?
  8. 代码智能体执行项目级操作时,如何限制权限和故障范围?
  9. MCP 一类工具协议接入大量工具后,为什么模型的工具选择准确率可能反而下降?
  10. 如何防止代码仓库中的恶意注释对编程智能体进行提示注入?
  11. 代码智能体生成补丁后,如何判断测试通过不是因为它偷偷削弱了测试?
  12. 如何让代码智能体在修改失败后恢复,而不是持续叠加错误补丁?
  13. 多文件补丁如何保证原子性?

《参考解析》

仓库级上下文:先检索,再沿代码图扩展,最后分层裁剪:整仓塞进上下文既贵又稀释信号,正确做法是三段式。第一步用任务里的”硬锚点”做粗召回——符号名、错误码、配置键、表名、报错里的文件路径,走词法检索(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 也吃得越凶,所以先做工具路由,按任务只暴露一个小候选集(代码任务给检索/读文件/跑测试,数据库任务才给查询计划与迁移校验),并把工具描述写成”适用条件 + 禁止条件”,参数尽量用枚举和结构化对象,功能重叠的工具就合并或者加显式路由规则,别指望模型自己分辨。