OpenRouter 的插件机制:plugin 与 server tool 不是一回事

2026-08-18

先说一个很容易踩的场景。

两个人给同一套服务加联网能力。A 在请求体里加了 plugins: [{ "id": "web" }],B 在请求体里加了 tools: [{ "type": "openrouter:web_search" }]。上线之后 A 发现,哪怕用户问的是”帮我把这段 JSON 格式化一下”,这次请求也照样走了一次搜索;B 那边则是另一个极端,有些明显需要查资料的问题,模型一次搜索都没发起。

两边都没配错。它们本来就是两套机制,介入的位置根本不在同一层。

差异不在功能,在写进哪个数组

OpenRouter 官方文档《Server Tools》那一页开头就摆了一张三列对照表,把 Server Tools、Plugins、User-Defined Tools 放在一起比。这张表值得逐行读,因为它比任何解释都直接:

维度Server ToolsPluginsUser-Defined Tools
谁决定要不要用模型总是运行模型
谁负责执行OpenRouterOpenRouter你的应用
每次请求调用几次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-highgeneral-budgetgeneral-fast 三个)、analysis_models(组成 panel 的模型,文档写明这个数组有允许的数量上下限,具体范围以官方文档为准)、model(出结构化分析的 analyst 模型)、max_tool_callsenabled。这些都是在请求发出前就要定好的东西,没有一项是模型运行中能改的。

{
  "model": "openrouter/fusion",
  "plugins": [
    { "id": "fusion", "preset": "general-budget" }
  ]
}

上面这段是官方文档里的原样示例。文档另外写明,显式给出的 analysis_modelsmodel 始终优先于 preset;文档默认配置里出现的模型标识只是它当时的示例值,平台上有哪些模型随时在变,不要当清单用。

Response Healing:plugin 还能站在响应这一侧

Fusion 展示的是请求侧注入,Response Healing 展示的是另一半——改写响应。

官方文档《Response Healing》页写明,这个 plugin 会校验并尝试修复模型返回的畸形 JSON,涵盖缺括号、多余的尾逗号、被 markdown 代码块包住、JSON 前面混了一段自然语言、键没加引号这几类。它的触发条件写得很具体:只在非流式请求下生效,且要求你用了 response_format,类型是 json_schemajson_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 这个名字在两处出现。

一处是顶层请求字段,和 messagestools 平级,管的是整个请求允许的 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 的公开内容整理。 该平台闭源,本文只复述官方文档写明的机制,不推断其内部实现我们没有对文中涉及的功能做过实测,因此不涉及界面外观与运行表现的任何描述。 该平台的供应商、模型与路由策略随时变动,文中不列具体供应商名单与模型清单; 价格、额度与限流的具体数值请以官方定价页与用量说明为准。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。