用LLM构建项目的实践:从原型到工程的隐藏成本与对策
2026/8/28 20:00:47 网站建设 项目流程

Picodevil 是我最近用 LLM 从零搭建的一个实验项目。如果放在几个月前,我大概率会打开编辑器,先想类名、接口、文件目录,然后一行一行写。这次我没有,而是先打开一个对话框,告诉 LLM:我要做一个叫 Picodevil 的东西,这是我目前的想法。结果很直接:项目骨架几分钟就有了,比我自己敲快得多。但接下来几周里真正花时间的,并不是敲代码,而是跟模型生成出来的代码不断“纠缠”——处理它没考虑到的异常、统一它前后不一致的命名、补齐它省略掉的边界判断。

这段经历让我对“用 LLM 构建项目”这件事有了一个更清晰的判断:LLM 真正改善的不是“写代码”这个动作,而是把项目从“想法”到“可运行原型”的启动成本大幅拉低。但原型不等于产品,生成顺利也不等于工程稳定。这篇文章想用 Picodevil 的开发过程,拆开聊聊用 LLM 构建项目时到底有哪些隐藏成本,以及哪些做法能让这个流程真的可持续。

1. 为什么用 LLM 构建项目,而不是只让它写几个函数

很多人每天都在用 LLM 写代码,但绝大多数用法是单点补全:“帮我写一个 Python 函数,读 CSV 文件然后转成 JSON”。这类需求边界清晰,模型很容易完成,人也很快验证。真正不一样的是另一种用法:你没有一个完整代码库,只有一个模糊想法,需要一个 LLM 帮你从目录结构、模块划分、依赖选择到核心实现,一路生成出来。Picodevil 走的就是这条路。

1.1 单点补全和全项目生成是两种完全不同的用法

单点补全的本质是“翻译”和“填空”。你描述清楚了输入输出,模型负责把这段逻辑写对。它对整体架构的影响很小,出了问题也好定位。全项目生成则完全不同,它需要模型理解“一个项目为什么长成现在这样”,比如哪些文件应该放在哪里、哪些逻辑应该拆成模块、哪些配置应该集中管理。我最初以为把需求说清楚就够了,后来发现模型能生成一个“看起来正常”的项目,但你一运行,各种问题就冒出来了:缺少初始化、环境变量没读、异常被吞掉、不同文件之间的命名对不上。

这不是模型能力不够,而是它缺少一个明确的信息来源。它不知道你的运行环境,不知道你打算怎么部署,也不知道你习惯什么样的工程结构。它只能基于训练数据里的“常见项目”经验做猜测。所以全项目生成真正考验的不是模型,而是你有没有一套能把“模糊想法”变成“可执行项目”的方法。

1.2 真正的门槛从“会写代码”转移到“把需求说清楚”

过去写项目,最消耗精力的是把设计变成代码。现在 LLM 能自动完成大量编码工作之后,最消耗精力的反而变成了“把设计说清楚”。我在启动 Picodevil 之前,先在文档里写下项目目标、要解决的任务、运行环境、输入输出格式、允许使用的依赖。这一步看起来很笨重,但后来帮我避免了很多次“模型生成完,但方向完全偏了”的情况。

我使用了一个非常简单的需求说明书模板:

# 项目:Picodevil(示例结构) ## 目标 - 在本地完成一个辅助型开发工具 - 不做复杂界面,优先保证命令行可用 ## 输入 - 一个描述任务内容的文本文件 - 一个存放临时数据的目录 ## 输出 - 处理结果写到 output/ 目录 - 每次运行在 logs/ 目录生成日志 ## 约束 - 使用 Python 3.10+ - 只允许使用标准库和 requests - 配置通过 config.yaml 读取,不硬编码路径

这个模板本身不复杂,但它把模型需要关注的范围圈起来了。LLM 在没有明确边界时,倾向生成一个“更完整、更通用”的版本,结果就是过度设计。有了约束,它反而会收敛很多。

1.3 为什么用个人项目来验证这条路径是值得的

我之所以选择做 Picodevil 这样的个人实验项目,是因为它风险足够低。错了可以重来,不会影响团队或线上系统。同时,它又能暴露真实工程问题:路径、权限、日志、异常处理、增量迭代,一个都跑不掉。如果你想判断“LLM 辅助开发能不能进入日常流程”,最好的方式不是看一堆评测,而是自己做一个规模适中、你能完全理解的项目。

