尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

一个人九个月20万行代码:Harness架构的40亿Token应用实践

一个人九个月20万行代码:Harness架构的40亿Token应用实践 一个人、九个月、20万行代码、每个月烧掉 40 亿 token——造出一款Harness架构应用。这个标题里的每一个数字都不是营销文案的夸张修辞而是我那段时间真实的生活账单。九个月里我几乎是独自一人从项目的第一个目录建起到最后跑完压力测试写掉了大约 20 万行代码而整个开发周期内花在 AI 辅助编码和模型调用上的 token 消耗峰值月份轻松越过 40 亿个大关。这篇文章想认真聊聊的不是“我多努力”而是这套 Harness 架构到底解决了什么问题、20 万行代码是怎么在九个月里从 0 长出来的、40 亿 token 究竟烧在了哪里以及那些在 token 和授权之间反复折腾的坑。如果你也正在用 LLM 做重一点的业务系统或者正处于“要不要自研架构”的纠结期这篇文章应该能给你一些不一样的参考。1. 为什么一个人也要执意自建 Harness而不是直接套现成的框架坦白说市面上不是没有现成的“Agent 框架”和“LLM 应用脚手架”从最火的编排库到各家云厂商出的 worklow 平台一抓一大把。但我当时要对付的不是“做一个聊天机器人”而是一套需要承载多个模型供应商、数十个外部工具、严格权限隔离、还要按月跑大量离线任务的生产级系统。套框架当然能起步快可到了中后期框架的抽象边界一旦跟我的业务需求不合拍改起来比从零写还要痛苦。这就引出了“Harness”这个词。我理解的 Harness 不是某个具体框架而是一种工程姿态在模型能力和业务系统之间套上一层完全由你控制的安全带。它负责统一接入各类模型、管理上下文、调度工具、采集指标、处理鉴权与配额。就像登山的时候你不会直接把安全扣挂在悬崖边的岩石上而是先在自己身上穿好整套安全背带再把所有受力点汇集到主锁上。我的核心需求其实很明确不能把业务逻辑写死在某个厂商的 SDK 里。OpenAI 的 SDK 写得很好可万一哪天要换成千问或者 DeepSeek或者企业内部要部署私有化模型呢所以从第一天起我就给自己立了一条铁律所有模型调用一律走我自己定义的统一接口。外部供应商的 SDK 只是适配器绝不能渗透到业务层。这套设计的价值在项目后期被验证得淋漓尽致。有段时间某家服务商接口异常频发我在路由器里做了一层自动 fallback用户几乎无感知业务照跑。如果当初每个业务模块直接调 OpenAI SDK那一次故障就得改几十处代码。还一个促使我自研的原因是 token 量级。常规框架假设你的调用量不大人家把监控、缓存、重试都做成了“够用”的水平。但一旦你每个月要处理几十亿 token很多细节就完全变了缓存命中率每差 1%成本就差出一大截并发控制稍微激进一点就会被限流打爆日志稍微啰嗦一点每天的存储成本就能让你肉疼。只有自己亲手掌控整条链路才能把每一分钱和每一次调用都抠到极致。听起来辛苦但结果告诉我这条路对复杂的生产系统是值得的。毕竟框架解决的是 80% 普通场景的便利而你需要的是剩下 20% 的差异化和控制力。当你恰好靠这 20% 吃饭的时候自研 Harness 就不是“重复造轮子”而是“给自己造武器”。1.1 Harness 的边界哪些能力必须内聚哪些能力坚决外置很多第一次接触 Harness 概念的人会问这套架构的上限和下限到底划在哪里我自己的划分方法很简单——跟模型交互的每个环节都必须内聚进 Harness跟业务关联的判断逻辑全部通过插件化接口外置。举个例子。模型调用的鉴权、重试、超时、结构化输出解析、token 统计、缓存读取这些属于“每个调用都要经过的公共管道”我会放在 Harness 核心层。但一个电商客服 agent 该不该主动推荐某件商品这种策略判断不属于基础管道就应该放到上层的业务配置里去。这样划分的目的是保证底层足够稳定上层足够灵活。在我的实现里最核心的抽象是一个LLMProvider接口所有模型供应商都以适配器方式接入。它统一了流式输出、非流式输出、函数调用、上下文窗口这几大基本能力。上层业务模块看到的永远是一个纯净的接口不管背后是 GPT、Claude 还是开源模型都长一个样。然后再往上一层是 Context 管理器它负责处理 token 的组装策略。每轮对话哪些历史要保留哪些要裁剪哪些要转成向量检索的摘要全部由它决定。可以说这一层直接决定了 40 亿 token 里有多少是真正有效的“聪明钱”又有多少是被浪费掉的“糊涂钱”。2. 九个月的时间线和 20 万行代码都花在了哪些子系统上一个人写 20 万行代码在九个月里听起来像个数字噱头但如果把 AI 辅助编码算进去其实并没有那么恐怖。我当时的平均速度大概是每天 700 多行代码其中相当一部分是 AI 结对编程帮忙生成的我要做的主要是设计好模块边界、写清楚接口契约然后逐段检查、修正、整合。但“写出来”从来不是难点难的是“写出来还能在三个月后改得动”。20 万行代码如果不讲究组织方式三个月后你自己看都会想删库重来。所以我在动工之前花了很多时间规划子系统的边界。现在复盘整个项目大致可以分成五个相对独立的部分这也是我建议所有做类似系统的人优先学到的经验先把模块切成能独立演进的小世界再开始填代码。2.1 前两个月上下文引擎和统一模型网关万事开头难而我的开头全部砸在了上下文引擎上。这个引擎要解决的问题非常朴素一个业务 agent 在回答问题时到底该把哪些信息塞进 prompt 里塞多了浪费 token塞少了答非所问。我给它的设计目标有两条第一任何一次调用消耗的 token 必须可预期第二上下文组装必须是策略驱动的模板化的不能是散落在各处代码里的字符串拼接。模型网关则是一个更偏基础设施的模块。它负责所有模型供应商的路由、鉴权、配额和 fallback。当时我给自己定了一个指标切换任何模型供应商只改配置不改代码。这个目标逼着我把所有供应商之间的差异都藏在适配器层把业务层完全架空。两个月的成果是核心代码约 4 万行但这 4 万行像地基一样撑住了后面所有的上层建筑。2.2 第三到第五个月工具注册中心和 Agent 执行器模型网关跑通之后真正的业务复杂度才开始显现。一个“能干活”的 agent光会聊天没用得能查数据库、调内部 API、操作工单系统。所以第二阶段的核心是一个工具注册中心。所有能被 agent 调用的外部能力都以 JSON Schema 的形式注册进来统一鉴权、统一传参、统一结果解析。这一阶段我写了约 6 万行代码。一半是在定义工具调用的协议层另一半是在打磨 Agent 执行器。执行器内部有一套简单的循环接收任务、分析意图、调用工具、观察结果、决定下一步。为了让这个循环稳定可控我写了不少约束机制比如最大步数限制、关键步骤人工确认点、失败自动回退策略。从这段经历里我得到的最重要认知是Agent 的可靠性不是靠模型聪明而是靠执行器的边界约束。模型负责发散执行器负责收敛。2.3 第六到第七个月安全、权限和审计把生产系统的底线补上很多个人项目死就死在只做了功能没做安全。我前期也一样代码写得很爽一聊到权限就头大。到了第六个月我不得不把安全和审计单独抽出来做了一轮集中攻坚。这里的难点在于权限系统不仅要管人还要管 agent。一个 agent 代表用户执行操作时它拥有的权限不能超过用户本身但又不能把所有操作都弹窗让用户确认那样体验太差。最后我设计了一套“权限叠加”机制agent 的执行权限等于“用户权限”乘以“agent 角色权限”任何一环缺失都直接拒绝。同时全部关键操作走审计日志每一步工具调用的入参、出参、耗时、token 消耗都有记录。这个阶段大约写了 4 万行代码加上大量配置和测试。坦白说代码量不算夸张但安全性这东西没有就是零。做完了之后我才敢把系统交给真实的业务方去跑。2.4 第八到第九个月可观测性和成本控制面板让系统真正可运营最后两个月的主题是“让系统可运营”。一个人维护一套复杂系统如果没有可视化的观测手段突发故障时压力会非常大。我搭了一套可观测性体系把每个 agent 的完整执行轨迹、每次模型调用的延迟与 token 数、工具调用的成功率等等全部接入追踪链路。成本控制面板是这个阶段另一个让我觉得“值回票价”的东西。既然每个月要烧掉大量 token我就必须清楚地知道钱花给谁了哪个业务方在烧哪类 prompt 特别费 token缓存命中率是多少有了这些数据我做成本优化时就不再是拍脑袋而是对着面板一处处做手术。3. 40 亿 token 的消耗结构我的 token 经济学复盘40 亿 token 听起来是一个天文数字但如果拆开看其实主要烧在四个去向AI 辅助编码、生产环境的推理调用、上下文缓存、以及测试评估跑批。我粗算过比例AI 辅助编码大约占了三成生产调用和缓存占了一半剩下两成在离线评估和测试上。先聊 AI 辅助编码。20 万行代码如果纯手写九个月根本写不完。我大量使用 AI 结对编程每天光是代码补全、代码解释、单元测试生成、bug 修复这些交互就要消耗大量 token。刚开始我很放飞什么代码都让 AI 生成后来发现不对AI 写代码爽Debug 的时候苦。后来我就改用一种更克制的方法先自己画好模块的结构再让 AI 去填充实现细节最后我来做 review 和边界修补。这样 token 花得更值代码质量也更高。再聊生产环境的推理调用。这个部分最容易失控。因为 agent 系统的调用不是一次性的而是多轮循环的。每轮循环都要调用模型每轮调用都要带上下文。上下文越长token 成本越高。为了控制这个大头我做了三件事上下文裁剪每轮对话结束把不重要的历史转成摘要核心信息保留噪声直接丢弃。分层缓存系统提示词、工具描述、常用上下文片段全部走缓存避免重复计费。缓存命中率高的时候整体成本能降三四成。模型路由简单任务用便宜的小模型复杂任务才上强模型。这个路由策略帮我省了不少钱因为大多数请求其实是比较简单的。测试评估跑批也是一笔不小的开销。每次发版我都要拿几百个真实场景跑回归每个场景烧几万 token一轮下来就是几百万。一个月发十几版开销自然就上去了。这个过程我保留了下来因为我始终认为对 agent 系统的改动没有大规模的回归测试兜底就是在拿生产环境当赌场。烧在这些 token 上的钱本质上是在买系统的稳定性。3.1 token 的三个向量key、query、value 对上下文设计的启发在我设计上下文引擎时有个很关键的认知来自检索系统的启发token 不是简单的字符串流它里面藏着 key、query、value 的三角色关系。什么是 key就是标志当前任务目标和身份的那部分 token。比如系统提示词里写明“你是客服助手负责处理订单退换货”这决定了模型如何定位当前状态。什么是 query就是当前这一次请求真正想要解决的问题。它是输入的主体也是模型必须响应的那部分。什么是 value是那些随时可以被查、被引用、被检索的背景知识它们不一定要全部塞进 prompt但模型需要时得能获取到。这套认知直接改变了我的上下文组装策略。以前我习惯把所有可能相关的资料一股脑塞进 prompt结果又贵又容易分散模型注意力。后来我改成把 key 做稳、把 query 做准、把 value 做薄。系统提示词要稳定且精简用户请求保持原样清晰背景资料尽量抽成摘要或走检索。每一次调用需要多少 token从一开始就在我的设计预期之内而不是等到账单出来才心痛。3.2 上下文窗口不是越大越好token 预算必须前置设计市面上各家模型的上下文窗口越做越大百万 token 级的产品也出来了。很多人一看窗口大就乐了——把整个文档库都塞进去省事。但我的经验是上下文窗口大不等于你应该把它当仓库用。窗口越大模型处理延迟越高单次成本越贵而且过长的上下文还会摊薄模型对关键信息的注意力。我在自己的系统里给每个 agent 都设置了独立的 token 预算。这个预算不是写死的数字而是一个可配置的策略先定硬上限再做动态分配。比如一个客服 agent我给它的 token 预算可能是 8K。其中系统提示词固定占 1K实时业务信息预算 2K历史对话摘要预算 3K剩余留给输出余量。如果用户的输入特别长我会先做一轮压缩摘要而不是直接突破预算。这样设计之后调用的 token 消耗变得非常可预测成本核算也更加简单。别人问我你们平均一次调用多少 token我可以精确到百位数。这在做资源规划和报价的时候是巨大的优势。4. token 的另一种含义身份认证与 JWT 续签的排障记录聊完了 LLM 的 token必须再聊另一个很容易被混淆的“token”——身份认证 token。做平台的这九个月里我被这一类 token 坑过好多次踩过 JWT 失效、refresh token 轮换失败、django cookie 存储 token、第三方登录 token exchange 失败等各种雷区。这部分的经验完全不比语言模型 token 管理少。我先说说最典型的一类故障现象读者可能看了会很有共鸣登录的时候提示sign-in could not be completed, token exchange failed或者login server error: token exchange failed: error sending request。很多人第一反应是“网络问题”但排查下来才发现大半情况其实是认证流程的设计缺陷。这块我花了很多精力去理顺。第三方 OAuth 登录本质上是拿授权码换访问令牌的过程。如果后端拿到授权码之后去第三方 Token Endpoint 换令牌的请求失败就会报 token exchange failed。失败原因有很多种最常见的三个授权码已经过期或被使用过、回调地址不一致、后端到第三方服务之间的网络不通。还有一个我踩过特别隐蔽的坑授权码是单次有效的如果前端因为重复提交把同一个回调请求发了两次第二次必定失败。排查这类问题的通用思路我整理成了几条经验先抓原始响应。不要只看客户端报错要去后端日志里看第三方 Token Endpoint 的完整响应。403、400 和网络超时的修复方向完全不同。检查回调地址的一致性。很多 token exchange 失败就是因为 OAuth 应用配置里注册的回调地址跟实际发起请求的地址不是严格相等多一个斜杠都不行。用 PKCE 增强授权码模式。尤其是移动端和单页应用必须加 PKCE否则不仅容易被拦截而且某些严格的服务商还会直接拒绝不带 PKCE 的授权码换取请求。至于 JWT 的续签问题我的核心建议只有一句话refresh token 一定要做轮换并且要绑定设备或会话指纹。不做轮换refresh token 一旦泄露等于把长期钥匙交给了攻击者做了轮换但每次刷新之后不把旧 refresh token 作废也会留下隐患。4.1 一个真实的“refresh_token is empty”问题复盘有一次我的登录系统突然大面积报错错误日志指向一条非常抽象的文案failed to refresh token: 400 bad request: invalid refresh_token: empty string. expected a string with minimum length 1, but got an empty string instead.按字面理解就是刷新令牌的时候系统收到了一个空字符串的 refresh_token。但我明明在刷新接口里做了校验空值应该提前被拦截才对。后来深入查才搞清楚问题出在一个不起眼的细节上刷新请求里的 refresh_token 是从请求体里读的但前端开发在调用接口的时候忘了把 refresh_token 放进请求体里而后端在读取时没做二次防御默认解析成了空字符串。这个案例很典型它告诉我们两个道理一是防御性编程要贯穿到解析原始请求的阶段不能过分相信上游二是错误信息应该尽量包含更多上下文因为invalid refresh_token这种提示根本分不清是“没有传”还是“传了错的”。我还有一次踩过更匪夷所思的坑登录系统在用户修改密码之后旧 refresh token 没有即时失效导致用户改完密码还能通过旧的 refresh token 继续访问。后来我把 refresh token 的失效策略改成了“密码变更后立即刷新整个 token 家族”并且同步清掉所有历史会话。自此之后这类安全问题再也没有出现过。4.2 Django 场景下的 Cookie 存储与 CSRF 防御因为我的服务端一部分用了 Django所以还专门处理了 Cookie 存储 token 的兼容问题。JWT 放在 Cookie 里最大的好处是能让浏览器自动携带前端不需要手动管理 localStorage。但 Cookie 方案有几个必须要处理好的点设置 HttpOnlyJavaScript 无法读取 HttpOnly Cookie能大幅度降低 XSS 攻击带来的令牌泄露风险。SameSite 策略如果是前后端同域直接设SameSiteLax就能挡住大部分跨站请求伪造如果有跨站需求必须上 CSRF Token 机制。Secure 标记生产环境必须启用 HTTPS并且 Cookie 要标记 Secure防止在明文链路里传输。我在实际项目里踩过最痛的一坑是因为没配好 CSRF 豁免导致所有带自定义 Header 的请求都被 Django 的 CSRF 中间件拦截前端登录成功了但后续业务请求全部 403。排查到后来才发现原来我新加的 API 路由没被纳入认证白名单而 Django 对跨域预检请求的处理又不够宽容。最后我把中间件配置重新梳理了一遍并明确区分了“需要 CSRF 的浏览器会话接口”和“走 Token 认证的无状态接口”才彻底解决。5. 单兵作战的取舍哪些东西我坚持自动化哪些事情必须人工一个人写 20 万行代码表面上靠的是 coding 速度实际上靠的是“什么该自动、什么该手工”的取舍。这几个月里我最深的感受是建立工具意识比写业务代码更重要。如果每件重复的事情都要手动处理九个月的时间绝对不够用。举个例子我的项目里有大量 JSON Schema 定义它们是工具注册中心的核心。手动维护这些 Schema 很费体力而且容易出错。我搭了一套从函数签名自动生成 Schema 的机制写代码的时候用类型注解编译前自动转成 Schema这样业务代码和工具描述永远是同步的。类似的自动化还有模型接口契约的自动化测试、token 消耗预算的自动校验、发版前的回归测试自动触发。这些自动化的东西前期搭建要花时间但后期省下来的时间是按倍数计算的。但我也保留了非常多的人工环节。尤其是代码 review 和架构评审我坚决不把自己的代码当成“一次通过”的产物。每天结束前我会留出至少一个小时回头翻白天的核心改动逐行检查逻辑边界。后来我发现这一个小习惯的价值比任何测试工具都大因为很多时候bug 不是出现在“你写了什么”里而是出现在“你应该想到但没想到”的边界情况里。5.1 如何保证九个月后的代码自己还能轻松维护这是一个很现实的问题。20 万行代码不维护就是负债维护精良才是资产。我只做对了三件事但效果很好模块边界清晰每个子系统只通过公开接口通信内部实现随你怎么改不影响外部。接口契约优先写代码之前先写接口签名和异常约定实现只是填空。这保证了整个系统在结构上的一致性。测试不是装饰每个核心模块都有对应的单元测试和集成测试尤其是模型网关和 Agent 执行器这两个地方的测试覆盖率几乎是百分之百。没有这些测试撑腰我根本不敢频繁重构。6. 尾声一个独立开发者的真实经验清单行文至此我不打算做什么高屋建瓴的总结。项目还没完迭代还在继续说“最终收获”太早。但从这九个月里我抽出几条真实、朴素的体会分享给可能正在走类似路的你第一别怕复杂但敬畏复杂度。Harness 架构的核心价值是把复杂度关在笼子里。每一层抽象都为了解决一类具体问题而不是为了听起来高级。模块内部可以复杂模块之间务必清晰。第二token 就是钱也是系统的度量衡。语言模型的 token 消耗本质上是你业务逻辑的信息密度。如果某个功能的 token 消耗很高往往这意味着它的 prompt 设计有问题或者上下文管理太粗糙。去优化它而不是单纯地加预算。第三身份认证这类基础模块一开始就要按生产标准来做。授权码模式要有 PKCEJWT 要设计好 refresh token 轮换Cookie 要设置 HttpOnly 和 Secure。这些环节在项目初期多做十分钟后期就能少熬几个通宵。token exchange failed 这种问题十有八九都是前期设计埋下的雷。第四AI 能帮你写代码但不能帮你做架构决策。我的 20 万行代码里AI 贡献了很多填空的部分但所有模块的边界、接口契约、异常处理策略都是我在白纸上亲手画的。希望你也一样把 AI 当成极为聪明的同事而不是替你思考的负责人。最后说一个当初没想到的收获这套 Harness 架构给了我把“换模型”变成一件小事的能力。现在每当我看到一个更强或更便宜的新模型发布我只需要写一个新适配器然后跑一遍回归测试就能决定是否切换。对一个靠 AI 能力吃饭的产品来说这种自由度比任何单点功能优化都值钱。
返回列表