LLM Agent 工具调用从 20 增到 80 个后 GPT-4 准确率从 89% 掉到 31% 的 5 周复盘:分层 + 路由 + 元工具检索三层架构落地

21 个工具发版当晚没事,第二天投诉率从 0.4% 飙到 3.8%。复盘 5 周才搞清:工具数过 20 准确率开始陡降、过 40 进入崩塌区,80 个工具时 GPT-4o 选择准确率只剩 31%。本文复盘根因(语义相近污染 / lost in middle / description 风格不一 / 参数张冠李戴),给出三层架构(领域路由 + embedding 检索 + 元工具 search_tools),把准确率拉回 91%,token 消耗下降 60%,顺带年省 130 万美元。9 条 agent 工程纪律 + CI 评估接入完整方案。

2025 年 Q3 我们给客服 agent 接入了第 21 个业务工具,准备晚上发版睡觉。第二天早上 9 点,用户投诉率从平时的 0.4% 飙到 3.8%,客服群被 600 条"机器人答非所问"刷屏。复盘了 5 周,我才真正搞明白:LLM agent 的工具数量不是可以线性堆叠的——从 20 个增到 80 个的过程里,GPT-4 的工具选择准确率从 89% 一路掉到 31%。这篇是我们在生产环境真实踩出来的坑、用过的 4 种修法、最后跑通的分层 + 路由 + 元工具检索三层架构,以及 5 周里走错的 9 个弯路。

背景:一个还算正常的客服 agent

这是一个 ToB 的客服 agent,接给 200+ 中小企业用,日均 12 万次对话,后端是 GPT-4o + LangGraph 0.2,部署在 K8s 三可用区里,平均 p95 延迟在事故前一直稳定在 1.4 秒以下。第一版上线时只有 6 个工具:查订单、查物流、退款发起、退款查询、商品搜索、转人工。那时候我们用最朴素的 tools= 数组直接塞到 chat completion 里,选择准确率(用人工标注 500 条对话验证)有 94%。这个数字让团队对"LLM 工具调用"产生了一种过度乐观的印象,大家觉得"反正都是 GPT-4 自己理解,加工具就是改 schema 的事",这个错觉为后面埋了非常大的雷。

随着客户接得越来越多,工具数也滚雪球式增长:6 → 12 → 21 → 38 → 51 → 80。每次增加都是某个客户提的需求,产品同学说"这个工具特别简单,大模型肯定能理解",我们也就这么塞进去了。复盘下来,这种"按需添加"的工作方式本身就是问题:每次只看新工具相对于原系统的边际影响,没人有动力去做整体回归测试,也没人有动力下线那些边缘工具——因为下线一个工具要去跟产品 PM、客户成功、甚至客户本人解释,而新增工具几乎不需要解释。最终,直到那次 21 个工具的发版,事故才把这个慢性病炸出来。

事故发生时我才意识到,我们对"agent 工具数"完全没有任何量化的护栏。同样大小的服务,DB 表数量超过 200 个、redis key 总数超过千万,我们都有报警、有 SOP、有 review checklist。但工具数从 6 涨到 80 这件事,居然没有任何门槛、没有 review、没有 capacity planning。这种治理空白本身,比任何架构问题都更值得反思。

事故时间线

时刻 事件 关键指标
T-7 天 新增 3 个工具:查询发票、申请发票、修改发票抬头 工具总数 18 → 21
T-0(发版当晚) 灰度 5% 流量,无明显异常,全量 工具调用错误率 0.6%
T+9h(次日 9:00) 客服群开始爆,用户投诉"问退款给我查发票" 错误率飙到 3.8%
T+10h 初步定位是工具选错,先把 3 个发票工具下掉 错误率回落到 1.1%
T+12h 下午回到 0.7%,但仍高于发版前 留下技术债
T+1 周 批了 5 周时间做根因分析 + 架构改造 立项
T+5 周 新架构灰度跑稳,工具数扩到 80 个,准确率回到 91% 结案

