ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用LLM构建项目的实践:从原型到工程的隐藏成本与对策

用LLM构建项目的实践:从原型到工程的隐藏成本与对策 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 文件的交互上你很难判断该让模型改哪里。我的做法是拆成四个阶段先搭目录结构和空程序入口保证项目至少能启动。实现一条最小路径比如“读配置 → 读取输入 → 输出结果”。补充异常处理和日志。优化细节比如统一日志格式、增加重试机制、整理错误信息。每个阶段结束我都会运行一次确认没有引入新的问题。这个“小步快跑”的习惯在 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 生成的项目稳定运行我最先补的三样东西日志。模型生成的代码常常是没有日志的或者只有简单的 print。我把 print 改成统一的日志输出记录每次运行的输入路径、关键步骤、结果摘要和错误堆栈。重试。只要涉及网络请求或临时文件我都会加上有限次数重试。Picodevil 里有一个模块会读取远程内容我加了三次重试并在每次失败后等待递增的时间。验证。每次读取配置或输入文件后我会先校验字段是否存在、类型是否正确再继续执行。这样错误能在早期暴露而不是在某个深层函数里突然失败。这其实是一个通用骨架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_retries3): for attempt in range(max_retries): try: resp requests.get(url, timeout10) 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 模型会因为你没写约束自己编造不存在的 APILLM 的底层机制是预测下一个字不是查文档。所以它会在你语焉不详时编造一个看起来很合理但实际不存在的函数。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 按输入、环境、依赖、参数、工具边界逐层排查我建议按下面的顺序排查而不是直接看代码先看输入。输入文件是否存在、编码是否正确、字段是否齐全、是否有空值。再看环境。操作系统、Python 版本、当前工作目录、权限设置是否正确。然后看依赖。依赖是否安装完整、版本是否匹配、有没有引入不存在的库。接着看参数。配置文件里的路径、超时时间、重试次数、并发数是否合适。最后看工具边界。当前模型、库版本、平台是否支持你要用的功能。下面是一个简化的对照表排查层常见问题检查方式输入文件不存在、编码错误、字段缺失打印输入摘要统计长度和类型环境Python 版本、工作目录、权限不对运行pwd、python --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 构建一个项目我的建议分三步走实验阶段选一个很小的题目比如“把 Markdown 文件批量转换成结构化 JSON”。用 LLM 生成初稿跑通一遍。固化阶段把项目说明、提示词、样例输入输出、依赖版本都固定下来。每次修改都用同一套验证方式。工程化阶段补充日志、测试、异常处理、配置管理再把项目放到真实场景里用。不要一开始就追求“让模型生成一个完美的完整系统”。完美系统不存在LLM 更做不到。它擅长的是让你更快地拿到一个“差不多能跑”的版本然后你在这个版本上不断修正、补充和打磨。Picodevil 最终没有成为一个复杂的项目反而它的价值在于让我验证了一件事用 LLM 构建项目不是会写提示词就结束了。它改变的只是起点剩下的判断、边界、验证和取舍仍然要由人来完成。如果你也想用 LLM 做一个自己的小项目我的建议是别等先挑一个小到能被你完整理解的题目把手放到键盘上跑通一条最小链路。用不了太久你就会知道 LLM 能帮你到什么程度而你自己又该补上哪一块。
返回列表