通过这个项目,我得到的最直接收获不是“代码生产率提升了多少”,而是知道了 LLM 的产出在哪个环节容易断裂。这个判断,只有在真实项目里才能建立起来。

2. 用 LLM 搭建 Picodevil 时,我是怎么把需求“喂”给模型的

很多人在 LLM 开发上遇到挫败,不是因为模型不够聪明,而是因为喂给模型的上下文有问题。要么给得太少,模型只能凭空想象;要么给得太多,模型被无关信息干扰。我在 Picodevil 的开发中逐渐形成了一套自己的做法。

2.1 先写一份项目说明书,而不是一句话需求

“帮我把这个项目写出来”是一句无效需求。模型不可能理解你的完整意图,它必须依赖你提供的上下文。我一般会在第一次对话开始时,把上面这类项目说明完整贴进去,然后明确告诉模型:先不要写代码,先根据说明拆解任务,列出你打算创建的文件和每个文件的职责。

这一步非常关键。它让模型在动手之前先做一次“项目规划”。如果它拆出来的文件结构和你心里想的不一致,这时候修改成本最低。等它真的开始写代码,再想纠正结构就费劲了。

2.2 把大工程拆成可验证的小任务

我不建议让模型一次性生成所有文件。Picodevil 的开发过程里,我明显感觉到:一次生成的代码越多,后续排查越难。因为问题可能出现在 A 文件和 B 文件的交互上,你很难判断该让模型改哪里。

我的做法是拆成四个阶段:

  1. 先搭目录结构和空程序入口,保证项目至少能启动。
  2. 实现一条最小路径,比如“读配置 → 读取输入 → 输出结果”。
  3. 补充异常处理和日志。
  4. 优化细节,比如统一日志格式、增加重试机制、整理错误信息。

每个阶段结束,我都会运行一次,确认没有引入新的问题。这个“小步快跑”的习惯,在 LLM 辅助开发里尤其重要。因为模型没有记忆压力,你在新对话里改动时,它不会自动记得上一个文件里写过的函数签名。

2.3 上下文管理:一次别放太多,但关键信息不能少

上下文窗口是有限的。如果我把整个项目的所有文件都贴进对话,模型会淹没在大量细节里,反而容易忽略真正重要的约定。我的策略是:在每次新的对话里,先重申项目目标和关键约束,然后只贴当前要处理的那个文件或模块。

比如要让模型修改某个处理函数,我不需要把整个项目的 README 都贴进去。我只需要告诉它:这是 Picodevil 的核心处理模块,输入是 X,输出是 Y,现在发现某个场景下会报错,请定位问题。然后附上相关代码片段。

同时,有一些信息是每次都要保留的:依赖版本、输入输出格式、项目路径。因为这些一旦被遗忘,模型就可能在生成的代码里引入不存在的依赖或错误的路径。

2.4 让模型先输出假设再生成代码

这是我后来养成的习惯,效果很好。在让模型写代码之前,我通常会在提示词里加一句:“先列出你的假设,再写代码。”比如它会写:

  • 假设输入文件是 UTF-8 编码。
  • 假设配置文件中已经存在data_dir字段。
  • 假设requests库已安装。

这些假设会让你提前看到模型没有考虑的边界。如果其中某一条不符合你的实际情况,你可以立即纠正,而不是等代码运行出错后才发现。

注意:生成代码前先确认假设,是一种成本极低的纠偏方式。别让模型默认你的输入是“完美输入”。

3. 从“单次生成能跑”到“整个项目能稳定跑”,中间发生了什么

第一次让 LLM 生成完整项目时,你会觉得事情变得特别顺利。代码看起来结构清晰,函数命名也规范,好像直接拿去用就行。但用久了你会发现,真正决定项目能不能长期跑下去的,不是那些好看的函数,而是模型经常忽略的异常路径。

3.1 模型生成的脚手架通常缺少异常处理