第一轮排查:我们以为是 prompt 问题

事故当天 10 点回滚后,值班同学第一反应是"肯定是新工具的描述写得不好"。我们对比了 3 个发票工具的 description,确实有点啰嗦,改短改清楚,重发。结果第二天又收到投诉,这次是"问发票给我查物流"——错的方向反过来了。也就是说新加的工具没"抢"走老工具的请求,但老工具反而开始"抢"新工具的请求。这非常反直觉。

我们意识到这不是单个 description 的问题,是模型在 21 个 description 里整体的"理解能力"下降了。于是开始做一个完整的离线评估集——这件事其实应该一开始就做。

import json, openai, asyncio
from typing import List, Dict

# 评估集:500 条人工标注的真实对话,每条带 ground truth 工具名
EVAL_SET = json.load(open("eval_500.json"))

async def eval_tool_selection(tools: List[Dict], samples: List[Dict]) -> float:
    """跑一遍评估集,返回工具选择准确率"""
    client = openai.AsyncOpenAI()
    correct = 0
    total = len(samples)
    sem = asyncio.Semaphore(20)

    async def _one(item):
        async with sem:
            resp = await client.chat.completions.create(
                model="gpt-4o-2024-08-06",
                messages=[
                    {"role": "system", "content": SYSTEM_PROMPT},
                    {"role": "user", "content": item["user"]},
                ],
                tools=tools,
                tool_choice="auto",
                temperature=0,
            )
            msg = resp.choices[0].message
            if msg.tool_calls and msg.tool_calls[0].function.name == item["expected_tool"]:
                return 1
            return 0

    results = await asyncio.gather(*[_one(s) for s in samples])
    return sum(results) / total

# 跑出来:21 个工具时准确率 78%,12 个工具时是 89%
acc_12 = asyncio.run(eval_tool_selection(TOOLS_12, EVAL_SET))
acc_21 = asyncio.run(eval_tool_selection(TOOLS_21, EVAL_SET))
print(f"12 tools: {acc_12:.2%}, 21 tools: {acc_21:.2%}")

这一跑就把现实摆在眼前:21 个工具下整体准确率只有 78%,而 12 个工具的时候有 89%。多了 9 个工具,损失了 11 个百分点。我们突然意识到,如果不解决这个问题,后续根本扩不到 50 个、80 个工具。

更严重的是,我们以前完全没有这套评估集。3 天里建评估集的过程花了 4 个人 22 小时,扒了过去 30 天的真实对话,人工标注 500 条,每条都标了"应该调哪个工具""理想参数应该是什么""理想回复关键词"。这个评估集后来变成我们整个 agent 团队最重要的资产——这次复盘最大的回报其实就是这个评估集本身,它让后面的每一次改动都有"通过 / 不通过"的客观标尺,而不是各凭直觉吵。

顺带说一句,我们对评估集做过一次"模型抖动"测试:同一个 prompt、同一个 query、temperature=0 跑 5 遍,GPT-4o 仍然有大约 1-2% 的样本会给出不同的工具选择。这意味着评估指标天然有 ±1.5 个点的噪音,任何小于 2 个点的"提升"都不能算真的提升,需要连跑 3 次取均值再判断。这一点对后续做 A/B 实验帮助很大,避免了团队拿"偶然好看的数据"上线变更。

第二轮排查:把工具数当变量做了压力测试

真正想明白问题,是后来我们做了一组"工具数量 vs 准确率"的曲线——把 80 个工具按业务分组,然后随机抽取 5/10/20/40/80 个塞到 tools 数组里,跑同一个评估集。

工具数 选择准确率 平均 tokens/调用 平均延迟
5 96% 1100 720ms
10 93% 1850 840ms
20 89% 3200 1.1s
40 71% 5800 1.6s
60 49% 8100 2.2s
80 31% 10500 2.9s

