dsl 策略——所以你的
应用照旧调用 orcarouter/{name},而路由逻辑存放在控制台里,带版本,
无需重新部署即可编辑。
何时该用 DSL
当”最便宜的在线模型”或”最高质量”就能表达你的意图时,用内置策略。当 路由取决于请求的内容或上下文时,才动用 DSL:- 任务特化 —— 把代码发给编码模型,视觉发给视觉模型,廉价闲聊发给 廉价模型。
- 难度感知路由 —— 只把困难请求升级到昂贵模型;简单的保持廉价。
- agent 感知路由 —— 根据会话状态做不同路由(agent 用过哪些工具、 测试是否刚刚失败、当前进行到第几回合)。
- 时间 / 租户 / 请求头规则 —— 按小时、用户组或某个请求头做不同路由。
启用
在控制台的 Routing 下,打开一个路由器,把它的 Strategy 设为 DSL。这会为该路由器显示 DSL 编辑器。路由器的其余配置依旧生效—— Allowed models glob、Default model 兜底,以及orcarouter/{name}
调用方式。
编辑器
编辑器的设计目标是让你从意图快速抵达一份可用的规则集:- Templates(模板)以你工作区里真实的模型为种子(通过一次性的 分层映射对话),所以你绝不会从空白文件开始,也不会撞上”未知模型”的 墙。
- Insert(插入)—— 从自动补全里放入一个 Model、一个 Router
(
orcarouter/<name>)或一个 Pool,而不用手敲标识符。 - Generate(生成)—— 用自然语言描述你想要的路由,得到一份编译过、 lint 干净、且立足于你真实模型的 DSL。
- Explain(解释)—— 用通俗英文复述当前规则集所做的事。
- Inline lint(内联 lint)—— 每个错误都报告
{line, column, message}, 每个 lint 码都有一个?解释器。优先级(first-match-wins)和常见的 CEL 模式会就地呈现。
文件结构
一份规则集是带三个顶层键的 YAML:when: 条件和 use: 效果组成:
when: 为真的规则胜出。**如果没有任何规则
匹配,则应用 default:。把最具体的规则排在最前面——一条过于宽泛且靠前
的规则会遮蔽它下面的一切。
when: —— 条件
条件用 CEL(Common Expression
Language)编写:设计上即安全——没有循环、没有 I/O、微秒级求值、仅支持
RE2 正则。下面这六种模式覆盖了绝大多数真实规则:
| 模式 | 示例 |
|---|---|
| 字段访问 | task_class == "agent" |
| 数值比较 | difficulty > 0.6 && request.input_tokens < 50000 |
| 布尔逻辑 | agent_state.has_edited && !agent_state.has_run_tests |
| 列表成员判断 | "Edit" in agent_state.tools_used |
| 正则宏 | system_prompt_matches("(?i)planning agent") |
| 工具宏 | tool_calls_present_any(["Edit","Write","apply_patch"]) |
变量
请求形态| 变量 | 类型 |
|---|---|
model | string |
request.input_tokens | int |
request.output_max_tokens | int |
request.stream | bool |
request.vision | bool |
request.message_count | int |
request.has_system_prompt | bool |
request.has_tools | bool |
| 变量 | 类型 | 含义 |
|---|---|---|
task_class | string | chat / code / agent / vision / audio / rag / creative |
difficulty | double | 0.0–1.0 |
code_keyword_density | double | 0.0–1.0 |
reasoning_cue_count | int | 在提示中检测到的推理线索数 |
tool_count | int | 请求上不同工具定义的数量 |
agent_state.*,在一次对话中持久保留)
| 变量 | 类型 |
|---|---|
agent_state.turn | int |
agent_state.tools_used | list<string> |
agent_state.files_read | list<string> |
agent_state.has_edited | bool |
agent_state.has_run_tests | bool |
agent_state.last_test_failed | bool |
agent_state.consecutive_errors | int |
agent_state.elapsed_seconds | int |
agent_state.models_tried | list<string> |
| 变量 | 类型 |
|---|---|
headers["x-foo"] | string |
user.id / user.group | int / string |
token.id / token.name | int / string |
time.hour / time.weekday | int (UTC) |
workspace.id | int |
宏
已注册的 CEL 函数,用于常见的”往请求内部看一眼”的检查:| 宏 | 返回 |
|---|---|
system_prompt_matches(regex) | 对拼接后的 system 消息做 RE2 匹配 |
user_message_matches(regex) | 对最后一条 user 消息做 RE2 匹配 |
tool_definitions_include(name) | 请求上声明了某个工具 |
tool_calls_present_any(list) | 请求携带了其中任意一个工具调用 |
tool_results_from_any(list) | 请求含有来自其中任意一个的 tool 角色消息 |
header_matches(name, regex) | 对某个请求头的值做 RE2 匹配 |
use: —— 效果
一个 use: 块命名一个目标(恰好一个)以及任意数量的可选逐调用
旋钮。
目标
delegate: dsl 会被拒绝(它会造成递归)。钉死到特定渠道
(channels: / @channel:)目前不可用,且会被 lint 为不支持——请改用
model、models 或 pool 来路由。逐调用旋钮
与任意目标组合,用以塑造上游调用:param_override 和 header_override 强制一份拒绝清单——你不能覆盖
model、messages、stream、tools、鉴权请求头等(这些会破坏计费、
审计或 agent 状态)。
置信度级联与集成(高级)
两种高级效果让一条规则能对一个偏弱的首答做出反应,或在若干模型间扇出。 它们的编写方式和任何普通规则一样。 Cascade(级联) —— 在低置信度信号出现时,用更强的效果重试:**集成 / 级联的运行时受开关控制,且默认关闭。**因为每条并行腿和每次
级联修复都会作为各自独立的调用计费,所以在逐腿计费得到验证之前,扇出
运行时被放在一个服务端 flag 之后。在它关闭的情况下,
parallel: 规则只
服务第一条腿,级联会记录它的信号但不会重新派发——规则集照旧能通过
lint、保存,并正常路由它的主效果。如需为你的工作区启用集成运行时,请
联系我们。安全地灰度上线
一份新规则集并不会在你保存的那一刻就接管你的流量:- Shadow mode(影子模式) —— 在首次保存后的一个窗口内,DSL 会被
求值但不被采用:你之前的策略仍在服务流量,同时网关记录 DSL 本会
做出的决定。控制台会展示一份 diff 报告——路由结果差异的百分比、预计
成本变化、每条规则的命中次数,以及它落到
default:的频率。在信任这 些规则之前先读它。 - Canary(金丝雀) —— 把 DSL 灰度到一定百分比的真实流量上 (5 → 25 → 50 → 100),观察各切片的指标,并能通过把百分比滑到 0 来 即时回滚。
限制与校验
每次保存都会运行一次严格的 lint;无效的规则集会被拒绝并返回{line, column, message, rule}:
- Schema —— 必需键、正确的类型/枚举、没有未知字段。
- Size —— ≤ 30 条规则、≤ 16 KiB 的 YAML、每个
when:≤ 200 字符。 - CEL —— 能解析、能针对变量环境做类型检查、没有未知标识符,且
when:必须求值为 bool。 - Effect —— 每个
use:块恰好一个目标;所有model/models/@pool:引用都必须能在你的工作区内解析。 - Knob ranges(旋钮取值范围) ——
thinking_budget_tokens ∈ [1024, 64000]、temperature ∈ [0, 2]、samples ∈ [1, 16]。 - Reserved(保留) —— 以
_开头的规则 id 被保留;用default作为 规则 id 会被拒绝(请用顶层的default:块)。
一个完整示例
X-Orca-Router 和
X-Orca-Resolved-Model
响应头。
API 参考
DSL 按路由器维度管理;写操作需要 Developer+。| 方法与路径 | 角色 | 用途 |
|---|---|---|
GET /api/user/routers/:id/dsl | Member | 源码 + 版本 + shadow/canary 状态。 |
PUT /api/user/routers/:id/dsl | Developer+ | lint + 保存(新版本,带审计)。 |
POST /api/user/routers/:id/dsl/lint | Member | 对草稿 lint → {errors:[…]}。 |
POST /api/user/routers/dsl/lint | Member | 无状态 lint(不带路由器 id)。 |
POST /api/user/routers/:id/dsl/dryrun | Member | 对一个合成请求求值 → trace + 命中的规则。 |
GET /api/user/routers/:id/dsl/history | Member | 版本历史,最新在前。 |
POST /api/user/routers/:id/dsl/rollback/:version | Developer+ | 重新 lint 并恢复一个较旧的版本。 |
FAQ
这和命名路由器的策略有什么区别?
这和命名路由器的策略有什么区别?
它就是一种策略——和 cheapest / quality / balanced / adaptive 并列的
dsl 选项。其他策略按价格和质量挑选;DSL 则按你针对请求形态、分类和
agent 状态编写的规则来挑选。你仍然可以把 delegate: 到一个内置策略
作为某条规则的效果或作为 default。如果没有任何规则匹配会怎样?
如果没有任何规则匹配会怎样?
会应用顶层的
default: 效果。它是必需的,所以总会有一个确定的结果
——常见的是 delegate: balanced 或某个具体的兜底模型。在热路径上运行不受信任的 CEL 安全吗?
在热路径上运行不受信任的 CEL 安全吗?
安全。CEL 运行在一个仅含标准库函数的沙箱里,有几毫秒的求值期限、RE2
正则(线性时间,无 ReDoS),且无法访问数据库、网络或文件系统。变量
环境是一组固定的标量和列表。
能在规则集触及真实流量之前测试它吗?
能在规则集触及真实流量之前测试它吗?
有三种方式:在编辑器里对一个合成请求 dry-run(试跑);把它留在
**shadow mode(影子模式)**并阅读 diff 报告;然后在灰度到 100% 之前,
先 **canary(金丝雀)**到一小部分真实流量上。
