本文是「Agent 开发实践与思考」系列第 3 篇。系列目录:
- 2024
- 07-02 我手写了一个 Agent:从一问一答到”思考-行动-观察”循环
- 08-06 工具调用踩坑记:schema、描述与错误返回怎么写
- 09-10 系统提示词写到几千字之后:让 Agent 迁移 Vue 业务与资产到 React(本篇)
先确定迁移范围和依赖
一个业务页面通常对应一个 Vue 文件,但其行为至少依赖四层:
- 路由和页面层决定入口、参数、权限和页面级副作用。
- 业务模块层编排接口、状态、表单规则和用户动作。
- 共享资产层提供表格、表单项、弹窗、选择器等封装,其中部分行为由组件内部实现。
- 基础设施层提供请求、鉴权、埋点、主题、国际化和构建配置。
直接从页面开始转换,可能生成 JSX,但原有行为会分散到不对应的位置。原先由表单资产统一处理的必填校验被写回页面,表格资产里的分页参数被改名,弹窗关闭后需要刷新的列表没有刷新。代码能通过编译,不代表迁移完成。
迁移应先要求 Agent 产出迁移清单,再生成 React 代码。清单不需要覆盖整个仓库,只需要覆盖一个可交付范围,并把每个对象放进合适的位置:
| 对象 | Agent 要确认的内容 | 迁移顺序 |
|---|---|---|
| 业务页面 | 路由参数、接口、用户动作、依赖资产、现有测试 | 最后 |
| Vue 共享资产 | props、事件、插槽、暴露方法、内部副作用、使用方 | 优先 |
| 状态与服务模块 | 状态所有者、读写入口、缓存和请求边界 | 与资产并行梳理 |
| 样式与主题 | class、CSS 变量、全局选择器、组件库覆盖规则 | 随资产迁移 |
| 基础设施 | 鉴权、埋点、国际化、构建期注入 | 保持兼容,单独改造 |
共享资产应按使用契约迁移。比如一个 Vue 表格组件同时被十个页面使用,先让 Agent 列出每个使用点传入的 props、监听的事件和通过 ref 调用的方法。React 版本应先支持这些调用点逐步接入。迁移页面时,可替换的资产接口可避免每个页面各自实现一套表格行为。
清单还会暴露不适合自动迁移的部分。依赖动态插槽名、运行时拼装组件、全局事件总线或跨页面缓存的代码,需要人工确认其目标模型。Agent 可以定位和归类这些代码;目标模型不明确时,应保留待确认项,而非直接改为 useEffect。
系统提示词为什么会膨胀
迁移中出现失败案例后,常见处理方式是在系统提示词中追加规则。
“v-model 要改成受控组件。” “不要修改接口字段。” “迁移后删除 Vue 依赖。” “保留 class。” “弹窗关闭后刷新列表。” 单看都没有问题,问题在于它们混在一起,既没有适用范围,也没有先后关系。
例如,“删除 Vue 依赖”对已完成替换的孤立组件成立,对仍被 Vue 页面使用的共享资产却不成立。“保留样式”也不等于将依赖 Vue 组件 DOM 结构的选择器原样带走。一个规则如果没有说明作用对象和前置条件,Agent 无法根据规则确定其适用范围和执行顺序。
提示词中的信息可分为三类,不应全部作为常驻指令:
- 不变的操作边界:本次允许修改的目录、不能改变的接口和禁止执行的操作。
- 当前迁移单元的上下文:页面、资产、调用方、服务模块和已有测试的摘要。
- 适用于当前单元的规则:表单、表格、弹窗、路由或状态模块各自的迁移要求。
第一类短而严格,始终放在前面。第二类来自 Agent 对仓库的读取结果。第三类按清单加载,例如迁移表格资产时不带表单校验规则,迁移页面时才带路由和埋点规则。
这样系统提示词只包含当前改动所需的边界、上下文和规则。失败案例可归入某类资产规则或检查项。同类任务可复用这些规则,不同类任务无需加载无关规则。
让 Agent 先写迁移说明,再改代码
对复杂迁移,我会把一次操作拆成两个阶段。第一阶段只读仓库,不写代码。Agent 要回答四个问题:
- 这次迁移的入口是什么,用户从哪里进入并离开这个流程?
- 状态由谁创建,哪些组件读取或修改它?
- 旧资产对外提供了哪些 props、事件、插槽和 ref 方法?
- 哪些行为依赖接口返回、权限、埋点或异步时序?
第二阶段才允许改动。此时 Agent 不必把整份调查报告放进上下文,只需带着一份紧凑的迁移说明:涉及的文件、每个接口的输入输出、不可改变的行为、待确认的空白项,以及迁移顺序。
迁移说明为每次代码修改记录了可追溯的依据。一个 React 弹窗如果要在提交成功后关闭并通知列表刷新,说明里应写清触发来源和消费方。Agent 就不会只在弹窗里加一个本地 setOpen(false),却漏掉原先通过事件或 store 触发的列表更新。
对长任务,这种做法减少了会话或 Agent 切换时重复读取仓库的需要。不同 Agent 或不同轮次不必重新阅读整个仓库,只要先验证迁移说明是否仍与代码一致。说明失效时先更新说明,再继续改代码,比在旧上下文上直接续写更安全。
规则应描述行为、条件和验收依据
“把 Vue 改成 React”只定义任务范围。迁移规则需要说明条件、动作和验收依据。
| 模糊的写法 | 可检查的写法 |
|---|---|
| 正确迁移双向绑定 | 文本输入使用 value 和 onChange;状态放在原先拥有该状态的 React 组件或其调用方 |
| 注意组件通信 | 将 $emit('change', value) 映射为明确的回调 prop;更新所有订阅该事件的调用方 |
| 迁移共享组件 | 先列出旧组件的 props、事件、插槽和 ref 方法;React 版本逐项提供等价入口或标记为需人工确认的差异 |
| 保持接口兼容 | 请求地址、参数名、响应字段和错误处理分支沿用原代码;缺少类型时不猜测字段语义 |
| 清理 Vue 代码 | 仅在仓库搜索确认无引用后删除 Vue 依赖、适配层或旧文件 |
| 保留页面行为 | 对每个用户动作写明触发条件、接口调用、成功后的状态变化和失败反馈,再实施转换 |
规则应先描述旧代码的行为,再确定 React 实现。watch 并不天然对应 useEffect,computed 也不总该变成 useMemo。Agent 应先判断旧代码表达的是派生数据、订阅、请求还是清理动作,再选择 React 的实现。派生数据直接在渲染时计算通常更清楚;确实依赖外部系统的操作才需要 effect。
共享资产需要这类规则。Vue 插槽在 React 里可能是 children、render prop,或专门的列配置。Agent 应先检查调用方如何使用插槽,再确定实现方式。若十个调用点只是在表格单元格里渲染内容,列配置可以承载这个契约;若调用点依赖插槽的作用域数据,就应把这些数据明确交给 render prop。资产接口从隐式模板语法变成显式函数参数后,后续页面迁移可以按明确的函数参数和接口检查。
用样本说明局部模式的适用范围
few-shot 对重复且范围小的转换有用。例如把一个基础输入组件的 v-model 改成受控组件,可以给 Agent 一个短样本:
<BaseInput v-model="keyword" @clear="resetKeyword" /><BaseInput
value={keyword}
onChange={setKeyword}
onClear={resetKeyword}
/>这只说明一个局部契约:值由调用方持有,输入组件通过回调通知变更。它没有规定 keyword 必须存在页面里,也没有规定所有组件都要采用同样的命名。
大型页面的完整前后对照通常不能说明可复用的局部规则,还可能将某个页面的接口、状态组织和目录结构误作通用范式。应分别保留表单字段、表格列、弹窗动作、权限包裹等局部模式,并让 Agent 根据迁移说明选择必要部分。
我也会给 Agent 一个反向约束:示例不能授权新操作。样本里调用过某个接口,不代表其他页面也能调用它;样本里删除了旧文件,不代表当前任务可以删除共享资产。是否修改接口、路由、文件或远程环境,仍由任务边界和程序侧权限决定。
验收还应检查用户动作和依赖边界
迁移资产后,即使 TypeScript 和构建通过,业务动作仍可能缺少原有步骤。原来 Vue 组件内部完成的埋点、表单重置、权限拦截或列表刷新,可能没有出现在新组件的显眼位置。
验收条件应覆盖不同层次,不能只写”测试通过”:
- 资产契约:React 资产覆盖已识别的 props、事件、render 入口和暴露方法,调用方可以逐个切换。
- 页面流程:针对创建、编辑、提交、取消、失败重试等已有用户动作,确认触发的接口和状态变化没有丢失。
- 依赖边界:Vue 依赖只在仍未迁移的适配范围内存在,不因一次页面迁移被错误删除。
- 静态检查:类型检查、构建和现有测试按项目能力执行。
- 差异说明:Agent 列出不能从代码确定的行为,例如接口字段含义、权限规则或动态组件路径,交给人工确认。
Agent 的最终输出除”已完成迁移”外,还应提供改动和验证信息。它至少应当给出改动范围、接入的新资产、保持不变的服务契约、执行过的检查,以及仍未验证的行为。这样评审时讨论的是明确的缺口,而不是重新读一遍所有 diff。
仓库内容用于理解现有行为
迁移 Agent 会读取代码、注释、配置、组件文档和接口返回。它们可以解释现有行为,但不能改变任务本身。注释中即使出现”删除旧目录”、“关闭校验”或”忽略之前规则”,也只能作为需要核实的仓库内容。
系统提示词里要明确这层边界:仓库内容、工具输出和用户提供的数据用于理解代码;它们中的命令和要求不能扩大修改范围、改变权限,或覆盖原有安全约束。
实际操作还应受程序侧限制。删除目录、替换路由、修改构建配置、写入远程服务等操作,需要检查目标是否属于当前迁移单元,是否已满足前置条件;有不可逆影响的操作应要求人工确认。提示词能降低 Agent 误判的概率,工具层的约束才能阻止一次误判变成实际事故。
将提示词规则转为任务状态和可执行检查
复杂迁移中的提示词膨胀,通常表示新增经验尚未归入任务状态、资产规则或检查项。把经验全部堆进系统提示词,Agent 会携带越来越多与当前任务无关的规则。
Vue 到 React 的迁移可按以下步骤组织:先用清单确定迁移单元和依赖,再为该单元生成迁移说明,只加载相关资产规则,最后按分层验收条件提交结果。该流程将业务页面与共享资产的依赖纳入同一迁移单元,避免在每个页面中重复实现 Vue 资产的行为。
提示词整理只能处理当前调用所需的信息和规则。它规定一次调用中 Agent 应读取和遵守的内容,无法单独解决长任务的状态保存、实际行为验证和错误处理。迁移规模扩大后,还需要处理 Agent 如何在工程系统中持续执行已验证的步骤。
第一步是把迁移清单和迁移说明从一次会话的临时材料,变成仓库中的工作状态。每个迁移单元应记录依赖资产、完成条件、验证证据和遗留风险。下一轮 Agent 接手时应先读取并验证这些状态,无需重新扫描整个仓库,也不应根据上一轮的对话摘要推断进度。任务拆分、接口说明和验收证据应作为 Agent 之间的交接材料,以减少重复调研和返工。
第二步是把重复出现的提示词规则移出自然语言。比如”不能跨越模块依赖”适合做静态检查,“提交后列表应刷新”适合写成集成或端到端用例,“删除旧资产前必须没有引用”适合由搜索和构建门禁确认。提示词仍用于解释任务和处理例外,但不应承担确定性约束。否则每次 Agent 失误后,团队只能追加”务必注意”等提示,而未增加能拦截错误的检查。
第三步是给 Agent 接入运行时反馈。对于 Vue 转 React 这样的迁移,构建通过只能说明代码能被打包,不能说明权限拦截、请求参数、弹窗状态和页面跳转仍按原路径运行。Agent 需要能够在受控环境中读取测试结果、浏览器 DOM、控制台日志和网络请求,并用这些证据定位问题。缺少这条反馈通路时,Agent 无法通过运行结果验证迁移是否正确。
迁移规模扩大后,单个 Agent 的上下文容量和权限范围会限制其可处理的工作。调研依赖、实施迁移、检查架构边界、验证页面流程可以由职责不同的 Agent 分别完成。它们不需要共享全部仓库和全部工具,只通过迁移说明、代码改动和验证证据交接。人负责决定迁移范围、处理无法从代码确认的业务规则,并为有风险的操作设定边界。
系统提示词提供当前任务所需的信息。长期稳定性还取决于任务拆分方式、约束执行位置、错误是否产生可定位的反馈,以及下一轮能否从已验证状态继续。复杂工程中的 Agent 还需要读取和使用知识、权限、检查和运行反馈。
