首页 / 文章 / 你的智能体为每个从未调用的工具付出代价
← 返回
AI技术

你的智能体为每个从未调用的工具付出代价

✍️ zhirenhun 📅 2026/8/2 👁 256 阅读 ⏱ 30 分钟
你的智能体为每个从未调用的工具付出代价

大多数智能体都会为它们不使用的工具付费。不是一次——而是每一轮都如此。

其机制简单到容易让人忽略。当你给模型一组工具时,每个工具的完整 JSON schema 都会进入请求中。名称、描述、参数类型、枚举值、嵌套对象,一应俱全。模型读取全部内容,选一个,然后调用。下一轮,整个目录再次通过线路传输,因为 API 是无状态的,工具列表是请求的一部分。

八个工具时,这几乎是看不见的。两百个工具时,它主导了你的输入 token 账单,挤掉了你真正关心的上下文,而且——更伤人的是——可测量地降低了工具选择的准确性。

微软 Foundry 在 Build 2026 上推出了 工具搜索,正是为了解决这个问题。这值得理解,而且值得超越 Foundry 去理解:同样的故障模式会出现在任何重 MCP 的智能体中,其缓解方案也具有普适性。

问题的形态

一个中等详细程度的工具 schema 一旦加上足以让模型正确使用的参数描述,就会消耗 150–400 个 token。廉价的 schema 会导致糟糕的工具调用,因此团队会编写详尽的 schema,这是正确的做法,同时也是昂贵的做法。

将这一成本乘以目录规模和轮次:

tokens_per_turn  = base_prompt + conversation_history + (n_tools × avg_schema_tokens)
tokens_per_task  = tokens_per_turn × turns
进入全屏模式 退出全屏模式

一个十二轮的任务,面对200个工具目录,每个模式250个token,仅工具定义就消耗大约60万个输入token。对话本身可能只有2万个。你大部分花费,是在反复阅读一个模型已经决定不用的目录,重复了十一次。

Token成本是可见的一半。不可见的一半更糟。随着目录增长,它们积累了近似重复项——get_customerget_customer_profilefetch_customer_recordlookup_account_by_customer——这些通常来自不同的团队、不同的MCP服务器、代码库的不同时期。选择准确率下降不是因为模型变笨了,而是因为你给了它一个真正含糊不清的菜单。

工具搜索改变了什么

Foundry 不是提供一个扁平的列表,而是暴露了两个元工具:tool_searchcall_tool。代理描述它试图做什么,返回一个小的排序候选集,然后调用其中一个。

工具图片描述

代价是一次检索往返,以换取不携带整个目录。大约三十个工具以上,这种交换非常有利。低于这个数量,通常不利——这是采用它之前首先要诚实面对的事情。

策略对比

工具搜索不是唯一的答案,也不总是正确的答案。

策略 每轮Token成本 大规模下的选择准确率 增加的延迟 运营成本
扁平工具列表 随目录大小线性增长 超过约50个工具后急剧下降 微不足道
按任务类型手动分区的目录 分区内较低 如果路由正确,则良好 高——路由规则随工具变化而失效
按领域划分的多代理 每个子代理较低 领域内良好,跨领域较差 移交开销 高——需要编排和共享状态
工具搜索 无论目录大小如何,大致持平 取决于检索质量 多一次往返 低——索引为您维护
工具搜索加固定 持平,加上固定的模式 现有最佳:热路径有保证,尾部通过检索获取 仅在尾部

固定(Pinning)是大家常常跳过而不应跳过的一环。Foundry 允许你固定关键工具,从而完全绕过搜索往返,添加上下文来说明你的团队实际上如何看待某个工具,并自动固定频繁使用的工具。实际上,少数几个工具就占了大部分调用;固定它们,检索其余内容,你就能获得平摊的 token 成本,而无需为常见路径付出检索延迟。

端到端构建

两条命令即可搭建一个附带有工具箱的托管代理。

mkdir my-toolbox-agent && cd my-toolbox-agent

azd ai agent init \
  -m "https://github.com/microsoft-foundry/foundry-samples/blob/main/samples/python/hosted-agents/agent-framework/responses/04-foundry-toolbox/agent.manifest.yaml" \
  --src src/toolbox-agent
进入全屏模式 退出全屏模式

然后根据样本的描述符创建工具箱:

azd ai toolbox create my-toolbox --from-file ./src/toolbox-agent/toolbox.yaml
进入全屏模式 退出全屏模式

这会打印出一个带版本号的 MCP 端点,也就是你的智能体所绑定的端点:

https://<account>.services.ai.azure.com/api/projects/<project>/toolboxes/my-toolbox/versions/1/mcp?api-version=v1
进入全屏模式 退出全屏模式

