☰
用Claude Haiku 5.5做子智能体路由,Agent成本直降六成
2026/10/19 12:58:12 网站建设 项目流程

做 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.11,API 走的是平台官方接口,依赖库主要是openai兼容模式的客户端和pydantic做数据校验,项目结构里单独放一个llm_client.py。

# llm_client.py import json import time class LLMClient: def __init__(self, api_key, base_url, default_timeout=30): 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, temperature=0.2, timeout=None): 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, temperature=0.1): # 强制要求模型返回 JSON 格式 content = self.chat(model, messages, temperature=temperature) 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 类请求,被分错的风险特别高,于是专门加了一条“金额计算一律升级到中端档”的硬规则。这种细节规则只能来自实际数据,任何一个统一的路由模板都替代不了。你业务里也一定有类似的隐形坑,跑一段时间日志就会自己浮出来。

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

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

立即咨询