用Claude Haiku 5.5做子智能体路由,Agent成本直降六成 做 Agent 项目最头疼的不是功能跑不通而是每一轮对话都在烧钱。我之前维护的一个内部自动化工具早期图省事所有子任务全部丢给旗舰模型处理上线第一个月账单出来直接把我从椅子上吓起来。后来我花了大概一周时间把原来的单一大模型调用改造成“Claude Haiku 5.5 做子智能体路由”的多层结构整体账单砍掉了六成左右而且响应速度还快了不少。这篇文章就把我的改造思路、路由设计、关键代码和踩过的坑一起整理出来给正被 Agent 成本压得难受的开发者们一个可复用的参考方案。这套东西适合谁如果你正在做客服机器人、文档自动化、智能问答、工作流编排这类项目每个请求都会调用大模型 API且任务类型混杂、难度差异大强烈建议花点时间看完。不需要你有多深的多智能体框架基础我也不是用什么重型框架做的核心就是“轻量模型做判断 分层路由 子智能体内部兜底升级”三招。1. 为什么要做子智能体路由1.1 成本浪费的根源模型能力和任务难度错配先说一个很朴素但经常被忽略的事实不是所有任务都需要最强的模型。我当时那个自动化工具主要处理的是用户的自然语言请求可能是“帮我查一下上周的报表数据”也可能是“分析一下这份二十页文档里的关键风险点”。前者只要做关键词抽取和数据库查询后者需要长上下文理解和深层推理。这两种任务放在同一个模型下处理其实是一种资源错配拦住所有请求的要是旗舰级模型才能保证最难的场景不出错但代价就是大量简单任务也在按旗舰模型的价格计费。这就好比请一位资深专家专门帮你接电话记录留言偶尔让他做个高难度决策当然没问题但是如果每天 90% 的时间都在做简单的信息转达那这个用人成本就彻底浪费了。我当时的调用结构就是这种“高射炮打蚊子”的状态。另一个容易被忽略的是 token 消耗结构。真实业务里的任务大部分都是短问题、短输出比如“把这段文本转换成 json 格式”“给这篇文章起三个标题”“判断用户情绪是正面还是负面”。这些任务即使让旗舰模型来做它给出的输出质量和小模型并没有本质差距但花费却可能差出一个数量级。任务难度和模型能力的错配才是成本爆炸的核心根源。1.2 路由方案的整体思路和选型考量我最终的方案不是简单地“简单任务用轻量模型复杂任务用旗舰模型”这种二选一而是做成了三层结构。第一层是入口路由。所有进来的请求先用 Claude Haiku 5.5 做一个快速预分类判断这个请求应该进哪个子智能体并且给出一个置信度分数。第二层是子智能体内部执行。每个子智能体有自己独立的 prompt 和工具集同时内部也有一套“难度分级”逻辑如果 Haiku 5.5 在执行过程中发现任务实际难度超出自身能力比如推理链变得很长或者用户反复追问需要综合多份文档会自动升级到更强的模型。第三层是结果校验和兜底重试。关键对话我们会在轻量模型输出后做一次结构化校验不合格就重新路由到强模型。选 Claude Haiku 5.5 作为路由判断的核心原因很直接它便宜、延迟低而且对分类和抽取这类“判断型任务”的完成质量非常高。路由层本质上是一个低成本的“调度员”它不需要写多好的长文只需要准确判断任务类型和难度就够了。用旗舰模型做这个判断当然更准一点但那个开销会让路由本身失去性价比。另外我用的是函数调用的方式来实现路由决策而不是让模型自由发挥这样可以减少输出格式不稳定的问题。1.3 模型组合方案的选择经过一段时间的对比我最终把任务分成三档对应三种模型策略档位典型任务默认模型兜底升级策略轻量档意图分类、格式转换、关键词抽取、短文本摘要、简单问答Claude Haiku 5.5结果校验不通过时升级到中端模型中端档多步骤信息整理、带工具调用的数据查询、中等长度文档理解中端模型推理复杂时升级到旗舰模型旗舰档长文档综合分析、复杂代码调试、深度规划、多轮复杂推理旗舰模型无直接使用最强能力这个分层逻辑本质上是把任务难度和模型能力做了一次匹配。一开始我也想只用“轻量 旗舰”两档但实测发现很多任务介于两者之间直接给旗舰太贵给轻量又容易出错中间档刚好能接住这部分流量。三层下来流量分布大概是 55% 落在轻量档、35% 落在中端档、10% 落在旗舰档。这正是成本能大幅下降的核心原因只有少数请求真正动用了高价模型。2. 子智能体路由的核心设计2.1 路由目标识别任务类型和难度路由层的核心目标只有两个判断任务类型决定进哪个子智能体评估任务难度决定默认用哪个档位的模型。这两件事合在一起本质是一次多标签分类。我给路由层设计的输出是结构化 JSON比如{ task_type: data_query, difficulty: easy, confidence: 0.93, reason: user requests a simple report lookup, requires_tools: [database_query], escalation_hint: false }为了让 Claude Haiku 5.5 稳定输出这个结构我在系统提示词里明确给出了可选的 task_type 枚举列表并且要求它只输出 JSON所有字段必填。这里有个关键经验不要让它自由生成字段而是要给出固定的 schema否则后面解析代码会写得很痛苦。另外我在路由层还做了一个“是否升级”的初步判断字段 escalation_hint。比如用户的问题是“帮我查一下这个订单的发货状态”任务简单、不需要升级路由层能高置信度识别但如果用户的问题是“帮我根据这三份政策文件拟一个合规建议”哪怕任务类型明确难度字段也应该标记为 hard直接进入旗舰档。这个判断并不需要多高深的推理但要非常细致地定义边界尤其要注意从句子的长度、需求中的指令数量、是否涉及多文档来源来推断。任务的第一步如果分错后面整个智能体链条都会跑偏所以路由层的准确性是整个系统的命脉。为了确保准确我建议把路由层的 prompt 写成一个“选择题”模式先是任务说明然后是候选类型列表每个类型给两到三个典型示例。模型本质上是在做模式匹配给它越清晰的选项输出就越稳定。2.2 子智能体内部的自适应升级机制入口路由做的是第一次分配但光有这一层还不够。现实场景里经常出现一种情况一个请求听起来很简单比如“帮我算一下这个项目的预算”结果真执行起来发现预算表里涉及汇率换算、税费计算、多版本数据对比复杂度远超预期。所以我给每个子智能体内部都加了自适应升级机制不是死板地“路由说简单就走轻量模型”而是让子智能体在执行过程中持续监测自己的状态。我定义了三个升级信号。第一个是结构信号如果工具调用链长度超过了预设阈值比如超过三步说明任务比预想复杂自动升级。第二个是重试信号当模型生成的中间结果出现了解析错误、参数缺失、工具返回空值等问题时触发升级。第三个是置信度信号执行完关键步骤后让模型给出一个整体信心分低于 0.7 就说明能力不够需要更强的模型接手。这套机制本质上是一个“能力边界感知器”。轻量模型在某些场景里会“逞强”——它会给出一个看起来很合理的答案但其实是错的。设置这些监控信号之后子智能体可以在犯错之前察觉到不对劲把请求上交。具体到代码实现上我会在每个子智能体的执行函数里返回一个额外的 meta 信息包含used_models、tool_call_count、confidence_score和escalated_to四个字段。这样整条调用链一目了然出问题也能快速定位是哪个环节决定的升级。2.3 路由层的完整调用形态整个调用形态我用文字描述一下不用画图谱也能理解。用户请求进入系统后先经过一个入口函数这个函数内部起一个名为route_task的轻量模型调用得到 JSON 分类结果。然后根据 task_type 查找路由表路由表中记录了每个子智能体的处理器函数以及该智能体的默认模型档位。之后调用对应子智能体子智能体内部根据难度字段决定第一次执行用的模型并在执行过程中根据升级信号动态调整。如果需要升级无非两种做法一种是把当前上下文原样拼到下一个模型的 system prompt 里重新执行一遍另一种是只让升级模型“接着写”把前面轻量模型的思考过程作为一个参考背景。我实测下来第二种对 token 更省但质量稳定性不如第一种。升级成本高但升级后是新模型全权接手能避免“两个模型思路打架”的问题。需要注意路由层本身也是一次模型调用它也会产生 token 消耗。但这笔开销很小我的路由 prompt 压缩到了 800 tokens 以内加上输出结构固定单次路由成本大约只有主任务调用成本的 2%~5%。这才是整个方案成立的关键调度员很便宜分错了也不心疼。3. 实操过程与关键代码实现3.1 先封装一个统一的模型调用客户端我不建议在业务代码里到处写原生的 API 调用那样后期改模型和调参数会变成一场灾难。我先封装了一个非常薄的客户端层统一管理模型名称、温度参数、超时时间和重试次数。写代码前先确认环境我用的是 Python 3.11API 走的是平台官方接口依赖库主要是openai兼容模式的客户端和pydantic做数据校验项目结构里单独放一个llm_client.py。# llm_client.py import json import time class LLMClient: def __init__(self, api_key, base_url, default_timeout30): self.api_key api_key self.base_url base_url self.default_timeout default_timeout def _post(self, model, messages, temperature, timeout): # 这部分按你实际使用的 SDK 来写 # 我用的是兼容 /chat/completions 的接口直接构造 POST 请求 payload { model: model, messages: messages, temperature: temperature, } resp self._raw_post(payload, timeout) return resp def chat(self, model, messages, temperature0.2, timeoutNone): t timeout or self.default_timeout resp_json self._post(model, messages, temperature, t) return resp_json[choices][0][message][content] def chat_json(self, model, messages, temperature0.1): # 强制要求模型返回 JSON 格式 content self.chat(model, messages, temperaturetemperature) try: return json.loads(content) except json.JSONDecodeError: # 这里是高频坑模型偶尔会把 markdown 代码块包在 JSON 外层 # 我用一个简单的清洗函数去掉噪音再做一次解析 cleaned self._strip_code_fence(content) return json.loads(cleaned) staticmethod def _strip_code_fence(text): text text.strip() if text.startswith(json): text text[len(json):] if text.startswith(): text text[3:] if text.endswith(): text text[:-3] return text.strip()这个封装层里有几个点值得说。第一temperature默认值不能乱给路由判断和结构化抽取用 0.1~0.2低温度能明显减少字段输出混乱的情况生成类任务可以放到 0.7 以上但那不在这个客户端的默认范围里。第二JSON 解析一定要做容错真实环境下模型输出经常带着 Markdown 代码块标记不做清洗的话一个json.loads直接暴毙整个链路就断了。3.2 路由函数与子智能体定义路由函数是整个系统的中枢。我的做法是定义一组子智能体处理器每个处理器是一个普通的 Python 函数签名统一为def handler(context: dict) - dict返回内容统一为{reply: str, meta: dict}。# router.py ROUTER_SYSTEM_PROMPT 你是一个任务调度器。用户会输入一个请求你需要输出一个 JSON字段如下 { task_type: report_query | data_analysis | text_rewrite | image_desc | code_debug | others, difficulty: easy | medium | hard, confidence: 0.0-1.0, requires_tools: [搜索工具名], escalation_hint: false } 规则 - 只输出 JSON不要输出任何解释文字。 - 如果请求长度很短且需求非常明确difficulty 为 easy。 - 如果请求包含多步骤指令、多文档引用、长文本分析difficulty 至少为 medium。 - confidence 低于 0.6 时escalation_hint 必须为 true。 def route_task(user_input: str, llm_client: LLMClient): messages [ {role: system, content: ROUTER_SYSTEM_PROMPT}, {role: user, content: user_input}, ] route_result llm_client.chat_json(claude-haiku-5.5, messages) task_type route_result[task_type] difficulty route_result[difficulty] confidence route_result[confidence] escalation route_result.get(escalation_hint, False) handler get_handler(task_type) return { task_type: task_type, difficulty: difficulty, confidence: confidence, handler: handler, escalation_hint: escalation, } def get_handler(task_type: str): # 这里可以用字典映射避免大量 if-else handlers { report_query: handle_report_query, data_analysis: handle_data_analysis, text_rewrite: handle_text_rewrite, image_desc: handle_image_desc, code_debug: handle_code_debug, others: handle_others, } return handlers.get(task_type, handle_others)子智能体的内部结构我拿其中一个handle_text_rewrite来举例。这个子智能体的职责是文本改写默认模型是轻量档但当文本长度超过一定阈值或是改写要求非常高比如要调整语气、适配不同平台风格就会走中端档。# handlers/text_rewrite.py REWRITE_PROMPT 你是一个文本改写助手。用户会给出原文和要求请按照要求改写并保持核心信息不变。如果原文超过2000字请先输出一句需要深度加工等待用户确认后再继续。改写结果需简洁自然。 def handle_text_rewrite(context: dict, llm_client: LLMClient): original_text context[user_input] demand context.get(demand, ) if len(original_text) 2000: # 明显的复杂度信号直接走中端档 model claude-sonnet-5.5 elif 论文 in demand or 专业术语 in demand or 严格格式 in demand: model claude-sonnet-5.5 else: model claude-haiku-5.5 messages [ {role: system, content: REWRITE_PROMPT}, {role: user, content: f原文{original_text}\n要求{demand}}, ] output llm_client.chat(model, messages) return {reply: output, meta: {model_used: model, tool_call_count: 0}}这里面其实藏了一个很关键的设计选择我把难度判断逻辑做成了显式规则 模型动态判断的混合体。纯靠模型判断长文本改写这种特征非常明显的场景偶尔也会被分错纯靠规则又覆盖不了千奇百怪的请求。所以规则的归规则模型的归模型最后两条路的结果一起决定走哪个档位准确率比单一方式高不少。3.3 成本统计与效果实测在看数字之前先明确一个统计口径我对比的是改造前和改造后的单月 API 账单业务流量基本一致任务样本相同没有额外促销抵扣。改造前所有请求统一走旗舰模型改造后所有请求先过 Claude Haiku 5.5 路由层然后在三个档位之间分配。我用一个简化的模拟数据来说明方便你理解比例关系指标改造前全旗舰改造后路由分层请求总量100000 次100000 次每次平均输入 tokens1200012000每次平均输出 tokens15001500轻量档流量占比0%55%中端档流量占比0%35%旗舰档流量占比100%10%单次平均成本相对值1.0约 0.38总成本相对值1.0约 0.38实际账单算下来我从原来的每月成本降到了原来的 38% 左右正好是“砍掉六成”的水平。其中路由层自身的开销因为每次只传递不到 1000 个 token所以占比非常小可以忽略不计。响应时间也顺手优化了轻量档的平均延迟大约比旗舰模型快了 40%用户体感明显更跟手。这个表里的数据只是一个基准线。如果你业务里的简单任务比例更高比如超过 70%那成本降幅会更大可能会到 70% 以上如果你的任务大多是长文档综合分析和复杂代码那分层收益就没那么大甚至可能因为路由开销而不划算。所以上线这个方案之前我强烈建议先拉一下自己业务日志里的任务长度和类型分布别盲目套用别人的数字。4. 常见问题与排查技巧实录4.1 路由误判和回退策略路由层最常见的坑就是误判。比如用户问“这个功能怎么打不开”它可能被分类成“技术咨询”但其实是“故障报修”后续的子智能体 prompt 和工具集就全不对了。我做了两层防护。第一层是置信度阈值 强制升级。当路由层的 confidence 低于 0.6 时不管难度字段是什么直接把任务升级到中端档并允许该档位内的模型重新评估路由结果。第二层是结果校验钩子。每个子智能体执行完后如果发现输出中包含“我不确定”“无法回答”“需要更多信息”这类弱信号并且任务属于 easy 档就会自动重新走一遍路由流程这次用中端档模型做二次分类。这个机制的思路不是让路由层永不犯错而是让错误有止损点。一次错误路由导致的额外成本可能只是正确路由成本的几倍但总比直接给用户一个答非所问的结果要好。4.2 延迟与缓存的平衡加了路由层之后链路多了一次调用理论上延迟会变高。但我的实际体验是因为大多数任务最终落在了响应更快的轻量模型上整体延迟反而降了。不过还是有两个点需要提前处理好。第一个是路由超时。路由判断如果超过 3 秒会严重影响用户体验所以我在调用客户端里单独设置了更短的超时时间。如果路由调用超时直接按 medium 难度、走中端档处理而不是让用户无限等待。第二个是缓存设计。同一个用户短时间内反复查询同一个问题比如“我的订单到哪了”每次都会触发路由和主调用纯属浪费。我把粗粒度缓存直接挂在路由层前面请求输入先做一次规范化比如去掉时间戳、去掉无意义空格然后查缓存命中就直接返回不再调用任何模型。还有一点和 token 成本直接相关上下文裁剪。子智能体在升级时会把之前的完整对话传给更强的模型但这里有个坑——旧上下文里可能包含大量无关的中间结果比如路由层的分析、工具调用的调试输出这些对最终答案没有帮助却占着宝贵的上下文窗口。我在升级时只传递用户原始问题、工具返回的关键数据和之前的最终回答中间污染信息全部丢掉。这个操作能让升级调用的 token 消耗减少 30% 左右。4.3 质量监控与回归测试成本降下来之后最怕的就是质量也跟着降。我建了一个非常轻量的回归集从历史日志里抽了 200 条请求覆盖各个任务类型和难度档位每轮改造后都跑一遍自动对比看输出答案是否符合预期。这里有一个很实用的技巧让旗舰模型当裁判。我用旗舰模型对轻量模型的输出做打分维度包括信息完整性、逻辑一致性和格式正确性。每批抽样 30 条算平均分低于阈值就告警。这时候旗舰模型的调用量不需要很大几百条抽样而已成本完全可控。另外一定要做 prompt 版本管理。同一个路由 prompt你改了一个字的规则可能对真实流量的命中率造成几个百分点的波动。我给每个版本的 system prompt 打一个版本号写进日志里。这样一旦线上质量出现波动可以马上回溯是哪个 prompt 版本引起的。4.4 降成本的边界条件最后聊一下这个方案的反面——什么情况不适合做子智能体路由。如果你的业务里所有请求都是高难度推理比如全是长文档综合分析、复杂代码生成那路由层的存在几乎没有意义因为每次都会命中旗舰档路由本身还多了一笔开销。如果你的业务对延迟极其敏感比如实时语音交互多一次路由调用会明显拖慢响应那也不划算更优的方案是直接用单个高质量模型加缓存。如果你的任务识别本身就很难比如开放式闲聊用户下一句要说什么根本无法预判路由分类的准确率会惨不忍睹这时候硬做分层只会把简单问题复杂化。我当时决定上这套方案是拉过真实日志确认了任务分布里至少有六成属于简单到中等难度才敢动手的。这个前置调研省不了否则就是纸上谈兵。结尾做这轮改造我最大的体会不是“换了个便宜的模型”而是把成本管理当成一个路由问题去解决——先分清楚什么任务值多少钱再决定让谁去干。Claude Haiku 5.5 在这里的角色不是主力干活的而是那个特别省钱的调度员真正复杂的活它干不了但“判断活难不难”这件事它干得又快又好。如果你正在被 Agent 账单困扰我建议你先把任务分布统计出来再按“轻量 中端 旗舰”的档位去设计路由规则至少不会亏。最后再分享一个小技巧上线路由方案后别忘了把路由层的每次判断结果都记录到日志里然后每周做一次“路由误判复盘”。我就是在复盘时发现凡是涉及金额计算的 easy 类请求被分错的风险特别高于是专门加了一条“金额计算一律升级到中端档”的硬规则。这种细节规则只能来自实际数据任何一个统一的路由模板都替代不了。你业务里也一定有类似的隐形坑跑一段时间日志就会自己浮出来。