☰
一个人、九个月、20万行代码:Harness架构下的大模型工作流平台实践复盘
2026/10/1 1:43:04 网站建设 项目流程

一个人、九个月、20 万行代码、每个月烧掉 40 亿+ token。这几个数字单独拿出来都还好,放到一起看,任何人第一反应都是:要么吹牛,要么疯了。我属于后者。这个项目是一款基于大模型的工作流自动化平台,核心架构用的是 Harness 模式:模型只负责做决策,外部代码负责校验、执行和兜底。九个月跑完之后我复盘了一下,开头那四个数字其实一点都不夸张,反而是这次开发过程里最诚实的账单。

这篇文章我没打算写成项目总结报告,就像同行之间聊天一样,把这九个月的完整过程拆开讲一遍:为什么选 Harness 架构而不是 Agent 模式,40 亿 token 到底烧在了哪些地方、怎么把成本压下来,20 万行代码是怎么一步步堆出来的,以及一个人单独扛一个重度依赖 AI 的项目,会踩到哪些平时文档里永远看不到的坑。

1. 项目定位与 Harness 架构的选择

1.1 这个项目到底在解决什么问题

先说清楚我造的是什么。它的定位是让用户用自然语言描述任务,系统自动拆解成可执行的工作流,然后调用大模型做规划、调用工具实际执行,最后把结果以结构化形式交付。听起来很像现在烂大街的 Agent 平台,但我从一开始就和它划清了界限:我要做的不是让模型自由发挥的通用 Agent,而是一个"套上缰绳"的自动化引擎。用户说"把上个季度的销售数据整理成报告,发给对应负责人",系统会生成计划,逐项执行查询数据库、计算指标、生成报告、发送邮件这些动作,整个过程模型的每一步输出都要经过严格校验,关键操作还要人工确认。

核心用户其实是两个群体:一部分是中小团队里被重复报表和跨系统搬运数据折磨的运营,另一部分是已经厌倦了在多个后台来回切换的管理者。项目的价值不在于模型多聪明,而在于它能把一堆琐碎的"数字搬运工"式工作给接住,并且出错的时候能找到责任环节——这是纯聊天机器人给不了的。

1.2 Harness 架构不是框架,是一种控制思想

这里必须把 Harness 架构讲透,因为很多人一听这个名字就以为是某个开源框架,实际上它是一种组织模型与外部代码关系的控制思想。

当前做 LLM 应用的主流套路有三种。Chain 模式是一根管道,把输入按固定顺序流过几个步骤,简单但几乎没什么智能。Agent 模式反过来,让模型在一个循环里自主决策、自主调用工具、自主判断完成与否,灵活但极其不可控,经常出现"让它查个数据,它给用户编了一段不存在的数字"的情况。Harness 模式正好在两者之间:模型确实在一个循环里做决策,但每一步都被外部代码用硬约束"圈"住。

我用一个比喻理解这件事:Agent 是放野马,Chain 是给马拴在磨盘上转圈,Harness 是建一个带围栏的跑马场。马能在围栏里随意跑,但跑不出边界,跑错了方向会被即时拉回来。落在工程上,就是四道闸门:结构化输出必须符合 schema、可调用的工具必须是白名单内的注册项、每一步状态变化都落库并在下一次请求时重建、工具执行结果必须过校验网关才能进上下文。模型有决策的自由,但没有任何"乱来"的自由。

1.3 一个人能不能扛住这么重的项目

九个月之前我自己也在问这个问题。当时摆在我面前的无非是两条路:找个合伙人,或者一个人硬扛。找合伙人的问题是对这类项目的认知很难对齐,对方要么觉得"这不就是个套壳应用",要么觉得"太复杂了做不成"。于是我做了个判断——正是 Harness 架构本身帮了大忙,因为这个架构天然把系统切成了边界清晰的模块:引擎层、工具层、数据层、前端各自独立,我一个人可以用"串行模块化"的方式推进,而不是所有事情同时开弓。

另外必须承认,AI 辅助开发的成熟是把这件事从"疯狂"变成"可以试试"的关键变量。九个月前我评估过,同样规模的代码如果全靠手写,一个人全职至少要一年半,但 AI 能把编码环节压缩掉大概一半时间,代价就是 token 账单飞涨。既然选择了这条路,就要接受一个铁律:每一笔 token 消耗都必须对应可验证的进展,烧而不学、烧而不产出,那是真烧钱。