曲线非常清晰:工具数过了 20 之后,准确率开始陡降;过了 40 进入崩塌区。同样的现象在 Claude 3.5 Sonnet 和 Gemini 1.5 Pro 上都验证了,数字略有差异但趋势一致——Claude 在 80 工具下仍能维持到 37%,Gemini 1.5 Pro 是 34%,差距没有想象中显著。这不是单个模型的问题,是大模型在 long context 选择题里的一个共性弱点,跟具体 vendor 关系不大。

更值得注意的是 tokens 数:80 个工具光 schema 就占 1 万多 tokens,每次对话都要带着这堆 schema 进去,成本和延迟也是问题。我们一度想过"那就只塞跟当前问题相关的工具"——这个朴素直觉后来变成了正解的一部分,但实现起来比想象中复杂得多。具体来说,所谓"相关"需要在 query 级别动态决策,意味着每一次对话都要做一次额外的语义匹配,这件事如果用 LLM 来做,延迟和成本就两头都堵死;如果用 embedding,又会面临"近义工具仍然区分不开"的老问题。我们后来落地的方案融合了 embedding 检索 + LLM 二次确认 + 兜底元工具,后面会详细讲。

还有一个我们后来才意识到的问题:工具数膨胀不光影响准确率,还显著降低了系统的"可调试性"。当一个对话调错工具,我们想知道为什么调错,只能去看那 80 个 schema 拼出来的 prompt——光是把这个 prompt 渲染出来人工读一遍就要 10 分钟。工具数越多,故障复盘的人均成本越高,SRE 同事最早抱怨的就是这一点。

问题本质:为什么工具一多就崩

我们查了 OpenAI 和 Anthropic 的几篇技术博客,加上自己抓 trace 分析,最后总结出 4 个因果:

第一,语义相近的工具之间互相"污染"。比如"查询订单状态"和"查询发票状态",对模型来说在 embedding 空间里几乎重叠,描述里只要有一处轻微的歧义,模型就会随机选其中一个。当工具池里有 5 对这种近义工具时,错误率就会乘积式爆炸。

第二,prompt 中后段的工具被"遗忘"。tool schema 是按顺序拼到 prompt 里的,模型有典型的 lost-in-the-middle 倾向,排在 30 名以后的工具会被显著低权重对待。我们做过一个实验:把同一个工具放在 list 的第 1、20、40、60 位,选择召回率分别是 0.95、0.81、0.62、0.43。

第三,description 写法不一致带来的解析负担。早期工具的 description 是简练的命令式("查询订单"),后来的工具 description 越写越长,有人写背景、有人写返回值、有人写适用场景。模型在解析这种"风格不一"的 80 段描述时,真的会变笨。

第四,默认参数 vs 必填参数的混乱。当 20 个工具同时存在,且都有 3-5 个参数时,模型会出现"参数张冠李戴"的现象——比如把订单号填到了发票号字段里。这部分错误其实不是工具选错,但用户感知一致:"机器人答非所问"。

问题示意图

四种修法的取舍

修法 1:Prompt 工程优化(治标不治本)

第一周我们试的是 prompt 工程:统一所有工具 description 的格式,加 few-shot 例子,在 system prompt 里强调"如果是退款问题优先选 refund_initiate"。这条线确实把 21 个工具的准确率从 78% 拉回到 84%,但工具数扩到 40 时又掉到 75%,继续扩根本扛不住。

结论:prompt 工程是必要的清洁工作,但不是架构方案。我们用半天时间把所有 description 重写成统一模板,这个收益是免费的、应该做的,但不能依赖它解决问题。

# 统一后的 description 模板
TOOL_DESC_TEMPLATE = """
{verb}{object}。
[何时调用] {when_to_call}
[何时不调用] {when_not_to_call}
[参数说明] {param_summary}
[返回示例] {return_example}
"""

