新闻详情

新闻详情

首页 / 资讯中心 / 详情

我把一个 AI 编程 Prompt 推翻了很多轮,最后才明白:Prompt 不是让 AI 写代码,而是防止它绕过架构

发布时间:2026/9/26 1:38:44来源:尧图网络
我把一个 AI 编程 Prompt 推翻了很多轮,最后才明白:Prompt 不是让 AI 写代码,而是防止它绕过架构
最近一直在折腾自己的开源项目AutoPilot-Test。这是一个基于 AI 的智能 Web UI 自动化测试平台核心链路是环境配置 ↓ 元素抓取 ↓ Excel 用例导入 ↓ AI 代码生成 ↓ 可视化执行 ↓ 失败自愈 ↓ 测试报告最开始做这个项目的时候我的想法其实很简单让 AI 帮我写代码我负责验收。但真正开始把项目往工程化方向推进以后我慢慢发现最难的事情不是让 AI 把代码写出来而是让 AI 在已经确定的架构边界里写代码。你告诉它“统一 URL。”它可能只统一了一个入口。你告诉它“支持 Firefox。”它可能只改了主执行流程。你告诉它“Selector 必须唯一。”它可能真的找到了一个唯一元素但那个元素根本不是你原来要找的那个。你告诉它“禁止 import。”它可能在 Runtime 阶段才报错而 Validator 还是放行的。甚至到了最后你以为测试全绿了重新看指标定义又会发现原来“验收标准”本身也可以被钻空子。所以这次我没有继续单纯修代码。而是把之前写给 AI 的 Prompt 当成了一份待验收材料重新审了一遍。然后就是一轮又一轮的推翻。最后我才真正意识到高质量 Prompt 的目的不是让 AI 更自由地写代码而是让 AI 没有机会绕过你的架构。一、最开始我以为 Prompt 只是“把任务讲清楚”最早的 Prompt 很像普通开发任务单修改执行逻辑 1. 统一 target_url 2. 支持 browser_type 3. 支持 headless/headed 4. 所有执行流程使用统一配置 5. 补充测试从人类开发者的角度来看这些话没有什么明显问题。但交给 AI 之后很快就暴露出一个问题你以为你描述的是一个功能AI 看到的可能是几个可以独立完成的局部任务。例如我最开始想解决 URL 不一致的问题。项目里有target_url test_path元素抓取需要target_url test_path但 AI Prompt、执行跳转、健康检查之前并没有完全统一。所以第一反应自然是defbuild_target_url(target_url,test_path):returntarget_url.rstrip(/)/test_path.lstrip(/)看起来很合理。统一入口。统一函数。问题解决。但继续往下审代码我突然发现真正的问题根本不是“有没有统一函数”而是“这个函数在什么生命周期里拿什么数据”。二、第一次真正把 Prompt 推翻同一个字段不代表同一个生命周期AutoPilot-Test 后来明确了一条非常重要的边界Admission 前 ↓ Project 是当前配置来源 Admission ↓ 冻结 Execution Manifest Admission 后 ↓ 只允许使用 Manifest于是同一个target_url实际上存在两种完全不同的语义。Admission 前例如元素抓取Project.target_url Project.test_path这是用户当前配置。Admission 后例如真正执行Manifest.target_url Manifest.test_path这是本次 Execution 的历史快照。所以如果 Prompt 只写“统一 target_url”远远不够。真正应该写的是Admission 前 使用 Project Admission 后 使用 Manifest 禁止 Admission 后重新读取 Project 当前值最后甚至连 URL Builder 的参数都钉死成build_target_url(target_url:str,test_path:str)而不是build_target_url(project)因为一旦允许把 ORMproject对象直接传进去AI 就很容易在后续代码中重新访问project.target_url project.test_path这样一来原本已经冻结的执行上下文又被偷偷解除了。最终整个关系变成Admission │ ┌─────────────┴─────────────┐ │ │ Project Manifest │ │ crawl / prompt / execution / heal pre-check这一轮之后我第一次意识到Prompt 不能只描述“数据是什么”还必须描述“什么时候允许读取”。也就是说字段约束 ≠ 生命周期约束三、第二次推翻“主流程修好了”不代表“系统修好了”接下来是browser_type。项目里本来已经有浏览器类型配置。理论上应该支持chromium firefox webkit但之前执行路径里存在写死的pw.chromium.launch(...)那怎么办很简单把 browser_type 接进去。于是就改。然后测试。然后继续审。结果发现BrowserType.launch 根本不止一处。至少存在三条路径主执行 自动 Heal 手动 Heal也就是说你把主执行改成firefox并不代表自动 Heal或者routers/heal.py真的会跟着变。最终这一类约束不能再写成“支持 Firefox”而必须写成当前共三处 BrowserType.launch 1. playwright_service.py 主执行 2. playwright_service.py 自动 Heal 3. routers/heal.py 手动 Heal 三处全部读取 Manifest.browser_type。验收也一样。不能只写browser_typefirefox 测试通过而要明确主执行 → Firefox 自动 Heal → Firefox 手动 Heal → Firefox同理execution_modeheaded也要三处同时验证。于是我又得到第二条经验当一个架构事实存在多个实现点时Prompt 必须枚举实现点。否则“这个功能已经支持了”很可能只是AI 修好了你最先看到的那一个入口。四、旁路永远藏在第二处这个问题后来在元素提取上再次出现。我先修的是ElementService结果发现HealService里面还有自己的一套_EXTRACT_JS _generate_selector _is_unique也就是说元素提取 │ ┌────────┴────────┐ ↓ ↓ ElementService HealService 新逻辑 旧逻辑这时候继续给 ElementService 打补丁没有意义。真正应该做的不是“把两个地方同时修好。”而是只允许存在一个实现。最后统一为element_extractor / | \ / | \ ↓ ↓ ↓ extract_elements generate_selector is_unique │ ┌────┴────┐ ↓ ↓ Crawl Heal这一点非常重要。因为很多工程问题不是“实现错了。”而是“同一个事实存在两个解释者。”五、Selector 又给我上了一课验证工具和运行工具必须是同一个语义世界原来的唯一性检查里存在这种思路document.querySelectorAll(selector)然后拿结果判断count 1乍看没有问题。直到出现button:has-text(Login)这里的问题就来了。:has-text()是 Playwright Selector 语法。而document.querySelectorAll(...)是浏览器原生 DOM API。两个根本不是一个 Selector 引擎。于是实际发生的是Playwright Selector ↓ :has-text() ↓ document.querySelectorAll() ↓ 无法正确解析 ↓ 唯一性检查失败所以最后改成page.locator(selector).count()因为验证逻辑必须使用和真正执行时一致的 Selector 语义。这个问题看起来只是一个 API 选错了。但实际上它背后还是一个更大的问题如果“验证系统”和“实际系统”不是同一个语义环境那么验证结果本身就不可信。六、然后我又发现count 1其实还是不够这一步是整个 Selector 修复里让我印象最深的地方。假设生成了div.container:nth-of-type(2)button然后page.locator(selector).count()1测试通过。是不是就代表 Selector 正确还不一定。因为可能出现原始目标元素 ↓ 生成 selector ↓ count 1 ↓ 恰好定位到了页面上另一个唯一元素结果就是唯一 ≠ 正确所以最终验收标准升级成count 1 resolved element original element也就是不仅要证明“只有一个”还要证明“它就是原来的那个”。因此 extractor 在内部必须保留原始 DOM identity context但对外输出的数据仍然必须是JSON serializable不能把ElementHandle直接存进数据库。最终变成原始元素 ↓ 生成持久化 selector ↓ 重新解析 ↓ count 1 ↓ identity original ↓ 才算真正成功这一轮让我对“验收测试”的理解发生了变化。以前我更容易写“功能有结果。”后来我开始写“结果满足什么不变量”七、真正的大坑来了命名空间安全前面的几个问题本质上还是代码路径到了执行命名空间这里事情突然复杂起来。最开始想做的是不让 AI 代码随便 import。但继续审代码发现 Web 和 Android 两套 namespace 都存在__import__而 Android namespace 里还直接注入了json time datetime AppiumBy driver于是你会发现Prompt 说 AI 只能使用受控能力 Runtime 却说 这是 driver 这是标准库 这是 __import__这实际上是在自己给自己的安全边界开后门。八、截图问题让我第一次真正意识到能力声明本身就是安全边界Android Prompt 当时存在这样的写法driver.save_screenshot(reports/screenshots/step_1.png)而系统本身又在建立受控截图能力safe.screenshot(...)并规定uploads/screenshots/作为截图路径白名单。于是就出现了Prompt ↓ driver.save_screenshot() ↓ 原生 driver ↓ 绕开 Safe API ↓ 绕开截图路径约束所以真正的问题并不是“截图函数路径写得不对。”真正的问题是原生 driver 不应该直接暴露给 AI 代码。最终架构变成Native Driver ↓ DriverProxy ↓ 受控能力而不是Native Driver ↓ AI Code这时候 Prompt 的截图条款也必须同步修改截图默认由后台监控器负责。 如确需自截 只能调用受控截图 API。 路径仅允许位于 uploads/screenshots/也就是说如果 Runtime 禁止某种能力但 Prompt 还在教 AI 使用这种能力那这套 Prompt 本身就是漏洞来源。九、然后我又发现只改 Runtime 也不够这一步是CodeValidator给我的教训。假设 Runtime 已经不允许importos那么直接运行importos最终确实会失败。看起来好像“安全了”。但如果 Validator 还是只做黑名单检查就可能出现第一层 允许生成 第二层 运行时报错这其实是把确定性约束推迟到了运行期。所以最终规则变成AI Code ↓ CodeValidator ↓ Import / ImportFrom 直接拒绝 ↓ Restricted Namespace ↓ Runtime也就是第一层就拒绝第二层再兜底。Runtime 的作用是防绕过。Validator 的作用是提前确定性拦截。两个职责不能混。十、这时候又出现了一个很尴尬的问题Mock 自己也是代码我一开始差点漏掉这一层。项目里的 Mock 模板本身也会生成 Python 代码。而原来的 Web Mock 模板里存在importasyncioimportjsonfromdatetimeimportdatetimeAndroid Mock 同样存在不符合新 namespace 契约的 import例如importjsonas_json这意味着新 Validator 所有 Import / ImportFrom 一律拒绝 项目 Mock 自己就在 Import于是会出现Validator 已经修对了 ↓ Mock 反而全部 invalid这不是 Validator 错了。而是Prompt / Validator / Namespace / Mock 四层之间的契约没有同步。最终必须一起修改。十一、所以最后整个安全链变成了这样Prompt │ ↓ AI Generated Code │ ↓ CodeValidator │ ↓ Restricted Namespace ↙ ↘ Web Android │ DriverProxy │ ↓ Runtime而且还有Prompt ↕ Mock Template也就是说Prompt 教 AI 什么Validator 允许什么Namespace 暴露什么Runtime 最终能做什么这几个东西必须说同一种语言。否则只修其中一个另外几个迟早会把它绕回去。十二、最后连指标都可能被“钻空子”做到最后 Task 15本来想着跑测试、整理 README、收尾。结果指标定义又让我重新审了一遍。项目原始量化目标是四项指标目标AI 首次生成准确率≥ 70%自愈后的最终执行成功率≥ 85%单条用例时间≤ 60sExcel 批量导入≥ 100 行表面上都没问题。但真正做验收以后我发现一个指标最容易被钻空子的地方经常不是分子而是分母。十三、比如批次平均就很容易把真实结果算漂亮假设有两批第一批1 / 1 第二批50 / 100如果按批次平均(100% 50%) / 2 75%看起来很好。但是按真实 Case 聚合51 / 101 50.5%差距非常明显。所以最终明确统计单位 Case 禁止 Execution 批次平均为什么因为用户最终面对的不是“某个批次平均表现是多少。”而是“这一批真实 Case 到底有多少成功。”十四、85% 的分母也必须钉死最终定义成最终执行成功率 normal_success / admitted production Case 且 terminal_reason 不属于 user_stopped interrupted并且business_failure execution_failed integrity_anomaly都保留在分母里。不能为了让数字好看平台执行失败 ↓ 踢出分母否则你得到的可能只是“成功的 Case 中成功率很高。”而不是“这个平台最终把用户交给它的生产 Case 做成功了多少。”十五、60 秒指标也不能只看成功 Case这也是一次很典型的统计陷阱。假设90 个 Case 平均 10 秒 全部成功 10 个 Case 平均 90 秒 最终失败如果 latency 只统计成功 CaseP95可能非常漂亮。但用户实际体验并不是这样。因此最后明确first-pass 没有创建 Heal Round 直接进入 terminal 的 Case healed path 至少成功 claim 一个 Heal Round 的 Case latency 样本 必须包含失败 terminal Case60 秒是否达标最终看E2E Case latency P95 60s如果P95 60s那就直接写未达到 60s 指标而不是基本达到更不能在报告里自己把需求口径改了。最终应该是实测数据 ↓ 提出口径修订建议 ↓ Owner 决策 ↓ README 采用最终确认口径这让我再次意识到验收标准本身也必须具备抗投机能力。十六、到这里我才真正理解什么叫 Prompt Engineering以前我理解的 Prompt Engineering 是怎么让 AI 更准确地生成代码现在我更愿意把它理解成怎么让 AI 在一个已经存在的架构里 只能沿着正确的边界修改代码于是现在一份真正有约束力的 AI 编程 Prompt至少应该回答五件事情类型要回答的问题事实源谁说了算生命周期什么时候允许读取边界哪些路径绝对禁止不变量什么东西无论如何都不能改变证据怎么证明真的完成这时候你再回头看 AutoPilot-Test 的这些 PromptExecutionAdmissionService Manifest ExecutionCodeResolver CaseStateResolver StepCanonicalizer element_extractor CodeValidator DriverProxy你会发现它们其实不只是“让 AI 完成某个任务”的说明。更像是在定义AI 修改这个项目时可以碰什么不能碰什么谁是真源以及完成以后拿什么证据证明。十七、我现在审 AI 编程 Prompt会先问这 8 个问题这轮 AutoPilot-Test 的过程最后给我沉淀下来的其实不是某一个 Prompt而是一套审 Prompt 的方法。1. 这个字段到底有几个来源例如target_url browser_type execution_mode active_code_id如果一个字段能在三个地方产生“真值”就必须先定义谁是真源2. Admission 前后语义有没有发生变化尤其检查Project Manifest Runtime State什么时候读当前配置什么时候只能读快照必须明确。3. 我说“所有路径”了吗看到主执行就继续搜Auto Heal Manual Heal Recovery Retry Router Mock因为主路径修好不代表旁路消失。4. 我的验收标准是不是可以被“投机”例如count 1不代表 Selector 一定正确。再比如成功率如果没有分母定义数字同样可以被包装。所以验收标准不能只描述“结果出现了。”还要描述“这个结果满足什么不变量。”5. 我是不是只改 Runtime却忘了 Prompt 和 Mock这是 AI 工程中特别容易遗漏的一层。真正产生代码的地方不只有AI Generator还有Prompt Example Mock Template Fixture Migration Script Manual Router所以如果新增了一项全局约束就必须搜索谁还在生成旧代码6. 我有没有第二个实现如果一个概念同时存在Service A Service B Utils Router就应该先问为什么它会有多个解释者能统一就不要让 AI 同时维护两个版本。7. 我是不是把“能跑”误认为“正确”了例如Selector 能定位到一个元素和Selector 能定位到原来的目标元素不是一个级别。又例如Runtime 最后把 import 拦掉了和Validator 在生成阶段就拒绝 import也不是一个级别。8. 有没有机器可验证的证据不要只写确保没有旁路。最好直接写成全库搜索 BrowserType.launch 全库搜索 __import__ 全库搜索 get_latest_code 确认只有指定实现点 确认没有第二套实现能被机器证明的约束尽量不要只留给自然语言理解。十八、回头看我发现每推翻一次完成标准都会升级这次最有意思的并不是我修了多少 Bug。而是每发现一次旁路我对“完成”的定义都会升级一次。最开始功能要做对后来所有路径都要做对再后来正确性必须可以证明继续往下Prompt / Validator / Runtime 必须一致最后变成连验收标准和指标定义本身 都必须无法通过取巧获得“好看的结果”于是整个思路慢慢变成功能 ↓ 架构 ↓ 边界 ↓ 不变量 ↓ 证据 ↓ 验收十九、这可能才是 AI 编程真正进入工程化以后最重要的问题AI 会越来越擅长写代码。这一点其实已经没有什么悬念了。真正麻烦的问题反而会变成当 AI 能够快速修改几十个文件时你怎么确保它没有顺手改变一个你根本没想动的架构边界比如你想统一 URLAI 顺便重新读取 Project。你想支持 FirefoxAI 只修主执行。你想解决 SelectorAI 修了 Crawl却留下 Heal 旧实现。你想禁 importAI 发现 Mock 自己也 import。你想提高指标AI 发现改变统计口径比优化系统更容易。这时候你会发现AI 最危险的地方不是它不会做。而是它太擅长补全你没有说清楚的地方。二十、所以我现在理解的 Prompt已经和以前不太一样了以前Prompt 告诉 AI 怎么做现在Prompt 告诉 AI 什么是真的 谁是真源 什么时候允许读取 哪些路径禁止绕过 哪些状态不能被改变 什么结果才算正确 什么证据才能证明完成它越来越像一份AI 可执行的工程约束契约。而不是一段普通的自然语言说明。结语AutoPilot-Test 这一轮工程化治理对我来说最大的收获其实不是又多学了几个 Python、Playwright 或 FastAPI 的技巧。而是开始真正理解当 AI 参与软件工程以后工程师负责的事情正在从“亲手写每一行代码”逐渐变成“定义事实、定义边界、定义不变量、定义证据”。AI 可以帮你生成代码。但最终真正决定一个工具能不能进入真实工程环境的永远不只是代码生成能力。而是事实是否唯一 边界是否清晰 状态是否一致 旁路是否被封住 指标是否可信 结果是否可证明所以现在再让我重新写一份 AI 编程 Prompt我不会第一时间去想“怎么写得更详细”我会先问“这份 Prompt有没有把 AI 绕过架构的路全部堵上”因为我现在越来越相信一件事AI 会越来越会写代码而工程师真正需要做好的是定义什么是真的、什么已经冻结、什么绝对不能绕以及什么证据才能证明它真的完成。项目地址GitHubhttps://github.com/ZipUp-dot/AutoPilot-TestGiteehttps://gitee.com/Mr-6Lawrence/auto-pilot-test关于作者我是 EthanPeng正在做 AutoPilot。我一直比较相信一件事工具应该适应人而不是人适应工具。AutoPilot 想做的事情也很简单让测试人员不需要把大量时间花在重复编写、维护自动化脚本上而是把更多精力放到测试设计、风险识别、质量保障和 AI 代码解释上。而这次测试工程化的过程也让我越来越确定AI 可以帮你生成代码但真正决定一个工具能不能进入真实工程环境的永远是代码之外的那一整套工程能力。欢迎交流。———————————————系列文章上一篇《P0-P3 落地实录三从测试隔离到并发竞态1109 个测试全绿以后我又发现了什么》本文《# 我把一个 AI 编程 Prompt 推翻了很多轮最后才明白Prompt 不是让 AI 写代码而是防止它绕过架构》下一篇《AutoPilot V1.2继续把 AI 感知、自愈与环境治理往前推》待续————————————————
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Dash to Panel 完全指南:让 Ubuntu 拥有 Windows 风格任务栏 2026/9/26 2:18:59

