资讯动态

Harness工作流Token成本优化实战:从涨价40%到降本51%

发布时间:2026/10/9 20:18:12 来源:尧图企业网站定制
上个月收到账单的时候我盯着数字看了十秒钟差点以为统计口径出了问题——一个内部Agent服务光Token费用就比上周期涨了40%。我没有换更便宜的模型也没有砍功能而是把整个Harness工作流重新排了一遍两周后Token消耗硬生生降了51%。这篇文章就把过程里拆过的问题、改过的配置、踩过的坑完整梳理一遍。如果你正在做Agent类应用或者用Coze、Dify、n8n这类工作流平台跑自动化任务却总觉得Token成本不可控那这篇应该能给你一套可落地的思路。1. 成本失控现场Token都烧到哪里去了1.1 一次看似简单的请求为什么烧掉几万Token先还原一个典型场景。用户问了一句“帮我查一下上周的订单数据和上上周对比再分析异常原因”。这句话本身只有几十个Token但智能体实际跑下来可能消耗了2万到5万Token。问题出在底层流程。Harness工作流为了执行这个任务通常会这样跑第一轮把系统提示词、全部工具定义、用户问题一起发给模型模型选择先查订单列表。第二轮携带第一轮的完整对话、工具返回的订单列表可能几百条让模型继续判断下一步。第三轮再携带前面的全部内容调用统计接口接口返回大量明细。第四轮再次携带全部历史让模型汇总对比。可能还有第五轮做异常归因时又翻了一遍订单数据。每轮都在重复发送几乎相同的系统提示词、工具定义、历史消息。工具定义一次可能有3000到8000 Token历史消息经过多轮累积后可能轻松超过1万Token。换句话说用户只看到一次对话底层已经因为多次重复带上“行李”而消耗了五倍以上的输入Token。更隐蔽的是工具返回。很多内部API返回的是给前端用的完整字段比如订单接口会把支付信息、物流信息、备注、甚至退换货标记全带回来。模型不关心这些但这些Token照样按输入计费。1.2 成本不是看单次价格而是看“倍率”很多人算Token成本只看每千Token的单价忽略了两个更重要的事实。第一输入Token和输出Token价格不一样。主流模型的输出价格通常是输入的3到5倍。所以一次让模型“自由发挥”的长篇输出可能比十次短输入还贵。第二Agent任务会放大Token消耗。一次任务往往不是“一轮”而是多轮多轮之间的历史消息每一轮都会重新计费。最终账单基本等于“单次请求消耗Token乘以请求次数”然后还要乘以工具调用轮数。我自己习惯用一个粗略公式估算单次任务的成本单次Agent任务成本 ≈ 输入Token总量 × 输入单价 输出Token总量 × 输出单价其中输入Token总量 ≈ (系统提示词 工具定义 历史消息 新输入) × 往返轮数从这个公式就能看出来降低成本的抓手不是去跟模型厂商砍价而是压缩三个变量往返轮数、单轮携带的固定内容、以及输出Token的浪费。1.3 Harness工作流到底在管什么这里说清楚“Harness”到底指什么。在AI Agent领域Harness可以理解为模型的“控制壳”或“运行框架”。它负责把大模型调用、工具选择、上下文组装、记忆读写、重试策略串成一条循环。你可以把它想象成一个操作系统模型是CPUHarness是调度器工作流是任务清单。有些开源项目直接叫DeepSeek Harness它实现的就是一种Agent运行时的抽象。它让你不需要每次都手写“while循环里调模型、解析输出、执行工具、再把结果喂回去”的样板代码而是通过声明式工作流把这些步骤串起来。Harness工作流里每一环都会直接影响Token消耗上下文怎么组装决定单轮携带多少Token。工具怎么定义、返回什么决定工具调用轮次和结果大小。记忆怎么管理决定多轮对话是不是无限膨胀。路由怎么设计决定是不是每件事都用最强最贵的模型。所以Harness工作流不是一个神秘的东西它就是你需要审计和改进的“成本中心”。搞清楚这一点后面的优化才有方向。2. 降本之前先把账算清楚2.1 没有度量就没有优化任何优化都应该从测量开始。我见过很多团队凭感觉说“感觉上下文有点大”然后盲调结果越调越乱。正确做法是在Harness里给每一次模型请求埋点记录以下字段请求ID、任务ID、会话ID模型名称请求时间prompt字符数、Token数补全Token数工具调用轮次是否命中缓存路由策略命中的级别如果你的Harness是自己写的那埋点很容易。在调用大模型API那一步统一包一层打印或上报即可。如果用的是开源Harness通常也支持回调或中间件可以拦截每次请求。一个简单的记录结构示例# 记录一次模型调用的Token用量 def record_usage(request_id, model, input_tokens, output_tokens, cached_tokens): log_entry { request_id: request_id, model: model, input_tokens: input_tokens, output_tokens: output_tokens, cached_tokens: cached_tokens, total_tokens: input_tokens output_tokens, timestamp: datetime.now().isoformat(), } usage_logs.append(log_entry)采集完日志之后按“任务”维度聚合统计每个任务跑了多少轮、平均每轮输入多少Token、哪个工具产生的历史消息最长。这一步做完你会非常清楚地看到钱烧在哪儿。2.2 一张表找出浪费大头我习惯把浪费归成六类每类都有对应的优化方向。下面的表格可以直接拿来对照排查浪费类型典型表现占比参考修复方向固定提示词膨胀系统提示词写了5000 Token且每个请求全量带入15% - 25%精简提示词、按需动态拼接工具定义冗余全部工具定义每次全量发送几十个工具加起来近万Token20% - 30%按意图动态注入工具定义工具返回过大API返回全量字段或全部分页数据模型根本用不上20% - 30%字段裁剪、分页控制、结果摘要历史消息无限制累积多轮对话把一个月前的聊天记录全带上10% - 20%滑动窗口、历史摘要无效往返模型反复调整参数调用同一工具或工具调用失败后整段重试10% - 15%结构化输出、失败重试降级重复相似查询用户反复问同一类问题每次都重新计算5% - 15%语义缓存、结果缓存我这次优化的项目里工具定义冗余和工具返回过大两项加起来占了差不多一半成本。这意味着只要把这两块控制住整体成本就能明显下降。2.3 50%目标怎么拆降本50%不能靠一个大招而是要靠多个小优化叠加。我当时把目标拆成四块语义缓存减少重复任务带来的Token消耗目标贡献10到15个百分点。上下文压缩与历史摘要目标贡献10个百分点。模型路由让简单任务走小模型目标贡献10到15个百分点。工具定义动态注入与结果瘦身目标贡献15到20个百分点。每一项都对应一类具体改造而且每一项都可以独立上线、独立验证。这种拆法有个好处即使某一项没达到预期整体收益依然可能逼近50%风险被分散了。3. Harness工作流成本优化的五个核心抓手3.1 语义缓存让重复查询不再烧钱在一个内部工具里用户问“最近一周的销售额是多少”和“上周到现在卖了多少”本质上是同一个问题。如果没有缓存Harness会原封不动地把这个问题送给大模型重新跑一遍工具调用和推理。语义缓存的思路是在进入Agent循环之前先把用户输入做向量化然后在历史缓存里找相似度最高的记录。如果相似度超过阈值且缓存结果没过期直接返回缓存答案完全不用调用大模型。实现上可以给Harness加一层前置拦截器def cached_agent_run(query, threshold0.92, ttl_seconds3600): query_vec embed(query) similar cache_store.search(query_vec, top_k1) if similar and similar.score threshold and not expired(similar.ts, ttl_seconds): return similar.answer, {cache: hit} answer agent.run(query) cache_store.save(query_vec, query, answer) return answer, {cache: miss}这里有个细节要提醒阈值别设太低否则容易误命中。比如“本月退货率”和“本月退款率”听起来相关但答案是两回事。我实际用下来0.90到0.93是一个比较合适的区间具体要看你的场景和Embedding模型。为了控制新鲜度还需要给缓存设置TTL。订单、报表这类数据往往需要分钟级刷新而FAQ类内容可以缓存几小时甚至一天。Harness里可以给不同类型任务打一个cache_policy标记让缓存规则可配置。3.2 上下文压缩与记忆分级多轮Agent最容易出现的问题就是“把所有历史都带上”。对话到第十轮时前九轮的原文一共可能有几万Token其中大部分是不重要的细节。我的做法是分级记忆最近两轮消息保留原文。更早的消息由摘要机制定期压缩成一段结构化摘要。关键事实用户ID、日期范围、筛选条件单独抽出来放进一个固定长度的“事实槽”。给Harness写一个简单的历史管理函数def compress_messages(messages, max_recent2): recent messages[-max_recent:] old messages[:-max_recent] summary summarize(old) # 调用一次模型生成摘要可外部控制 facts extract_facts(old) return build_system_context(summary, facts) recent注意摘要本身也是一次模型调用会产生Token成本。所以不要让每一轮都做摘要。更合理的方式是设定一个触发条件比如历史消息总长超过6000 Token时才触发压缩。也别把摘要做得太激进。如果摘要丢掉了关键实体比如订单号、客户名后面工作流就没法正确查数据。所以压缩时我一般把事实列表单独保留摘要只负责描述过程性的内容。3.3 模型路由大模型把关小模型干活不是所有任务都需要满血版最强模型。很多Agent任务里意图判断、分类、简单格式化这类工作完全可以用小一号的模型完成。我在Harness里加了一条路由规则def route_task(task): if task.type classification: return fast-model # 便宜小模型 if task.need_tools and task.complexity 0.7: return strong-model # 强模型 if task.type extract and len(task.text) 500: return middle-model # 中档模型 return default-model比如用户要“把这段文本里的日期、金额、订单号提取成JSON”这个任务根本不需要多强的推理能力用一个便宜的小模型就能完成一次调用花费可能只有强模型的十分之一。更极端的做法是“级联路由”先让小模型给任务打一个置信度分数只有置信度低的任务才升级到强模型。这种方式可以再压一部分成本代价是实现复杂一些需要维护置信度阈值。需要注意的是模型路由不能只看价格。如果小模型频繁犯错导致重试重试成本可能抵消节省。所以我对路由策略的要求是“宁可把模棱两可的任务升级也不要为了省钱牺牲准确率”。3.4 工具结果瘦身别让模型看不需要的字段很多时候工具API是给前端用的返回了一堆模型不需要的字段。Harness在调用工具之后、把结果塞进上下文之前应该做一个瘦身步骤。举一个实际例子查订单接口原来返回{ order_id: A123, user_name: 张三, amount: 199.0, items: [{sku: x, price: 99.0}, {sku: y, price: 100.0}], shipping_address: ..., payment_receipt: ..., remark: ..., status_history: [...] }但Agent当前只关心订单金额和SKU列表。那就应该只留下这两个字段{ order_id: A123, amount: 199.0, item_count: 2 }这个操作要在Harness的工具调用代理层完成定义每个工具的“返回字段映射”。如果工具本身不支持字段选择就由代理层接收完整响应后做裁剪再决定要不要把结果传给模型。另外对于可能返回大量列表数据的情况必须处理分页。不要让工具一次性返回1000行订单明细。可以改成先返回“总数前50条摘要”让模型决定是否需要翻页。很多场景里模型只需要汇总值根本不需要明细直接让工具端做聚合返回一个总数就够了。3.5 并行调用与步骤合并减少往返轮次每多一轮模型调用就会多带一遍上下文成本是乘法级别的。所以降低往返轮次是降本的一个关键点。传统Harness工作流可能是串行的调用订单列表 - 等待 - 模型决定 - 调用统计接口 - 等待 - 模型总结这其实可以改成同一轮并行调用订单列表和统计接口 - 模型拿到两个结果直接总结很多模型已经支持在单次回复里发起多个工具调用。Harness完全可以用一个循环把相互独立的调用合并在同一轮执行。这样原先需要三到四轮的流程可能压缩到两轮。我改造时在Harness里加了并行工具调用的支持先解析模型本轮想调用的工具列表执行依赖分析没有依赖关系的工具同时执行然后合并结果再发给模型。这个优化对Token成本的影响很大。一轮并行调用相比三轮串行调用至少省掉了中间两轮携带全部上下文的开销。4. 实操复现一个Harness工作流的改造全过程4.1 改造前的流程设计为了便于理解我把一个简化版的Harness工作流配置拿出来展示。改造前的配置大致长这样代表了一种“简单但费钱”的写法agent: name: data_analyst model: strong-model system_prompt: | 你是一个数据分析助手。你可以访问订单系统、用户系统、商品系统。 请根据用户问题主动选择工具并完成分析。 注意要给出详细的分析过程和结论。 tools: - order_search_tool - user_search_tool - product_search_tool - report_tool - notification_tool memory: mode: full_history max_rounds: 8这个配置的问题很明显模型选择单一、工具定义全量注入、记忆模式是全量历史、最大轮次给到8。每个请求都会把四个工具的完整定义加载进去不管用户是否涉及用户系统。实际跑一次的时候日志显示平均每轮输入Token约1.8万平均往返轮次约4.2次单任务总输入Token接近7.6万。如果再算上输出Token单任务总Token基本在9万左右。4.2 改造后的流程设计改造之后我把Harness配置重构为更分层的方式agent: name: data_analyst_v2 routing: router_model: fast-model complexity_rule: threshold: 0.7 high_model: strong-model low_model: middle-model system_prompt_loader: mode: dynamic sections: - general_rules - current_time - task_specific_hint tool_registry: enable_by_intent: true intent_mapping: order: [order_search_tool] customer: [user_search_tool] report: [report_tool] default: [order_search_tool, report_tool] memory: mode: hierarchical recent_window: 2 summary_trigger_tokens: 6000 facts_slot_size: 500 cache: semantic: enabled: true embedding_model: embedding-model threshold: 0.92 ttl: report: 300 faq: 86400 tool_output: prune: enabled: true field_map: order_search_tool: keep_fields: [order_id, amount, item_count, status] report_tool: keep_fields: [summary, total, trend] max_rounds: 4 parallel_tool_calls: true对应到代码层面核心循环也变干净了def optimized_agent_run(query, cache): # 第一层语义缓存 if cache.hit(query): return cache.get(query) # 第二层意图路由与工具动态注入 intent classify(query) # 用小模型 tool_specs load_tools_by_intent(intent) # 第三层上下文组装 system build_system_prompt(intentintent, datetoday()) history hierarchical_memory(query) # 第四层并行工具调用 plan strong_model.decide(system, history, tool_specs) if plan.tool_calls: results parallel_execute(plan.tool_calls) bounded prune_results(results) final_answer strong_model.finish(plan, bounded) else: final_answer plan.answer cache.save(query, final_answer) return final_answer这套流程的核心变化是不再一股脑把所有工具定义塞给模型而是先做意图识别动态加载相关工具。固定提示词按任务拼装不再每次全量带入。记忆改为分层超过阈值就触发摘要。工具调用支持并行且输出字段经过裁剪。前置语义缓存重复任务直接返回。4.3 数字对比和收益量化改造后我做了A/B测试用同样的100条真实历史请求跑了两套配置。为了避免干扰测试期间没有改模型本身。指标改造前改造后降幅平均单任务输入Token760002800063%平均单任务输出Token14000700050%平均总Token900003500061%平均往返轮次4.22.150%语义缓存命中率0%18%-单任务估算成本按统一模型单价0.90元0.42元53%虽然不是每一项都刚好砍半但最终汇总成本下降了53%超过了50%的目标。其中工具定义动态注入和提示词精简贡献最大缓存命中率在前置的语义层刚上线时还不高因为历史数据少但运行一周后命中率稳步上升到25%左右。我还观察到优化后单次任务的响应耗时长了一些因为并行工具调用虽然减少了往返次数但同步等待多个工具返回需要时间。不过整体用户体感变化不大因为模型输出的轮次少了。5. 常见问题与排查技巧实录5.1 token失效与403报错怎么查实际运行中Harness工作流经常要跟外部系统做OAuth认证Token失效是一个高频问题。最常见的报错是类似“sign-in could not be completed token exchange failed”或“token endpoint returned status 403 forbidden”。这类报错背后的通用原因可能包括授权码或刷新令牌过期用户长时间未使用刷新令牌被吊销。令牌作用域不足Harness请求头里带的scope和实际接口要求的不匹配服务端返回403。服务器时钟偏差JWT校验时如果服务器时钟偏差超过容限会直接拒绝。刷新令牌已被消费有的授权服务器规定刷新令牌只能使用一次重复使用会失效。排查步骤我从踩坑里总结出四步先看日志里的完整错误码和响应体不要只看“403”就完事。检查刷新请求里是否带了正确的client_id、client_secret、grant_type。对比认证服务器返回的expires_in和本地时间确认是否存在时钟偏差。查看是不是存在并发场景下两个请求同时用同一个刷新令牌导致后一个被吊销。特别提醒一件事不要把刷新令牌存在多实例共享的无持久化内存里。一旦其中一个实例刷新成功、另一个实例还用旧令牌发起刷新必然失败。最好用Redis或数据库持久化并加锁保证同时只有一个实例执行刷新。5.2 上下文超长与摘要丢失优化后经常遇到的一个新问题是“摘要把关键信息弄丢了”。比如原始对话里用户说过“不要统计退款订单”摘要只写了“用户要求统计订单”模型在后面真正跑统计时就把退款订单也算进去了。这种问题非常隐蔽因为流程能跑通但结果错了。我的解决办法是事实槽单独抽取关键约束比如“排除退款订单”“只看北京地区”“时间范围是上周一到上周日”。这些约束不放进摘要而是作为独立结构体注入系统提示词。摘要只负责归纳“来龙去脉”不负责保存数字、人名、日期等精确事实。压缩触发前先做一次事实校验把新增的事实列表和上轮已有事实合并出现冲突时以最新为准。另外上下文超长报错也不能完全靠压缩规避。Harness里应该在把消息发给模型之前做一次Token预检估算消息长度超过模型最大上下文窗口的80%就强制触发压缩。5.3 缓存命中率低于预期语义缓存上线初期命中率可能只有个位数先别急着怀疑缓存没用。需要检查三点相似度阈值是不是太高了。0.95以上的阈值基本等于要求一模一样建议从0.90开始往下调。Embedding模型是否一致。有的查询用模型A编码缓存也是模型A编码但后来换了模型B两个向量空间不同相似度计算完全失真。用户表达是否高度口语化。同样是“查订单量”用户可能说“订单多不多”“最近卖了几单”“给我看看销量”。这种多样性会让单纯向量相似度很难覆盖。应对方式有两种一是用LLM做查询改写把用户问题归一化成标准问法再走缓存二是在缓存层做“同义词扩展”例如把“销量”“订单量”“销售额”相关词映射到同一个模板。第二种更便宜。5.4 用Coze、Dify、n8n时同样要注意Token很多读者可能用的是现成的工作流平台而不是自研Harness。这些平台的底层逻辑一样只是把Harness的概念变成了“节点”“编排”和“流程”。在Coze里每个节点之间的文本传递都会消耗Token。如果一个节点的输出是一个长JSON下一个节点全量引用那Token一样会爆炸。我的建议是在节点之间加一个“数据清洗”步骤只保留下一个节点需要的字段。在Dify里如果开启Chatflow历史消息默认会带上。需要在记忆组件里设置“最大消息数”并开启摘要。上下文超长报错比如标注为“上下文超长”大多是记忆组件配置问题。在n8n里没有内置的Token计量但可以通过HTTP Request节点调用大模型时手动记录响应里的usage字段再把日志汇到表格里做分析。无论哪个平台优化原则都和自研Harness一样减少每条消息的体积、减少调用轮次、缓存重复结果。6. 一点心得体会这次改造给我最大的一个感受是Token成本优化不是上线一个缓存就完事的而是一个系统性的工程。把成本拆到“固定开销”和“可变开销”两个维度去理解会清晰很多。固定开销是系统提示词、工具定义、摘要这些每轮都要带的上下文可变开销是工具返回、历史累积、无效往返这些随任务不同而波动的部分。50%的降本目标本质上是把固定开销压缩到原来的三分之一同时把可变开销里的重复部分用缓存和并行调用抹掉。还有一个小技巧值得一提不要把优化后的配置一次性全量上线。先开一部分比如只做工具结果瘦身观察一个周期再加动态工具注入再观察。每一层收益单独记录出问题的时候回滚也容易。最终这轮改造完成之后我又顺手把日志面板加了一个Token成本趋势图每周自动汇总。现在每次看到成本波动第一反应不再是焦虑账单而是去翻日志找波动来源。这个习惯比任何单次优化都重要。如果你也在做一个Agent服务或工作流建议先从记录Token用量开始。花一个下午把每一次模型调用的Token数打出来按任务聚合你大概率会发现成本大头和自己想象的不太一样。找准大头再动手50%并没有想象中那么难。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