2. 九个月时间线:20万行代码是拆出来的,不是写出来的

2.1 前三个月:原型期,验证闭环比功能全更重要

前三个月我的目标极其克制:不追求功能多,只验证一件事——"模型输出 → 工具执行 → 校验反馈 → 修正重试"这个闭环能不能稳定跑通。

第一个月全部花在技术选型和最小原型上。后端定了 Python + FastAPI,原因是 AI 生态最成熟;前端用 React + TypeScript;存储选 PostgreSQL 做业务数据,Redis 做队列和缓存;模型侧不绑死任何一家,做了一个多路由层,云端和本地模型按任务类型分流。这个阶段的核心 demo 只有两个动作:解析用户自然语言指令,然后调一个查天气的接口,把结果按固定 JSON 结构返回。听起来寒酸,但足够验证 schema 校验、工具调用、错误重试这一整套机制。第二个月开始往里面加真实业务工具,比如从数据库里拉表、计算汇总、生成 Markdown 报告。第三个月我的目标只有一个:让端到端 demo 稳定连续跑一周不崩。

整个原型期大概烧了 3 到 5 亿 token,大头都在探索性的 prompt 调试和模型能力测试上。这个阶段不要心疼钱,选型和架构方向错了,后面省下的 token 都会变成返工成本,反过来烧更多。

2.2 中间三个月:从"会跑"变成"能扛"

原型跑通之后的三个月,是整个九个月里最痛苦也最高产的时间。这期间我把 demo 往真实产品方向推:多租户与权限体系、任务队列、审计日志、多模型路由、限流降级、失败任务的自动恢复。

有一个细节我记得很清楚,就是幂等设计。因为任务里很多工具调用是不可逆的副作用操作,比如发邮件、写入数据库、调用第三方接口。如果任务中途断了重试一次,能不能保证不重复发邮件?解决办法是给每个任务生成全局唯一的执行 ID,工具调用前先登记"将要执行",执行后再登记"执行结果",恢复执行时先查登记状态,已经成功过的操作直接跳过。这个设计不复杂,但极其重要,而且必须趁早做,等到任务类型多了再补幂等会改动大量代码。

这段时间也是代码量爆发期。从第三个月末的几千行,到第六个月末已经接近 13 万行。前端界面从一页变成十几个页面,工具集成从两三个扩展到三十多个,测试也从零增加到差不多 2 万行。中间三个月里我每天的节奏几乎是固定的:上午处理前一天跑出来的报错,下午给 AI 辅助工具下编码任务,晚上做代码审查和数据模型调整。

2.3 最后三个月:打磨、压测、部署、交付

第七个月开始,重心从"造功能"转向"去毛病"。这个阶段做的事主要有四件:第一,性能压测,把并发从 10 个任务压到 100 个,过程中暴露了不少数据库连接池、Redis 队列积压和上下文重建耗时的问题;第二,prompt 和 schema 的全面收敛,把所有散落各处的系统提示词收编成配置化管理,配上版本号,每次改动可追溯;第三,补 CI/CD,把自动化测试跑进流水线,同时接上代码签名和跨平台打包;第四,专门花了两周时间梳理 token 链路,把模型服务的认证、刷新、限时重试做了统一封装。

代码量统计在这里有一个很有意思的现象:最后三个月新增代码只有 5 万行左右,但 git 提交次数占了全项目的大约一半。原因是大量时间花在重构和修 bug 上,我才真正理解"20 万行代码不是写出来的,是改出来的"这句话。

2.4 20 万行代码到底长什么样

很多人对"20 万行"没有概念,我拆个账给你看:

模块代码量(约)说明
核心引擎(Harness Runtime)3.5 万行请求调度、schema 校验、重试网关、状态管理
工具与集成层4 万行30+ 工具,每个含注册定义、执行实现、参数校验
前端6 万行工作流编排界面、任务监控、结果可视化
数据层与迁移1.5 万行业务表、任务表、审计表、迁移脚本
测试2.5 万行单测、集成测试、Eval 回归样本
运维、部署与脚本1.5 万行CI/CD、打包、监控、日志采集
文档与配置1 万行API 文档、配置模板、prompt 版本化文件