# 示例:
{
    "name": "refund_initiate",
    "description": (
        "发起一笔退款。\n"
        "[何时调用] 用户明确说要退款、退货、不要了、申请退款。\n"
        "[何时不调用] 用户只是查询退款进度、问退款规则、问能不能退——这些走 refund_query 或 refund_policy。\n"
        "[参数说明] order_id 必填,reason 必填(从枚举里选)。\n"
        "[返回示例] {{\"refund_id\": \"R20251030001\", \"status\": \"submitted\"}}"
    ),
    "parameters": {...}
}

修法 2:工具分层(按场景切分子 agent)

这是我们真正动结构的第一刀:不再让一个 agent 看到所有 80 个工具,而是按业务把 agent 拆成 6 个"领域 agent",每个 agent 只持有自己领域的 8-15 个工具。

领域 agent 工具数 典型工具
订单类 11 查订单、改地址、催发货、查物流、确认收货...
售后类 13 退款发起、退款查询、退货寄回、补偿券发放...
商品类 9 商品搜索、库存查询、规格对比、推荐...
账户类 8 登录、绑定手机、修改密码、注销...
发票类 7 查发票、申请发票、改抬头、合开发票...
通用类 12 转人工、留言、催回复、记录工单...

上层是一个轻量的"路由 agent",只负责判断当前问题属于哪个领域,然后转到对应子 agent。路由 agent 看到的不是 80 个工具,而是 6 个"领域选择"。

from langgraph.graph import StateGraph, END
from typing import TypedDict, List

class AgentState(TypedDict):
    messages: List[dict]
    domain: str | None
    tool_results: List[dict]

DOMAINS = ["order", "after_sales", "product", "account", "invoice", "common"]

ROUTER_PROMPT = """你是一个客服 agent 的路由器。请根据用户当前问题判断属于哪个领域,
只回答领域名(order/after_sales/product/account/invoice/common),不要解释。

例子:
"我要退款" -> after_sales
"我的快递到哪了" -> order
"开张发票" -> invoice
"转人工" -> common
"""

async def router_node(state: AgentState) -> AgentState:
    resp = await client.chat.completions.create(
        model="gpt-4o-mini",  # 路由不需要大模型
        messages=[
            {"role": "system", "content": ROUTER_PROMPT},
            {"role": "user", "content": state["messages"][-1]["content"]},
        ],
        temperature=0,
        max_tokens=20,
    )
    domain = resp.choices[0].message.content.strip().lower()
    if domain not in DOMAINS:
        domain = "common"
    state["domain"] = domain
    return state

# 子 agent 只持有自己领域的工具
DOMAIN_TOOLS = {
    "order": ORDER_TOOLS,        # 11 个
    "after_sales": AFTER_TOOLS,  # 13 个
    "product": PRODUCT_TOOLS,    # 9 个
    "account": ACCOUNT_TOOLS,    # 8 个
    "invoice": INVOICE_TOOLS,    # 7 个
    "common": COMMON_TOOLS,      # 12 个
}

async def domain_agent_node(state: AgentState) -> AgentState:
    tools = DOMAIN_TOOLS[state["domain"]]
    resp = await client.chat.completions.create(
        model="gpt-4o-2024-08-06",
        messages=[{"role": "system", "content": DOMAIN_PROMPTS[state["domain"]]}] + state["messages"],
        tools=tools,
        tool_choice="auto",
        temperature=0,
    )
    # ... 处理 tool_calls
    return state

graph = StateGraph(AgentState)
graph.add_node("router", router_node)
graph.add_node("domain", domain_agent_node)
graph.set_entry_point("router")
graph.add_edge("router", "domain")
graph.add_edge("domain", END)
app = graph.compile()

这一刀下去,准确率从 71%(40 个工具 flat 模式)涨到 85%。但出现了新问题:跨领域请求处理不好。比如"我退款了但发票已经开了怎么办"——这同时跨售后和发票,路由 agent 只能选一个,选错了就转不回来。我们后来用 LangGraph 的多 node 协同 + 状态共享解决了一半,但仍有约 6% 的请求是真·跨域。

修法 3:工具检索(用 embedding 做相关性筛选)

