OpenRouter presets 怎么用:把一整套参数收成一个名字
一、它解决的是”改一个参数要发一次版”
如果你的服务里散着好几处调用大模型的代码,大概率也散着好几份几乎一样的配置:模型名写死在常量里,系统提示塞在某个字符串模板里,provider 的路由偏好在另一个封装函数里被硬编码。想把某处的模型换一版,或者把系统提示改两句,流程就变成了改代码、过评审、发版本。更麻烦的是同一份配置在 Python 后端和 TypeScript 前端各抄了一遍,改的时候还得记得两边都改。
OpenRouter 的 preset 就是冲着这件事去的。官方文档《Presets》页把它的定位写得很直白:preset 让你把 LLM 配置从代码里分离出来,在 OpenRouter 的网页应用里创建和管理,然后在 API 请求里引用。文档举的例子是给不同用途各建一个:email-copywriter、inbound-classifier、code-reviewer。
按该页列举,一个 preset 可以管理六类设置:provider 路由偏好(按价格、延迟等排序)、模型选择(单个模型或带回退的模型数组)、系统提示、生成参数(temperature、top_p 等)、provider 的包含/排除规则,以及 tools——包括 OpenRouter 的服务端工具,文档点名了 web search、advisors 和 subagents。
这篇只讲一件具体的事:preset 里到底存了什么结构,以及当请求里也带了同名字段时,谁盖过谁。
二、前置条件
账号与密钥。 你需要一个 OpenRouter 账号和一个 API key。文中所有示例里的密钥都写成环境变量 $OPENROUTER_API_KEY,不要把 key 直接写进代码或命令历史。
创建入口在网页端。 官方文档写明 preset 通过 OpenRouter 的网页应用创建和管理,给出的地址是 openrouter.ai/settings/presets,新建页是 openrouter.ai/settings/presets/new。我们没有登录过这个页面,因此这里只转述文档给出的路径,不描述它长什么样。
组织账号的可见范围。 文档在”Other Notes”里写明:如果用的是组织账号,所有成员都能访问组织的 presets。也就是说 preset 是工作区级别的共享资源,不是个人私有配置——改动会影响到引用同一个 slug 的所有调用方,这一点在多人协作时要先说清楚。
slug 是标识。 从 API 建 preset 时,路径参数 {slug} 是”URL-safe identifier”。文档写明:如果工作区里已存在同 slug 的 preset,会创建一个新版本并把它指定为 active version;不存在则新建一个 preset。
三、按文档写明的步骤走一遍
步骤 1:把一个跑通的请求体存成 preset
除了在网页端建,官方文档还提供了从推理请求体直接建 preset 的做法——文档自述这适合”把一个已知可用的请求捕获成可复用配置,而不必在 UI 里重新敲一遍”。每种推理 skin 各有一个端点:
| 端点 | 对应的推理 skin |
|---|---|
POST /api/v1/presets/{slug}/chat/completions | Chat Completions |
POST /api/v1/presets/{slug}/messages | Anthropic Messages |
POST /api/v1/presets/{slug}/responses | Responses |
用法是把你本来要发给对应推理路由的同一份 JSON body,改发到这个端点。文档给出的 Chat Completions 例子原样如下:
curl https://openrouter.ai/api/v1/presets/email-copywriter/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-4o",
"temperature": 0.7,
"provider": { "sort": "price" },
"messages": [
{ "role": "system", "content": "You are a helpful assistant." },
{ "role": "user", "content": "Write a marketing email." }
]
}'
这里的 openai/gpt-4o 只是官方文档写这页时用的示例值。平台上有哪些模型、哪些端点随时在变,别把它当成推荐清单或可用清单来用。
这一步实际改了什么:文档写明 OpenRouter 只持久化与 preset config 重叠的字段,并以「e.g.」的口吻举了 model、temperature、provider、top_p、system、tools——这是举例不是封闭清单,别把它当成”只有这六个字段会被存”的保证。反方向的那句写得更死一些:messages、input、prompt、stream 这类瞬时字段会被静默忽略。所以上面那段 body 里的 messages 数组不会被存进去,只有配置字段和从中抽取的系统提示会留下。
三种 skin 的系统提示来源不同,这点容易踩:Chat Completions 是从 messages 里抽 system 消息;Anthropic Messages skin 用的是顶层 system 字段;Responses skin 用的是 instructions 字段。三者最终都落到 preset 的同一个 system_prompt 上。
Windows 侧的差别。 上面这条命令是官方文档里的 bash 写法。在 Linux/macOS 的 shell 里可以直接粘。在 Windows 上,如果你用的是 PowerShell,curl 默认是 Invoke-WebRequest 的别名、且单引号内的 JSON 引号处理与 bash 不同,直接粘会报错;可行的做法是显式调用 curl.exe,或把 JSON 存成文件后用 -d "@body.json"。用 Git Bash / WSL 则与文档写法一致。这一段是通用的命令行常识,不是 OpenRouter 官方文档的内容,以你本机 curl --help 的实际输出为准。
步骤 2:在请求里引用它
文档写明有三种引用方式,都是官方文档里的原样示例:
把 preset 当模型名用(Direct Model Reference):
{
"model": "@preset/email-copywriter",
"messages": [
{ "role": "user", "content": "Write a marketing email about our new feature" }
]
}
用独立的 preset 字段:
{
"model": "openai/gpt-4",
"preset": "email-copywriter",
"messages": [
{ "role": "user", "content": "Write a marketing email about our new feature" }
]
}
把两者写在一起:
{
"model": "openai/gpt-4@preset/email-copywriter",
"messages": [
{ "role": "user", "content": "Write a marketing email about our new feature" }
]
}
后两种写法里请求同时带了 model,谁生效由下一节的覆盖规则决定。
步骤 3:看清返回的字段结构
三个创建端点都返回带”designated version”的 preset 对象。文档给出的响应形状是这样嵌套的:
data.id/data.name/data.slug/data.status/data.created_at/data.updated_atdata.designated_version_iddata.designated_version:内含id、version、system_prompt、config、created_at、updated_at
关键在于 config 和 system_prompt 是分开的两层:系统提示单独占一个字段,其余配置(示例里是 model、temperature、provider)收在 config 对象里。你在做配置审计时,要比对的是 designated_version.config 这一层,而不是 preset 顶层。
四、覆盖优先级:普通字段是覆盖,tools 是并集
这是 preset 最容易想当然的地方,官方文档分两处写,规则并不一样。
普通字段——浅合并。 文档写明:如果你在请求里提供了参数,它们优先于 preset 的值;两者是 shallow-merged(浅合并),意思是请求级字段覆盖 preset 中同名字段,而请求里没出现的 preset 字段被保留。所以 preset 更像是一层默认值,请求是补丁。
浅合并这个词值得多留意一句:文档只说明了顶层字段级别的覆盖,对 provider 这类嵌套对象内部的键是否逐键合并,官方文档没有说明这一点。稳妥的做法是——凡是你要在请求里覆写的嵌套对象,就把它整体写完整,别指望只写其中一个键、其余从 preset 继承。
tools 数组——并集加按 identity 覆盖。 这条是例外,文档单独用一节写了它:当引用 preset 的请求自己也发了 tools 时,两个数组取并集,并且请求里的工具会覆盖 preset 中同一 identity 的工具。identity 按工具种类分三类判定:
| 工具种类 | identity 的判定依据 |
|---|---|
openrouter:advisor、openrouter:subagent | 类型 + parameters.name |
| Function tools | 函数名 |
其它 OpenRouter 服务端工具(如 openrouter:web_search) | 单例,按类型判定 |
顺序上,文档写明 preset 的 tools 保持它们原有的相对顺序,请求里独有的工具追加在后面。
advisor 和 subagent 按”类型 + 名字”判定这一条很实用:文档写明这两类工具可以在一个 preset 里出现多次、一个条目对应一个具名实例,所以一个 preset 可以定义一整班具名的 worker,而请求可以只覆盖其中某一个具名实例、保留 preset 里其余的。文档给的例子里,一个 subagent 条目长这样(节选自官方示例):
{
"type": "openrouter:subagent",
"parameters": {
"name": "ideator",
"model": "anthropic/claude-fable-5",
"instructions": "You brainstorm creative directions, naming, and copy. Return several distinct options."
}
}
同样,里面的模型值只是官方文档的示例值,不构成模型清单。文档还写明:任何引用 @preset/{slug} 的客户端都会在服务端被应用上这些 tools,不需要 SDK 或编排代码,之后增删、调整工具也不用动客户端。
版本这一层。 文档在两处提到版本:一处说版本历史会保留,便于了解改动并回滚;同一处紧接着写”通过 API 寻址一个 preset 时,永远使用最新版本”。另一处是创建端点,新版本会被”designated as the active version”,响应里也带 designated_version_id。把这两处放在一起看:通过 API 引用 slug 拿到的是当前生效的那一版,回滚要在版本历史那一侧做。至于推理请求里能不能显式指定某个历史版本号,官方文档没有说明这一点——SDK 文档里的 getVersion 是按 slug 加版本号读取某一版的内容,不是让推理请求走某一版。
五、边界
- preset 的可用范围:《Presets》这一页在落盘时没有出现
beta、preview、experimental或deprecated的标注。但页内引用到的 advisor、subagent 等服务端工具各有自己的文档页,它们的状态要以各自那一页为准,别因为能写进 preset 就当成稳定能力。 - 数量与配额:一个工作区能建多少 preset、preset config 有多大限制,本文不涉及此类会变动的数值,也请以官方定价与用量说明页为准。
- 组织内的权限粒度:文档只写了组织成员都能访问组织 presets,谁能改、谁只能读,官方文档没有说明这一点。
- 跨 skin 的字段兼容:三个创建端点接受三种不同 skin 的 body,但它们最终写进的是同一份 preset config。某个 skin 特有的字段是否也能落进 config,文档只举了几个重叠字段(
model、temperature、provider、top_p、system、tools)并且明确写成举例,举例之外的字段会怎样,官方文档没有说明这一点。想确认某个字段有没有被存,只能建完之后回读一遍。
以上组合出的用法,均为按官方文档中的参数语义组合的示例,未经实测,以官方文档与 --help 的实际输出为准。该平台迭代频繁,端点、字段与合并规则随版本变动,请以官方文档最新内容为准。
六、怎么验证配对了
按官方 SDK 文档,presets 一共有七个操作:list、get、createPresetsChatCompletions、createPresetsMessages、createPresetsResponses、listVersions、getVersion。做验证主要用后面几个读取类的:
- 确认 preset 存在且内容是你想要的:
get按 slug 取回 preset,文档写明它会把当前 designated version 内联返回。重点看designated_version.config与system_prompt两处,确认你以为存进去的字段真的存进去了——尤其是从请求体建 preset 时,被静默忽略的瞬时字段不会有任何报错提示。 - 确认新版本确实生成了:
listVersions按 slug 列出所有版本,文档写明按版本号升序(最旧在前)。重复 POST 同一个 slug 之后,这里应当多出一版。 - 确认某一版的具体内容:
getVersion接受slug和version两个参数,用来核对历史版本改了什么。 - 确认请求侧的覆盖真的生效:先只发
@preset/{slug}不带任何参数发一次,再带上你要覆盖的字段发一次,比对返回中的生成结果元数据。这一步是按上面那条浅合并规则推演出的验证方法,不是官方文档给出的验证步骤。
list 会列出该账号下的所有 preset,文档写明按最近更新时间倒序。SDK 文档给每个操作都列了错误状态表,读起来有两点值得记:读取类操作统一列了 400、401、500,其中按 slug 取的三个(get、listVersions、getVersion)多一个 404——slug 拼错时会落在这里,而不是静默回退到默认配置;三个创建端点的表更长,除上述之外还列了 403 与 404,以及一个 409。这几个状态码分别在什么条件下触发,SDK 页只给了错误类型名(如 errors.ConflictResponseError),官方文档没有说明这一点——所以写客户端时别按猜出来的语义分支处理,先把错误体原样打出来看。
真要说 preset 最值钱的地方,不是省了几行配置代码,而是把”改配置”这件事从发版流程里摘了出来,同时保留了版本历史。代价是配置不在代码仓库里,改动不走 code review——所以谁能改、改了通知谁,得靠你自己的流程补上。
本文依据 OpenRouter 官方文档(openrouter.ai/docs)于 2026-08-18 的公开内容整理。
该平台闭源,本文只复述官方文档写明的机制,不推断其内部实现;
我们没有对文中涉及的功能做过实测,因此不涉及界面外观与运行表现的任何描述。
该平台的供应商、模型与路由策略随时变动,文中不列具体供应商名单与模型清单;
价格、额度与限流的具体数值请以官方定价页与用量说明为准。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。