系统提示词写到几千字之后:让 Agent 迁移 Vue 业务与资产到 React

3 分钟阅读
·

本文是「Agent 开发实践与思考」系列第 3 篇。系列目录:

先别转组件,先找出系统的接缝

一个业务页面通常看起来是一个 Vue 文件,实际至少有四层依赖:

  • 路由和页面层决定入口、参数、权限和页面级副作用。
  • 业务模块层编排接口、状态、表单规则和用户动作。
  • 共享资产层提供表格、表单项、弹窗、选择器等封装,并把一些行为藏在组件内部。
  • 基础设施层提供请求、鉴权、埋点、主题、国际化和构建配置。

直接让 Agent 从页面开始转换,最常见的结果是 JSX 写出来了,但行为散了。原先由表单资产统一处理的必填校验被写回页面,表格资产里的分页参数被改名,弹窗关闭后需要刷新的列表没有刷新。代码能通过编译,不代表迁移完成。

所以第一步不应该是生成 React 代码,而是要求 Agent 产出迁移清单。清单不需要覆盖整个仓库,只需要覆盖一个可交付范围,并把每个对象放进合适的位置:

对象 Agent 要确认的内容 迁移顺序
业务页面 路由参数、接口、用户动作、依赖资产、现有测试 最后
Vue 共享资产 props、事件、插槽、暴露方法、内部副作用、使用方 优先
状态与服务模块 状态所有者、读写入口、缓存和请求边界 与资产并行梳理
样式与主题 class、CSS 变量、全局选择器、组件库覆盖规则 随资产迁移
基础设施 鉴权、埋点、国际化、构建期注入 保持兼容,单独改造

这里有一个容易被忽略的变化:共享资产不能按目录逐个”翻译”,而要先按使用契约迁移。比如一个 Vue 表格组件同时被十个页面使用,先让 Agent 列出每个使用点传入的 props、监听的事件和通过 ref 调用的方法。React 版本的第一目标不是设计得更漂亮,而是能让这些调用点逐步接上。这样迁移页面时,面对的是一个可替换的资产接口,而不是每个页面各自实现一套表格行为。

清单还会暴露不适合自动迁移的部分。依赖动态插槽名、运行时拼装组件、全局事件总线或跨页面缓存的代码,需要人工确认其目标模型。Agent 可以定位和归类它们,但不该把不确定性伪装成一个 useEffect

系统提示词为什么会膨胀

迁移过程中出现的每个失败案例,都会诱使人往系统提示词里加一句补丁。

v-model 要改成受控组件。” “不要修改接口字段。” “迁移后删除 Vue 依赖。” “保留 class。” “弹窗关闭后刷新列表。” 单看都没有问题,问题在于它们混在一起,既没有适用范围,也没有先后关系。

例如,“删除 Vue 依赖”对已完成替换的孤立组件成立,对仍被 Vue 页面使用的共享资产却不成立。“保留样式”也不等于将依赖 Vue 组件 DOM 结构的选择器原样带走。一个规则如果没有说明作用对象和前置条件,Agent 只能在当前上下文里猜。

我会把提示词中的信息分成三类,而不是把它们全部作为常驻指令:

  1. 不变的操作边界:本次允许修改的目录、不能改变的接口和禁止执行的操作。
  2. 当前迁移单元的上下文:页面、资产、调用方、服务模块和已有测试的摘要。
  3. 适用于当前单元的规则:表单、表格、弹窗、路由或状态模块各自的迁移要求。

第一类短而严格,始终放在前面。第二类来自 Agent 对仓库的读取结果。第三类按清单加载,例如迁移表格资产时不带表单校验规则,迁移页面时才带路由和埋点规则。

这让系统提示词从一本包含所有历史经验的手册,变成当前改动的作业单。失败案例也不再只能追加一句自然语言,而是可以归入某类资产或某个检查项。之后同类任务会复用规则,不同类任务不会被无关规则干扰。

让 Agent 先写迁移说明,再改代码

对复杂迁移,我会把一次操作拆成两个阶段。第一阶段只读仓库,不写代码。Agent 要回答四个问题:

  1. 这次迁移的入口是什么,用户从哪里进入并离开这个流程?
  2. 状态由谁创建,哪些组件读取或修改它?
  3. 旧资产对外提供了哪些 props、事件、插槽和 ref 方法?
  4. 哪些行为依赖接口返回、权限、埋点或异步时序?

第二阶段才允许改动。此时 Agent 不必把整份调查报告放进上下文,只需带着一份紧凑的迁移说明:涉及的文件、每个接口的输入输出、不可改变的行为、待确认的空白项,以及迁移顺序。

这份说明的价值不在文档本身,而在于它让每次代码修改都有可追溯的依据。一个 React 弹窗如果要在提交成功后关闭并通知列表刷新,说明里应写清触发来源和消费方。Agent 就不会只在弹窗里加一个本地 setOpen(false),却漏掉原先通过事件或 store 触发的列表更新。

对长任务,这种做法还解决了上下文切换的问题。不同 Agent 或不同轮次不必重新阅读整个仓库,只要先验证迁移说明是否仍与代码一致。说明失效时先更新说明,再继续改代码,比在旧上下文上直接续写更安全。

规则要描述行为,而不是框架语法

“把 Vue 改成 React”只是任务名称,不是规则。真正有用的规则要写清条件、动作和验收依据。

模糊的写法 可检查的写法
正确迁移双向绑定 文本输入使用 valueonChange;状态放在原先拥有该状态的 React 组件或其调用方
注意组件通信 $emit('change', value) 映射为明确的回调 prop;更新所有订阅该事件的调用方
迁移共享组件 先列出旧组件的 props、事件、插槽和 ref 方法;React 版本逐项提供等价入口或标记为需人工确认的差异
保持接口兼容 请求地址、参数名、响应字段和错误处理分支沿用原代码;缺少类型时不猜测字段语义
清理 Vue 代码 仅在仓库搜索确认无引用后删除 Vue 依赖、适配层或旧文件
保留页面行为 对每个用户动作写明触发条件、接口调用、成功后的状态变化和失败反馈,再实施转换

这里的关键是把语法转换降到最后。watch 并不天然对应 useEffectcomputed 也不总该变成 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 参与复杂工程,知识、权限、检查和运行反馈都需要成为它能够读取和使用的工程能力。


573 字 · 68 段落
xi ming

Written by xi ming You should follow him on Github

评论与讨论