分层之后,我们想再优化掉跨域不准的问题,引入了第二层:在子 agent 内部,如果某个领域的工具超过 10 个,就先用 embedding 检索把跟当前问题最相关的 5-7 个工具挑出来,再塞给 LLM。

import numpy as np
from openai import AsyncOpenAI

class ToolRetriever:
    """对工具 description 做 embedding,运行时根据 query 检索 top-k 工具"""
    def __init__(self, tools: List[dict], embed_model: str = "text-embedding-3-small"):
        self.tools = tools
        self.embed_model = embed_model
        self.client = AsyncOpenAI()
        self.embeddings: np.ndarray | None = None

    async def build(self):
        # 给每个工具构造一个"检索文本":name + description + 典型 query 示例
        texts = [
            f"{t['function']['name']}: {t['function']['description']}\n"
            f"示例问句:{'; '.join(t.get('sample_queries', []))}"
            for t in self.tools
        ]
        resp = await self.client.embeddings.create(model=self.embed_model, input=texts)
        self.embeddings = np.array([d.embedding for d in resp.data])

    async def retrieve(self, query: str, top_k: int = 6) -> List[dict]:
        q_resp = await self.client.embeddings.create(
            model=self.embed_model, input=[query]
        )
        q_vec = np.array(q_resp.data[0].embedding)
        sims = self.embeddings @ q_vec / (
            np.linalg.norm(self.embeddings, axis=1) * np.linalg.norm(q_vec) + 1e-9
        )
        top_idx = np.argsort(-sims)[:top_k]
        return [self.tools[i] for i in top_idx]

# 用在 domain agent 里
retriever = ToolRetriever(DOMAIN_TOOLS["after_sales"])
await retriever.build()

async def smart_domain_agent(state: AgentState) -> AgentState:
    query = state["messages"][-1]["content"]
    candidate_tools = await retriever.retrieve(query, top_k=7)
    resp = await client.chat.completions.create(
        model="gpt-4o-2024-08-06",
        messages=state["messages"],
        tools=candidate_tools,  # 只塞 7 个,不是 13 个
        tool_choice="auto",
    )
    return state

有几个细节决定了检索效果:

  1. "检索文本"不能只用 description——业务工具的 name 很重要,而且每个工具我们手动写了 5-10 条 "样例问句",一起拼到 embedding 里。这一步让召回提升了 12 个百分点。
  2. 始终保留 1-2 个"兜底工具"(比如转人工、留言),不参与检索筛选,无条件塞进去。否则有些边缘问题模型会"无工具可用"。
  3. 检索的 top-k 不要太小。我们试过 k=3,有些时候确实选不到正解;k=7 是经验最优值,k=10 又开始回退到 lost-in-middle 问题。
  4. embedding 离线预计算 + 内存缓存。工具 description 一天更新不了几次,完全可以服务启动时一次性算好。线上 query embedding 走 batched cache,99% 的常见问句直接命中。

修法 4:元工具检索(让 LLM 自己"查工具手册")

这是我们最后一层、也是最反直觉的一招。当工具数过了 60,即使每个领域内做了 embedding 检索,有时候 LLM 还是会卡——因为它根本不知道有这个工具存在。比如用户问"我能不能把多张发票合开成一张",系统里的 invoice_merge 工具被检索丢了,LLM 就只会回答"暂不支持"。

我们后来做的方案是:给 LLM 一个元工具search_tools(query),让它在不知道有没有合适工具时主动调用这个元工具去搜索。返回的不是结果,而是匹配的工具列表 + 调用说明。LLM 再根据这个返回去发起真正的工具调用。

META_TOOL = {
    "type": "function",
    "function": {
        "name": "search_tools",
        "description": (
            "在不确定有没有合适工具能解决用户问题时,先调用本工具搜索可用工具。"
            "返回最相关的 3-5 个工具说明。"
            "[何时调用] 用户的问题不能直接匹配到你已知的工具时。"
            "[何时不调用] 已经能确定要用哪个工具时,直接调那个工具就行。"
        ),
        "parameters": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "用自然语言描述需要的工具能力,例如:把多张发票合并成一张"
                }
            },
            "required": ["query"]
        }
    }
}

