1. 什么是提示词注册表
提示词注册表是一个工作区级别的可复用系统消息库。 你保存一次提示词,将任意 API 密钥绑定到它(或每个请求发送prompt_ref),网关会在将请求转发到上游模型之前
将该提示词注入为系统消息。
编辑提示词会在下一次调用时更新所有绑定到它的密钥。
无需重新部署。无需修改代码。无需升级 SDK。绑定关系存在于
网关中,而不是你的应用中。
这与 Langfuse 和 LangSmith 开创的思路一致,但有一个
区别:OrcaRouter 是交付层。你的应用代码像以前一样调用
/v1/chat/completions;网关负责解析并
注入提示词。应用中无需安装任何东西。
提示词是工作区级别的——每个成员都能看到该工作区的提示词;
不会跨租户边界。
2. 快速开始——5 步绑定你的第一个提示词
创建提示词
在控制台中前往
/console/prompts,点击 New prompt。
将其命名为 support-agent。粘贴系统消息:“你是 Acme 的简洁支持代理。请用不超过 2 句话作答。”保存——这会创建版本 1。
发送请求
使用该密钥,像以前一样调用 OrcaRouter:网关会在转发前预置你保存的系统消息。响应头
X-Orca-Prompt: support-agent@production:v1 确认
注入了哪个提示词。3. 概念:提示词、版本、标签
| 概念 | 定义 | 可变性 |
|---|---|---|
| 提示词 | 一个命名的、工作区级别的条目。标识符:name(正则 ^[a-zA-Z0-9._-]{1,128}$)。 | 可软删除(30 天回收站 + 清除)。 |
| 版本 | 提示词内容的不可变快照。每次保存时自动创建。标识符:单调递增的 int。 | 不可变——永不编辑,永不复用。 |
| 标签 | 指向版本的可移动指针(例如 production → v7)。 | 通过晋级原子性地移动;审计日志记录每次移动。 |
保留标签
production在每个新提示词的第一个版本上自动固定到 v1。 移动它就是生产流量切换——Owner 专属 RBAC。latest由网关自动维护,始终 指向最新版本。你不能手动移动latest。
staging、
canary、eu-prod)并将密钥绑定到它们。在标签尚未固定到某个版本
之前,绑定到 name@<该标签> 的密钥会以”无注入”的方式失败开放。
为什么是这种形态
不可变版本和可移动标签之间的分离是 “无需修改代码即可部署”的原语。应用代码引用一个标签 (通过密钥绑定隐式引用,或通过prompt_ref 显式引用)。
晋级移动标签——应用在下次调用时看到新内容,
无需任何代码更改。回滚就是将一个更旧的版本晋级到该标签上。
4. 生产模式:晋级、回滚、分阶段发布
晋级
在提示词行打开 Labels,选择目标版本,点击 Promote。 标签移动是原子的且经过审计(审计日志显示谁将哪个标签 从哪个版本移到哪个版本,何时移动)。绑定到name@<label> 的每个密钥在下次请求时都会获取新版本。
Owner 专属。 晋级是生产流量变更,仅限工作区
Owner 操作(
POST /api/prompt/:id/label)。Developer 和 Viewer
能看到标签列表和审计历史,但看不到 Promote 按钮;
对话框会显示内联的”请联系 Owner”提示,使该限制可见
而非静默。回滚
在 History 抽屉里对一个旧版本点击 Restore。Restore 会将 该版本的内容向前复制为新版本(历史永不可变) 并将latest 移到它。要让流量实际回退,需要将
相关标签 Promote 到该已恢复的版本。
分阶段发布
将金丝雀密钥绑定到name@staging,将生产密钥绑定到
name@production。将 staging 晋级到新版本,在
Insights 中观察,然后在满意时晋级 production。无需修改密钥、
无需部署、无需更新 SDK。
A/B 流量切分
Label 对话框有一个 Split traffic 切换。启用它可以将 单个标签指向多个版本并按权重分配 (例如 v7: 60%、v8: 40%)。分桶按(workspace, token, request-id) 确定性进行,因此单个对话在重试时
仍保持在同一桶中。
5. 模板:{{var}} 替换
提示词内容支持 Mustache 风格的 {{var}} 占位符。调用方
值来自 prompt_ref.variables(参见 §6)。
规则:
- 单次替换。 变量值作为字面文本发出。
它们 NOT 会被作为模板重新求值——这可以防止调用方
提供的值试图注入更多
{{...}}指令的提示注入。 - 未知占位符按原样保留。 如果占位符
{{foo}}没有 匹配的变量,会发出字面{{foo}}(并 记录警告)。请求绝不会因变量缺失而失败。 - 点号访问。 当调用方传递嵌套映射时,
{{user.name}}会遍历嵌套对象。 - 区块。
{{#flag}}...{{/flag}}仅在flag为真时显示该块。反向区块({{^flag}}...)在flag缺失/为假时显示该块。 - 渲染上限 256 KiB。 渲染后的最终文本若超过该阈值,
整次注入会被跳过(响应不携带
X-Orca-Prompt头), 请求按原样转发——防止变量膨胀放大攻击。
- Langfuse 提示词使用相同的
{{var}}Mustache 语法。 - LangSmith 提示词在其清单中声明
template_format: f-string | mustache。 网关遵循该声明。
6. 每请求覆盖:prompt_ref
在不修改密钥绑定的情况下按请求覆盖或选择提示词。
在请求体中添加顶层 prompt_ref 字段:
prompt_ref > 密钥绑定(原生
PromptId/PromptLabel 或 PromptProviderId)> 通道 SystemPrompt
无。
prompt_ref 由网关消费并在转发到上游之前剥离——严格的
提供方永远看不到这个未知字段。
形态:
7. 聊天形态的提示词(系统 + few-shot)
大多数提示词是单个系统字符串。但有时你希望 网关注入更丰富的模板——系统消息加上 few-shot 的 用户/助手轮次序列。注册表通过kind: 'chat' 支持这一点。
控制台的 Create prompt 模态框暴露了 Text / Chat 切换。当
你选择 Chat 时,内容编辑器变为
{role, content} 行列表(system、user、assistant)——按需添加。
保存时,这些行被持久化为 messages_json。一旦创建,
kind 即不可变。
注入行为:
- 请求中没有系统消息 ⇒ 网关预置 模板的系统消息,模板的 few-shot 轮次出现在 调用方消息之前。
- 请求中有系统消息 ⇒ 注入行为由格式适配器决定。
OpenAI 形态的请求会把模板的系统消息预置到前面;Claude 形态
的请求会把模板的系统消息放进原生的
system参数里。
8. 与网关其他部分的关系
| 模块 | 如何与提示词组合? |
|---|---|
| Models | 提示词与模型无关。同一个提示词可以在 GPT-5、Claude、Gemini 上运行。路由根据请求的 model 和密钥的分组选择上游模型——提示词永不覆盖该选择。 |
| Routing | 路由先运行;提示词解析器随后运行。所以解析出的提示词会跟随路由器选择的任何通道,包括跨越回退链。 |
| Guardrails | Guardrails 是独立的关卡,用于检查和脱敏内容。提示词注入系统消息;它们不绕过策略。请求可以同时携带两者——guardrails 始终运行。 |
| API Keys | 密钥在某个标签上绑定到提示词(例如 support-agent@production)。绑定存在于网关的密钥上,所以晋级新版本会一次性切换该标签上的每个密钥。 |
| Insights | 每个请求都会在其日志行上打上 prompt_id、prompt_version、prompt_label。Insights 按提示词切片——用量、错误率、延迟、成本。 |
config(Langfuse config.model、LangSmith
model_config)——网关也会忽略这些字段。提示词仅注入文本;
模型选择是路由器的工作。
9. 外部源:Langfuse、LangSmith、Generic HTTP
联邦:一次性连接外部提示词源,然后绑定密钥或针对那里托管的名称 发送prompt_ref。原生和外部
提示词的绑定和服务方式完全一致——只有解析器后端不同。
支持的源:
- Langfuse —
GET {base}/api/public/v2/prompts/{name}?label=..., 使用public:secret对的 Basic auth。支持文本和聊天提示词。 - LangSmith —
GET {base}/commits/{owner}/{name}/{tag|hash|latest}, 使用x-api-key头。网关解析序列化的清单以 提取 messages/text 以及template_format声明。嵌入的model_config/model_provider字段会被剥离(纵深 防御:注册表仅提供文本)。 - Generic HTTP — 运营方配置的连接器,适用于任何通过单次 HTTP 调用 完成获取的提示词注册表。可配置字段见下文。
Generic HTTP 连接器字段
Generic HTTP 源是一个”描述一次 HTTP 调用和一种响应形状”的 适配器。用于自托管的提示词存储以及不需要专用后端集成的 第三方平台(PromptLayer、简单的自定义 API 等)。 这些字段被有意保持精简——多步流程或 provider 特定协议不在范围内。| 字段 | 默认值 | 作用 |
|---|---|---|
| URL template | 必填 | 完整请求 URL,带 {name} / {label} / {version} 占位符。路径中的占位符使用 PathEscape;查询字符串中的占位符使用 QueryEscape,这样提示词名称中的 &/= 就不能注入额外的查询参数。 |
| HTTP method | GET | GET 或 POST。当平台需要请求体时选择 POST。 |
| Auth header name | Authorization | 发送密钥所用的 HTTP 头。对于使用自定义头的提供方,设为 X-API-KEY(或类似)。 |
| Auth scheme prefix | Bearer (带尾部空格) | 放在头部值密钥前的字符串。如果平台期望原始 API 密钥,则设为空;或设为 Token / 其他自定义前缀。 |
| Body template | 空 | 仅 POST。原始请求体,带两族占位符。逐字:{name} / {label} / {version} 替换为字面值(用于 form-encoded、XML 或模板正文——你自己负责转义)。JSON-safe:{name_json} / {label_json} / {version_json} 替换为完全加引号的 JSON 字符串字面量(例如 "hello")——在 JSON 正文内使用这些,这样包含 " / \ / 控制字符的请求侧提示词名称就不会向上游注入相邻字段。 |
| Response JSON path | 空 | 可选的点号路径,指向响应 JSON 中存放提示词负载的位置(例如 data.0.template.messages)。空 = 自动检测顶层 text / prompt / messages 形态。 |
弹性
- TTL 缓存(默认 60 秒),让提示词编辑在一分钟内传播。
- Stale-while-revalidate——缓存值继续提供服务,同时 下一次刷新在后台进行。
- Stale-on-error——如果外部源返回 5xx 或超时, 网关提供上次已知的良好响应。用户流量 永不会因 provider 故障而硬性失败。
10. 可观测性
每个注入提示词的请求都会留下四条面包屑。响应头
- 原生:
name@label:vN (native)(当版本 int 未知时, 为name@label (native))。 - 外部:
name@label:<provider-version-tag> (langfuse)等。 - 省略标签 ⇒ 无
@label段。
日志列
Log.PromptId、Log.PromptVersion、Log.PromptLabel——类型化列,
为 Insights 查询建立索引。
Insights 下钻
在/console/insights 中,过滤行有一个 Prompt facet——选择一个
提示词,每个标签页(延迟、错误、成本)都会过滤到该
prompt_id。这就是”我编辑了一个提示词——流量发生了什么变化?”
的闭环。
审计
每次标签移动和回滚都记录在提示词的 Promote history 中,包含操作者用户 id、时间戳、起始版本、目标版本。 每个成员可见;变更受 Owner 角色限制。11. API 参考
所有路由都通过X-Workspace-Id 头进行工作区限定。RBAC
执行一致:读取对每个成员开放;写入
为 Developer+;生产流量变更(标签移动、回滚、
提供方配置、webhook)仅限 Owner。
提示词
| 方法与路径 | 角色 | 用途 |
|---|---|---|
GET /api/prompt/ | Member | 列出提示词(分页,支持 ?tag=)。 |
GET /api/prompt/?in_trash=true | Owner | 列出软删除的提示词(仅 Owner——恢复类)。 |
GET /api/prompt/search | Member | 关键字 + 标签搜索(限速)。 |
GET /api/prompt/tags | Member | 工作区标签 typeahead。 |
GET /api/prompt/:id | Member | 单个提示词详情。 |
GET /api/prompt/:id/versions | Member | 版本历史(最新优先)。 |
GET /api/prompt/:id/labels | Member | 当前标签 → 版本映射。 |
GET /api/prompt/:id/tags | Member | 单个提示词的标签集。 |
GET /api/prompt/:id/label_history | Member | 晋级审计日志。 |
GET /api/prompt/:id/analytics | Member | 单提示词用量图表数据。 |
GET /api/prompt/analytics/top | Member | 工作区范围内使用最多的提示词。 |
POST /api/prompt/ | Developer+ | 创建提示词(text 或 chat)。 |
PUT /api/prompt/ | Developer+ | 更新提示词(创建新版本)。 |
POST /api/prompt/:id/tags | Developer+ | 替换标签集。 |
POST /api/prompt/:id/run | Developer+ | Playground “Try it”(限速 30/分钟/工作区)。 |
DELETE /api/prompt/:id | Developer+ | 软删除到回收站(默认);?purge=true 仅 Owner 可硬删除。 |
POST /api/prompt/:id/restore | Owner | 从回收站恢复。 |
POST /api/prompt/:id/rollback | Owner | 将旧版本恢复为新版本。 |
POST /api/prompt/:id/label | Owner | 将标签移到某个版本(原子、审计;也接受 split 载荷用于 A/B)。 |
提示词提供方(联邦)
| 方法与路径 | 角色 | 用途 |
|---|---|---|
GET /api/prompt_provider/ | Member | 列出已连接的源(屏蔽密钥)。 |
POST /api/prompt_provider/ | Owner | 连接一个源。 |
PUT /api/prompt_provider/ | Owner | 更新一个源。 |
DELETE /api/prompt_provider/:id | Owner | 断开连接。 |
POST /api/prompt_provider/test | Owner | 保存前 dry-resolve。 |
GET /api/prompt_provider/:id/prompts | Member | 列出外部源中可用的提示词。 |
POST /api/prompt_provider/:id/prompts/import | Developer+ | 将外部提示词导入到本地注册表。 |
提示词 webhook
| 方法与路径 | 角色 | 用途 |
|---|---|---|
GET /api/prompt_webhook/ | Member | 列出 webhook。 |
POST /api/prompt_webhook/ | Owner | 添加 webhook(密钥仅返回一次)。 |
PUT /api/prompt_webhook/:id | Owner | 编辑。 |
DELETE /api/prompt_webhook/:id | Owner | 移除。 |
POST /api/prompt_webhook/:id/test | Owner | 发送示例事件。 |
Webhook 事件投递
每次投递会向你配置的 URL POST 一个 JSON 信封:prompt.created、prompt.updated、prompt.deleted、
label.promoted、version.rolled_back。
每次投递都会带这些请求头:
X-Orca-Webhook-Id—— 你这个 webhook 的 id(用于去重)。X-Orca-Event—— 与信封中event字段相同。X-Orca-Signature—— 格式为sha256=<hex>,其中<hex>是对原始 请求体计算的 HMAC-SHA256(密钥为 webhook secret)。请使用常量时间 比较。
请求载荷新增
12. FAQ
如果请求上没有解析出任何提示词会怎样?
如果请求上没有解析出任何提示词会怎样?
行为与从未启用该功能的工作区完全字节一致。如果密钥
没有绑定,没有
prompt_ref,也没有设置通道
默认值,网关不做任何修改。响应
不携带 X-Orca-Prompt 头。日志列为 NULL。这是回归保证:当什么都没绑定时,解析器
是经过验证的 no-op。SystemPromptOverride 如何与注册表交互?
SystemPromptOverride 如何与注册表交互?
SystemPromptOverride 是现有的通道级系统提示词
默认值。绑定的注册表提示词会覆盖通道默认值——
这是有文档记录且故意为之的。当什么都没解析出来时,
通道默认值仍按原样工作。当调用方请求已包含系统消息时,行为由格式适配器决定:
OpenAI 形态的请求会把模板的系统消息预置到前面;Claude
形态的请求会把模板的系统消息放进原生的 system 参数里。我能限制特定密钥可以使用哪些提示词吗?
我能限制特定密钥可以使用哪些提示词吗?
v1 中不能。任何密钥都可以
prompt_ref 其所在
工作区中的任何提示词。这与 Langfuse 和
LangSmith 的工作区级密钥模型一致。跨工作区访问在解析器
层被拒绝(在中继路径中重新检查;绝不信任陈旧的
绑定)。每个密钥的提示词白名单是可能的未来功能。注入的提示词 token 会计费吗?
注入的提示词 token 会计费吗?
会。注入的系统提示词 token 像任何其他系统消息一样
计入用量 / 配额 / 计费。超长提示词
超出模型上下文窗口时,返回上游的常规
错误——网关不会预先截断。
注册表会覆盖模型吗?
注册表会覆盖模型吗?
不会。外部提供方的
config.model / model_config 字段
会被忽略。模型选择仍由路由器独占决定——提示词
仅注入文本。绑定到已删除提示词的密钥会发生什么?
绑定到已删除提示词的密钥会发生什么?
解析器将缺失 / 已删除 / 未授权的提示词视为
fail-safe 跳过——请求按原样转发,
不向调用方返回错误。编辑和晋级模态框会显示”被 N 个
密钥使用”徽章,这样你在删除或晋级前
可以看到爆炸半径。
标签移动传播多快?
标签移动传播多快?
原生标签移动近乎即时(网关以秒级有界间隔
从 DB 同步,加上控制器写入路径上的本地映射写入)。
外部标签移动在配置的缓存 TTL 内出现(默认 60 秒)。
两者都是有文档记录的预期,而非
缺陷。
我可以在 UI 中编辑聊天提示词吗?
我可以在 UI 中编辑聊天提示词吗?
可以。Create prompt 模态框暴露了
Text / Chat 切换;
chat 模式显示结构化的 {role, content} 编辑器。一旦
提示词创建完成,其 kind 即不可变(要更改形态需
创建新提示词)。提示词印记的面包屑出现在哪里?
提示词印记的面包屑出现在哪里?
- 用户面向响应上的响应头
X-Orca-Prompt。 - 请求日志行上的
Log.PromptId/PromptVersion/PromptLabel列。 - Insights 的 Prompt 过滤 facet——选择一个提示词;每个
Insights 标签页都会过滤到该
prompt_id。
如何轮换 webhook 密钥?
如何轮换 webhook 密钥?
通过
PUT /api/prompt_webhook/:id 编辑 webhook,并提供一个新的
secret 值。新密钥仅在响应中返回一次——那时复制;之后
密钥会被屏蔽。(没有专门的轮换端点;轮换就是一次普通编辑。)