Picodevil 第一次能跑通的时候,我给它的输入是正常输入:文件存在,格式正确,网络也正常。所以它很顺利。第二次我故意给了一个空文件,程序直接抛 KeyError。原因是模型生成代码时,默认配置里一定有某个键,输入里也一定有某条记录。它没有写防御性逻辑。

这不是大问题,但暴露了一个规律:LLM 擅长处理“正常流”,不擅长主动考虑“异常流”。如果你不问它“文件不存在怎么办”,它就不会写。这个问题不是通过换一个更强的模型能解决的,而是要在需求阶段就把异常场景写进去。

3.2 真正的工程问题都藏在输入输出边界

做 LLM 辅助开发时,最常见的坑不是算法复杂,而是输入输出边界。比如:

  • 输入文件路径中有空格,程序没有处理。
  • 输出目录不存在,没有自动创建。
  • 配置文件编码不是 UTF-8,导致中文乱码。
  • 临时文件没有清理,第二次运行结果受影响。
  • 网络请求超时,程序直接崩溃。

这些都是单个看起来很小的点,累积起来就会让项目变得很“脆”。我在 Picodevil 开发中补的很多代码,都是这类边界处理。它不是核心功能,但没有它,项目就只能在“演示环境”里运行。

3.3 我后来补上的三样东西:日志、重试、验证

要让一个用 LLM 生成的项目稳定运行,我最先补的三样东西:

  1. 日志。模型生成的代码常常是没有日志的,或者只有简单的 print。我把 print 改成统一的日志输出,记录每次运行的输入路径、关键步骤、结果摘要和错误堆栈。
  2. 重试。只要涉及网络请求或临时文件,我都会加上有限次数重试。Picodevil 里有一个模块会读取远程内容,我加了三次重试,并在每次失败后等待递增的时间。
  3. 验证。每次读取配置或输入文件后,我会先校验字段是否存在、类型是否正确,再继续执行。这样错误能在早期暴露,而不是在某个深层函数里突然失败。

这其实是一个通用骨架:

import logging import time logger = logging.getLogger(__name__) def read_config(path): if not path: raise ValueError("config path cannot be empty") if not Path(path).exists(): logger.warning("config file not found, will use defaults: %s", path) return {} # 其他解析逻辑 return {...} def fetch_with_retry(url, max_retries=3): for attempt in range(max_retries): try: resp = requests.get(url, timeout=10) resp.raise_for_status() return resp.text except Exception: if attempt == max_retries - 1: raise time.sleep(2 ** attempt)

这段代码不是某个特定项目的实现,而是后来整理出来的通用做法。但它解决了 LLM 生成代码时最容易遗漏的两件事:可观测性和容错能力。

3.4 版本管理不只是代码,还有提示词和依赖版本

用 LLM 开发项目时,我很容易陷入一种状态:今天在这个对话里让模型改了日志模块,明天又在新对话里让它改核心算法。代码确实在推进,但如果依赖版本、提示词内容、模型输出结果没有记录,过两周再回头看,很可能无法重现当时的结果。

后来我会在项目里额外维护一个prompts/目录,把每次和模型交流的关键提示词按日期保存下来。同时,依赖文件锁定版本,不轻易用“latest”。这样即使模型版本升级导致输出行为变化,我至少还有一个可以回退的基准。

4. Picodevil 开发中最容易踩的 4 类坑

用 LLM 构建项目的过程,和传统开发最大的不同在于:你的协作对象不是人,而是一个“什么都懂一点,但什么都不敢保证”的模型。它的输出会给你信心,但也会制造假象。下面这四类坑,是我在 Picodevil 开发过程中真实遇到过的。

4.1 “看起来对了”和“真的对了”是两回事

模型生成代码后,最危险的反应是看一眼觉得“好像没问题”,然后就收下。因为语法正确、函数命名合理并不代表逻辑正确。Picodevil 里有一个很典型的问题:模型写了一个循环,用来过滤无效记录。运行时没有任何报错,但输出结果比预期少了 3 条。我查了很久才发现,过滤条件写反了,等于把有效记录过滤掉了。

这种问题在传统开发里也很常见,但在 LLM 辅助开发里更容易出现。因为你没有逐行“写”代码,你对它的信任度天然更高。所以我会准备一个小样本集,在每次修改后跑一遍,确认输出数量、字段值、格式都符合预期。

