1. 从一次工具调用超时说起:Pi Agent 为什么要给 MCP 做三种模式
上周帮朋友排查一个 Pi Agent 的线上问题,现象很典型:Agent 在调用某个 MCP 工具时卡了整整 40 秒,最后超时返回,用户侧看到的就是一句冷冰冰的"工具执行失败"。翻日志发现,这个工具其实只需要读一个本地配置文件,理论上 200 毫秒就能返回,但它走的是默认的同步阻塞链路,中间还夹着一层网络往返。问题不在工具本身,而在工具被"挂载"到 Agent 上的方式。
这件事让我重新审视了 Pi Agent 把 MCP 加进核心之后引入的那套工具模式设计。很多人第一次看到direct、deferred这些词的时候,第一反应是"这不就是懒加载吗",但实际用下来会发现,这三种模式解决的根本不是同一个问题。它们分别对应了工具调用生命周期里的三个不同阶段:发现、绑定、执行。搞混了这三个阶段,就会出现我朋友那种"明明很简单的工具却慢得离谱"的情况。
先把结论摆在这儿:Pi Agent 把 MCP 纳入核心之后,工具模式大致分成三类——direct(直接模式)、deferred(延迟模式),以及介于两者之间的混合/按需模式。这三种模式的差别,不在于功能能不能用,而在于工具在什么时机被加载、被谁持有、以及调用时走哪条路径。选错了模式,轻则响应变慢,重则整个 Agent 的上下文被工具描述撑爆。
这篇文章适合三类人看:一是正在用 Pi Agent 接 MCP 工具、但被模式选择搞晕的开发者;二是想把现有 MCP 服务迁移进 Pi Agent 核心的团队;三是单纯想搞明白"工具模式"这个词到底在说什么的技术爱好者。我会把每种模式的适用场景、底层逻辑、实操配置和踩坑经验都摊开讲,尽量让你看完就能对着自己的项目做判断。
2. 三种工具模式的核心差异拆解
2.1 direct 模式:工具描述常驻,调用路径最短
direct 模式的逻辑最直白:Agent 启动的时候,把所有 MCP 工具的描述(name、description、input schema)一次性拉进上下文,之后每次调用都直接命中已经绑定好的工具实例。你可以把它理解成"开机就把所有工具箱搬到工作台上",用的时候伸手就够得着。
这种模式最大的优势是调用延迟最低。因为工具描述已经在上下文里了,模型在规划阶段就能直接看到工具签名,不需要额外的发现步骤。对于高频调用、参数结构稳定的工具,direct 模式几乎是唯一合理的选择。比如一个专门做时间戳转换的 MCP 工具,或者一个查询内部用户信息的工具,这类工具在整个会话里可能被调用几十次,每次省下几百毫秒的发现开销,累积起来就很可观。
但 direct 模式的代价也很明显:上下文占用是固定的、前置的。假设你接了 30 个 MCP 工具,每个工具的描述加 schema 平均 200 token,那就是 6000 token 常驻在上下文里。这 6000 token 不管你这次会话用不用得上,都得占着。对于上下文窗口本来就紧张的模型来说,这是实打实的浪费。我见过一个项目接了 50 多个工具,光工具描述就吃掉了将近 1 万 token,导致真正留给对话历史的空间被严重压缩。
所以 direct 模式的适用边界很清楚:工具数量少(建议 10 个以内)、调用频率高、描述精简。超过这个范围,就该考虑 deferred 了。
2.2 deferred 模式:按需加载,用空间换时间
deferred 模式走的是另一条路:Agent 启动时不加载任何工具描述,只保留一个"工具目录"的索引。当模型判断需要某个能力时,先通过一次轻量的检索或匹配,把相关工具的描述动态注入上下文,然后再执行调用。
这个设计思路其实和数据库的索引很像。你不会把整张表加载到内存里,而是先查索引定位到行,再回表取数据。deferred 模式就是给工具集建了个"索引",用的时候才"回表"。
它的好处是上下文占用是动态的、后置的。50 个工具在 deferred 模式下,启动时可能只占几百 token 的索引开销,只有真正被调用的那两三个工具才会把完整描述加载进来。对于工具数量多、但单次会话只用到少数几个的场景,这种模式能省下大量上下文空间。
代价则是多了一次发现步骤。每次调用前都要先做一次工具匹配,这个匹配可能是基于关键词的,也可能是基于向量检索的,无论哪种,都比 direct 模式多一个环节。如果匹配逻辑写得不好,还可能出现"该调的工具没匹配上"的尴尬情况。我实测下来,deferred 模式的单次调用延迟通常比 direct 高 100 到 500 毫秒,具体取决于检索实现。
2.3 混合模式:把选择权交给运行时
混合模式不是第三种独立的加载策略,而是允许在同一个 Agent 实例里,对不同工具采用不同模式。比如把高频的核心工具设为 direct,把长尾的、低频的工具设为 deferred。Pi Agent 把 MCP 加进核心之后,这套混合能力是内置的,你可以在工具注册的时候逐个指定模式。
这种做法的价值在于精细化的资源分配。一个典型的 Agent 可能同时具备:查询天气(高频,direct)、发送邮件(中频,direct)、生成 PDF 报表(低频,deferred)、调用内部审批流(低频,deferred)。把前两个设为 direct 保证响应速度,后两个设为 deferred 节省上下文,整体达到一个平衡。
下面这张表把三种模式的关键差异列清楚,方便你对照自己的场景做判断:
| 维度 | direct 模式 | deferred 模式 | 混合模式 |
|---|---|---|---|
| 工具描述加载时机 | Agent 启动时全量加载 | 调用前按需加载 | 按工具逐个配置 |
| 上下文占用 | 固定、前置、较高 | 动态、后置、较低 | 介于两者之间 |
| 单次调用延迟 | 最低 | 较高(多一次发现) | 取决于该工具的模式 |
| 适用工具数量 | 少(10 个以内) | 多(几十上百) | 任意 |
| 实现复杂度 | 低 | 中(需检索逻辑) | 中高(需配置管理) |
| 典型场景 | 高频核心工具 | 长尾低频工具 | 真实生产环境 |
2.4 为什么不是"越懒越好":模式选择的本质是权衡
很多人有个直觉:deferred 既然省上下文,那全用 deferred 不就行了?这个想法我一开始也有,但实测下来会踩坑。
问题出在发现步骤的可靠性上。deferred 模式依赖一次工具匹配,而匹配本身是有误差的。如果模型在规划阶段没能准确描述出它需要什么能力,检索就可能漏掉正确的工具。direct 模式没有这个问题,因为所有工具都摆在明面上,模型自己就能看到哪个合适。
所以模式选择的本质,是在上下文成本和发现可靠性之间做权衡。高频、关键路径上的工具,宁可多占点上下文也要保证 direct 的可靠性;长尾、容错性高的工具,用 deferred 省空间更划算。这个判断没有标准答案,得结合你的具体工具集和调用分布来定。
3. 底层机制:MCP 工具在 Pi Agent 里是怎么被"挂载"的
3.1 MCP 协议本身只定义了"能力",没定义"加载策略"
要理解 Pi Agent 的三种模式,得先搞清楚 MCP 协议本身管什么、不管什么。MCP(Model Context Protocol)定义的是工具如何被描述、如何被调用、结果如何返回这一套通信规范。它规定了tools/list用来列出工具,tools/call用来执行工具,但它没有规定这些工具应该在什么时机被加载进模型上下文。
这个空白正是 Pi Agent 三种模式发挥作用的地方。MCP 负责"工具长什么样、怎么调",Pi Agent 负责"工具什么时候进上下文、以什么方式进"。两者是分层的关系:MCP 是能力层,工具模式是调度层。
理解这一点很关键,因为它意味着工具模式是 Pi Agent 的实现选择,不是 MCP 的强制要求。你在别的 Agent 框架里可能看到不同的模式命名,但底层要解决的问题是一样的。
3.2 direct 模式的绑定过程
direct 模式的绑定发生在 Agent 初始化阶段,大致流程是这样的:
- Agent 启动,读取配置里标记为 direct 的 MCP 服务列表
- 对每个服务发起
tools/list请求,拿到完整工具清单 - 把工具描述转换成模型能理解的格式(通常是 JSON Schema 加自然语言描述)
- 把这些描述拼接到系统提示或工具定义区,完成绑定
- 后续调用直接走
tools/call,不再有发现步骤
这个流程里有个容易被忽略的细节:第 2 步的tools/list是启动时同步执行的。如果某个 MCP 服务响应慢,整个 Agent 的启动就会被拖住。我遇到过启动要等 8 秒的情况,就是因为其中一个 direct 工具的服务端在做冷启动。所以 direct 模式对 MCP 服务的启动速度是有要求的,服务端最好能快速响应tools/list。
3.3 deferred 模式的检索与注入
deferred 模式的流程复杂一些,核心在于"检索"这一步怎么实现。常见的有两种做法:
基于关键词的检索:把工具名和描述做分词,建立倒排索引,调用时用模型输出的意图关键词去匹配。实现简单,但对同义表达的鲁棒性差。比如用户说"帮我看看明天热不热",工具描述里写的是"查询气温",关键词匹配可能就漏了。
基于向量检索:把工具描述做 embedding,调用时把模型意图也做 embedding,算相似度取 top-k。鲁棒性好,但需要额外的 embedding 服务和向量存储,实现成本高。
Pi Agent 核心内置的 deferred 实现,据我观察更偏向第一种,但留了扩展点让你接入自己的检索逻辑。实际项目里,如果你的工具命名规范、描述清晰,关键词检索其实够用了;如果工具描述比较随意,那向量检索的收益会明显一些。
检索到候选工具后,deferred 模式会把它们的完整描述注入当前轮的上下文,然后模型再基于这些描述决定调哪个。注意这里有个两阶段决策:先决定"我需要哪类能力",再决定"具体调哪个工具"。direct 模式只有后一阶段。
3.4 混合模式的配置管理
混合模式的复杂度主要在配置管理上。你需要维护一张"工具到模式"的映射表,并且在 Agent 启动时按这张表分别处理。Pi Agent 的做法是在工具注册接口里加一个mode字段,取值direct或deferred,不填则走默认值。
这里有个实践中的坑:同一个 MCP 服务下的不同工具,可以配不同的模式吗?答案是可以,但要小心。因为tools/list是按服务粒度请求的,如果你把一个服务里的工具 A 设为 direct、工具 B 设为 deferred,那启动时你还是得把这个服务的工具列表全拉一遍,只是 B 的描述不注入上下文而已。这省的是上下文,不是网络请求。所以更彻底的做法是按服务粒度划分模式,把高频服务整体设为 direct,低频服务整体设为 deferred。
4. 实操:在 Pi Agent 里配置三种模式的完整流程
4.1 环境准备与 MCP 服务接入
假设你已经有一个跑起来的 Pi Agent 实例,现在要接入两个 MCP 服务:一个是内部的用户信息查询服务(高频),一个是报表生成服务(低频)。先确认你的 Pi Agent 版本支持 MCP 核心集成,这个能力是在较新的版本里才进核心的,老版本可能需要单独装插件。
接入 MCP 服务的基本配置长这样(以常见的配置文件形式为例):
{ "mcpServers": { "user-info": { "command": "node", "args": ["./servers/user-info/index.js"], "mode": "direct" }, "report-gen": { "command": "python", "args": ["./servers/report-gen/main.py"], "mode": "deferred" } } }注意mode字段就是 Pi Agent 加进核心之后新增的配置项。不写的话走默认值,默认值通常是direct,但这个默认值在不同版本里可能变过,建议显式写清楚,别依赖默认。
4.2 direct 模式的配置要点与验证
配好之后,启动 Agent,观察启动日志。direct 模式的工具应该在启动阶段就完成tools/list并注入上下文。你可以通过一个简单的验证方法确认:在对话里直接问 Agent"你有哪些工具可用",如果 direct 工具被正确加载,Agent 应该能列出它们的名字。
direct 模式配置时有几个参数值得关注:
- 启动超时:
tools/list的等待时间,默认可能是 5 秒。如果你的 MCP 服务启动慢,需要调大,但调大意味着 Agent 启动变慢,权衡着来。 - 描述精简:direct 工具的描述建议控制在 100 token 以内。描述越长,常驻上下文占用越大。把详细说明放到工具的参数描述里,而不是工具本身的 description 里。
- schema 收敛:input schema 里不要放太多可选参数,每个参数都会增加 token。能用枚举就别用自由字符串。
我实测过一个对比:同一个工具,描述写 50 token 和写 200 token,在 20 个 direct 工具的场景下,上下文占用差了 3000 token。这个差距在长会话里会明显影响可用轮数。
4.3 deferred 模式的检索配置与调优
deferred 模式的配置重点在检索环节。Pi Agent 核心提供了一套默认的关键词检索,但你可以通过配置调整匹配策略:
{ "mcpServers": { "report-gen": { "command": "python", "args": ["./servers/report-gen/main.py"], "mode": "deferred", "retrieval": { "strategy": "keyword", "topK": 3, "minScore": 0.3 } } } }topK是每次检索返回的候选工具数量,minScore是匹配分数阈值。这两个参数直接影响 deferred 模式的表现:
topK太小,可能漏掉正确工具;太大,注入的上下文又变多,失去 deferred 的意义。一般 3 到 5 比较合适。minScore太高,匹配过于严格,容易漏;太低,会引入不相关的工具干扰模型判断。0.3 是个常见的起点,具体得根据你的工具描述质量调。
调优的方法论是:准备一批真实的用户意图样本,跑一遍检索,看正确工具是否稳定出现在 topK 里。如果经常漏,先检查工具描述是不是写得太抽象,再考虑调低 minScore 或换向量检索。
4.4 混合模式的落地:一张配置表管到底
真实项目里,我建议直接上混合模式,按下面这张思路表来分配:
| 工具类型 | 调用频率 | 推荐模式 | 理由 |
|---|---|---|---|
| 身份/权限查询 | 每轮几乎都调 | direct | 高频,延迟敏感 |
| 时间/日期工具 | 高频 | direct | 描述极短,占用可忽略 |
| 业务数据查询 | 中频 | direct | 关键路径,可靠性优先 |
| 报表/导出 | 低频 | deferred | 描述长,用得少 |
| 第三方集成 | 低频 | deferred | 数量多,长尾 |
| 实验性工具 | 极低 | deferred | 不值得占常驻上下文 |
按这张表配下来,通常能把常驻上下文控制在合理范围,同时保证关键路径的响应速度。我手上一个接了 40 多个工具的项目,用混合模式之后,常驻工具描述从 9000 token 降到了 2500 token 左右,而高频工具的调用延迟没有变化。
5. 踩坑实录:三种模式最容易出问题的地方
5.1 direct 模式:上下文被工具描述悄悄吃光
这是最常见的坑,而且很隐蔽。因为工具描述是启动时注入的,你不会在对话过程中感知到它的存在,直到某天发现"怎么聊了几轮就提示上下文超限了"。
排查方法:在 Agent 启动后,打印一次完整的系统提示或工具定义区,数一下工具描述占了多少 token。如果超过总窗口的 20%,就该考虑把部分工具迁到 deferred 了。
另一个相关的坑是工具描述里的示例代码。有些开发者喜欢在工具 description 里塞一大段使用示例,觉得这样模型更容易理解。但这段示例是常驻的,每个 direct 工具都塞一段,累积起来非常可观。我的建议是:示例放在参数描述里,或者干脆不放,靠清晰的参数命名让模型自己推断。
5.2 deferred 模式:该调的工具没被检索到
deferred 模式最典型的故障就是"工具明明存在,但 Agent 说它没有这个能力"。根因通常是检索没匹配上。
排查思路分三步:第一,确认工具确实注册成功了,可以手动触发一次tools/list看返回;第二,看检索日志,确认模型输出的意图关键词是什么,和工具描述的匹配分数是多少;第三,如果分数确实低,要么改工具描述让它更容易被匹配,要么调低 minScore。
我遇到过一个案例:工具叫generate_monthly_report,描述写的是"生成月度报表"。用户说"帮我出一下上个月的经营分析",关键词匹配里"经营分析"和"月度报表"没有重叠词,分数很低,没匹配上。后来在描述里补了一句"也用于经营分析、业绩汇总等场景",问题就解决了。deferred 模式下,工具描述不只是给模型看的,也是给检索器看的,得兼顾两者的理解习惯。
5.3 混合模式:模式切换后的缓存不一致
混合模式因为同时存在两种加载路径,容易出现缓存层面的不一致。具体表现是:某个工具从 direct 改成 deferred 之后,Agent 行为没有立即变化,还在用旧的加载方式。
这通常是因为 Agent 有工具描述的缓存机制。改配置之后需要清缓存并重启,不能只热重载配置。Pi Agent 核心在这块的处理是启动时构建工具注册表,运行时不动态变更,所以改模式必须重启。这一点在文档里不一定写得很显眼,但实操中必须记住。
5.4 一张速查表收尾常见问题
| 现象 | 可能原因 | 排查方向 | 解决方式 |
|---|---|---|---|
| 启动特别慢 | direct 服务tools/list慢 | 看启动日志各服务耗时 | 调大超时或改 deferred |
| 上下文很快超限 | direct 工具描述过多 | 统计工具描述 token 占比 | 迁移部分工具到 deferred |
| 工具调不到 | deferred 检索未命中 | 看检索日志匹配分数 | 改描述或调低 minScore |
| 改模式不生效 | 缓存未清 | 确认是否重启 | 清缓存后重启 |
| 调用偶发超时 | 工具执行本身慢 | 看tools/call耗时 | 与模式无关,优化工具实现 |
6. 我的选择建议:什么场景用哪种模式
6.1 小工具集、高频调用:无脑 direct
如果你的 Agent 总共就接五六个工具,而且都是高频调用的核心能力,别犹豫,全上 direct。这个规模下上下文占用完全可控,deferred 带来的那点空间节省不值得你多维护一套检索逻辑。简单直接,出问题的概率也最低。
6.2 大工具集、调用分散:deferred 为主,关键工具 direct
工具数量上了二三十个,调用又比较分散,这时候 deferred 是主力。但别全 deferred,把其中调用最频繁、最不能出错的那几个拎出来设成 direct。这个"关键少数"通常不超过 5 个,但它们决定了 Agent 的基础体验。
6.3 不确定的时候:先 direct,观察后再迁
如果你对工具的调用分布还没摸清楚,我的建议是先全 direct 上线,同时埋点统计每个工具的调用次数。跑一两周之后,把调用次数排在后 50% 的工具迁到 deferred。这样迁移是有数据支撑的,不会拍脑袋。
这个渐进式的方法比一上来就搞复杂配置要稳得多。我见过太多项目在还没搞清楚工具使用情况的时候就上了复杂的混合配置,结果调优调了半天,收益还不如先把工具描述精简一遍。
6.4 一个容易被忽略的维度:工具描述的稳定性
除了调用频率,工具描述的变更频率也影响模式选择。direct 工具的描述是启动时加载的,改了描述必须重启 Agent 才生效。如果你的某个工具描述还在频繁迭代,把它放 deferred 会更灵活,因为 deferred 是调用时加载,改完描述下次调用就能生效,不用重启。
这个维度在选型时经常被忽略,但在快速迭代阶段很实用。等工具描述稳定了,再考虑迁到 direct 优化延迟。
7. 关于 MCP 工具模式,我踩过之后想说的几句
回到开头那个 40 秒超时的案例。最后查出来的原因是:那个读本地配置的工具被设成了 direct,但它所在的 MCP 服务启动时要做一次远程鉴权,鉴权服务当时在抖动,导致tools/list卡住,进而拖慢了整个 Agent 的启动和首次调用。解决方案很简单——把这个工具迁到 deferred,让它的加载和 Agent 启动解耦,同时给鉴权加了本地缓存。改完之后,Agent 启动从 8 秒降到 1 秒出头,工具调用也回到了 200 毫秒级别。
这件事让我更确信一个判断:工具模式的选择,本质上是在管理"加载时机"和"故障传播路径"。direct 模式把工具的加载绑在了 Agent 启动上,好处是调用快,坏处是工具的启动问题会传染给整个 Agent。deferred 模式把加载推迟到调用时,隔离了启动故障,代价是每次调用多一步。混合模式让你可以按工具的重要性分别决定,把关键路径和长尾路径分开管理。
如果你现在正在给 Pi Agent 接 MCP 工具,我的建议是先把工具清单列出来,标上调用频率和描述长度,然后按第 4 节那张表过一遍。别一上来就追求"最优配置",先跑起来,用真实数据驱动调整。工具模式这东西,没有放之四海皆准的答案,只有贴合你场景的答案。