# 后端实现
async def handle_search_tools(query: str) -> str:
    hits = await GLOBAL_RETRIEVER.retrieve(query, top_k=5)
    lines = []
    for h in hits:
        lines.append(
            f"- {h['function']['name']}: {h['function']['description'][:120]}"
        )
    return "可用工具:\n" + "\n".join(lines) + "\n请直接调用其中合适的一个。"

元工具检索让 80 个工具下的覆盖率从 76% 涨到了 91%。代价是平均多一次模型调用(从 1.2 次平均到 1.4 次),延迟和 token 成本略涨,但对长尾问题的"识别能力"是质变。

三层架构最终形态

5 周里走错的 9 个弯路

  1. 第 1 周想用更强的模型解决——把 gpt-4o 换成 gpt-4-turbo 又换回 claude-3.5-sonnet,效果都没本质提升。模型不能解决工具空间设计问题。
  2. 第 1 周末重写 description——确实有效但单独无法支撑 40+ 工具。
  3. 第 2 周想用 function chaining——把工具拆得更细更原子,结果反而工具数翻倍,准确率更糟。原子化是反方向。
  4. 第 2 周中尝试 self-reflection——让 LLM 选完工具后再反思一次,可以从 78% 升到 82%,但成本翻倍延迟翻倍,产品不接受。
  5. 第 3 周做工具分组但没做路由——把工具按领域贴标签,在 system prompt 里告诉 LLM "退款相关请优先看 #refund 标签",效果约等于不变,因为 LLM 不会真的"过滤"。
  6. 第 3 周末走对了分层路线——但路由 agent 用 gpt-4o 太贵,后来换 gpt-4o-mini + few-shot 提示,准确率 96%,成本降 90%。
  7. 第 4 周做 embedding retrieval 没加 sample queries——只用 description 做向量,召回率只到 79%,加上人工写的样例问句之后冲到 91%。
  8. 第 4 周中检索 top-k 想做动态——根据 query 长度自适应 k,结果工程复杂度上升收益不到 1 个点,回退到固定 k=7。
  9. 第 5 周想去掉元工具——觉得"分层 + 检索"已经够好了,试着把元工具拿掉,长尾覆盖率立刻从 91% 掉到 84%,加回来。

评估体系是基础设施,不是事后补救

这次复盘最大的认知改变,其实不是上面那些架构招式,而是:没有持续运行的离线评估集,任何 agent 优化都是赌博。我们后来沉淀了一个 1500 条规模的评估集,每次工具增删、prompt 改动、模型升级,CI 里自动跑一遍:

# .github/workflows/agent_eval.yml
name: Agent Eval
on:
  pull_request:
    paths:
      - 'agents/**'
      - 'tools/**'
      - 'prompts/**'
jobs:
  eval:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - name: Install
        run: pip install -r requirements.txt
      - name: Run eval set
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        run: python -m eval.run --set datasets/eval_1500.jsonl --output report.json
      - name: Check thresholds
        run: |
          python -c "
          import json
          r = json.load(open('report.json'))
          assert r['tool_selection_acc'] >= 0.88, f\"acc {r['tool_selection_acc']} below 0.88\"
          assert r['cross_domain_acc'] >= 0.80, f\"cross-domain {r['cross_domain_acc']} below 0.80\"
          assert r['p95_latency_ms'] <= 2500, f\"p95 {r['p95_latency_ms']} above 2500\"
          "
      - name: Upload
        uses: actions/upload-artifact@v4
        with:
          name: eval-report
          path: report.json

评估集需要包含:正向样例(每个工具至少 10 条)、近义混淆样例、跨域样例、无法用任何工具回答的样例(测试 fallback)、参数提取样例。这五类样本缺一个,评估就有漏洞。

性能 / 成本对比