4.2 模型会因为你没写约束,自己编造不存在的 API

LLM 的底层机制是预测下一个字,不是查文档。所以它会在你语焉不详时,编造一个看起来很合理但实际不存在的函数。Picodevil 早期版本里,模型写了一个merge_configs方法,它认为这是某个库的接口。结果一运行,直接 ImportError。

这件事让我意识到:在提示词里一定要写明“不要假设第三方库存在,如果使用了任何外部库,请先确认安装命令和版本”。对于不确定的 API,我会要求模型在代码注释里标注# TODO: 需要确认此函数是否存在,然后我再统一检查。

4.3 上下文窗口限制导致改了一处、丢了另一处

LLM 不会自动记住你前面所有对话。即使同一个对话里有上下文窗口,也会有截断。当你让模型修改某个功能时,它有时会忘记另一个文件里已经定义好的同名函数,结果生成一个结构相似但细节不同的版本。最麻烦的是,这种情况不会立刻报错,而是等到两个模块交互时才暴露。

我的应对方法是:减少“大改”。每次修改只聚焦一个点,并且把修改完成后立刻测试。如果需要同时修改多个文件,我会让模型先生成一个修改计划,再按计划逐步执行,而不是让它一次性把所有改动都做完。

4.4 重复生成的代码不一致,后期维护变难

如果你每天都用 LLM 生成代码,很快就发现不同对话里生成的代码风格差异很大。一个函数有时用snake_case,有时用camelCase;有的模块用logging,有的模块用print。这些不一致会大幅提高维护成本。

我后来总结出来的经验是:在每次项目初始化时,就把“代码风格约定”写进说明文档,并在每次请模型生成或修改时都重申一次。比如:

  • 所有函数使用snake_case
  • 所有外部依赖在文件顶部导入
  • 每一级函数必须有 docstring
  • 错误优先抛出,不要在内部吞掉

这些看起来细碎,但能显著减少后续整理代码的时间。

5. 一套适合 LLM 辅助开发的排查链路

当 LLM 生成的项目出问题时,很多人会直接把错误信息贴回给模型,要求修复。这个方法有用,但不适合所有问题。我后来总结了一个更稳定的排查顺序,它让我的调试效率提高了很多。

5.1 先看现象,别急着改代码

先判断问题的性质,再决定怎么处理。通常有这几类现象:

  • 报错:有明确异常信息,可以通过堆栈定位。
  • 卡住:程序没有退出,也没有进度输出。
  • 无输出:程序正常退出,但没有产生结果文件。
  • 输出异常:有结果,但内容不对、数量不对、格式不对。
  • 速度慢:结果正确,但耗时远超过预期。

不同现象对应的排查路径不一样。报错通常最好处理,原因明确,模型也能很快修复。输出异常则要小心,因为可能是逻辑错误,不是代码错误。

5.2 按输入、环境、依赖、参数、工具边界逐层排查

我建议按下面的顺序排查,而不是直接看代码:

  1. 先看输入。输入文件是否存在、编码是否正确、字段是否齐全、是否有空值。
  2. 再看环境。操作系统、Python 版本、当前工作目录、权限设置是否正确。
  3. 然后看依赖。依赖是否安装完整、版本是否匹配、有没有引入不存在的库。
  4. 接着看参数。配置文件里的路径、超时时间、重试次数、并发数是否合适。
  5. 最后看工具边界。当前模型、库版本、平台是否支持你要用的功能。

下面是一个简化的对照表:

排查层常见问题检查方式
输入文件不存在、编码错误、字段缺失打印输入摘要,统计长度和类型
环境Python 版本、工作目录、权限不对运行pwdpython --version、检查文件权限
依赖库没装、版本不兼容查看requirements.txt,运行pip list
参数路径写死、超时太短、并发太高检查 config 文件,先降并发、加超时
工具边界函数不存在、能力有限制查文档,直接写最小示例验证

这个表看起来像是通用清单,但它能防止你被 LLM 生成的错误信息带偏。很多时候,模型会非常自信地告诉你“这是配置问题”,实际上只是输入文件路径少了一个斜杠。

5.3 每次修复后做一个最小回归