看到没有,核心引擎本身只占不到两成。剩下的全是围绕它的"基础设施",这就是 Harness 架构的特点:模型决策是心脏,但让心脏能安全跳动的是外面那一层层血管和骨架。一个人写 20 万行确实累,但拆成模块之后,每天推进几百行是可行的,关键是要有清晰的结构感,不能想到哪写到哪。

3. 每个月 40 亿 token 是怎么烧掉的:成本台账与优化手段

3.1 先看账单:钱到底花在哪四个地方

每个月烧 40 亿 token 听起来像天文数字,但把它拆到四个账户里就完全不奇怪了。

第一个账户是开发期 AI 辅助编程,大概占 9 到 10 亿。现在用代码助手写代码,随便一个跨文件的改动就可能消耗几十万 token,因为要反复把上下文喂进去。九个月算下来,开发期累计消耗超过 80 亿。第二个账户是运行时推理,也就是产品本身被用户使用时调用的模型,占比最大,每月 20 到 22 亿。我们的任务平均每次要携带业务上下文和检索结果,输入经常到 2 万 token 以上,加上输出和重试,单个任务的 token 消耗能到 4 万左右。第三个账户是自动测试与回归,每月 6 到 8 亿。我会用 AI 批量生成测试样本,再把样本集跑一遍比对结果,这个 Eval 过程非常费钱但非常值。第四个账户是数据分析和 prompt 实验,每月大约 1 到 2 亿。

算一笔粗略的账:40 亿每月,除以 30 天,每天 1.3 亿;假设单个任务平均消耗 4 万 token,相当于每天要处理 3300 个任务。对一个真实的自动化平台来说,这个量级其实谈不上夸张,只是把成本集中到一个账户里时,数字显得吓人。

3.2 第一次看到账单时我做了什么

第一次月账单出来的时候我是有点崩溃的,因为实际花费比预估高了将近一倍。我做的第一件事不是换便宜模型,而是把所有任务的日志拉出来,按模块统计 token 消耗分布。结果发现问题不在模型单价,而在两个我之前完全没意识到的点:一是上下文里塞了太多用不到的字段,系统从数据库查出整个业务对象就往 prompt 里丢;二是重试逻辑写得粗糙,几乎每次失败都要把全部上下文重发一遍,重试成本翻倍。

那个星期我干了三件事:写了一个按模块聚合的 token 记账脚本,给每个任务打了 token 预算标签;把所有工具上下文改成字段裁剪模式,只传和当前决策相关的字段;重试改成增量模式,错误信息单独追加,而不是整个上下文重发。这三件事做完,第二个月账单直接降了 22%。

3.3 压 token 成本的六个实操手段

烧了九个月的钱,我总结出六个真正有效的手段,按投入产出比排序:

第一,字段裁剪与摘要替代。能只传 10 个字段就不要传整个对象,数据量大时先让便宜的模型生成摘要,再把摘要传给贵的模型做决策。这个手段省下的 token 最多。

第二,按任务复杂度做模型路由。简单任务用本地小模型或者便宜的云端模型处理,复杂决策才上旗舰模型。我先在小模型上做一轮工具调用和 schema 校验,只有识别为高复杂度任务时才升级模型。

第三,缓存工具结果。相同参数的查询、相同模板的报告,直接命中缓存,连模型调用都省了。语义缓存需要做向量检索,精确缓存只需要一个 hash key,先做精确缓存,见效最快。

第四,重试预算机制。每个任务定义 token 预算,比如 5 万 token,预算耗尽或者重试超过三次,任务自动降级为"进入人工处理队列"。预算机制最大的好处是让成本可控,而不是让单次异常请求把账单拖爆。

第五,上下文窗口管理。历史对话记录不要全量重放,用滚动窗口保留最近几轮加之前轮次的摘要。Harness 架构这里的优势很大,因为状态都在库里,上下文本来就是每次重建的,重建时该放什么完全可控。

第六,prompt 瘦身。系统提示词每多一百个 token,乘以每天成千上万次调用,一个月就是几亿 token。我每两周做一次 prompt 审查,去掉冗余的 few-shot 示例和重复指令。

