本地部署一个 Open-Weight Model(开源权重模型),然后让它从一个 GitHub Issue 的描述里直接生成一个可运行的 Web 应用,这件事现在已经成为一条可以复现的工程链路,而不只是技术演示。开源权重模型在本地运行,意味着需求数据不出机器、模型参数可以审查、生成调用的边际成本很低;GitHub Issue 作为需求输入源,又让“需求描述到代码生成”的流程被压缩到一段提示词之内。本文会围绕一个具体任务展开:把一个待办事项 Web 应用的 Issue 描述交给本地模型,由它产出需求文档、技术方案、后端接口和前端页面,最后启动服务,并用 UI 自动化录制回放的方式验证页面功能。整条链路都跑在本地,适合想把代码生成接入日常开发流程,又不希望依赖外部云接口的团队和个人开发者。
1. 先理解“Issue 到 Web 应用”这条链路的关键环节
1.1 为什么选择 Open-Weight Model 而不是云接口
Open-Weight Model 指权重公开、可以自行下载和部署的模型,常见的有 Qwen、Llama、DeepSeek 等系列。它和只能通过 API 访问的闭源模型相比,区别不在“能不能生成代码”,而在“模型跑在哪里、参数由谁控制”。
本地部署开源权重模型有三个直接收益:
- 数据不出内网。GitHub Issue、代码片段、内部需求文档在生成过程中只会经过本地推理进程,不会把内容发送到外部服务。
- 调用成本可控。本地推理按显卡或内存资源计费,而不是按 token 计费,批量生成任务和团队内部的反复尝试会更有优势。
- 模型版本可固定。团队可以把某个具体版本的权重固定下来,后续生成行为不会因为上游接口升级而突然变化,这对自动化流程很重要。
代价同样明显。本地模型受限于硬件,参数规模一般小于云端可用的大模型;推理速度取决于 GPU 或内存带宽;模型的系统指令遵循能力和代码生成质量,需要靠提示词工程和工程约束来弥补。
下面是本地开源权重模型与云接口方案在常见场景下的对比:
| 对比维度 | 本地 Open-Weight Model | 云端模型 API |
|---|---|---|
| 数据流向 | 数据在本机或内网处理 | 数据发送到外部服务 |
| 调用成本 | 主要是硬件成本 | 按 token 计费,量越大成本越高 |
| 模型版本控制 | 权重固定,可离线复现 | 由服务方控制,可能自动升级 |
| 部署复杂度 | 需要配置推理引擎和硬件 | 开通账号即可使用 |
| 生成质量上限 | 受本地模型参数规模限制 | 通常更大参数量,质量更稳定 |
| 适用场景 | 内部工具、隐私敏感、批量生成 | 高质量单次生成、快速验证 |
本文后面的流程,默认采用本地部署方案。如果你的机器显存比较有限,也可以先把流程用一个小参数模型跑通,再替换成大参数模型。
1.2 GitHub Issue 作为需求入口的原因
GitHub Issue 本质上是一种半结构化需求文档。它有标题、描述、标签、评论和状态,标题承担了需求的摘要,描述承载了功能预期和验收条件。对代码生成流程来说,这种结构比一段零散聊天记录更适合作为输入,因为模型的注意力可以集中在“标题 + 描述 + 标签”这一段明确的文本上。
不是所有 Issue 都适合直接生成 Web 应用。适合的 Issue 通常具备三个特征:
- 需求边界清晰,例如“一个待办事项管理页面”“一个接口状态监控面板”。
- 验收条件可描述,例如“提交表单后列表刷新,刷新浏览器数据不丢失”。
- 技术依赖说明完整,例如“数据存在本地文件即可”“不需要登录体系”。
如果 Issue 本身写得含糊,模型生成代码前需要先做一轮澄清。常见的做法是让模型列出“需求理解、假设、待确认问题”,由人工补充后再进入生成阶段。这一步不是浪费时间,它能显著减少后续返工。
1.3 整体流程:解析、设计、生成、验证
从 Issue 到 Web 应用,最小闭环可以拆成四个阶段:
- 解析。把 Issue 文本交给本地模型,模型输出结构化需求清单、数据模型和接口定义。
- 设计。根据需求清单生成项目结构、技术栈和文件树,明确每个文件承担什么职责。
- 生成。按文件逐个生成代码,先后端接口,再前端页面,最后连接静态资源。
- 验证。启动服务,用 curl 验证接口,用 UI 自动化录制工具回放关键操作,确认页面行为符合需求。
每个阶段都要有检查点。解析阶段检查需求是否遗漏;设计阶段检查技术选型是否合理;生成阶段检查代码是否能运行;验证阶段检查操作路径和断言是否符合验收标准。模型生成的内容会不稳定,检查点的作用就是把不稳定控制在可发现、可修正的范围内。
2. 本地模型环境准备:选型、部署与连通性检查
2.1 模型选型与硬件匹配
代码生成任务对模型的要求,主要是代码理解能力、指令遵循能力和上下文长度。参数规模越大,生成质量通常越好,但硬件门槛也越高。在本地环境,需要根据显存和内存来决定模型规模。
以下是一个常见的匹配参考,具体以你选择的量化版本和官方发布信息为准:
| 模型参数规模 | 常见量化内存需求 | 适合场景 | 硬件参考 |
|---|---|---|---|
| 7B 级别 | 8GB 左右 | 小型 Web 应用、脚本、接口生成 | 消费级显卡或 16GB 内存的 CPU 机器 |
| 14B 级别 | 16GB 左右 | 中等复杂度项目、较长上下文推理 | 24GB 显存或更高内存配置 |
| 32B 级别 | 24GB 以上 | 复杂项目、需要稳定遵循长指令 | 多卡或大显存服务器 |
对于第一次跑通链路,建议先从 7B 级别的代码模型开始。它的生成速度更快,硬件门槛低,适合用来验证整个工程流程。等到流程稳定后,再根据生成质量决定是否升级到更大模型。
选择模型时还要注意上下文长度。生成一个完整 Web 应用需要把“需求文档 + 技术方案 + 提示词”都装进上下文。建议至少选择支持 8k 上下文以上的模型,如果 Issue 较长,可以先把 Issue 拆成多个子需求分别生成。
2.2 用 Ollama 部署本地模型
Ollama 是目前部署本地开源权重模型比较常用的工具,它把模型下载、权重管理、推理服务和本地 API 封装到了一起,适合作为这组流程的推理引擎。
在 Linux 或 macOS 环境,可以使用官方安装脚本安装:
curl -fsSL https://ollama.com/install.sh | shWindows 环境建议直接从 Ollama 官网下载安装包。安装完成后,启动服务并拉取一个代码模型:
ollama serveollama pull qwen2.5-coder:7bollama pull会从模型仓库下载权重到本地,之后就可以用ollama run进入交互式对话,也可以通过本地 HTTP 接口调用。拉取完成后,用下面的命令确认模型已经就绪:
ollama list启动服务后,检查本地 API 是否能正常访问:
curl http://127.0.0.1:11434/api/tags正常响应会返回模型列表 JSON。这一步的作用是确认推理服务已经启动,后续脚本和代码生成都会通过这个本地接口进行调用。
2.3 环境检查清单
在进入生成流程前,先用一张清单确认环境完整,避免中途才发现依赖缺少:
| 检查项 | 检查命令 | 期望结果 |
|---|---|---|
| Ollama 服务可用 | curl http://127.0.0.1:11434/api/tags | 返回 JSON 模型列表 |
| 代码模型已下载 | ollama list | 能看到目标模型 |
| Python 版本 | python3 --version | 3.10 或更高 |
| 依赖安装目录 | pip install fastapi uvicorn | 安装成功,无版本冲突 |
| 浏览器自动化环境 | pip install playwright && playwright install chromium | Playwright 可启动浏览器 |
| 本地目标目录 | mkdir todo-app && cd todo-app | 目录创建成功 |
这一节完成后,你已经有了一个可用的本地模型推理环境。接下来要解决的是,如何把 GitHub Issue 变成模型能理解、能执行的结构化需求。
3. 把 GitHub Issue 转成可执行需求:提示词是第一道关卡
3.1 一个适合演示的 Issue 示例
为了把流程讲清楚,我们用一个最小 Issue 作为输入。它的规模不大,但包含了“增删改查、持久化、刷新不丢失”这几个 Web 应用最常见的验收点:
标题: 待办事项 Web 应用 描述: 需要一个个人待办事项 Web 应用。用户在浏览器中可以新增待办事项、 标记完成、删除事项。数据不需要账号体系,保存在本地文件即可。 页面要简洁,刷新浏览器后数据不丢失。这个示例足够小,适合观察模型在每一步的输出。如果你的 Issue 比这个复杂,先按这个流程拆成多个子任务,而不是让模型一次吃掉全部需求。
3.2 提示词设计:要求模型输出结构化需求
直接让模型“写一个待办应用”,得到的往往是逻辑不完整、结构随意的代码。更稳妥的做法是先用一个提示词,让模型输出结构化需求,再根据这份需求生成代码。
第一次调用的提示词模板:
你是一名后端架构师。请分析下面的 GitHub Issue,先输出需求清单, 再输出技术方案。 输出格式要求: 1. 需求清单:每条需求用编号 R1、R2 表示,标注优先级。 2. 数据模型:用 JSON 描述实体字段和类型。 3. 接口定义:列出 method、path、请求参数、返回值。 4. 技术选型:说明选择理由。 Issue 内容: """ 标题: 待办事项 Web 应用 描述: 需要一个个人待办事项 Web 应用。用户在浏览器中可以新增待办事项、 标记完成、删除事项。数据不需要账号体系,保存在本地文件即可。 页面要简洁,刷新浏览器后数据不丢失。 """调用本地模型的方式有两种。交互式调试用ollama run:
ollama run qwen2.5-coder:7b脚本化调用用 HTTP API,方便把输出保存到文件。下面是一个 Python 调用示例:
import requests import json response = requests.post( "http://127.0.0.1:11434/api/generate", json={ "model": "qwen2.5-coder:7b", "prompt": prompt_text, "stream": False, }, timeout=300, ) result = response.json() with open("requirements.json", "w", encoding="utf-8") as f: json.dump(result["response"], f, ensure_ascii=False, indent=2) print(result["response"])stream设为False可以让接口一次性返回完整结果,便于后续把输出直接写入文件;如果生成时间较长,建议把timeout设置为 300 秒以上。
一个较理想的结构化输出示例:
{ "需求清单": [ {"编号": "R1", "需求": "新增待办事项", "优先级": "高"}, {"编号": "R2", "需求": "标记完成状态", "优先级": "高"}, {"编号": "R3", "需求": "删除待办事项", "优先级": "高"}, {"编号": "R4", "需求": "数据持久化到本地文件", "优先级": "高"}, {"编号": "R5", "需求": "刷新页面后数据不丢失", "优先级": "中"} ], "数据模型": { "Todo": { "id": "int", "title": "string", "done": "boolean" } }, "接口定义": [ {"method": "GET", "path": "/todos", "返回": "待办列表"}, {"method": "POST", "path": "/todos", "参数": {"title": "string"}, "返回": "新建项"}, {"method": "PUT", "path": "/todos/{id}", "参数": {"done": "boolean"}, "返回": "更新结果"}, {"method": "DELETE", "path": "/todos/{id}", "返回": "删除结果"} ], "技术选型": { "后端": "FastAPI,适合快速搭建 JSON 接口", "存储": "JSON 文件,满足本地持久化需求", "前端": "静态 HTML + fetch,避免引入前端构建工具" } }3.3 为什么先要需求文档再写代码
很多人在使用模型生成代码时,会跳过需求文档直接让模型写完整项目。这样做的结果是,模型在生成每个文件时都会重新理解一遍需求,前后文件之间的接口定义很可能不一致。
先产出结构化需求,等于给模型一个“锚点”。后续所有生成提示词都可以引用这份需求文档,让模型在同一个基础上继续工作:
- 后端提示词引用接口定义,保证路由和返回结构一致。
- 前端提示词引用数据模型,保证字段名和类型匹配。
- 验证阶段可以直接对照需求清单逐条检查。
如果原始 Issue 描述模糊,模型在结构化输出时还会暴露理解偏差,比如数据模型缺少字段、接口缺少删除方法等。这时修正成本很低,只是改一段 JSON;如果等到代码生成完再发现问题,就要在多个文件之间来回调整。
注意:模型生成的“需求文档”只能作为初稿,需要在写代码前人工复核一遍。特别要检查需求是否有遗漏、字段类型是否合理、接口语义是否完整。
4. 让模型生成最小 Web 应用:前后端代码一次跑通
4.1 第二次提示词:按需求生成项目结构
需求确定后,第二次提示词负责生成项目结构。不要把“生成整个项目”和“生成每个文件”混在一次调用里,否则模型容易在长输出中途丢失细节,或者生成的文件之间接口不一致。
先让模型输出文件树:
请根据以下需求和技术方案,输出一个最小的项目结构。 要求: 1. 每个文件一行,说明文件职责。 2. 后端使用 FastAPI,前端使用静态 HTML + fetch。 3. 数据存储使用 JSON 文件。 需求清单: R1 新增待办事项 R2 标记完成状态 R3 删除待办事项 R4 数据持久化到本地文件 R5 刷新页面后数据不丢失对于示例项目,得到的文件树通常是:
todo-app/ ├── app.py # FastAPI 后端,提供待办接口 ├── static/ │ └── index.html # 前端页面,包含交互逻辑 ├── requirements.txt # Python 依赖 └── todos.json # 数据文件,首次运行自动创建在这个结构里,app.py负责 CRUD 接口和静态页面托管,index.html负责页面展示和调用接口,todos.json是持久化载体。把后端和前端拆到两个目录,是为了让提示词和后续验证都更聚焦。
4.2 后端代码:FastAPI 保存待办事项
在文件树确定后,让模型生成app.py。对于这个示例,一个可运行的后端代码是这样的:
from fastapi import FastAPI from fastapi.staticfiles import StaticFiles import json import os app = FastAPI() DATA_FILE = "todos.json" def load_todos(): if not os.path.exists(DATA_FILE): return [] with open(DATA_FILE, "r", encoding="utf-8") as f: return json.load(f) def save_todos(todos): with open(DATA_FILE, "w", encoding="utf-8") as f: json.dump(todos, f, ensure_ascii=False, indent=2) @app.get("/todos") def list_todos(): return load_todos() @app.post("/todos") def create_todo(payload: dict): todos = load_todos() new_id = max([t["id"] for t in todos], default=0) + 1 item = {"id": new_id, "title": payload["title"], "done": False} todos.append(item) save_todos(todos) return item @app.put("/todos/{todo_id}") def update_todo(todo_id: int, payload: dict): todos = load_todos() for t in todos: if t["id"] == todo_id: if "done" in payload: t["done"] = bool(payload["done"]) break save_todos(todos) return {"ok": True} @app.delete("/todos/{todo_id}") def delete_todo(todo_id: int): todos = load_todos() todos = [t for t in todos if t["id"] != todo_id] save_todos(todos) return {"ok": True} app.mount("/", StaticFiles(directory="static", html=True), name="static")这段代码的关键点有三个:
load_todos和save_todos把所有文件读写集中在一起,后续要换成 SQLite 或 Redis,只需要改这两个函数。id通过max([t["id"] for t in todos], default=0) + 1生成,避免删除后主键冲突。app.mount("/", StaticFiles(...))放在最后一行,先把 API 路由注册完,再挂载静态文件,否则/todos等接口会被静态文件路由截获。
4.3 前端页面:静态 HTML 加 fetch 请求
前端页面用单个index.html承载,不需要构建工具。页面逻辑是:加载时请求/todos渲染列表;点击新增时发送POST;点击完成或删除时分别发送PUT和DELETE。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <title>待办事项</title> </head> <body> <h1>我的待办</h1> <input id="title" placeholder="输入待办内容" /> <button id="add">新增</button> <ul id="list"></ul> <script> const titleInput = document.getElementById('title'); const list = document.getElementById('list'); async function loadTodos() { const res = await fetch('/todos'); const todos = await res.json(); list.innerHTML = ''; todos.forEach(t => { const li = document.createElement('li'); const span = document.createElement('span'); span.textContent = t.title; if (t.done) span.style.textDecoration = 'line-through'; const doneBtn = document.createElement('button'); doneBtn.textContent = t.done ? '取消完成' : '完成'; doneBtn.onclick = () => toggleTodo(t.id, !t.done); const delBtn = document.createElement('button'); delBtn.textContent = '删除'; delBtn.onclick = () => deleteTodo(t.id); li.appendChild(span); li.appendChild(doneBtn); li.appendChild(delBtn); list.appendChild(li); }); } async function addTodo() { const title = titleInput.value.trim(); if (!title) return; await fetch('/todos', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title }) }); titleInput.value = ''; loadTodos(); } async function toggleTodo(id, done) { await fetch('/todos/' + id, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ done }) }); loadTodos(); } async function deleteTodo(id) { await fetch('/todos/' + id, { method: 'DELETE' }); loadTodos(); } document.getElementById('add').onclick = addTodo; loadTodos(); </script> </body> </html>页面里的每个操作都对应一个接口调用,操作完成后统一调用loadTodos()重新渲染。这种“操作后重新拉取数据”的写法,比在前端手动修改 DOM 更简单,也更容易保证页面状态和后端数据一致。
4.4 为什么把后端和前端拆开生成
把整个项目一次性丢给模型,要求它在一次输出中处理路由、数据文件、前端渲染、事件绑定,输出长度会迅速接近模型的上下文限制,错误也更难定位。
拆开生成的好处是每次调用只负责一个单一职责:
- 第一次调用只分析需求,输出 JSON。
- 第二次调用只输出文件树。
- 第三次调用只生成后端路由和存储逻辑。
- 第四次调用只生成前端页面和交互脚本。
这样每一段输出都能独立验证。后端单独跑通后,再生成前端;前端页面打不开时,问题范围被限定在静态文件和接口调用,而不是整个项目。
5. 运行验证:接口测试与 UI 自动化录制回放
5.1 启动服务并验证接口
代码生成后,先安装依赖并启动服务:
cd todo-app pip install fastapi uvicorn uvicorn app:app --host 0.0.0.0 --port 8000启动后先验证接口。用一个空数据文件初始化:
echo "[]" > todos.jsoncurl http://127.0.0.1:8000/todos预期返回一个空数组:
[]然后验证新增、列表、更新、删除四个接口:
curl -X POST http://127.0.0.1:8000/todos \ -H "Content-Type: application/json" \ -d '{"title": "测试任务"}'curl http://127.0.0.1:8000/todoscurl -X PUT http://127.0.0.1:8000/todos/1 \ -H "Content-Type: application/json" \ -d '{"done": true}'curl -X DELETE http://127.0.0.1:8000/todos/1每个接口返回后,检查todos.json的内容是否同步变化。这一步能确认持久化逻辑正常工作,而不只是接口在内存里返回了正确数据。
5.2 UI 自动化录制生成脚本的基本思路
接口验证通过后,下一步是验证页面操作。这正是 UI 自动化录制工具发挥作用的场景。目前有一类开源项目专门做“录制界面操作、生成自动化脚本、回放校验流程”,主要面向 Web 端、Android 端和 iOS 端。Web 端常用 Playwright 的 Codegen 工具,移动端则通常基于 Appium 或各平台 UI 自动化框架。
录制生成脚本的思路是:人工在浏览器里操作一遍,工具记录点击、输入、滚动等事件,再生成可执行的测试脚本,之后可以重复回放。
启动 Playwright Codegen,指向本地页面:
npx playwright codegen http://127.0.0.1:8000操作页面的“新增待办”、“标记完成”、“删除”三个动作,工具会实时生成脚本。生成后的脚本结构类似下面这段 Python 代码:
from playwright.sync_api import sync_playwright, expect with sync_playwright() as p: browser = p.chromium.launch(headless=False) page = browser.new_page() page.goto("http://127.0.0.1:8000/") page.get_by_placeholder("输入待办内容").fill("录制生成的待办") page.get_by_role("button", name="新增").click() expect(page.get_by_text("录制生成的待办")).to_be_visible() page.get_by_role("button", name="删除").click() expect(page.get_by_text("录制生成的待办")).not_to_be_visible() browser.close()这段脚本里,录制工具自动生成的定位信息是通过 placeholder 和 button 名称来查找元素,比 CSS 类名更稳定。后半部分的expect(...).to_be_visible()是手工补充的断言,用来验证新增和删除后的页面状态。
对于 Android 和 iOS 的 App 端,录制生成的脚本通常基于 Appium 描述会话和元素定位,规则类似:录制操作、生成脚本、回放验证。落地的差异主要在驱动安装、设备连接和元素定位方式上,需要按目标平台单独配置。
5.3 录制回放的局限:必须有断言
录制回放虽然能自动生成脚本,但它本身只能证明“操作路径可以重复执行”,不能证明“页面结果正确”。如果没有断言,即使新增按钮失效,回放也可能“成功”结束,因为脚本根本没有检查页面是否出现了新待办项。
因此,给录制脚本补断言是验证环节的必要动作。断言应该覆盖三类状态:
| 操作 | 断言内容 | 验证点 |
|---|---|---|
| 新增待办 | 新增文本出现在列表中 | 写入成功后页面重新渲染 |
| 标记完成 | 文本样式变为删除线 | 状态更新后前端样式生效 |
| 删除待办 | 文本从列表中消失 | 删除接口生效并刷新列表 |
| 刷新页面 | 已新增数据仍存在 | 持久化逻辑正常 |
注意:录制回放只能证明页面在录制时的操作路径可复现。生成脚本后要回归一次完整流程,并检查断言是否覆盖了需求和接口文档里的所有验收点。
6. 常见问题排查:生成、运行到验证的四个典型故障
6.1 模型生成的代码缺依赖或版本不匹配
现象:运行uvicorn app:app时报ModuleNotFoundError,或者某些接口的行为和预期不一致。
常见原因:模型生成代码时假设某些库已经安装,但环境里没有;或者生成的代码使用了某个 API 版本不存在的写法。
检查方式:先看完整报错栈,记录缺失模块名和版本要求,再对照requirements.txt检查本地环境。
处理建议:
pip install fastapi uvicorn如果模型在代码里使用了pydantic的某个新特性,先确认 pydantic 版本:
pip show pydantic预防方式是在提示词里明确要求“列出所有第三方依赖,并写进 requirements.txt”,生成后先人工检查 requirements 再安装。
6.2 上下文长度不足导致生成内容丢失
现象:模型生成到中段开始重复已生成的内容,或者遗漏了需求清单里的某条功能。
常见原因:单次提示词和要求的输出长度加起来超过了模型的上下文窗口,长输出的后半部分被截断或注意力分散。
检查方式:统计原始 Issue、需求文档和提示词的 token 数,对比模型公开的上下文长度。也可以通过模型输出末尾是否完整收尾来判断。
处理建议:把大项目拆成多个子任务。先让模型总结 Issue 并生成需求文档,再分别生成后端和前端;如果单个文件仍然超长,可以按“接口层、存储层、页面渲染”继续拆分。选择模型时,优先选上下文长度更大的版本。
6.3 JSON 文件并发写入导致数据丢失
现象:连续快速点击新增按钮后,todos.json里出现少数据或格式损坏的情况。
常见原因:示例代码中的load_todos和save_todos没有加锁。两个请求同时读取旧数据、分别写入,后写入的请求会覆盖先写入的数据。
检查方式:用自动化脚本同时发出多个POST请求,观察todos.json中的数据条数是否等于请求次数。
处理建议:如果是单进程开发环境,可以在示例中加入一个线程锁,保证同一时刻只有一个请求执行“读取-修改-写入”:
import threading lock = threading.Lock() @app.post("/todos") def create_todo(payload: dict): with lock: todos = load_todos() new_id = max([t["id"] for t in todos], default=0) + 1 item = {"id": new_id, "title": payload["title"], "done": False} todos.append(item) save_todos(todos) return item如果是生产环境,应该直接换成 SQLite 或 PostgreSQL 这类支持事务的存储,而不是继续用 JSON 文件加锁的方式拼性能。
6.4 UI 自动化录制回放不稳定
现象:录制时操作正常,回放时元素找不到,或者点击位置偏移,脚本在某一处失败。
常见原因和排查方式如下表:
| 问题现象 | 常见原因 | 检查方式 | 处理建议 |
|---|---|---|---|
| 元素找不到 | 页面数据变化,元素不存在 | 打开页面截图,核对元素文本 | 改用文本或 placeholder 定位,避免 CSS 类名 |
| 点击位置漂移 | 页面加载未完成就点击 | 查看脚本是否有等待逻辑 | 添加显式等待或expect断言 |
| 回放偶发失败 | 接口返回慢,页面未刷新 | 检查接口响应时间 | 在操作前增加等待条件 |
| 数据重复 | 之前回放遗留了脏数据 | 查看 todos.json 内容 | 回放前执行数据清理脚本 |
录制生成脚本适合用来快速建立冒烟测试,但它不等于完整的自动化测试体系。数据清理、环境隔离、断言覆盖和失败截图,这些仍然需要按测试工程的规范补齐。
7. 最佳实践:从“模型能生成”到“代码能交付”
7.1 让模型先输出计划,再逐文件生成
无论 Issue 多简单,都建议保持“需求文档 → 文件树 → 后端 → 前端 → 验证”的顺序。模型在每个阶段都会产生一个中间产物,这些中间产物既是检查点,也是下一个提示词的上下文。
如果跳过了计划阶段,模型生成的代码可能看起来完整,但字段命名不一致、接口语义混乱,问题会积累到运行阶段一起爆发。先输出计划,相当于把模型可能出错的区域,从整段代码缩小到一次调用。
7.2 限制生成范围,分模块迭代
不要指望模型一次生成一个完整的 ERP 系统。每次生成的范围应该是一个能独立验证的模块:
- 一个路由文件。
- 一个数据访问函数。
- 一个页面。
- 一个接口测试脚本。
范围越小,模型的输出质量越稳定,排错也越容易。生产项目可以把“一次生成一个模块”作为团队使用模型生成代码的默认规则。
7.3 验证层要有断言,不能只跑通
接口验证必须检查返回值和数据文件;UI 验证必须有断言,不能只录制回放。一个简单的判别标准是:如果脚本跑完后,你无法说出“哪些需求被验证了”,说明验证层还不够。
建议在项目里维护一份需求清单,把每个需求编号和对应的接口测试、UI 断言对应起来。模型生成代码后,逐条标记验证状态。
7.4 本地生成模型的生产边界
本地开源权重模型适合内部工具、原型验证和数据敏感场景,但在生产环境使用前需要明确边界:
- 模型生成的代码仍然需要人工代码审查,尤其是涉及权限、输入校验、文件路径和外部接口的部分。
- 本地模型可能生成表面正确但逻辑有缺陷的代码,比如缺少异常处理、硬编码密码、忽略边界条件。
- 生成结果应该进入版本管理,而不是直接部署;部署前还需要补上日志、监控和回滚方案。
注意:模型是一个高效的草稿生成器,不是代码审阅者。生成代码后,至少要经过一次人工审核和一次自动化测试,才能进入可交付状态。
7.5 可复用的交付前检查清单
每次用本地模型生成 Web 应用后,按下面这份清单逐项确认:
- 需求清单是否覆盖了 Issue 中的所有验收点。
- 文件树是否包含后端、前端、依赖声明和数据存储位置。
- 后端接口是否按照需求文档的 method、path、参数实现。
- 数据持久化是否验证过,重启服务后数据仍然存在。
- 前端页面是否覆盖新增、修改、删除、刷新等核心操作。
- UI 自动化脚本是否包含断言,断言是否对应需求编号。
- 依赖是否写进 requirements.txt,版本是否在本地可复现。
- 代码是否经过人工审查,是否处理了文件路径和基础异常。
- 本地生成的敏感信息是否被排除在版本控制之外。
- 生产部署前是否补齐了日志、监控、备份和回滚方案。
这条从 GitHub Issue 到 Web 应用的链路,本质上是把代码生成拆成了“解析、设计、生成、验证”四个可检查的环节。本地开源权重模型在其中承担了执行者,但真正保证交付质量的,是提示词设计、分模块生成、自动化断言和人工审查这一整套工程约束。下一步可以考虑的方向,是把这套流程接入团队的 Issue 工作流,让新建的 Issue 自动触发需求分析和代码生成,再配合移动端的 UI 自动化录制工具,把验证范围从 Web 页面扩展到 Android 和 iOS 客户端。对一个刚开始接触本地代码生成模型的团队来说,最有效的第一步,是用本文的最小示例完整跑通一次,再逐步增加项目复杂度。