有一个值得指出的注意点,因为它让我花了二十分钟:azd ai toolbox create 需要一个本地的 azd 项目和运行环境,即使你显式传递了 --project-endpoint 参数也是如此。如果你不是从 azd ai agent init 开始,请先运行 azd init --minimal,并将端点设置到环境中:

azd init --minimal
azd env set FOUNDRY_PROJECT_ENDPOINT https://<account>.services.ai.azure.com/api/projects/<project>
进入全屏模式 退出全屏模式

你的 toolbox.yaml 文件用于声明目录及其搜索行为。下面的形状仅供参考——预览模式会移动,所以复制前请检查当前示例:

# toolbox.yaml — illustrative structure, verify against the current sample
name: my-toolbox
description: Order operations, customer lookup, and fulfilment tools

toolSearch:
  enabled: true
  # Pinned tools skip retrieval entirely and are always in context.
  # Keep this list short — every pin is a permanent token cost.
  pinned:
    - get_order_status
    - search_customers
  autoPin:
    enabled: true
    minCallsPerWindow: 25

tools:
  - name: get_order_status
    source: mcp
    server: orders-mcp
    # Retrieval context: describe the tool the way your team describes it,
    # including the vocabulary users actually type.
    searchContext: >
      Look up the current fulfilment state of a single order. Use when the
      user mentions an order number, tracking number, "where is my package",
      or asks whether something shipped.

  - name: issue_refund
    source: mcp
    server: billing-mcp
    searchContext: >
      Issue a full or partial refund against a completed order. Requires an
      order ID and an amount. Do not use for cancellations of unshipped
      orders — use cancel_order instead.
进入全屏模式 退出全屏模式

最后那个searchContext确实在发挥实际作用。检索质量就是工具搜索的关键所在,而检索是基于你的描述进行的。明确的反面示例——不要用这个,用那个——是你能在那儿写下的最具价值的内容,因为近乎重复项恰恰是选择失败的地方。

一个回合,按顺序

序列描述

衡量它,因为你不应只凭我的一面之词

这个话题之所以值得写下来而不只是直接启用,是因为这个收益是可衡量的,而收益的大小完全取决于你的目录。在改动任何内容之前,先接通追踪,取得一个基线。

pip install azure-ai-projects azure-identity opentelemetry-sdk azure-core-tracing-opentelemetry
进入全屏模式 退出全屏模式
from azure.core.settings import settings
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor, ConsoleSpanExporter
from azure.ai.projects.telemetry import AIProjectInstrumentor

settings.tracing_implementation = "opentelemetry"

span_exporter = ConsoleSpanExporter()
tracer_provider = TracerProvider()
tracer_provider.add_span_processor(SimpleSpanProcessor(span_exporter))
trace.set_tracer_provider(tracer_provider)

# Emits GenAI spans for every agent and model call in this process
AIProjectInstrumentor().instrument()
进入全屏模式 退出全屏模式

运行前设置 AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING=true,每一轮——提示、工具调用、模型响应——都会以附加了令牌使用量的 span 形式呈现。将 ConsoleSpanExporter 替换为 Azure Monitor 导出器,相同的 span 将出现在 Foundry 门户的 Observability 选项卡中。

现在运行比较。下面的测试工具故意设计得很简单:一个固定的任务集,两种配置,聚合这些 span。

import os
import json
from dataclasses import dataclass, field
from statistics import mean

@dataclass
class RunResult:
    config: str
    input_tokens: list = field(default_factory=list)
    turns: list = field(default_factory=list)
    correct_tool: list = field(default_factory=list)

TASKS = [
    # (prompt, tool the model should end up calling)
    ("Where is order 88231?",                      "get_order_status"),
    ("Refund the second item on order 88231",      "issue_refund"),
    ("Cancel order 90114, it hasn't shipped",      "cancel_order"),
    ("Which customers in Ohio ordered twice?",     "search_customers"),
    ("Send the June invoice to billing@acme.com",  "email_invoice"),
]

def score_run(spans, expected_tool):
    """Pull token usage and the actually-invoked tool out of collected spans."""
    input_tokens = sum(
        s.attributes.get("gen_ai.usage.input_tokens", 0) for s in spans
    )
    invoked = [
        s.attributes.get("gen_ai.tool.name")
        for s in spans
        if s.attributes.get("gen_ai.tool.name")
    ]
    # With Tool Search the target arrives as a call_tool argument, so unwrap it.
    resolved = [
        json.loads(s.attributes["gen_ai.tool.arguments"]).get("name", n)
        if n == "call_tool" else n
        for s, n in zip(spans, invoked)
    ]
    return input_tokens, len(invoked), expected_tool in resolved