4. Harness 架构核心实现:让模型只做决策,不碰状态

4.1 结构化输出的硬约束:schema 就是骨架

Harness 架构的第一道闸门是结构化输出。所有模型的输出,不管是任务计划、工具参数还是最终报告,都必须先过 json schema 校验,校验不过就重试。

我前后端各选了一个 schema 工具:Python 侧用 Pydantic,前端侧用 Zod。定义一个任务计划的 schema 大概长这样:

from pydantic import BaseModel, Field from typing import List, Literal class ToolCall(BaseModel): tool_name: str = Field(description="工具注册名,必须在白名单内") params: dict = Field(description="严格符合工具定义的参数字段") reason: str = Field(description="为什么要调用这个工具") class TaskPlan(BaseModel): objective: str = Field(description="对用户目标的重述") steps: List[ToolCall] = Field(min_length=1, max_length=8) validation_rules: List[str] = Field(default_factory=list)

模型输出之后,代码用 Pydantic 解析,失败就带着具体的校验错误让模型重新生成。这里有个关键技巧:重试时只把"哪一步校验失败、为什么失败"喂回去,而不是整个原始输出原样重发,token 省一半,成功率反而更高。

4.2 工具注册中心:把所有副作用关进笼子

Harness 架构里,模型能碰到的所有外部世界都经过工具注册中心。每个工具在注册时定义三样东西:工具名称和描述、参数 json schema、执行函数。模型永远不能直接写数据库或直接调 HTTP 接口,它只能输出"我想调用 tool_a,参数是 xxx",然后由注册中心决定放不放行。

放行逻辑也很严格。首先是白名单检查,工具必须存在于注册表;然后是参数校验,用工具的参数 schema 跑一遍;第三是权限绑定,当前用户的角色决定他能调用哪些工具;最后是副作用隔离,工具执行放在独立的工作进程或者容器里,即使工具代码写崩了,也不会拖垮整个引擎。

这里我踩过一个印象深刻的坑。早期有个工具是执行用户上传的 Python 脚本,我图方便直接在主进程里用了 exec。结果某次测试脚本里有个死循环,把整个服务拖挂了,所有任务全部失败。后来我把脚本执行挪到了独立沙箱进程,加了内存和 CPU 限制才算解决。Harness 的核心精神就是:模型是大脑,但四肢必须带锁链。

4.3 状态外置:上下文只是工作记忆,不是仓库

很多人做 LLM 应用最容易犯的错误,是把所有历史记录、业务对象、中间结果统统塞进上下文,觉得模型"记得住"就万事大吉。Harness 架构对这件事的态度是完全相反:所有状态都必须外置到数据库,上下文只是每次请求重建出来的"工作记忆"。

具体实现是这样的。任务开始后,引擎在 PostgreSQL 里创建任务记录,每一步的工具调用输入输出都落库,状态机记录任务当前处于 planning、executing、waiting_confirm、completed 和 failed 哪个阶段。下一次请求模型时,不是把历史全量塞给它,而是用摘要器从库里生成一段紧凑的进展摘要,再附加当前这一步需要的具体数据。

这样做有三个好处。第一,上下文不会无限膨胀,token 消耗可预测;第二,任务崩溃后可以从库里的状态恢复,而不是从头再来;第三,每一笔操作都有审计痕迹,出问题时能准确回答"这个任务当时是执行到哪一步、结果是什么"。最后这个好处对 To B 场景简直是刚需,客户法务来问数据从哪来到哪去的时候,没有审计日志你会非常被动。

4.4 校验网关与人工介入通道

前面几道闸门管格式和权限,校验网关管的是内容和业务规则。模型生成的计划,要检查它的目标是否和用户请求一致,工具调用的参数有没有明显不合理的地方,输出结果会不会包含不该出现的内容。

我设计了一个双层校验。第一层是自动校验,用固定的规则集,比如金额必须大于零、日期格式必须正确、涉及删除操作必须附带确认标记;第二层是语义校验,让一个便宜模型快速检查输出是否偏离了原始意图,相当于"廉价版的二次审核"。两层都过了才放行,任何一层不过就进入重试或人工队列。