每次修复一个问题后,我都会运行一个很小的样例。这个样例通常只包含一个输入文件和一个预期输出文件,耗时几秒钟。通过之后,再运行真实数据。这个小习惯可以避免“修好一个问题,引入另一个问题”的循环。

我把这个样例放在tests/smoke/下,命名很简单:test_basic.py。它不追求覆盖所有情况,只验证核心路径是通的。在 LLM 辅助开发里,这个最小回归非常重要,因为模型往往只关注你让它修改的那一块,不会主动检查关联模块。

python -m pytest tests/smoke -x -q

只要这条命令能跑通,我就可以放心进入下一轮修改。

5.4 把排查结论沉淀成注释或文档

传统开发里,你写了代码,自己会记得为什么这么写。但在 LLM 辅助开发里,很多代码不是你写的,你只是“审核”过。如果不记录原因,过两周再看,你可能完全不理解这个try/except到底是为了解决什么问题。

我后来会在每个关键模块顶部加一段简短注释,记录这个模块的主要职责和已知限制。如果某次排查发现了特别隐蔽的坑,我会把它写进项目的docs/troubleshooting.md。这样下次遇到同类问题,不需要重新推理一遍。

6. 用 LLM 构建个人项目的适用边界和长期建议

不是所有项目都适合用 LLM 从零构建。Picodevil 是一个实验项目,失败成本很低,所以我敢放开手脚。如果你要维护的是一个线上系统,或者要求高稳定性的生产服务,那就要谨慎很多。

6.1 适合谁、不适合谁

先给一个粗略的判断框架:

人群是否适合原因
有编程基础,但想加快原型验证的人很适合LLM 能快速生成初稿,你能读懂并修改
完全不懂编程的人不适合代码出错后无法定位,容易陷入死循环
想学习编程的人有一定帮助,但要注意边界可以看生成代码,但不能只看不思考
维护复杂业务系统的人不适合直接全量生成需要大量上下文和工程实践,模型难以替代
做一次性脚本、实验工具的人很合适需求相对简单,容错要求低

Picodevil 处在一个比较理想的位置:它有足够多的边界问题需要处理,但范围和风险都可控。这种项目最适合用来测试“LLM 辅助开发”的工作流。

6.2 从“玩具项目”到“生产项目”还差什么

用 LLM 生成的项目,天然更接近“原型”而不是“产品”。如果你要把它变成真正的生产项目,至少还要补上这些:

  • 自动化测试,尤其是异常路径测试。
  • 持续集成,每次改动后自动运行测试。
  • 日志和监控,程序运行状态要可观测。
  • 文档,说明如何部署、配置、使用和排障。
  • 安全和权限,不要硬编码密钥,不要以过高权限运行。
  • 数据备份和回滚策略,处理线上运行时可能出现的脏数据。

这些都不是 LLM 能替你完成的。它能帮你节省写代码的时间,但设计一套适合项目的测试策略、划分模块边界、评估安全风险,仍然取决于你对问题的理解程度。

6.3 我的建议:先把最小可用流程跑通,再逐步扩展

如果你现在也想用 LLM 构建一个项目,我的建议分三步走:

  1. 实验阶段:选一个很小的题目,比如“把 Markdown 文件批量转换成结构化 JSON”。用 LLM 生成初稿,跑通一遍。
  2. 固化阶段:把项目说明、提示词、样例输入输出、依赖版本都固定下来。每次修改都用同一套验证方式。
  3. 工程化阶段:补充日志、测试、异常处理、配置管理,再把项目放到真实场景里用。

不要一开始就追求“让模型生成一个完美的完整系统”。完美系统不存在,LLM 更做不到。它擅长的是让你更快地拿到一个“差不多能跑”的版本,然后你在这个版本上不断修正、补充和打磨。

Picodevil 最终没有成为一个复杂的项目,反而它的价值在于让我验证了一件事:用 LLM 构建项目,不是会写提示词就结束了。它改变的只是起点,剩下的判断、边界、验证和取舍,仍然要由人来完成。如果你也想用 LLM 做一个自己的小项目,我的建议是别等,先挑一个小到能被你完整理解的题目,把手放到键盘上,跑通一条最小链路。用不了太久,你就会知道 LLM 能帮你到什么程度,而你自己又该补上哪一块。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询