← 返回 FEED
AGENT2026-04-28

Context Provider:Agent 与工具之间的第三层

如果你做过接入多个工具的 Agent,应该体验过这三堵墙:

  1. Context 污染:每个工具有自己的 schema、描述、用法示例,全塞进系统提示词。Slack 工具集 8-12 个,Gmail 6-10 个,Calendar 再加 6 个,再加上 Drive、GitHub、CRM、搜索——还没算自定义工具,数量已经轻松破 50。超过 20 个工具后,模型开始幻觉出不存在或形状错误的工具。

  2. Scope 重叠无法组合:两个工具都接受 workspace 参数,一个是 Slack 的,一个是 Google 的。搜索功能在不同的 MCP 里撞名。send_message 可能是 Slack、邮件或者 CRM。任何命名规范都解决不了这个问题,因为同一个词在不同数据源里本来就意味着不同的事情。

  3. 工具使用逻辑和主 Agent 强绑定:这是最深的一堵墙,也是麻烦最多的。对 Agent 来说用好 Slack,系统提示词就必须包含"发 DM 前先查用户 ID"、"发消息前先把频道名解析成 ID"这类 Slack 专用指导。现在再加上 Gmail、Calendar……系统提示词变成了所有 API 怪癖的并集。每次对话都带着所有规则,哪怕用户只是在问 Slack 的事。主 Agent 既要回答用户问题,又要在脑子里装着每一个可能调用的 API 细节。

缺失的那一层

现在 Agent 的典型架构是这样的:

Agent ← Tools

加了 MCP:Agent ← MCP Server ← Tools

加了 Skills:Agent ← Skill 指令 ← Tools

无论哪种,主 Agent 看到的都是每个数据源的原始工具全集。每个 Slack 工具,每个 Drive 工具,每个 CRM 工具全摊在面前。系统提示词里必须写清楚怎么用每一个工具。

Context Provider 模式的思路是在中间加一层很薄的抽象:

Agent ↔ ContextProvider

每个 ContextProvider 包装一个数据源(Slack、文件系统、Drive……)。对调用的 Agent 来说,它只暴露两个工具:

  • query_<数据源>(question) — 自然语言读
  • update_<数据源>(instruction) — 自然语言写

就这两个。主 Agent 看不到 Slack 的 12 个工具,只看到 query_slackupdate_slack。它不需要知道 Drive 的 API 怪癖,只需要会问query_drive。接十个数据源,主 Agent 的工具列表最多也就 2N 个,不会再长。

每个工具背后是一个子 Agent,被限定只访问那一个数据源。子 Agent 拥有这个数据源的所有工具、所有 API 细节、"写之前先查"的模式、分页逻辑。它跑在自己独立的 context 里,返回一个干净的结果,主 Agent 拿到的是最终答案,不是 API 调用的中间状态。

from agno.agent import Agent
from agno.context.slack import SlackContextProvider
from agno.context.gdrive import GDriveContextProvider
from agno.context.database import DatabaseContextProvider

slack = SlackContextProvider(id="slack", token=...)
drive = GDriveContextProvider(id="drive", service_account_file=...)
crm = DatabaseContextProvider(id="crm", sql_engine=engine)

agent = Agent(
    model=...,
    tools=[*slack.get_tools(), *drive.get_tools(), *crm.get_tools()],
)

这个 Agent 看到的是四个工具:query_slackquery_drivequery_crmupdate_crm。它不需要知道 Slack 工具有哪些、Drive 工具有哪些。

Skills 怎么配合

Skills 是对第三堵墙的一个很好的尝试。Skills 把"这里是如何使用 Slack"这种任务专用指令封装成模块,在相关时才加载,而不是一直塞在系统提示词里。

但 Skill 加载之后,Slack 工具还是会出现在主 Agent 的工具列表上。两个 Skills 一起加载,搜索功能照样会冲突。其实 Skills 和 ContextProvider 配合使用效果更好:一个 Slack ContextProvider 的子 Agent 本身可以加载一个 Slack Skill——Skill 在真正执行 Slack 操作的那个上下文里发挥作用,而不是在只是想要一个答案的主 Agent 里。

两者的大致分工是:Skills 压缩的是"怎么做"的知识,ContextProvider 隐藏的是"存在一个任务"这件事,直到主 Agent 决定把任务委托出去。

一些意外的发现

  • 子 Agent 比预想的便宜。原本以为额外的调用 hops 会成为瓶颈,但实际上主 Agent 的 context 小了那么多,它的调用反而更快、更准,子 Agent 只在真正用到那个数据源时才触发。测到的结果是:低数据源数量时 token 总量基本持平,数据源越多优势越明显。每个测量节点上的等待时间都在下降。

  • 主 Agent 的提示词显著缩小。以为好歹要写点编排逻辑,结果发现有了统一的工具表面,路由规则就一条:选对正确的 query_<数据源>。配合足够强的模型,根本不需要告诉它"怎么用"某个数据源。

  • 组合天然 work。两个 Provider 可以在同一轮里互相读(query_slack 查讨论,query_drive 查文档),主 Agent 做综合。多个数据源在一起工作效果好很多。

  • 加一个数据源是一行代码,删也是一个。换底层实现(Exa 换 Parallel)只改 Provider 内部,主 Agent 完全感知不到。

还有一些待解决的问题:主 Agent 的提示词能缩到多小?跨调用 session 的缓存怎么做?OAuth 场景下的 per-user 认证怎么穿透?


信源https://x.com/ashpreetbedi/status/2048817143974613089