def report(results):
    for r in results:
        print(f"\n{r.config}")
        print(f"  mean input tokens/task : {mean(r.input_tokens):>8,.0f}")
        print(f"  mean turns/task        : {mean(r.turns):>8.1f}")
        print(f"  tool selection accuracy: {mean(r.correct_tool):>8.1%}")
进入全屏模式 退出全屏模式

先对一个扁平目录运行一次,再对启用了 Tool Search 的同一目录运行一次,然后在你固定了前五个工具后再运行一次。三个数字,一张表格,你就能知道这对你的目录来说是否值得做,而不是对博客文章里的目录而言。

它没有帮助的地方

坦率地说明它的局限,因为这些失败模式是真实的:

小目录会变得更糟,而不是更好。 在工具数量少于大约三十个时,你为节省本来就不会花费的 token,却增加了一次往返和一种检索失败模式。别这么做。

检索遗漏是无声的,也是令人困惑的。 当扁平列表代理选错工具时,追踪记录会显示它曾考虑过正确的那个。而当 Tool Search 从未把正确的工具呈现出来时,追踪记录显示的是一个在拿到同样信息后表现合理的代理。调试的问题就从“它为什么选错了”变成了“它为什么不在候选集中”,这是一种不同的技能,也更不容易看出来。

延迟被转移到了错误的地方。 额外的一次往返出现在回合的开头,先于任何有用的工作。对后台代理来说这没有代价;但任何有人在旁观的场景,请果断固定工具。

你的描述现在是承重的基础设施。 一个含糊的 searchContext 过去只是让你偶尔付出一次糟糕工具调用的代价;现在它让你付出的代价,是工具在功能上完全不可见。要像为 API 契约预留时间那样,为它们预留审查时间。

更普遍的启示

抛开 Foundry 再看,这个原则依然成立:你在每一轮发送的任何内容,都应当在每一轮中赢得自己的位置。 工具 schema 是最先撞上这堵墙的,因为它们会随着集成数量不断膨胀;但同样的审计也适用于:积累了六个月没人读过的指令的系统提示词、为某个你已不再运行的模型版本保留的少样本示例,以及因为修剪它曾是某人的 Q3 任务而被整段粘贴进来的检索上下文。

Tool Search 是一个朴素想法的出色实现——检索而不是广播。采用它的理由不是因为它新,而是因为你用一个下午就能量化这种差异,而且这个数字通常会比你预期的更大。


此领域的预览 API 变化很快——上面的 azd 命令和追踪配置在 2026 年 6 月的 Foundry 版本中是最新的,但发布前请对照当前示例核实 schema 级别的细节。

参考资料

  1. Brady, N. "Microsoft Foundry 新动态 | 2026 年 6 月。" Microsoft Foundry 博客——https://devblogs.microsoft.com/foundry/whats-new-in-microsoft-foundry-june-2026/
  2. “Microsoft Foundry 中的工具箱和例程.” Microsoft Foundry 博客 — https://devblogs.microsoft.com/foundry/toolbox-build-26/
  3. “Microsoft Foundry 中的新增功能 | Build 版.” Microsoft Foundry 博客 — https://devblogs.microsoft.com/foundry/whats-new-in-microsoft-foundry-build-2026/
  4. “Build 2026:从可观测性到任何框架上 AI 代理的投资回报率.” Microsoft Foundry 博客 — https://devblogs.microsoft.com/foundry/build-2026-from-observability-to-roi-for-ai-agents-on-any-framework/
  5. “Microsoft Foundry 为生产代理增加运行时、工具和治理.” InfoQ, 2026年6月 — https://www.infoq.com/news/2026/06/microsoft-foundry-agents/
  6. “Microsoft Foundry 文档:新增内容.” Microsoft Learn — https://learn.microsoft.com/en-us/azure/foundry/whats-new-foundry

——

🧑‍💻

zhirenhun

一个热爱技术的程序员,喜欢分享前沿AI知识和开发经验。

microsoft ai foundry agents
← 上一篇
CI 中的 Claude Code:对每个 Pull Request 运行代理式代码审查、测试生成与自动修复
下一篇 →
长上下文LLM服务中的真实权衡:内存、延迟、成本与准确性

📌 相关推荐

停止相信仅文本代理排行榜:来自 Cua-Bench 和 Factorio 的教训
2026/8/26
Agent Memory 有两种不同含义,回答引擎给出的却是错误的那一种
2026/8/26
LLM的止境:AI辅助VAPT流水线的确定性评分
2026/8/22
← 返回文章列表