Dash to Panel 完全指南:让 Ubuntu 拥有 Windows 风格任务栏

1. 为什么偏偏是 Dash to Panel:从一个移植癖的视角看桌面改造先说个背景。我身边大半朋友是从 Windows 转过来的 Ubuntu 用户,理由五花八门,有的是开发环境需要,有的是受够了 Win 自动更新的脾气,有的纯粹想折腾。但几…

阅读更多 →
海康工业相机接入ROS2:从MVS SDK到话题发布的完整指南 2026/9/26 2:18:59

海康工业相机接入ROS2:从MVS SDK到话题发布的完整指南

简介:这份资源面向在ROS2环境下开发海康工业相机驱动的开发者与机器人方向学习者,系统讲解如何将HIKROBOT相机接入ROS2生态,完成图像采集、参数配置与话题发布。包内共19个文件,约66KB,以h头文件、cpp源文件、zbak备份…

阅读更多 →
离线语音模块误识别调优实战:命令词设计与防误触灵敏度调参指南 2026/9/26 2:18:59

离线语音模块误识别调优实战:命令词设计与防误触灵敏度调参指南

1. 离线语音模块误识别到底难在哪离线语音模块这两年出货量暴涨,从智能台灯、小家电到玩具、工业面板,几乎只要带个按键的产品都想换成"动嘴不动手"。但真正把模块塞进产品、跑完小批量试产的人都知道,误识别才是那个让人半夜爬起来…

阅读更多 →
Sandcastle blank 模板 prompt.md 指南:从空骨架搭建自定义沙箱编码 Agent 的提示词工程 2026/9/26 2:18:53

Sandcastle blank 模板 prompt.md 指南:从空骨架搭建自定义沙箱编码 Agent 的提示词工程

【免费下载链接】sandcastle Orchestrate sandboxed coding agents in TypeScript with sandcastle.run() 项目地址: https://gitcode.com/gh_mirrors/sandcastl/sandcastle 点击查看 免费下载 导读 Sandcastle 是一个用 TypeScript 编排沙箱化编码 Agent 的库&am…

阅读更多 →
驱动总裁免扫码绿色单文件版2.21.0.0:离线装机与驱动管理实战指南 2026/9/26 2:18:47

驱动总裁免扫码绿色单文件版2.21.0.0:离线装机与驱动管理实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
水下机器人ROV技术详解:系统组成、关键技术与作业工具选型 2026/9/26 2:18:47

水下机器人ROV技术详解:系统组成、关键技术与作业工具选型

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