
1. 读代码这件事Agent 和人是两个物种先说个我最近的真实经历。接手了一个前后端分离的电商后台项目线上反馈“商品列表页点了筛选没反应”看着很简单的一个 bug结果我在浏览器 DevTools、前端代码和后端日志之间来回切了半个多小时筛选按钮确实触发了请求但请求参数里少了价格区间后端接口也报了个 400但前端异常处理把这条错误吞了只弹了个“请稍后重试”。如果这时候手头有一个能读懂前后端代码、并且能沿着“用户点击 → 前端事件 → HTTP 请求 → 后端处理 → 数据返回”这个调用链一路查下去的 Agent这类问题也就是一两分钟内的事。这篇文章要聊的就是给 Agent 补上这种能力让 Agent 拥有阅读前后端代码的能力并能沿着用户操作去定位调用链中的每一环。很多人第一次听说这个需求时第一反应是“把代码喂给 Agent 不就行了”。但实际跑过就知道这条路根本走不通。我上一期提到过 Agent 的上下文窗口和推理深度是有限的把整个仓库的代码一次性塞进去结果往往是两件事同时发生Token 很快烧完关键调用关系被海量无关代码冲淡。这里面的核心差异在于——人会“扫描”代码Agent 只能“检索”代码。1.1 人类开发者排查问题的习惯路径我自己排查前后端问题时的常规套路是复现操作打开 DevTools 看 Network 请求是否有异常、请求参数对不对。看控制台报错从堆栈信息点进源码位置。顺着报错位置向上下游扩展比如看是哪个函数发起请求的、后端哪个接口处理这个请求。翻日志查接口入参、异常堆栈定位到 Service 层逻辑。这套流程的本质是以“报错信息”或“异常现象”为锚点向调用链两端扩散。人眼扫代码是并行的可以直接浏览整个函数体、相邻文件靠经验和直觉判断哪里可疑。但我们做不到把整个系统“一次性预读”本质上是边查边学。1.2 Agent 阅读代码的“天生存款”与“天生缺陷”Agent 的强项是给一段上下文就能快速推理多个文件的关键片段放进同一个窗口时可以准确找出它们之间的调用关系。这比人强得多——人切三四个文件之后工作记忆就开始不够用了。Agent 的弱项也很明显没有“全局视图”。它不能像人一样扫一眼目录结构就知道“这个页面文件引入了个公共组件公共组件里又调了 utils 里的请求方法”。如果喂给它的代码缺少了某个中间文件它的推理就会断裂甚至开始编造一个不存在的调用链。所以让 Agent 阅读前后端代码这件事本质上不是“把代码给它”而是建立一套代码检索与上下文拼装的方法。当我解决完这个问题之后整个排查效率提升非常明显后面会结合我实际跑的一个场景把方法完整拆开讲。先记住一个结论Agent 适合的阅读模式是“先地图、再路径、最后进函数体”这和人的“老板键一开、全仓搜索”是完全不同的策略。2. 先让 Agent 认路仓库地图的构建方法给 Agent 读代码的第一步从来不是读具体的业务代码而是先把仓库结构喂给它。这一步做不好后面全是空中楼阁。2.1 为什么必须显式构建“代码地图”我在刚开始做这个功能时踩过一次坑我直接把项目里一个核心页面组件和对应的 Controller 文件丢给 Agent让它分析“从点击到接口的调用链”。结果它把 Vue 组件里一个import { getList } from /api/product当成了直接发 HTTP 请求的函数还一本正经地解释了 axios 的封装逻辑——但实际上/api/product这个文件里的getList只是调用了一个request()封装方法真正的 URL 前缀在别的地方配置。这个问题的根源是Agent 缺失了中间层的文件信息。它看到了组件的调用点和 API 模块的导出点但不清楚这一层的函数签名、baseURL 拼接规则以及拦截器做了什么。所以我现在实践中第一件事永远是把项目的“地图”构建好。这一步我用两种方式对小型项目直接让 Agent 解析配置文件package.json、vite.config.js、pom.xml等。对中大型项目先跑一遍目录树生成命令把关键目录和文件名列表给 Agent让它先标注出“这可能是一条调用链上的节点”再按需读取具体文件。推荐使用tree命令macOS/Linux 自带Windows 可以用tree /Ftree src -L 3 --dirsfirst tree src/main/java -L 4 tree src/main/resources -L 32.2 前后端分离项目的“地标文件”清单每个项目架构不同但前后端分离项目通常有比较固定的“地标文件”。我把它们整理成一张清单实际构建 Agent 的仓库地图时会按这个清单逐个确认。端地标文件作用前端src/main.js/main.ts应用入口挂载根组件注册全局插件如 router、pinia前端src/router/路由配置页面路径与组件的映射关系调用链的“页面入口”前端src/api/或src/services/所有对后端的 HTTP 请求定义处前端src/utils/request.js/http.jsaxios 或 fetch 的封装baseURL、拦截器、错误码转换都在这后端pom.xml或build.gradle依赖和模块结构能看出是否包含 Web、MyBatis、Redis 等后端application.yml/application.properties端口、数据源、Redis、MyBatis 配置等后端controller包请求入口URL 与方法的映射后端service包及其实现业务逻辑事务边界后端mapper包 resources/mapper/*.xml数据访问SQL 语句就在 xml 里这组清单的构建目的很明确让 Agent 知道“什么类型的代码应该出现在哪个位置”。比如它看到一个RestController注解就应该知道这是 Controller 层它的职责是参数接收和数据返回不应该在这里纠结复杂的业务条件逻辑。有了这层“先验结构”后续阅读代码时的推理准确率会高非常多。2.3 构建地图时用到的 Prompt 参考以下是一个我常用的“地图构建 Prompt”你可以根据项目调整你现在是一个全栈代码分析专家。下面是一个前后端分离项目的目录结构树状图 [在这里粘贴 tree 输出] 请完成以下任务 1. 识别前端入口文件、路由配置文件、API 请求定义目录。 2. 识别后端请求入口Controller 包、业务逻辑层Service 包、数据访问层Mapper/DAO 包。 3. 指出可能存在全局配置的文件如 axios 封装、请求拦截器、数据库配置。 4. 输出一个“代码地图”包括每个关键文件的路径和一句话说明。 注意这一步只做地图标注不要进入具体业务代码分析。这一步跑完Agent 已经知道了“哪些文件是调用链上的必经站点”。但它仍然不知道每个站点内部长什么样所以下一步是分别从前端侧和后端侧把链路铺开。3. 前端那半条链从用户操作到 HTTP 请求“沿用户操作查调用链”这句话里最难的一个词是“沿”。要让 Agent 具备这种追踪能力前端代码的阅读必须从“用户做了什么”这一具体动作开始。3.1 常见的事件绑定形态与对应检索法浏览器端的用户操作落到代码层面就是事件。我整理了三种最常见的绑定方式Agent 阅读时需要根据特征做不同的检索绑定方式代码特征检索要点模板指令VueclickhandleSubmit、v-on:keyup.entersearch直接在标签上查事件名再跳转到对应方法定义事件监听ReactonClick{handleSubmit}、addEventListener(click, handler)查组件里的处理函数属性或addEventListener回调原生绑定document.getElementById(btn).onclick ...查 DOM 操作代码中的onclick、onchange赋值对于前后端分离项目Vue 和 React 占绝大多数。前端 Agent 分析时我一般让它执行一个三步检索找到目标页面文件比如ProductList.vue/ProductList.jsx。在文件内定位用户操作的绑定位置得到处理函数名。跳到处理函数定义处看它内部做了什么——尤其是是否调用了api模块导入的方法。这一步的关键经验是不要让 Agent 直接从页面文件“跳”到 api 模块而是强制它先读处理函数体内的全部代码。因为处理函数里通常还有参数组装、 loading 状态处理、错误捕获、甚至多个分支调用了不同接口。跳过了这个函数体就等于跳过了整条链路上最关键的一环。3.2 确认 HTTP 层的真实请求目标拿到 api 模块的方法之后还不能急着下结论。因为api/product.js里的方法通常只是封装了一层export function getProductList(params) { return request({ url: /product/list, method: get, params }) }这里的/product/list是一个相对路径。实际请求的完整 URL 取决于request.js里的baseURL。有些项目还会通过环境变量区分开发/生产接口前缀比如VITE_API_BASE_URL。所以我在给 Agent 的阅读指令里会额外注明在分析 HTTP 请求时必须找到 request 封装文件说明 baseURL、请求拦截器、响应拦截器都对这条请求做了什么处理再给出当前环境下的完整请求 URL。别小看这一步。我曾经碰到过一个案例前端代码里明明请求的/product/list后端路由也是/product/list但就是 404。结果排查下来发现响应拦截器里对 401 做了静默刷新 token 的逻辑刷新失败后把原本的请求跳到了登录接口Agent 如果不读拦截器根本解释不了这个行为。3.3 前端侧容易被忽略的“伪调用”有三种情况经常让 Agent 在前端分析时得出错误结论你在实际使用时要特别注意防抖/节流函数clickdebouncedSubmit绑定的实际是一个包装函数真正的提交函数在debounce内部。Agent 如果只看绑定处会以为用户每次点击都会立刻触发请求。动态路由参数比如router.push({ path: /detail/ id })Agent 需要去路由配置里查:id参数名才知道后端 URL 里的最终形态。组件的二次封装页面模板里用的是BaseTable operationhandleOp /但BaseTable内部可能对它自己的按钮做了事件转发。分析时必须往子组件里再走一层。针对这些情况我的处理办法是给 Agent 增补一条规则当事件绑定函数与业务方法名不一致或者出现$emit、nextTick、debounce、throttle这类包装时不要直接跳结论而是继续追问“这个函数从哪来、被谁持有、最终执行了什么”。4. 后端另外半条链从接口入口到数据落点前端追踪到完整请求 URL 之后调用链就进入到了后端。后端这半条链的结构相对前端更规整但层数更深也更容易让 Agent 在抽象接口和具体实现之间迷路。4.1 Controller 层的快速定位与参数解析后端的入口很好找在 Controller 类上根据前端请求的 method 和 URL 匹配GetMapping、PostMapping、RequestMapping等注解。这里容易出问题的不是定位而是请求参数与入参对象的映射。一个典型的 POST 接口PostMapping(/product/list) public ResultListProductVO list(RequestBody ProductQuery query) { return productService.listByCondition(query); }Agent 需要明白几件事RequestBody会把请求体 JSON 反序列化成ProductQuery对象query的属性名和前端传的参数字段必须一致如果不一致后端接收到的有可能是 null 或者直接 400。所以在后端入口这一步我给 Agent 的任务清单是找到匹配的 Controller 方法。列出方法的注解URL、HTTP Method、参数绑定方式。如果参数是对象类型读取该对象的字段定义并与前端的请求参数做逐一对照。4.2 Service 层才是逻辑分叉的重灾区很多 Agent 追踪调用链时有个偷懒的倾向从 Controller 一层直接跳到 Mapper把中间的 Service 层略过。这在大促活动这类复杂业务场景里是致命的因为 Service 层通常包含条件分支比如当query.getType() 1时走 A 查询否则走 B 查询。事务控制Transactional范围里可能调用了多个 Mapper 方法。外部服务调用比如查完数据后调用了 Redis 缓存、消息队列或者第三方接口。我遇到过的一个典型案例是用户点击“导出报表”前端调/report/export后端 Controller 的export方法看起来就是调用了reportService.export()。但export()里其实分了两大步先查数据库列表然后异步调文件生成服务。如果 Agent 只看 Controller 和 Mapper会以为导出结果直接来自数据库查询完全解释不了“为什么接口返回了成功、文件却迟迟没生成”这种现象。所以在后端链路这一段我的 Agent 指令会强制要求展开 Service 层的所有分支而非只读取主路径。展开方式在 Service 方法中如果存在 if/else、switch、forEach、try-catch 等分支结构请逐条列出每个分支下调用的方法或 Mapper 操作并标注该分支由什么条件触发。这样一来调用链就从单一路径变成了一棵条件树后续排查问题时会非常有用——用户的操作参数差一个值走的可能就是完全不同的代码分支。4.3 Mapper 层与 SQL 的对应关系最后一段是数据访问层。Java 项目里 MyBatis 最常见的两种形态注解 SQL 和 XML SQL。我遇到过 Agent 只看了 Mapper 接口没看 XML 文件导致误判表名字段的情况。比如接口上只写着ListProduct selectByCondition(Param(query) ProductQuery query);真正的 SQL 在resources/mapper/ProductMapper.xml里。所以我给 Agent 的规则是凡是 Mapper 接口方法没有直接写 SQL 注解的默认需要去同目录或 resources 下查找同名 XML 文件并在分析结果中附上完整 SQL 及涉及的数据库表。数据层还有一个容易忽略的点多表关联。比如查询商品列表时SQL 里LEFT JOIN category把分类名称也查出来了。Agent 如果不关注到这一点会以为分类名是独立请求获取的从而在“调用链”里多画一条不存在的线。这也是为什么我要求 Agent 把 SQL 里的 JOIN 表都要列出来与前端展示字段做交叉比对。5. 沿用户操作追溯调用链的完整流程与 Prompt 设计到这里前后端两半条链的阅读能力都有了。真正要解决“沿用户操作查调用链”还需要把它们拼成一个连贯的追踪流程。我在实际操作中总结了一套“追踪七步法”每次排查问题都按这个顺序让 Agent 执行稳定性和准确率比自由发挥高很多。5.1 追踪七步法步骤动作产物1复述用户操作路径操作步骤清单如“打开列表页→点击筛选→选择价格区间→点击查询”2定位前端事件绑定页面文件路径 事件绑定代码3展开事件处理函数函数体代码 参数组装逻辑4定位 HTTP 请求定义api 模块方法 request 封装处理5匹配后端 ControllerURL、Method、参数绑定方式6展开 Service 业务逻辑分支条件树 事务边界 外部调用7落到 Mapper/SQLSQL 语句、涉及表、返回字段这套流程最妙的地方是每一步的“产物”都会成为下一步的“输入”。Agent 不会跳跃式推理每一步都有前一步的代码片段作为依据大大降低了幻觉概率。5.2 一个真实场景的全链路追踪演示我用一个实际的“商品筛选”场景来说明整个追踪过程。用户操作打开商品列表页在筛选区选择了分类“手机”和价格区间“3000-5000”点击查询按钮页面无数据返回。按照七步法Agent 的分析输出是这样的步骤 1 产物用户操作 点击“查询”筛选条件 { categoryId: 101, priceMin: 3000, priceMax: 5000 }。步骤 2 产物src/views/ProductList.vue第 88 行button clickhandleSearch。步骤 3 产物const handleSearch () { loading.value true queryParams.value { categoryId: filterForm.categoryId, priceMin: filterForm.priceMin, priceMax: filterForm.priceMax } fetchList(queryParams.value) }步骤 4 产物src/api/product.js中fetchList调用request({ url: /product/page, method: post, data })request 封装中 baseURL 为/api响应拦截器会在 200 时直接返回res.data.data。步骤 5 产物后端ProductController.page(RequestBody ProductPageQuery query)匹配POST /api/product/page。步骤 6 产物ProductServiceImpl.page()里有个条件分支当query.categoryId ! null时extraSql AND category_id #{categoryId}当priceMin不为空时extraSql AND price #{priceMin}。步骤 7 产物最终 SQL 里WHERE status 1 AND category_id 101 AND price 3000 AND price 5000关联表product、category。如果页面无数据此时就可以直接对比 SQL 结果是筛选条件本身没有匹配数据还是前端参数没传给后端还是 SQL 拼接漏了条件。整条链路一目了然。5.3 调用链追踪专用 Prompt 模板下面是我这几个月反复修改后稳定下来的 Prompt 模板它结合了前四节的单侧分析能力与追踪流程。请作为一个全栈代码分析 Agent按照下面七步对用户操作进行调用链追踪 1. 复述用户操作路径 2. 定位前端事件绑定位置先查页面模板中的事件指令 3. 展开事件处理函数梳理参数组装逻辑 4. 定位 HTTP 请求定义并说明 request 封装层baseURL、拦截器对请求的影响 5. 匹配后端 Controller 方法说明 URL、HTTP 方法、入参绑定方式 6. 展开 Service 层逻辑列出所有条件分支和调用的下层方法 7. 追踪到 Mapper/SQL说明查询涉及的表与字段 规则 - 每一步都必须给出具体文件路径和代码行号。 - 不得跳过任何一步直接给出最终结论。 - 遇到分支结构时按条件列出不同路径。 - 如果某一步无法在代码中找到对应实现必须明确标注“未找到”不得猜测。用好这个模板的关键是不要在一个 Prompt 里把七步全做完。我的经验是分两轮第一轮让 Agent 走步骤 1-3只输出前端结果第二轮把前端结果作为上下文再让 Agent 走步骤 4-7。这么做的原因是如果七步强制性全部连贯执行Agent 很容易在很长的分析过程中“迷失重点”早早就忘记了第一步用户操作的具体参数。分轮执行后Agent 每一步都聚焦在当前任务上输出质量明显提升。6. 这套方案的实际效果、踩坑记录与调优心得方法讲完了说说实测效果。我在一个模拟真实电商后台的前后端分离项目Vue3 Spring Boot MyBatis上跑了这套方案模拟了 20 条不同难度的调用链追踪请求包含正常链路、参数错误、分支选错、请求被拦截器等场景。6.1 数据对比追踪准确率从 40% 提到了 85%最开始我用的“一次性全部代码丢给 Agent”的方式20 条场景只对了 8 条准确率 40%。原因五花八门上下文太长导致 Agent 在开头就忘了用户操作参数中间跳过了 Service 层导致分支判断全错还有两次是 Agent 编造了不存在的文件路径。改成“仓库地图 前端追踪 后端追踪 七步法分轮执行”的组合后20 条场景对了 17 条准确率 85%。剩下 3 条错误出在两个地方一条是动态生成 SQL 的 XML 里包含script标签动态条件Agent 读 XML 时没有识别出来另一条是前端组件里使用了递归组件事件绑定的实际来源在父组件Agent 没追上去还有一条是代码里同时存在新旧两套接口Agent 匹配错了 Controller。6.2 我总结的四个关键调优点如果你要复刻这套方案有四个细节我强烈建议你在搭建时就考虑进去。第一代码片段必须有“来源标注”。只是把代码贴给 Agent 远远不够。我在每次注入代码片段时都会在片段前面加上file: src/views/ProductList.vue这样的文件路径标注并且告诉 Agent“这一步只针对该文件的第 80-100 行”。这能显著降低 Agent 引用错文件的概率。第二大文件要按函数切块而不是按文件切块。有些 Vue 单文件组件能超过 800 行一个页面里包含五六个业务函数。如果直接整个文件怼进去Agent 在处理其中一个函数时注意力很容易被同文件里的其他函数干扰。我现在用的策略是先用 grep 或 Agent 的代码定位能力找到目标函数起始行号然后只截取该函数体及与其直接相关的模板片段。第三后端链路必须检查事务注解。Transactional这类声明式事务的边界直接决定了多个 Mapper 操作是否在一个连接里执行。Agent 在分析 Service 层时应该明确读一下方法或类上的事务注解。否则它会把“先 insert 再 update”两个操作当成独立的一旦第二个失败回滚就解释不了为什么数据库里没有第一条数据。第四正则表达式和通配符匹配要提前排查。Spring MVC 的路径匹配在有些项目里不是纯粹的字符串相等比如RequestMapping(/product/**)这类通配写法。如果 Agent 只会精确匹配 URL碰到通配就会漏掉真正的 Controller 入口。我在注入后端代码地图时会把所有 Controller 类的类级RequestMapping单独列一张表让 Agent 在做 URL 匹配前先看过一遍这张表。6.3 最后一个让调用链真正可解释的小技巧追踪的结果如果只是告诉用户“问题出在 X 文件”价值就折半了。我让 Agent 输出的最终结论必须是一条带路径的链式结构像这样用户点击查询按钮 → ProductList.vue:88 clickhandleSearch → ProductList.vue:102 handleSearch 组装 queryParams → src/api/product.js:31 fetchList → request({ url: /product/page }) → request 封装层baseURL 为 /api响应拦截器透传 data → ProductController.page PostMapping(/product/page) → ProductServiceImpl.page() 第 45 行条件拼装 categoryId → ProductMapper.selectPageXMLProductMapper.xml 第 12 行 → SQL: SELECT ... FROM product WHERE status1 AND category_id101 ...这条链式输出有两个作用。一是用户拿到路径后可以快速验证哪一步的参数和实际行为对不上问题点自然浮现。二是如果 Agent 推理错了这条链是透明的你一眼就能看出它是在哪一环“掉链子”的而不是拿到一个无法核实的黑盒结论。我自己在用这套方案做 Agent 日常巡检时最大的体感是排查问题从“问 Agent 问题”变成了“让 Agent 走链路”。后者不需要它有多强的临时推理能力只需要它严谨地按图索骥把每一步的真实代码摊开调用链自然就出来了。你如果正在给自己的 Agent 补“读代码”的能力我建议先别急着上大模型推理框架把仓库地图、分端阅读、七步追踪这三层基础打好后面的路会顺很多。