人工介入通道是 Harness 架构最后一道保险。凡是对外发送邮件、删除数据、调用第三方写接口这三种操作,必须先进入 waiting_confirm 状态,等用户在界面上点确认才真正执行。这个设计牺牲了一点全自动的程度,但换来了极高的信任度。实际运营下来,大约有 15% 的任务会触发人工确认,用户没有任何抱怨,反而觉得系统"稳重靠谱"。

5. 一个人开发最容易踩的坑:问题排查实录

5.1 上下文漂移:AI 改代码越改越乱

一个人加 AI 辅助开发,遇到的最大敌人不是 bug,而是"上下文漂移"。现象很典型:让 AI 助手改一个功能 A 的代码,它顺手把相邻功能 B 的代码也改了,改动还不一定是错的,但风格和逻辑已经和原来不一致。等到下次再改 B 的时候,又基于被改过的代码继续发散,代码越改越乱,最后整个模块像被人反复揉过的纸团。

我的解决方法是三板斧。第一,小步提交,每次让 AI 干的活必须限定在一个文件内,改完立即审查并提交 git,绝不攒着;第二,用强类型检查和 lint 做门禁,接口签名变了、函数没返回值了,这些错误在构建阶段就会被拦住;第三,维护一份"禁改清单",核心引擎和状态机代码标注为"只能由我手动修改",AI 的修改建议只做参考不当真。效果非常明显,第六个月之后,代码熵增的速度明显降下来了。

5.2 token 认证链路:应用层最经典的翻车现场

做这类平台,绕不开认证和 token 管理。用户侧的登录我用的是 JWT 方案:access token 有效期短,refesh token 有效期长,每次请求带着 access token 访问,过期后用 refresh token 换新的,换了就旋转,旧 refresh token 作废。这个方案本身很成熟,但实现时问题多得让人头大。

最经典的一个错误场景是 sign-in could not be completed token exchange failed,看到这个报错先别怀疑是模型的错,大概率是 refresh token 没存好或者过期策略设计有问题。我把常见问题整理成了速查表:

报错关键词可能原因处理方式
token exchange failed / refresh_token 为空refresh token 没写入存储,或 cookie 过期被清检查存储时序,先落库再返回;统一封装刷新接口
400 bad request: invalid refresh_tokentoken 被旋转后旧 token 未清理,重复使用旋转后立即把旧 token 标记失效,客户端并发改签时加锁
failed to refresh tokenrefresh token 过期或签名密钥变更设置合理的过期时间,密钥轮换要提供旧密钥宽限期
登录后立刻失效access token 和 refresh token 的签名算法不一致排查签发和验签的 JWT 配置是否相同

第三方模型服务的 token 链路同样要处理。我的做法是写了一个统一鉴权中间件,负责获取、缓存、刷新模型服务的访问凭证,刷新失败就自动进入优雅降级模式,把请求转到备用模型通道,而不是直接报错吓用户。这些模块平时不显山不露水,但恰恰是用户"用得顺不顺"的分水岭。

5.3 打包部署的经典暗坑:以 Windows 环境为例

最后的发布阶段遇到一个特别典型的坑:用户下载了客户端,双击报"由于找不到 msvcp140.dll 无法继续执行代码"。这个错对开发者来说见怪不怪,但对用户来说就是"你软件有问题"。

原因是开发机装了完整 VC++ 运行库,打包时没有把运行库打进去。解决方案有两条,要么在打包配置里把 VC++ 运行库的 DLL 一并带上,要么让安装包在静默阶段检测环境并自动安装对应运行库。我当时因为懒得改安装包逻辑,直接改成了静态链接,包变大了 30 兆,但问题彻底消失。

顺便说一句代码签名。Windows 上分发绿色版 exe,SmartScreen 弹"未知发布者"会让信任度大打折扣,所以我又去签了一个 Certum 的代码签名证书。这里要注意的是证书申请要提前准备,因为验证流程要好几天,别等到发布前一天才想起来,那只能干着急。

5.4 测试的取舍:一个人不可能什么都测

一个人、20 万行代码,你说要全量测试覆盖,那是骗自己的。我的取舍原则是:核心路径彻底测,边缘功能冒烟测,界面 UI 不测。

