5.2 为什么不直接用工具:资源的设计动机 本节摘要:本节回答一个每位初学者都会问的问题——「资源能做的,工具也能做,为什么不把所有东西都做成工具?」 答案不在功能(工具确实能返回数据),而在控制权、时序与成本。资源由应用预加载、不消耗调用轮次、只读语义清晰;工具由模型运行时调用、每次消耗一轮、带副作用语义。把只读数据硬塞进工具,会让模型浪费轮次、可能漏调、语义错乱。本节用一个反面案例讲透这个取舍,让你之后遇到「该用工具还是资源」时不再纠结。 一、问题的提出 先承认一个事实:从「能不能返回数据」的角度,工具完全可以替代资源。下面两个写法,都能让模型拿到配置: 写法二能跑,模型也能调它拿到配置。那为什么还要资源?这是本节要讲透的。 二、差别一:控制权归属 最根本的差别在第 2.
本节摘要:本节回答一个每位初学者都会问的问题——「资源能做的,工具也能做,为什么不把所有东西都做成工具?」 答案不在功能(工具确实能返回数据),而在控制权、时序与成本。资源由应用预加载、不消耗调用轮次、只读语义清晰;工具由模型运行时调用、每次消耗一轮、带副作用语义。把只读数据硬塞进工具,会让模型浪费轮次、可能漏调、语义错乱。本节用一个反面案例讲透这个取舍,让你之后遇到「该用工具还是资源」时不再纠结。
先承认一个事实:从「能不能返回数据」的角度,工具完全可以替代资源。下面两个写法,都能让模型拿到配置:
# 写法一:配置做成资源 @mcp.resource("config://app") def get_config() -> dict: return {"theme": "dark"} # 写法二:配置做成工具 @mcp.tool() def get_config() -> dict: """Get app configuration.""" return {"theme": "dark"}
写法二能跑,模型也能调它拿到配置。那为什么还要资源?这是本节要讲透的。
最根本的差别在第 2.2 节讲过——谁决定调用:
| 原语 | 谁决定 | 时序 |
|---|---|---|
| 资源 | 应用(宿主) | 模型说话前预加载 |
| 工具 | 模型 | 模型推理中主动调 |
这个差别对「配置该用什么」有直接影响:
配置做成资源(正确) 宿主在对话开始时一次性加载 config://app → 模型从一开始就带着配置上下文 → 每次推理都基于正确配置 配置做成工具(错误) 模型每次推理都要决定「要不要调 get_config 工具」 → 模型可能忘记调(它不知道需要配置) → 模型可能在不同轮次调多次(浪费) → 配置进上下文的时机不确定
控制权归属错了,行为就不可控。配置这种「该预先准备好」的东西,交给「会忘记、会浪费」的模型决定,就是错配。
工具调用消耗一轮对话——模型发出调用、等结果、再继续。这意味着:
配置做成工具 模型第 1 轮:决定调 get_config 工具返回配置 模型第 2 轮:基于配置真正回答用户 → 多消耗 1 轮(token 成本、延迟) 配置做成资源 宿主预加载配置(不消耗模型轮次) 模型第 1 轮:直接基于配置回答 → 不多消耗轮次
对单个工具可能看不出差别,但真实场景里一个对话可能涉及多份上下文(配置、用户资料、项目说明)。全做成工具,每次对话都多消耗好几轮;做成资源,预加载一次,模型直接用。
💡 技巧:把「模型每轮都很贵」记住。每多一轮工具调用,就是多一次模型推理、多一份 token 成本、多一段延迟。资源把「准备上下文」这件事从模型手里拿走,交给宿主一次性做完,省的是真金白银。
第三层差别是语义——资源是只读的,工具通常有副作用。把只读数据做成工具,语义就错乱了:
资源的语义:GET 「我读这份配置」→ 纯查询,不改世界 → 宿主可放心预加载,无副作用顾虑 工具的语义:POST(通常) 「我调这个工具」→ 可能有副作用(写库、调 API) → 模型调前要权衡,宿主加载前要确认
配置是只读的,做成资源,语义清晰;做成工具,模型与宿主都要把它当「可能有副作用」对待,徒增谨慎与开销。
把所有能力都做成工具,会怎样?看一个反面场景:
场景:用户问「根据我的配置,推荐一个方案」 全工具化的服务端: - get_config() (配置,只读) - get_user_profile() (用户资料,只读) - get_project_doc() (项目说明,只读) - recommend() (推荐,真正干活) 模型推理过程: 第 1 轮:我要不要先调 get_config?嗯,要。 第 2 轮:拿到配置了,要不要调 get_user_profile?要。 第 3 轮:拿到资料了,要不要调 get_project_doc?要。 第 4 轮:终于调 recommend。 → 4 轮才到正题,且每轮都可能出错(忘记调、调错顺序)
而把只读的做成资源:
资源化 + 工具化的服务端: - 资源:config://app、user://me、doc://project - 工具:recommend() 模型推理过程: 宿主预加载三份资源(不消耗模型轮次) 第 1 轮:模型直接基于完整上下文调 recommend。 → 1 轮到正题,上下文完整
这个对比说明,资源不是「工具的重复」,而是把不该消耗模型轮次的东西挪出去,让模型专注于「真正需要它决策的事」。
把上面的分析浓缩成三个判断问题,遇到「该用工具还是资源」时依次问:
问题一:这个能力有副作用吗? 有 → 工具 无 → 继续问二 问题二:它该由模型主动调,还是预先加载? 模型主动调 → 工具 预先加载 → 资源 问题三:它每次调用结果一样吗(幂等只读)? 一样(只读数据) → 资源 不一样(有状态变化) → 工具
三个问题答完,归属基本清楚。举个例子:
⚠️ 注意:有些能力在边界——「查余额」既是只读(像资源),又可能需要参数动态调(像工具)。这种边界情况,优先看「谁该决定调用」:模型根据对话决定→工具;应用预加载→资源。拿不准时,工具更通用(模型能自己决定),资源更高效(不消耗轮次)。
资源的设计动机清楚了,下一节转向提示词——用户控制的第三类原语。