
1. 为什么是 Harness把大模型的蛮力套上缰绳去年这个时候我站在一个选择面前加入一个成熟团队按固定节奏迭代产品还是自己把脑子里那个 AI 原生工作台真正做出来。我选了后者然后用了整整九个月。九个月后项目上线并稳定运行留下的数字是一个人、约 20 万行代码、每个月烧掉 40 亿 token。这篇文章不是融资故事也不是励志鸡汤而是这个系统怎么从零到一以及最关键的三层设计——为什么选 Harness 架构、怎么把 20 万行代码拆开、怎么把每月 40 亿 token 的账管明白。先说 Harness 架构到底是什么。Harness 这个词来自软件工程里的 Test Harness指的是一套围绕被测对象搭起来的驱动、约束与观测装置。我把它借用到大模型应用里以受控方式驱动模型、约束模型行为、并完整观测每次调用的框架就是一套 LLM Harness。模型再强本质还是一匹力气很大的马奔跑的方向、速度、停在哪个路口都需要马具来控制。没有马具你只是在跟着马乱跑有了马具你才能把它的能力用于完成实际的运输任务。1.1 失控感从哪来裸调 API 的混乱最早期我也写过一版原型思路非常直接业务代码哪里需要模型就在哪里发一次请求。写起来确实爽两三行代码就能让一个函数具备智能。但两周之后问题开始像潮水一样涌出来。首先同一个业务场景的提示词散落在十几个文件里。用户反馈某个回答变啰嗦了我要全局搜索字符串手工找出所有相关调用点逐一修改改完还不能确定有没有漏。其次模型输出的格式极不稳定今天返回 JSON明天可能在 JSON 外面包一层 Markdown 代码块后天又给你多写几个中文字段。每个调用点都要自己处理解析、校验、重试二十个调用点就是二十份各自的解析逻辑。第三上下文怎么传永远是个玄学传少了任务做不成传多了成本翻倍还会把模型注意力带偏。第四也是最难受的——我无法回答一个基本问题今天整体系统跑得怎么样没有统一埋点看不到 token 消耗分布看不到哪个环节失败率最高一切全靠感觉。这个阶段我称之为裸调 API 阶段。它的本质问题是把大模型的输出当成了普通函数的返回值。但模型调用不是普通函数它有不确定性、有延迟、有成本、有上下文状态。你用普通函数的思维去管它所有和不确定相关的复杂度就会散落到系统各处最终变成无数个难以定位的脏点。1.2 Harness 在项目里承担的五类职责后来我停下来重构核心思路就一条把模型调用从业务代码里抽出来统一收到一个受控的中间层。这个中间层就是 Harness。它在项目里承担五类职责具体如下表。职责解决的核心问题落地形态调用编排一个业务任务往往需要多轮、甚至并行的模型调用顺序和重试逻辑不能散落在外层统一任务队列、多轮调度、失败重试、并行分支工具注册与沙箱模型需要读文件、查数据、执行脚本但不能越权访问系统白名单工具注册表、权限上下文、单次执行超时上下文管理对话历史无脑传递会爆成本、带偏模型滑动窗口裁剪、核心摘要压缩、向量检索召回评测反馈改了一个提示词或换了一个模型版本看不出整体是变好还是变坏评测集、自动评分、回归基线对比成本配额单个模块失控月底账单直接爆炸按模块配额、实时 token 记账、熔断降级这五类职责如果散落在业务代码里每一个都要在无数地方重复实现收进 Harness 之后它们只在一处实现所有业务模块共享。举个例子工具注册与沙箱没有 Harness 时模型让你读取 /etc/passwd你就得自己写权限判断有 Harness 时工具注册表里根本没有这个工具模型连调用的入口都触达不到。这是结构性差异不是修修补补能补出来的。1.3 一个人做项目为什么更需要重架构很多独立开发者的习惯是轻量优先能跑就行。我当时也犹豫过就一个人搞这么重的框架是不是给自己找事但九个月下来我的答案非常明确一个人做 AI 应用恰恰更需要重架构。原因是个人的注意力是唯一不可再生的资源。裸调 API 的方式写起来快但后面每次排查问题、每次改需求、每次面对模型升级带来的行为漂移都要消耗你的注意力。我统计过一个星期的开发日志重构之前真正写新功能的时间只占三成剩下七成在干什么处理模型返回异常、查上下文超限、定位某个调用点的凭证失效、重复调试同一个提示词。重构到 Harness 之后这个比例反了过来。新功能开发时我不需要关心模型调用细节只需要描述这个任务需要哪些工具、走什么流程剩下的交给框架。看起来前期多花了几周时间换回来的是未来每个月几十上百个小时的可支配注意力。对一个单人项目来说这笔账非常划算。2. 20 万行代码的工程量不是指标是账本20 万行这个数字看起来吓人但我必须坦白它不是一个复杂度的证明而是几十个模块的工程量总和。很多人一听到 20 万行就以为核心算法有多深其实不是。这个项目真正棘手的不是某个算法而是把大量业务规则、工具协议、评测用例、部署细节一条条写对。2.1 代码构成盘点什么叫20 万行我按模块做了盘点分布大概是这样模块约行数说明核心 Harness 引擎40,000任务调度、状态机、工具协议、上下文管理工具沙箱与连接器30,000内置 30 工具每个连接器都要处理鉴权、超时、错误码提示词模板与 DSL25,000几十个业务模块的 Prompt、解析规则、Schema业务流程与任务定义20,000用 DSL 写的业务工作流不是传统代码逻辑评测与回归体系20,000评测集、评分器、基线对比、报告生成Web 前端与控制台30,000任务列表、状态可视化、人工介入面板后端 API 与数据层25,000用户/任务/账单看板的数据服务部署、运维与数据脚本10,000安装、备份、迁移、指标导出合计约 200,000从这张表能看出两件事。第一真正长脑子的核心引擎只有 4 万行左右其余都是围绕可控性的基础设施。第二业务模块之间高度相似大部分是模板加 DSL这给单人开发创造了条件——模块之间不会互相踩脚。2.2 九个月的时间线我其实做了五次小型迭代九个月不是均匀的九个月。我把它拆成了五段每一段都有一个可交付的里程碑。阶段时间目标结束时状态1第 1-2 月技术选型 最小闭环跑通任务到拆解、工具、评测的最短链路约 1.5 万行2第 3-4 月核心 Harness 引擎调度、状态机、工具协议稳定约 5 万行3第 5-6 月业务模块与工具链接入 30 工具20 业务工作流可用约 7 万行4第 7-8 月评测、观测与成本治理回归基线、Token 记账、配额熔断上线约 5 万行5第 9 月压测、修边、上线容量压测、灾备演练、正式发布约 1.5 万行很多人低估了最后一个月的工作量。上线不只是把服务跑起来还要处理资源不足、任务堆积、监控告警漏报这些琐碎但致命的问题。我在第九个月只写了 1.5 万行代码但解决的生产事故比前八个月加起来都多。2.3 模块自治与契约一个人的并行开发一个人写代码听起来只能是顺序开发但我实际上做到了并行——靠的是模块自治和契约先行。每个模块定义好对外接口、数据结构、错误语义然后各自实现。模块之间不直接依赖具体类只依赖抽象的协议。这里的技巧是大量使用 Mock。模型还没接入的时候我先写一个 MockModel按预设规则返回结果工具还没对接真实服务时先写 MockTool。外层业务逻辑可以在不碰真实模型的情况下完整跑通。等真实能力接入时只替换底层实现业务代码一行都不用改。这个习惯让我同时推进多个模块不会陷入A 没写完 B 就没法开工的串行死锁。另外整个开发过程我一直借助 AI 编程辅助工具来完成生成、重构、补测试的工作。这个过程本身也在消耗 token——所以我在项目里专门给开发辅助记了一笔账。这从侧面解释了为什么每个月烧掉 40 亿 token里包含的不仅是线上业务调用还有相当一部分是开发期的模型调试与辅助生成。2.4 行数是结果不是 KPI必须说一句20 万行不值得骄傲它只是这个项目真实工作量的结果。行数多意味着你需要更强的工程纪律来对抗熵增——接口要稳、命名要准、注释要少而精确、重构要先定目标再动手。我见过一些团队为了显得有技术含量拼命堆代码结果维护成本远超价值。这个项目的 20 万行每一行基本都有存在的理由有的是业务规则有的是容错处理有的是评测用例。如果你也在做一个类似规模的项目我的建议是不要太早在意行数把注意力放在系统是否可观测、模块是否可替换、改动一个地方会不会影响十个别处上。行数自然会随着这些工程动作增长。3. 每个月 40 亿 token烧在哪里怎么省出来40 亿 token 是一个什么概念按一个任务平均需要五次模型调用、每次调用输入约 8000 token、输出约 1200 token 来粗算一个任务大概消耗 4.6 万 token40 亿 token 对应的月度任务量接近 9 万个折算到每天约三千个任务。这个量级对一款由个人维护的早期产品来说并不小如果把所有上下文都无脑塞给模型成本立刻失控。这就是 Token 治理必须从第一天开始做的原因。等月底账单出来再治理你只能看着数字发呆。3.1 先给 40 亿 token 记一笔账我把一个月的 token 消耗按用途拆开大致是这样用途占比月消耗量说明线上业务调用68%约 27.2 亿用户任务的全部模型调用评测与回归12%约 4.8 亿每天定时跑评测集对比模型版本和提示词改动开发调试与辅助8%约 3.2 亿本地开发时对话调试、AI 编程辅助后台批量任务8%约 3.2 亿定时汇总、数据抽取、报告生成重试与异常消耗4%约 1.6 亿解析失败重试、超时后重复调用等注意重试与异常消耗这一项看起来只占 4%但早期一度到过 20% 以上。模型偶尔返回无法解析的格式任务就自动重试重试等于再烧一轮 token。Harness 里把解析失败重试次数从无限改为最多两次并且第二次强制使用更保守的输出指令之后这项直接降到 5% 以下。成本感也得有个数。按当时主流模型 API 的市场行情混合粗算40 亿 token 的月度账单大致在几万美元量级。这个钱省不省得下来直接决定项目的生死。所以后面对 Token 的每一刀都不是抠门是生存需要。3.2 第一刀上下文瘦身与结果缓存最大的浪费来自上下文。很多初稿设计里模型每次调用都把整个任务历史、全部文档片段原样塞进 prompt读一遍烧一遍。这是最典型的烧 token 方式。我做了两件事。第一上下文分层长期对话只保留最近 N 轮完整原文更早的内容压缩成结构化摘要需要细节时按向量检索召回相关片段而不是把全文都塞进去。改造之后单个长任务的输入 token 平均下降约 60%。第二结果缓存针对业务中大量相似的查询比如总结这个项目进展生成周报这类高频任务把模型的输出结果按任务模板和输入的摘要键做缓存命中缓存时直接返回不再调用模型。这类任务的 token 消耗直接降为零。3.3 第二刀模型分级路由别什么都让最强的模型干我在整个系统里引入了三档模型路由轻量模型处理分类、抽取、格式转换等低难度任务中档模型处理一般性总结和改写强模型只处理复杂推理、规划、冲突消解这类真正需要思考的环节。一个典型的任务拆解流程拆解、判断工具、检查中间结果这些步骤可能全部用轻量模型真正的大段生成和复杂规划才走强模型。这个策略上线后同期业务需要消耗的 token 下降了约 25%而任务完成率没有下降——因为多数步骤本来就不需要顶级模型。这里有个反直觉的结论不是用的模型越强结果越好。很多场景下轻量模型的输出反而更稳定、更省时因为它的随机性和自由发挥空间更小。3.4 第三刀配额、熔断与实时记账如果没有配额任何单一模块都有可能因为一个异常循环把当天的预算全部烧光。我在 Harness 里实现了模块级 Token 配额每个模块设置了每日 token 上限比如assistant模块每天 1000 万 token、report模块每天 500 万 token超过上限后该模块自动降级为轻量模型优先或直接返回缓存结果而不是无限续调用。实时记账是这一切的基础。每一次模型调用在中间件里写一条记录模块、模型档位、输入 token、输出 token、耗时、是否重试、任务 ID。每天晚上汇总出成本日报哪个模块在烧钱、哪个工具调用失败最多、哪个任务反复重试一目了然。没有这套记账任何优化都是空谈。3.5 治理结果烧得少反而干得更好治理不是一锤子买卖我持续了近两个月。最终上线时的数据是整体 token 消耗比初版下降了约四成五月调用从 40 亿 降到 22 亿左右。更意外的是任务完成率反而提升了——原因在于优化过程倒逼我砍掉了大量无效上下文、绕圈子的工具调用以及无谓重试。这些操作原本不但烧钱还把模型带偏。所以如果你也在做一个重度依赖大模型的应用我把最核心的建议放在这里Token 治理不是省钱技巧它是系统稳定性的一部分。每一次多余的调用都在增加出错的可能。4. 长任务与认证令牌续签我踩过的坑第三个关键问题是认证。很多 AI 应用不是一锤子买卖而是长任务。一个任务从开始到结束可能要半小时中间经历十几轮模型调用。如果用户在任务进行到一半时登录态失效后续调用全部开始报错——这是我在早期踩得最深的一个坑。4.1 问题本质任务的生存周期比登录会话长传统 Web 应用的鉴权模型是请求到响应一次请求几十毫秒用户登录态过期了重新登录就行影响很小。但 Agent 任务是任务到多轮调用再到最终交付一个任务的生存周期可能远远超过一次访问令牌的有效期。这时候认证就不能只在入口检查一次而要贯穿整个任务生命周期。Harness 引擎里我把认证做成了调用前检查的中间件。每次模型调用之前先检查当前任务绑定的凭证是否仍然有效、剩余有效期是否充足。这个设计听上去很简单但真正复杂的是过期了怎么办续签失败怎么办多个并发请求同时续签怎么办。4.2 双令牌结构与续签中间件我采用了经典的短令牌长令牌结构访问令牌生命周期短用于实际调用刷新令牌生命周期长用于续签。Harness 在调用前检查剩余有效期低于阈值就用刷新令牌去换新访问令牌换到后缓存起来避免每次调用都触发一次刷新。并发是一个隐藏的雷。任务内部可能同时发起多个并行模型调用如果每个调用都发现令牌快过期、同时去刷新刷新端点会被打爆还会出现先刷新失败、后刷新成功的竞态。我在续签逻辑外面加了一把进程内锁同一时刻只允许一个刷新请求在飞其余的排队等待结果。# 伪代码续签中间件的核心逻辑 refresh_lock threading.Lock() def before_model_call(task_ctx): remaining task_ctx.access_token.exp - time.time() if remaining EXPIRY_THRESHOLD: return with refresh_lock: # 二次检查可能等待锁期间已被其他协程刷新 remaining task_ctx.access_token.exp - time.time() if remaining EXPIRY_THRESHOLD: return new_token_pair auth_client.refresh(task_ctx.refresh_token) task_ctx.update_token(new_token_pair)这段逻辑的关键是二次检查。拿到锁之后先重新确认令牌是否真的需要刷新因为在你等待锁的过程中另一个协程可能已经刷新过了。这个细节很容易被忽略却是高并发任务场景下避免重复刷新的关键。4.3 刷新失败后的分级处理宁可挂起不要失败刷新令牌也不是永远有效。用户可能七天没登录、可能主动退出、也可能是网络瞬时抖动。我把刷新失败分成了两类处理。第一类瞬时错误比如网络超时、服务端临时故障。这种情况不盲目重试而是按 1 秒、5 秒、15 秒的间隔退避重试最多三次。超过三次后把任务挂起而不是直接判失败。第二类凭证失效类错误表示令牌本身已经无效。这种情况不再重试。任务进入挂起状态持久化到队列里同时给用户一个通知任务已暂停请重新登录后继续。用户重新登录后任务从最后一个成功步骤继续执行不用从头再来。在设计上我坚持一个原则对长时间运行的任务来说挂起永远好过失败。失败意味着前面的投入全部作废用户拿到一半产物的机会都没有挂起至少把状态保住了。这个原则挽救了无数次本应让用户愤怒的任务中断。4.4 多提供方凭证的集中管理一个实际运行的 AI 应用不会只对接一个模型提供方。我同时用了多家服务它们的调用凭证格式、过期策略、错误语义都不一样。如果把凭证散落到各个业务模块轮换密钥就是一场灾难。我的做法是单独做一个 Secret Store 模块所有凭证统一存储、统一读取、统一轮换。业务代码永远不接触原始密钥只通过任务上下文拿到当前可用的调用凭证对象。轮换时先写入新版本、保留旧版本平滑切换数据接口不变。这套设计后来在我更新密钥、处理某个提供方临时故障时替我省掉了大量时间。5. 一个人守二十万行代码的底线评测、观测与应急代码写多了真正的问题不是写不出来而是改不动。我九个月里经历了无数次小步重构能一直改下去没有崩盘靠的是三道防线评测回归、全链路观测、应急降级。5.1 评测回归LLM 应用的自动化测试传统软件的自动化测试可以断言输入 A 输出 B。LLM 应用做不到这么精确同一个提示词每次输出都会有细微差别。我的办法是搭一套评测回归体系每个业务模块维护一批典型任务样本每天自动跑一遍按多个维度打分。评分维度包括任务完成率、输出可用率、平均耗时、平均 token 消耗。每次改动提示词、升级模型、调整工具之后先跑一轮评测对比改动前后的分数分数下降就回滚。这套体系把感觉上没问题变成数据上没退步是我敢连续改 20 万行代码的最大底气。5.2 全链路观测一个表回答所有问题评测解决改了会不会变坏观测解决现在哪里在坏。我在 Harness 里给每次模型调用和工具调用都写了一条结构化日志字段包括任务 ID、模块、模型档位、输入 token、输出 token、耗时、工具调用序列、结果状态。每天晚上脚本自动汇总生成三张表成本排行看哪个模块烧 token 最多失败排行看哪个调用点最容易出错任务复盘看哪些任务反复重试、耗时最长。遇到用户反馈某个任务很慢我直接按任务 ID 搜日志几秒钟就能定位到卡在哪一次工具调用而不是靠猜。5.3 印象最深的四个坑第一个坑是上下文爆炸。早期有些任务设计成把整个工作区所有文档都作为上下文任务跑到十几轮后输入 token 呈指数级增长成本翻倍效果却越来越差。解决的方案是我前面说的上下文分层核心思路是模型不是硬盘它只该看到当前决策真正需要的少量高信号信息。第二个坑是模型输出格式漂移。明明指定了返回 JSON模型偶尔会在外面包一层 Markdown 代码块或者多写一个逗号导致解析失败。我写了一个通用解析函数先剥离代码块标记再解析解析失败时强制让模型只输出 JSON 不要任何额外文字重新生成一次。直接把格式相关失败率降低了九成。第三个坑是工具调用死循环。模型在某个任务里反复调用同一个工具拿不到确定结果也不放弃直到把配额烧光。我加了两个限制单任务最大工具调用次数默认 20 次以及单工具连续三次结果无变化就终止。后者特别管用等于给模型的固执装了一个刹车。第四个坑是并发配额打满。并行任务一多模型提供方就开始限流。解决思路是设计了一个共享令牌桶所有任务共用配额按需排队限流错误码触发时按指数退避重试而不是立即重试。5.4 应急降级手边永远有个关掉模型的开关最后一道防线是可控降级。我在每个业务模块都配置了功能开关出问题时可以瞬间把某个模块的模型调用切换到静态兜底逻辑比如该模块暂时不可用请稍后再试而不是让整个服务挂掉或无限重试烧钱。同时我在控制台里保留了人工介入能力管理员可以看到每个正在运行的任务的实时状态必要时可以手动终止任务、回滚状态、重启某个步骤。这套自动为主、人工兜底的机制在一个人的运维条件下是避免事故扩大的最后保险。6. 项目做完之后我对 Harness 的理解又变了一层九个月做下来我对 Harness 架构的看法从一个架构选项变成了一个人维护大型 AI 应用的生存工具。最大的体会是项目真正的瓶颈从来不是写代码的手而是大脑里同时维护的状态数量。20 万行代码可以慢慢写完但几十个模块的状态如果都要记住人是记不住的。Harness 的价值在于它把大量不确定性压缩到核心可控范围让你只需要关注一个地方——中间层的配置和状态。如果你也想做类似的项目我给三个真建议第一从第一天开始做 Token 记账不管是一万 token 还是十亿 token先把账记起来第二先跑一个窄得不能再窄的任务闭环再把 Harness 做小做稳然后往外长不要一上来就造通用平台第三把评测回归当成和写代码同样重要的工作没有它你的每一次改动都是在赌运气。最后再分享一个我自己的习惯每周五下午不写新功能只做三类事——看这一周的成本日报、跑一遍完整回归、清理日志里出现最多的三个错误。别小看这三个小时的固定动作它是我能连续九个月保持项目不失控的最大功臣。一个人做系统不需要时刻冲刺但需要时刻看得见系统在哪里。