具体来说,schema 校验、状态机转换、工具调用幂等性这三块是核心路径,必须有完整单测和集成测试;30 多个工具里,高频的那七八个做深度测试,其余只测参数校验和返回格式;前端 UI 不做自动化测试,把所有逻辑尽量下沉到可单测的纯函数里,界面只负责渲染。

另外我搭了一套很简陋但极好用的 Eval 回归:准备 200 个典型任务样本,每周全量跑一遍,用一个便宜模型给结果打分,分数下降超过阈值就触发告警。这套东西在迭代 prompt 时简直是救命稻草,因为它能在我手动测试之前就把"这次改动导致整体变差"这种事暴露出来。

6. 给同样想单干的人:经验与建议

6.1 工具链怎么配才顺手

一个人干这种项目,工具链必须精悍。代码编辑器我用 VS Code 配上 AI 助手插件,虽然不少人推荐更"重"的 AI 原生 IDE,但 VS Code 胜在稳定和生态。有一个小坑倒是可以提醒:很多人说"vscode 写 C 没有代码提示",大部分情况不是插件问题,而是没装 C/C++ 扩展的 IntelliSense 配置没做,在 settings 里指定一下编译器路径就好,和 AI 没什么关系。

命令行侧我同时用了 Codex 和 Claude 的 CLI 工具,各有分工。Codex 适合处理明确的、范围小的代码生成任务,Claude 适合做跨文件重构和逻辑梳理。两个工具共同的特点是吃 token 极快,所以一定要配合前面说的记账脚本用,每周看一次消耗,能及时发现异常。

版本管理没什么特别的,就是 git 加一个强制规范:每次 AI 改动必须开新分支,测试通过才允许合并。这个规范我执行得最彻底,也最受益。因为 AI 生成的代码质量方差很大,新分支隔离让我可以随时回退,避免污染主干。

6.2 时间与心态的四个原则

九个月的周期是合理的,不要幻想压缩到三个月。我把整个周期拆成三个阶段,每个阶段都有一个明确的"定义为成功"的标准。原型期成功 = 闭环稳定跑一周;工程期成功 = 并发 100 不崩;打磨期成功 = 连续两周零致命告警。有了定义,就不容易把项目做成无底洞。

第二个原则是每两周砍一次需求清单。我建了一个"想法池",所有临时冒出来的功能点先丢进去,每两周只挑一个最值得做的捞出来,其余继续躺着。这个动作治好了我"什么都想要"的毛病。

第三个原则是永远维持一个可演示状态。不管代码多丑,每个周末结束的时候,系统必须能跑通一两个完整的端到端任务。这不是为了给别人看,是为了让自己保持正反馈,人不是机器,连续三周看不到成果是会崩溃的。

第四个原则是保护自己的精力。我每天只给自己安排三个深度工作时段,每个 90 分钟,上午、下午、晚上各一个,其余时间处理零碎事务和休息。AI 辅助开发最大的风险是让人以为"效率可以无限压榨",实际你会发现,上下文质量比输入速度重要得多,脑子不清醒的时候喂给 AI 的指令也是糊的,返工成本比休息的成本高十倍。

6.3 最后想给 token 消耗正名

现在很多人看到"烧掉 40 亿 token"就觉得是浪费,我不太同意。重度的 AI 辅助开发确实烧钱,但它的价值在于把一个人的生产力拉到了接近一个小团队的水平。前提是每一笔消耗都对应可验证的进展:要么是新功能跑通,要么是旧 bug 修复,要么是知识补齐。烧而不学、烧而无产出,那才是真的浪费。

我的做法是写了一个记账脚本,把 token 消耗按模块和时间线聚合,再和 git 提交记录关联。这样每个月光看报表就知道钱花到哪了,哪块产出高,哪块是无效消耗。九个月下来,我清理掉了大概 15% 的无效 token 消耗,主要来自重复实验和上下文冗余。

如果你也想走这条路,记住一句话:代码行数和 token 消耗都不是目标,它们只是你在解决真实问题时留下的脚印。九个月之后真正沉淀下来的,不是那份 20 万行的代码库,而是你对"如何让模型安全地干活"这件事的完整理解。这个东西,任何框架都替代不了。

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

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

立即咨询