OpenRouter 的插件机制:plugin 与 server tool 不是一回事
先说一个很容易踩的场景。
两个人给同一套服务加联网能力。A 在请求体里加了 plugins: [{ "id": "web" }],B 在请求体里加了 tools: [{ "type": "openrouter:web_search" }]。上线之后 A 发现,哪怕用户问的是”帮我把这段 JSON 格式化一下”,这次请求也照样走了一次搜索;B 那边则是另一个极端,有些明显需要查资料的问题,模型一次搜索都没发起。
两边都没配错。它们本来就是两套机制,介入的位置根本不在同一层。
差异不在功能,在写进哪个数组
OpenRouter 官方文档《Server Tools》那一页开头就摆了一张三列对照表,把 Server Tools、Plugins、User-Defined Tools 放在一起比。这张表值得逐行读,因为它比任何解释都直接:
| 维度 | Server Tools | Plugins | User-Defined Tools |
|---|---|---|---|
| 谁决定要不要用 | 模型 | 总是运行 | 模型 |
| 谁负责执行 | OpenRouter | OpenRouter | 你的应用 |
| 每次请求调用几次 | 0 到 N 次 | 一次 | 0 到 N 次 |
| 通过什么指定 | tools 数组 | plugins 数组 | tools 数组 |
| 类型前缀 | openrouter:* | 无 | function |
回到开头那个场景。A 的搜索每次都跑,是因为文档对 plugin 的定义就是”启用后总是运行一次”(原文写的是 always run once when enabled);B 的搜索一次没发起,是因为 server tool 的调用次数是 0 到 N,决定权在模型手里,模型判断不需要就是零次。
这里要照实标一句:《Server Tools》页顶部挂着 Beta 标记,页内的提示框写明 server tools 目前处于 beta,API 与行为都可能变动。拿它做生产链路要按 beta 对待。
《Plugins》页对 plugin 的描述是另一个角度:plugin 通过注入或改写一个请求 / 响应来添加能力,文档举的例子是 PDF 处理、自动 JSON 修复、上下文压缩。注意这句话里的两个动词——注入和改写,一个发生在请求装配阶段,一个发生在响应回来之后。这就是 plugin 的介入点,它不在模型的推理循环里面。
Fusion:plugin 干的事就是”注入”
要把介入点看清楚,Fusion 是最好的例子,因为它同时以两种形态存在。
官方文档《Fusion》页写明:Fusion plugin 是 openrouter:fusion server tool 的配置面(configuration surface),同时也是 openrouter/fusion 这个模型别名背后的机制,三个入口打的是同一条 pipeline。然后是关键的一句——文档在讲工作流程时写的第一步是:plugin 把 openrouter:fusion 这个工具注入到你的请求里;第二步才是你的模型读提示词、自己决定要不要调用 openrouter:fusion。
这两步把分工说得很干净:plugin 负责把工具塞进请求,server tool 负责在模型想调的时候被调。plugin 那”一次”运行,跑的是注入这个动作本身,不是跑那次搜索或那次多模型评议。
同一页的”递归保护”一节又从反面印证了这个位置。文档写明:内层的 fusion 调用会带上 x-openrouter-fusion-depth 这个请求头,panel 与 analyst 模型不能递归调用 openrouter:fusion,plugin 会拒绝第二次注入这个工具,把评议限制在单层。能”拒绝第二次注入”,说明它的位置就在注入这一关。
Fusion plugin 的配置字段也值得看一眼,因为它们全是”装配期”参数:preset(引用一个 OpenRouter 的 preset slug,slug 形如 <task>-<tier>,文档列出了 general-high、general-budget、general-fast 三个)、analysis_models(组成 panel 的模型,文档写明这个数组有允许的数量上下限,具体范围以官方文档为准)、model(出结构化分析的 analyst 模型)、max_tool_calls、enabled。这些都是在请求发出前就要定好的东西,没有一项是模型运行中能改的。
{
"model": "openrouter/fusion",
"plugins": [
{ "id": "fusion", "preset": "general-budget" }
]
}
上面这段是官方文档里的原样示例。文档另外写明,显式给出的 analysis_models 或 model 始终优先于 preset;文档默认配置里出现的模型标识只是它当时的示例值,平台上有哪些模型随时在变,不要当清单用。
Response Healing:plugin 还能站在响应这一侧
Fusion 展示的是请求侧注入,Response Healing 展示的是另一半——改写响应。
官方文档《Response Healing》页写明,这个 plugin 会校验并尝试修复模型返回的畸形 JSON,涵盖缺括号、多余的尾逗号、被 markdown 代码块包住、JSON 前面混了一段自然语言、键没加引号这几类。它的触发条件写得很具体:只在非流式请求下生效,且要求你用了 response_format,类型是 json_schema 或 json_object,同时把 response-healing 放进 plugins 数组。
{
"model": "openai/gpt-5.2",
"messages": [...],
"plugins": [
{ "id": "web", "max_results": 3 },
{ "id": "response-healing" }
],
"response_format": {
"type": "json_schema",
"json_schema": { ... }
}
}
边界也得照实说。文档的警告框写明:Response Healing 只适用于非流式请求;而且有些畸形 JSON 是修不回来的,特别是响应被 max_tokens 截断的情况,这个 plugin 无法修复。把它当兜底可以,当保证不行。
对比一下就明白位置差异了:《Server Tools》页描述的调用流程,从模型决定调用、到 OpenRouter 服务端执行、再到把结果交回模型继续生成,全部发生在”模型正在生成”这段时间里;我们在这几页文档里没有找到 server tool 在响应生成完之后还能介入的说明。而《Plugins》页对 plugin 的定义里,“改写响应”是写在定义句里的。《Plugins》页可用插件表里的上下文压缩也是同一类,文档说明它用 middle-out truncation 压缩超出模型上下文窗口的提示词——同样是在请求 / 响应上做改写,模型完全不知情。
同一个能力可以两套入口,而且旧的那套已被标记弃用
网页搜索和 Fusion 一样,plugin 与 server tool 两套形态都有,而网页搜索是最容易配混的那一个。
- plugin 形态:
plugins: [{ "id": "web" }],还有一个更短的写法是给模型 ID 追加:online。 - server tool 形态:
tools: [{ "type": "openrouter:web_search" }]。
这里必须照实标出来:官方文档在《Plugins》页的可用插件表里把 Web Search 标为 deprecated,并写明改用 openrouter:web_search server tool;《Plugins》页”模型变体作为插件快捷方式”一节的警告框也写明,:online 变体与 web search plugin 均已弃用。新写的代码不要再走这条路。
至于为什么弃用,文档没有单独说明,这一点我们不替它解释。文档写明的只是推荐语——《Web Search》插件页顶部的提示框里写着:改用 openrouter:web_search server tool 可以得到更好的结果,因为 server tool 把”何时搜、搜几次”的控制权交给模型,而不是每次请求固定跑一次。这句推荐语正好把本文的落点又说了一遍——两套机制的区别,从头到尾都是介入点的区别。
plugin 独有的一层:账户默认与”Prevent overrides”
还有一处差异是 server tool 那边没有对应物的:plugin 可以在账户层配默认值。
官方文档写明,组织管理员和个人用户都可以配置一份对所有 API 请求生效的默认插件设置,路径是 Settings → Plugins;文档给的步骤是打开该页、用开关把插件默认启用或关闭、点 configure 按钮调整插件设置,以及按需打开 “Prevent overrides”。文档另有一个警告框写明:在组织里,这个插件设置页只有管理员能访问。
优先级是文档白纸黑字列出来的两级:请求级设置(单次请求 plugins 数组里的配置)优先于账户默认。账户里启用了但请求里没写,就套用默认配置;请求里写了,就覆盖默认。反过来,如果你要让账户设置说了算,就打开该插件的 “Prevent overrides”,此后单次生成无法覆盖这份配置。
想在某一次请求里关掉账户默认启用的插件,文档给的写法是显式传 enabled: false:
{
"model": "openai/gpt-5.2",
"messages": [...],
"plugins": [
{ "id": "web", "enabled": false }
]
}
server tool 这边,我们在上述几页文档里没有找到对应的账户级默认配置说明——它是逐请求写进 tools 数组的。这一点如果你要做”全组织统一策略”,差别会很实在。
一个同名字段,两个位置,两套语义
最后提醒一个容易看串的地方:max_tool_calls 这个名字在两处出现。
一处是顶层请求字段,和 messages、tools 平级,管的是整个请求允许的 server tool 步数总额,跨所有 server tool 计算;文档还写明另有一个 stop_server_tools_when 字段接收一组停止条件,设置之后会覆盖 max_tool_calls。
另一处是 Fusion plugin 配置里的 max_tool_calls,管的是每个 panel 模型和 analyst 自己那圈 openrouter:web_search / openrouter:web_fetch 循环的步数。文档明确说这些内层预算与外层请求预算相互独立。
两处都有官方写明的默认值与上限,但这类数值随版本变动,用之前请回官方文档核一次,别照抄任何二手数字(包括这篇)。
关于 Windows
这几页文档给的都是 HTTP API 示例,TypeScript 和 Python 两版跨平台没有差异。需要留意的只有 cURL 那一版:官方示例用单引号把整段 JSON 包起来(-d '{ ... }'),这个写法在 Linux/macOS 的 shell 里直接可用,在 Windows 的 cmd 与 PowerShell 里对引号的处理规则不同,通常需要改写引号或把请求体存成文件再用 -d @文件名 传入。这一段是通用的命令行常识,不是 OpenRouter 官方文档的内容,写在这里只是提醒 Windows 读者别把示例直接粘进终端就报错。手边有 TypeScript 或 Python 环境的话,用官方那两版示例可以绕开这个坑。
以上代码片段均原样取自官方文档;组合多个字段时,以上为按官方文档中的字段语义组合的示例,未经实测,以官方文档与 API 的实际响应为准。该平台迭代频繁,字段名、默认值与弃用状态随版本变动,请以官方文档最新内容为准。
怎么选
把落点收一下,判断顺序其实只有三问:
第一问,这件事该由谁决定跑不跑?答案是”每次都得跑”(比如所有结构化输出请求都要过一遍 JSON 修复),走 plugin;答案是”看情况,模型自己判断”,走 server tool。
第二问,这件事发生在什么时候?要在请求发出前改请求、或在响应回来后改响应,只有 plugin 能做到;要在模型生成过程中查东西、跑东西,那是 server tool 的位置。
第三问,配置要落在哪一层?需要组织统一强制、单次请求不许覆盖的,用 plugin 的账户默认加 “Prevent overrides”;只在某个接口某次调用生效的,写进请求体。
最后重复一遍两条边界:server tools 整体处于 beta,文档明说 API 与行为可能变;web search 的 plugin 形态与 :online 变体已被文档标记为弃用,官方指向 openrouter:web_search server tool。这两条比上面任何字段都更该记住。
本文依据 OpenRouter 官方文档(openrouter.ai/docs)于 2026-08-18 的公开内容整理。
该平台闭源,本文只复述官方文档写明的机制,不推断其内部实现;
我们没有对文中涉及的功能做过实测,因此不涉及界面外观与运行表现的任何描述。
该平台的供应商、模型与路由策略随时变动,文中不列具体供应商名单与模型清单;
价格、额度与限流的具体数值请以官方定价页与用量说明为准。