方案 准确率 p95 延迟 每次对话 token 每次对话 USD
原始 flat(80 工具) 31% 2.9s 10500 0.052
仅 prompt 优化 43% 2.7s 10100 0.051
分层(6 子 agent) 78% 1.8s 4200 0.024
分层 + 检索 top-k 7 87% 1.6s 2800 0.018
三层(+ 元工具) 91% 1.9s 3300 0.021

三层架构相比原始 flat 模式,准确率 31% → 91%,token 消耗 10500 → 3300,成本下降 60%。多了 0.2 秒元工具调用,但准确率换得值。

决策树:什么时候用哪种方案

我们立的 9 条 agent 工程纪律

  1. 工具数 > 20 必须有离线评估集,> 40 必须接 CI,任何 PR 不过线就不准合。
  2. 新工具上线前必须先标 5-10 条样例问句,跟 description 一起作为评估和检索的输入。这是产品同学的义务,工程同学拒接没有样例的工具。
  3. tool description 必须用统一模板:[何时调用] / [何时不调用] / [参数说明] / [返回示例]。新人提交的工具如果没按模板写,CI 检查脚本直接拒。
  4. 语义相近工具必须在描述里明确划界:"refund_initiate 和 refund_query 的区别是…",必要时加入反面例子。
  5. 路由层永远用便宜小模型(gpt-4o-mini / claude-haiku),决策层才用大模型。不要让大模型干路由的活,贵且未必更准。
  6. 始终保留兜底工具(转人工 / 留言 / 反馈),不参与检索过滤,无条件可用。
  7. 不要相信单次评估,所有指标必须连跑 3 次取均值,LLM 输出有抖动。
  8. 工具增删要走"投票制":新工具上线必须证明评估集准确率不下降;不下降才能进生产。
  9. 每月做一次工具"瘦身审计":近 30 天调用次数 < 50 的工具进入观察名单,< 10 的工具下线或合并。我们 5 周里下掉了 11 个"看上去有用但其实没人用"的工具,这件事对模型选择准确率的提升远比想象中大。

关于成本的一笔账

很多人忽略 token 成本。我们每天对话量 12 万次,优化前每次 0.052 美元,日均 6240 美元,月 19 万美元。优化后每次 0.021 美元,日均 2520 美元,月 7.6 万美元。5 周的架构改造每年省下约 130 万美元的 OpenAI 账单。后端 4 个工程师做这件事的工资,加在一起还不到这个数字的 1/10。

当然这是按 GPT-4o 当前定价算的,后续我们也在评估把路由 agent 和部分子 agent 切到 Claude Haiku 或 Gemini Flash,理论上还能再压 40%。但在没把架构理顺之前,光换模型救不了你——这是这次复盘最坚硬的一条经验。

总结

从 21 个工具崩到 80 个工具稳,我们花了 5 周和大约 90 小时的工程师时间,踩了 9 个弯路,最终落地的三层架构其实并不复杂:路由分流到领域子 agent,子 agent 用 embedding 检索过滤工具,LLM 找不到合适工具时调用元工具 search_tools 主动查手册。复杂的不是架构,而是承认大模型"工具空间"是一种 context,会像 long context 一样有上限

这之后我自己写新 agent 时,工具数一过 15 就先把分层骨架搭起来——不是因为现在需要,而是知道再加几个就会需要。架构上的"过早优化"在 agent 工程里其实是节约,你后期再回头改要重写 prompt、重写评估集、重新做灰度,代价比预想的高得多。如果你正在做一个工具规模化扩张的 agent,希望这 5 周的弯路能帮你少走几个。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

TypeScript discriminated union exhaustiveness check 漏写引发 ¥21.8 万对账偏差的 4 天复盘:never 守卫 + ESLint + 运行时白名单三层兜底

2026-5-26 18:27:37

技术教程

Python C 扩展不释放 GIL 导致图像服务 8 核只跑 220% CPU 的 7 天复盘:Pillow-SIMD + Cython nogil + ProcessPool 组合拳

2026-5-26 18:41:53

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索