ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

逃离LLM编码仓鼠轮:用spec coding与Coding Plan构建工程化AI编程流程

逃离LLM编码仓鼠轮:用spec coding与Coding Plan构建工程化AI编程流程 很多同学在使用 LLM 辅助编程时应该都有过类似的体验开一个新对话把需求粘贴进去模型生成一段代码运行报错再把报错复制回去让模型继续改改完又抱一个新错误……如此循环十几轮之后功能似乎终于能跑了但代码里已经堆满了临时补丁变量命名前后矛盾甚至不敢再让 AI 动任何一行。这种情况就是我们常说的 LLM Coding Rat Race——LLM 编码仓鼠轮。它描述的不是某个大模型的缺陷而是一种普遍存在的工作方式靠“聊天 试错”驱动开发看起来一直在忙碌实际产出却越来越低。本文想聊的正是如何逃离这个循环。核心思路不是换一个更强的模型也不是彻底放弃 AI 编程而是把“对话式编码”升级成“可规划、可验收、可沉淀”的工程化流程。文章会从问题根因、spec coding 与 Coding Plan 的概念讲起再通过一个网页抓取与智能摘要的小项目完整演示如何按计划驱动 LLM 开发最后给出高频问题排查清单和团队协作建议。无论你是刚接触 AI 编程的新手还是已经在用 Cursor、Trae、Cline 等工具的开发者这套方法都值得收藏。1. LLM Coding 的“仓鼠轮”现象1.1 什么是 LLM Coding Rat Race先给一个比较直接的定义LLM Coding Rat Race是指在人机协作编程过程中因为缺少任务边界、验收标准和上下文管理导致开发者与模型陷入“反复对话、反复修改、反复试错”的低效循环。这里的典型表现非常清晰用户把需求发出去模型返回第一版代码但不符合预期。用户粘贴报错模型给出补丁同一个函数被改了四五遍。聊天记录越来越长模型开始“忘记”最初的需求甚至出现前后矛盾的逻辑。功能最终能跑通但代码不可读、不可测试、不可维护。用户一旦想加新功能又不敢让 AI 随便改整个项目变成僵局。本质上问题不在于模型能力而在于开发流程没有为 LLM 设计好“轨道”。人跟人协作时我们会写需求文档、拆分任务、约定接口、写单元测试这些工程手段在 AI 编程里同样重要只是很多人没有意识到。1.2 为什么会陷入“无限试错”的循环要逃离仓鼠轮得先看清它形成的四个根因。第一个根因是上下文失控。大模型的注意力机制决定了当一段对话中堆积了几十条历史消息每条消息又包含大量报错和代码片段时模型很难分辨哪些信息是当前真正需要的。于是它开始“平均用力”回复质量随上下文增长明显下降。第二个根因是反馈回路缺失。不少 AI 编程工具的使用方式是让模型写代码然后人工复制到项目中运行看到报错再拷回去。这个回路里缺少自动化的验证手段。换句话说模型本身并不知道自己写的代码是否通过测试它只是在“猜”。第三个根因是目标漂移。最初你可能只想实现一个简单的命令行工具但随着对话推进你开始纠结 CSS 样式、日志格式、异常提示文案最后甚至变成了“调 prompt”而不是“写代码”。目标一漂所有后续修改都会失去方向。第四个根因是没有验收标准。很多需求描述停留在“帮我写一个爬虫”“做一个登录页面”这种模糊表达模型只能靠猜。猜对了算运气猜错了就进入下一轮试错。1.3 逃离内卷的正确方向既然问题出在流程解法自然也在流程。这里给一个总的结论想把 LLM Coding 用出生产力必须从“模型中心”转向“工程中心”。所谓“模型中心”是指开发者把大模型当作一个神通广大的对话对象期望它理解一切、完成一切。而“工程中心”正好相反开发者负责定义边界、拆解任务、设计验收标准模型则负责在每个明确的小步骤中输出代码。前者靠运气后者靠系统。接下来的章节我们会围绕这条主线展开一套 LLM 编程工作流包括用 spec 明确“做什么”。用 Coding Plan 明确“分几步做”。用可执行的验证命令明确“做到什么程度算完成”。用版本管理、知识沉淀把结果固化下来。2. 从 vibe coding 到 spec coding2.1 vibe coding 的舒适区与危险区最近一年“vibe coding”是个非常热门的词由 Andrej Karpathy 提出。它描述的是一种“跟着感觉走”的编程方式开发者把主要意图告诉 AI让 AI 顺着上下文自然地把代码写出来人再根据整体感觉做调整。这种方式适合快速验证想法也非常适合初学者体验 AI 编程的威力。但 vibe coding 有一个隐藏前提项目足够小、生命周期足够短、使用者对代码质量没有太高要求。一旦进入生产环境、多人协作或长期维护只靠“vibe”就会出问题。因为模型生成代码时会保留它自己惯用的风格不同会话之间的命名方式、模块划分、异常处理策略都可能不一致。时间一长项目就会变成一团乱麻。这并不意味着 vibe coding 要被全盘否定而是提醒我们在“原型验证”和“生产交付”之间需要一座桥梁这座桥梁就是规格和计划。2.2 spec coding先写规格再写代码spec即规格说明Specification的缩写。所谓 spec coding就是在让 LLM 写代码之前先把功能需求、接口定义、验收条件、边界情况写成一份清楚的文件再让模型按规格实现。为什么要这样做因为大模型本质上是“对齐任务”的工具它的输出质量高度依赖你对任务的描述是否清晰。你可以把大模型理解为一位非常聪明但过于听话的新同事需求里没写清楚的地方它不会反问而是会主动按自己的理解补全。补全对了是惊喜补全错了就是返工。来看一个对比。模糊的需求描述帮我写一个网页抓取工具能从 URL 抓取内容还能用 LLM 总结。清晰的 spec 描述# 网页内容抓取与智能总结工具 ## 功能要求 - F1: 通过命令行参数接收 URL - F2: 抓取并解析新闻/博客正文去掉导航、广告等无关内容 - F3: 调用 LLM 生成 3 条要点式总结 - F4: 输出结果保持 Markdown 格式支持写入文件 ## 技术约束 - 使用 Python 3.9 - HTTP 请求使用 requests 库 - 正文提取使用 readability-lxml - LLM 调用封装为独立模块便于替换供应商 ## 验收标准 - 输入无效 URL 时输出友好错误信息 - 本地网络异常时程序不崩溃给出可读提示 - 摘要输出必须为 Markdown 列表格式两份描述之间的差距就是一次“顺利开发”和“无限试错”之间的差距。2.3 Coding Plan把大任务拆成可执行步骤光有 spec 还不够因为一个稍大的功能仍然包含多个模块直接让模型“一口气写完”又回到了 vibe coding。这时需要引入 Coding Plan。Coding Plan 的核心理念是分而治之把一个大任务拆成一连串小步骤每个步骤都有明确的输入、输出和验收条件。模型按计划逐个执行开发者则按步骤审查和验证。这样做有几个明显好处每次上下文都聚焦在单个步骤上模型不容易跑偏。每个步骤都能用测试或命令验证反馈回路变短。出错时可以精准定位到某个步骤而不是整段推倒重来。多个开发者或 Agent 可以并行处理不同步骤。现在很多 AI 编程平台也开始把 Coding Plan 作为内置能力例如阿里云百炼的 coding plan、火山方舟的 agent plan / coding plan。使用者只需给出目标平台会自动生成带步骤的执行计划。不过依赖平台的前提是理解“规划”本身的价值否则即使平台生成了计划你也很难判断它是否合理。3. 建立 LLM Coding 的工程化工作流3.1 上下文瘦身让模型只看到关键信息前面提到上下文过长是 LLM Coding 进入仓鼠轮的重要原因。要想解决一个非常有效的手段是“上下文瘦身”。上下文瘦身的核心原则是模型不需要知道你这几天的全部聊天记录只需要知道“当前步骤要完成什么”和“当前代码长什么样”。具体做法包括把 spec 与 Coding Plan 保存为仓库内的 Markdown 文件作为唯一事实源。每个步骤开启新的会话只粘贴“当前步骤的要求 相关代码 报错信息”。不要在一个会话里不断追加需求而是把需求变化先写回 spec再开启新任务。举个例子假设你正在让模型实现第 2 步“正文提取”。新会话中你应该这样组织上下文# 角色 你是一名资深 Python 开发者。 # 当前步骤 请根据 spec.md 中的功能 F2实现网页正文提取。 # 相关依赖 - readability-lxml - BeautifulSoup4仅做辅助 # 代码现状 项目根目录下已有 fetch.py实现了 fetch_page(url) 函数 返回原始 HTML 文本。 # 验收标准 python -m pytest tests/test_extract.py 全部通过这样模型看到的是一个范围清晰、目标明确的小任务而不是整段“帮我写一个爬虫”的模糊对话。上下文长度变短了生成质量反而会提升。3.2 可验证的分步执行工程化工作流的第二根支柱是“可验证”。简单来说每个步骤不能只停留在“看起来差不多”而是要有可执行的验收命令。对于 Python 项目验收命令通常是 pytest对于前端项目可能是 eslint 或 tsc对于接口类项目可能是 curl 或 Postman。这些命令要提前写在 Coding Plan 的每个步骤里模型在执行完代码后能够自己运行并确认结果。来看一个典型的步骤设计## Step 2正文提取 - 目标实现 extract_main_content(html: str) - str 函数 - 输出src/extract.py - 验收 1. pytest tests/test_extract.py -k extract_main_content 通过 2. 提取结果中不包含 script、style 标签内容 3. 对空字符串输入返回空字符串而不是抛异常要注意验收标准必须是机器可检查的而不是“代码质量不错”这种主观描述。当模型能自动验证自己的输出时LLM Coding 的质量上限会显著提高。3.3 多 Agent 协作与职责划分当任务规模进一步扩大单个 Agent 处理不过来时可以考虑引入多 Agent 协作。这个思路并不复杂每个 Agent 负责一个角色所有 Agent 共享同一份 spec 和 Coding Plan并且通过文件系统或版本管理工具交接中间产物。常见的角色划分方式如下需求分析 Agent负责把用户描述翻译成 spec 和验收用例。编码 Agent按照 Coding Plan 的某个步骤实现功能。评审 Agent检查代码是否符合规范、是否覆盖边界条件。测试 Agent编写和执行测试用例输出测试报告。这里最容易踩的坑是“让多个 Agent 同时改同一个文件”必然产生冲突。正确做法是让每个 Agent 在独立分支或独立目录中工作然后由开发者统一合并。如果你想使用更复杂的工具链也可以关注 MCPModel Context Protocol等协议它能让 Agent 以标准化方式访问文件、数据库、Git 仓库等外部工具资源。4. 完整实战抓取网页内容并用 LLM 总结为了把前面讲的方法串起来我们来实现一个命令行小工具输入一个 URL抓取网页正文再用 LLM 生成 Markdown 格式的摘要。作为演示项目我会把 LLM 调用部分做成“可替换接口”并提供一个演示模式保证没有任何 API Key 时也能跑通整个链路。4.1 定义 SPEC首先在项目根目录创建 spec.md。# 网页内容抓取与智能总结工具 ## 背景 用户需要快速了解一篇文章的核心内容 希望用一个命令行工具输入 URL 后自动生成 3 条要点式摘要。 ## 功能要求 - F1: 通过命令行参数接收 URL - F2: 抓取并解析新闻/博客正文去掉导航、广告等无关内容 - F3: 调用 LLM 生成 3 条要点式总结 - F4: 输出结果保持 Markdown 格式支持写入文件 ## 技术约束 - 使用 Python 3.9 - HTTP 请求使用 requests 库 - 正文提取使用 readability-lxml - LLM 调用封装为独立模块不绑定具体供应商 ## 验收标准 - 输入无效 URL 时输出友好错误信息退出码非 0 - 网络异常时程序不崩溃给出可读错误提示 - 演示模式下不依赖任何 API Key 也能跑完整流程 - 摘要输出格式为 Markdown 列表这份 SPEC 的核心价值是让模型和开发者对“完成”有统一的定义。4.2 编写 Coding Plan接下来创建 plan.md把整个开发拆成 4 个步骤。# Coding Plan ## Step 1项目骨架与网络请求模块 - 目标初始化项目结构实现 URL 校验和 HTML 抓取 - 输出fetch.py - 验收 1. 能抓取 example.com 首页返回字符串 2. 对非法 URL 抛出 ValueError ## Step 2正文提取 - 目标实现正文提取函数过滤无关标签 - 输出extract.py - 验收 1. 提取结果中不包含 script、style、nav 标签 2. 对空输入返回空字符串 ## Step 3LLM 摘要接口 - 目标封装 LLM 调用提供演示模式与真实模式 - 输出llm.py - 验收 1. 提供 build_summary_prompt(content) 生成提示词 2. 提供 demo_summarize(content) 返回演示摘要 3. 真实调用使用独立函数 call_llm(prompt) ## Step 4CLI 组装与输出 - 目标串联完整链路支持 --output 参数 - 输出cli.py - 验收 1. python cli.py --url https://example.com 正常输出 2. --output 参数能将结果写入文件按照这份计划每一步都短小精悍模型比较容易高质量完成任务。4.3 创建项目结构并安装依赖项目结构非常简单llm-scraper/ ├── cli.py ├── extract.py ├── fetch.py ├── llm.py ├── requirements.txt ├── plan.md └── spec.md在 requirements.txt 中写入requests2.28.0 readability-lxml0.8.1然后创建虚拟环境并安装依赖python -m venv .venv source .venv/bin/activate # Windows 系统使用.venv\Scripts\activate pip install -r requirements.txt4.4 编写核心代码创建 fetch.py。# 文件路径llm-scraper/fetch.py import requests def fetch_page(url: str, timeout: int 10) - str: 抓取指定 URL 的 HTML 原始内容。 Args: url: 目标网页地址。 timeout: 请求超时时间单位秒。 Returns: 网页的 HTML 文本。 Raises: ValueError: URL 格式非法时抛出。 requests.RequestException: 网络请求异常时抛出。 if not url.startswith((http://, https://)): raise ValueError(URL 必须以 http:// 或 https:// 开头) headers { User-Agent: Mozilla/5.0 (compatible; LLMScraper/1.0) } resp requests.get(url, headersheaders, timeouttimeout) resp.raise_for_status() return resp.text这里需要注意两点一是加了 User-Agent避免部分站点默认拒绝爬虫二是对 URL 做了前缀校验减少无意义的请求。创建 extract.py。# 文件路径llm-scraper/extract.py from readability import Document def extract_main_content(html: str) - str: 从 HTML 中提取正文内容。 基于 readability-lxml 的正文识别算法 可以自动过滤导航、广告、脚本等无关信息。 Args: html: 原始 HTML 文本。 Returns: 提取后的正文 HTML 片段。 if not html.strip(): return doc Document(html) return doc.summary()readability-lxml 会返回一段包含正文的 HTML 片段这个片段里已经没有主要的导航和脚本内容。创建 llm.py这里是整个项目中最值得展开的文件。# 文件路径llm-scraper/llm.py from typing import Protocol class LLMClient(Protocol): 定义 LLM 客户端的统一接口。 实际项目中可以把 OpenAI、通义千问、本地 Ollama 的 SDK 适配成这个协议。 def chat(self, prompt: str, system: str ) - str: ...然后添加提示词构建函数和演示模式函数。def build_summary_prompt(content: str) - str: 构造让 LLM 生成摘要的提示词。 return ( 请阅读以下网页正文内容提取 3 条核心信息 使用中文要点列表输出不要添加原文没有的内容。\n\n f正文内容\n{content[:8000]} ) def demo_summarize(content: str) - str: 演示模式不依赖任何 LLM直接截取正文前 200 字作为摘要。 这样做的目的是让整个流程在没有 API Key 时也能跑通 验证抓取、提取、输出三个环节是否正常。 plain_text content[:200].replace(\n, ).strip() return ## 摘要演示模式\n\n plain_text def call_llm(prompt: str, clientNone) - str: 调用真实 LLM 的示例函数。 不同云厂商、不同本地推理框架的 SDK 差异较大 这里刻意不绑定具体实现读者可以按自己使用的模型服务替换。 注意不要将 API Key 硬编码在代码中建议通过环境变量注入。 raise NotImplementedError(请替换为你自己的 LLM 客户端实现)这里要特别说明真实项目中的 LLM 调用一般需要我们自己根据所选供应商的 SDK 来实现。不同厂商的接口、鉴权方式、模型名称都不一样直接给出某一家 SDK 的写法反而容易误导读者。所以本文把调用点抽象出来后续接 OpenAI、通义千问、Moonshot、Ollama 时只需要实现这个函数即可。创建 cli.py把前面的模块串起来。# 文件路径llm-scraper/cli.py import argparse from fetch import fetch_page from extract import extract_main_content from llm import build_summary_prompt, demo_summarize def main() - None: parser argparse.ArgumentParser( description抓取网页并生成 LLM 摘要 ) parser.add_argument(--url, requiredTrue, help目标页面地址) parser.add_argument( --output, default, help输出 Markdown 文件路径 ) parser.add_argument( --real, actionstore_true, help启用真实 LLM 调用需要自行实现 llm.call_llm, ) args parser.parse_args() try: html fetch_page(args.url) content extract_main_content(html) if args.real: # 真实模式需要先在 llm.py 中实现 call_llm from llm import call_llm # 延迟导入避免无实现时影响演示模式 prompt build_summary_prompt(content) result call_llm(prompt) else: result demo_summarize(content) if args.output: with open(args.output, w, encodingutf-8) as f: f.write(result \n) else: print(result) except ValueError as e: print(f输入错误{e}) raise SystemExit(1) except Exception as e: print(f执行失败{e}) raise SystemExit(1) if __name__ __main__: main()这里把异常处理放在了 CLI 入口处保证网络错误、解析错误不会让程序直接抛出毫无可读性的堆栈。4.5 运行与验证先运行演示模式python cli.py --url https://example.com预期输出类似## 摘要演示模式 Example Domain This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.再测试写入文件python cli.py --url https://example.com --output summary.md cat summary.md如果需要真实 LLM 摘要则需要先在 llm.py 中实现 call_llm然后运行export LLM_API_KEYyour-key-here python cli.py --url https://example.com --real完成这个实战项目后你可以回顾一下整个开发过程的核心是先写 spec 约束目标再用 Coding Plan 拆分步骤最后才让模型逐步实现。即使换成更高难度的项目这套工作流也是通用的。5. 常见问题与排查思路问题现象常见原因解决思路Agent 一直改代码但越改越差上下文堆积过多历史修改记录目标漂移重新开启会话只粘贴原始 SPEC、当前代码和最新报错生成的代码能跑但后续维护困难只强调功能交付没有约定模块结构和代码风格在 SPEC 中增加“技术约束”提前约定文件名、函数签名、异常策略提示词越来越长效果却没有提升提示词内卷缺少可执行的验收标准把“希望模型怎么做”改成“希望模型输出什么”用测试命令做约束多 Agent 协作时出现重复代码或冲突职责边界不清晰多个 Agent 同时修改同一份文件用 Coding Plan 固定每个 Agent 的输入输出目录让开发者统一合并敏感信息被拼进提示词代码中硬编码了 API Key 或数据库密码使用环境变量注入禁止把密钥文件提交到 Git模型生成了项目里不存在的依赖SPEC 没有规定技术栈模型自由发挥在 SPEC 中写死依赖清单新增依赖必须由开发者批准下面挑三个最容易反复出现的坑仔细展开。第一个是“越改越差”问题。很多人以为给模型更多报错信息就能解决问题但实际并非如此。当对话中已经存在几百行失败代码时模型很难分清哪些是“废弃方案”哪些是“当前方案”。我建议的做法是一旦连续两轮修改都失败就果断开启新会话把 spec.md 中对应步骤的验收标准、当前文件的完整代码、最新一条报错粘贴进去重新开始。这比在旧会话里强行续命高效得多。第二个是可维护性问题。LLM 生成代码的风格往往不稳定可能这次生成一个 fetch_data 函数下次生成一个 get_html 函数。因此在 SPEC 的技术约束中应该尽可能明确模块文件名、核心函数签名和返回类型。这相当于给模型规定了“施工图”而不是让它自由创作。第三个是安全问题。如果你使用云端 LLM任何拼入 Prompt 的内容都会发送到第三方服务。不要将数据库密码、内部接口 Token、用户隐私数据直接放进提示词。更稳妥的方式是先用程序读取环境变量再在代码内部引用而不是让模型把密钥写死在配置文件里。6. LLM Coding 的最佳实践与工程建议6.1 设定“轮次预算”避免无限返工既然是逃离 Rat Race就必须约束“这个任务最多尝试几次”。我建议每个步骤设置一个轮次预算例如最多让模型修改 3 轮。如果 3 轮之后仍然无法通过验收那就不要再继续调试提示词而是停下来做三件事检查当前 SPEC 和 Coding Plan 是否足够清晰。检查是否有过时的上下文干扰了模型判断。考虑换一个模型或换一种思路重新实现。轮次预算的意义不只是控制时间更是强制你把注意力从“调 prompt”拉回“调方案”。很多看似是模型能力不足的问题根源其实是任务描述不合理。6.2 用“LLM Wiki”沉淀长期知识AI 编程中还有一个非常容易被忽视的点知识沉淀。我们自己学到的报错解决方案、项目中的架构决策、常用的命令片段如果每次都要靠重新聊天让模型再生成一遍那就等于反复做同样的事情。一个有效实践是建立个人或团队的 LLM Wiki。它本质上是一个 Markdown 目录比如放在 docs/wiki/ 下包含命令、踩坑记录、代码片段、项目决策等内容。在开启新的 LLM 会话时将相关条目粘贴进 Prompt作为模型可读取的长期上下文。举个例子假设你的团队经常处理 Electron 打包失败问题那么可以在 wiki 中写一条“electron-builder 在 Windows 下报 winCodeSign 错误的解决方案”。下次模型再遇到同类问题时直接把这条记录丢给它可以大幅压缩试错成本。这其实和 Andrej Karpathy 提到的 LLM Wiki 范式很相似把知识外部化、结构化让 LLM 不再是每次从零猜测的“脑子”而是配合一套可检索的外部知识库。6.3 团队协作让 Coding Plan 成为事实源如果你们团队已经在用 AI Agent 协作编码那 Coding Plan 的重要性会进一步提升。建议约定几条基本规则所有需求变更先修改 spec.md再让 Agent 动手。Agent 每次只领取 Coding Plan 中的一个步骤并在独立分支上工作。合入主分支前必须有开发者人工 review并由 CI 运行测试。任何 Agent 生成的代码都要能通过原有测试和新增测试。把 Coding Plan 当作团队的事实源本质上是把流程约束前置。开发者不再需要靠嘴记“上次让 AI 改了哪里”只需要看 plan.md 和 Git 提交记录即可。6.4 安全与权限边界最后强调一下安全和权限。无论是个人还是团队在使用 AI Coding 工具时都要注意密钥注入使用环境变量不要写死在代码或 Prompt 中。涉及数据库、线上环境变更的操作必须严格遵循最小权限原则先在测试环境验证。在真实模式下调用云端模型前先检查代码中是否可能包含敏感信息。对 Agent 生成的代码执行删除、覆盖、迁移数据等操作时务必先备份。这些建议不是限制开发效率而是为了避免“为了快几秒钟赔上整个晚上”。7. 总结与下一步回到文章开头那个场景当你发现自己正陷入 LLM Coding 的仓鼠轮真正要改变的不是“换一个更强的模型”而是工作流本身。这篇文章介绍了几个核心概念vibe coding 适合原型验证spec coding 适合生产交付而 Coding Plan 是连接两者的桥梁。围绕这三个概念我们拆解了一套工程化工作流先写规格文件约束目标再拆成带验收条件的小步骤每次只让模型处理一个明确任务最后用测试命令验证结果。为了帮助大家落地还完整演示了网页抓取与智能摘要项目的开发过程覆盖了从 spec.md 到 cli.py 的全部代码。接下来你可以从三个方向继续深入把这套方法用在一个你正在进行的真实项目中看看多久能脱离“无限试错”状态。学习多 Agent 协作和 MCP 等工具链让 Agent 能访问文件、数据库和外部 API进一步提高自动化程度。为你的团队制定一份 AI 编程规范明确 SPEC 模板、Coding Plan 格式和代码评审流程。如果这篇文章对你有帮助建议收藏备用尤其是第 4 节的模板代码和第 5 节的排查表格在实战中可以直接对照使用。接下来最重要的是找一个小任务亲自动手试一次。逃离仓鼠轮从来不是靠想清楚而是靠跑通一个完整的小项目。
返回列表