Skip to content

Meta Model DSL ​

专业版功能

Meta Model DSL,也叫 Meta Module Language,是专业版提供的元模型路由语言。社区版不会注册元模型数据表、管理接口和运行时解析 Hook。

元模型是对外暴露给用户和 API 密钥的虚拟模型名。请求进入网关时,如果 model 命中一个启用的元模型,专业版会先执行这段 DSL,把虚拟模型解析成一个真实上游模型,然后继续走普通网关流程。

这个语言只负责选择模型,不负责定价、鉴权、网络请求和内容处理。真实调用仍然由网关的渠道、模型、余额、密钥、限流和安全检查接管。

实现状态 ​

当前实现可保存并执行:

Action状态说明
call可执行直接解析到一个真实模型
route可执行按条件从上到下选择第一个匹配分支
switch可执行按权重或概率随机选择一个分支
parallel预留语法可解析,但保存校验会拒绝
judge预留语法可解析,但保存校验会拒绝

生产环境只应使用 call、route 和 switch。parallel、judge 保留给未来编排能力,当前不能保存为可运行元模型。

运行流程 ​

  1. 客户端请求 /v1/* 或 /v1beta/*,请求体里的 model 是元模型名。
  2. 网关检查该元模型是否存在并启用。
  3. API 密钥的模型白名单先按元模型名校验。
  4. 专业版解析 DSL,并根据请求、用户、密钥上下文计算运行时变量。
  5. DSL 返回一个真实模型名。
  6. 网关把请求目标模型替换为真实模型,继续进行渠道选择。
  7. 真实模型仍受用户渠道绑定、模型启用状态、余额、密钥额度、限流、SSRF 防护、敏感词过滤和用量记录约束。

TIP

元模型不会创建独立的用户或渠道上下文。它只在普通网关路由之前做一次模型名解析。

管理入口 ​

前端管理页面在系统管理里的元模型区域提供 DSL 编辑器、模板和语法参考。保存前建议先点击校验,或者直接调用校验接口。

管理接口只在专业版注册,并且需要管理员登录态:

http
GET    /api/meta-models
POST   /api/meta-models
PUT    /api/meta-models/:id
DELETE /api/meta-models/:id
POST   /api/meta-models/validate

创建和更新的请求体字段:

json
{
  "name": "meta-smart",
  "description": "按上下文和概率选择模型",
  "dsl": "call \"gpt-4o-mini\"",
  "provider": "openai",
  "provider_name": "OpenAI",
  "provider_icon_url": "https://example.com/openai.png",
  "expose_referenced_models": false,
  "billing_mode": "actual",
  "input_price": "0",
  "output_price": "0",
  "cached_input_price": "0",
  "enabled": true
}

POST /api/meta-models/validate 使用相同字段,但不会写入数据库。成功时返回 plan,也就是解析后的 AST。创建和更新成功时返回 meta_model 和 plan。

计费模式 ​

计费不是 DSL 的一部分,由元模型配置字段 billing_mode 控制。

模式值行为
按真实模型计费actual使用最终选中的真实模型价格。元模型自身价格会被保存为 0。
按元模型计费meta使用元模型自身配置的输入、输出和缓存输入价格。上游调用仍然使用 DSL 选出的真实模型。

两种模式都会按当前用户和 API 密钥记录用量。actual 模式下日志模型名是实际模型;meta 模式下计费模型名是元模型名。

公开目录 ​

公开模型目录会合并启用的元模型。元模型条目的关键字段:

字段说明
model_name元模型名
provider公开目录里的供应商标识,默认 meta,可由管理员设置
provider_name公开目录里的供应商显示名,默认 Meta Module,可由管理员设置
provider_icon_url供应商图标 URL,可为空
is_meta_model固定为 true
meta_billing_modeactual 或 meta
referenced_modelsDSL 引用到的真实模型目录条目,仅在 expose_referenced_models 开启时返回

公开目录不会返回 DSL 源码。referenced_models 是完整的模型目录条目,且不会继续嵌套引用,便于前端展示价格和可用渠道。管理员关闭 expose_referenced_models 后,后端仍会用引用模型计算元模型价格和可用渠道,但不会把 referenced_models 下发给前端,模型广场也不会展示背后的真实模型列表。

如果希望元模型在模型广场看起来像普通模型,可以把 provider、provider_name 和 provider_icon_url 设置成目标供应商展示信息,同时关闭 expose_referenced_models。

语言结构 ​

一个程序由零个或多个 option 声明,以及一个根 action 组成。

text
option audit_label = "balanced-router"

route {
  when request.input_tokens <= 2000 => call "gpt-4o-mini"
  otherwise => call "gpt-4o"
}

当前 option 会进入 AST,但运行时尚未使用。建议只把它当作未来扩展或审计标签预留,不要依赖它改变执行结果。

注释和分隔符 ​

行注释以 # 开头。; 和 , 在允许的位置是可选分隔符。

text
# 优先处理图像请求
route {
  when request.has_image == true => call "gpt-4o";
  otherwise => call "gpt-4o-mini",
}

字面量 ​

类型写法
string双引号字符串,支持 \"、\\、\n、\r、\t
number无符号十进制数,如 2000、0.75。不支持 -1、+1、1_000
booltrue 或 false
identifier首字符为字母或 _,后续可含字母、数字、_、.

Actions ​

call ​

直接选择一个真实模型:

text
call "gpt-4o-mini"

规则:

  • 模型名必须存在于模型管理里的真实模型。
  • 不能引用自己。
  • 当前不能引用另一个元模型。

route ​

按条件顺序选择第一个匹配分支:

text
route {
  when request.has_audio == true => call "gpt-4o-audio"
  when request.has_image == true => call "gpt-4o"
  when request.input_tokens <= 2000 => call "gpt-4o-mini"
  otherwise => call "claude-sonnet-4"
}

规则:

  • when 从上到下求值,第一个结果为 true 的分支胜出。
  • otherwise 必须存在,且只能有一个。
  • otherwise 后面不能再写 when。
  • 分支里的 action 可以是 call、route 或 switch。

switch ​

按权重随机选择分支:

text
switch {
  weight 80 => call "gpt-4o-mini"
  weight 20 => call "gpt-4o"
}

weight、chance、probability 是等价关键字,数字含义相同。没有 otherwise 时,所有加权分支会按相对权重归一化。上面的例子就是 80% 对 20%。

使用小数概率时,可以用 otherwise 承接剩余概率:

text
switch {
  chance 0.9 => call "gpt-4o-mini"
  otherwise => call "gpt-4o"
}

这表示 gpt-4o-mini 约 90%,gpt-4o 约 10%。只有当加权分支总和小于 1 时,otherwise 才会获得剩余概率。如果权重总和大于或等于 1,随机范围按总权重计算,otherwise 不会在常规情况下被抽中。

规则:

  • 至少需要一个带权重分支。
  • 权重必须是大于 0 的数字。
  • otherwise 可选,且只能有一个。
  • otherwise 后面不能再写带权重分支。
  • 分支里的 action 可以继续嵌套 call、route 或 switch。

parallel 预留 ​

text
parallel {
  call "gpt-4o"
  call "claude-sonnet-4"
} synthesize "gpt-4o-mini"

解析器能识别这个结构,但保存校验会返回 parallel meta model execution is not implemented yet。当前不要在生产配置里使用。

judge 预留 ​

text
judge "gpt-4o-mini" {
  prompt "Return exactly one label: cheap, balanced, strong."

  route {
    when judge.output == "cheap" => call "gpt-4o-mini"
    when judge.output == "strong" => call "claude-sonnet-4"
    otherwise => call "gpt-4o"
  }
}

解析器能识别 judge 模型、可选 prompt 和内部 route,但保存校验会返回 judge meta model execution is not implemented yet。

表达式 ​

表达式只能比较一个变量和一个字面量:

text
request.input_tokens <= 2000
request.has_image == true
request.text contains "invoice"
request.last_user_message matches "(?i)reason|analyze"

支持的运算符:

运算符类型说明
==string、number、bool同类型相等
!=string、number、bool同类型不相等
<number小于
<=number小于等于
>number大于
>=number大于等于
containsstring左侧包含右侧
not_containsstring左侧不包含右侧
starts_withstring左侧以前缀开头
ends_withstring左侧以后缀结尾
matchesstring左侧匹配右侧正则

类型规则:

  • 数值比较要求两侧都是 number。
  • 字符串运算要求两侧都是 string。
  • 布尔值只能用 == 和 !=。
  • 不支持算术、括号、and、or、not。
  • matches 使用 Go 的正则语法,正则无效时运行会报错。

需要组合多个条件时,优先用多层 route 或按顺序拆成多个分支。

运行时变量 ​

请求变量 ​

变量类型说明
request.input_tokensnumber用请求体估算的输入 token 数
request.max_output_tokensnumber从 max_tokens、max_completion_tokens、maxOutputTokens 读取的最大输出 token
request.total_estimated_tokensnumber输入 token 加最大输出 token
request.message_countnumbermessages、contents 或数组形式 input 的项目数量
request.textstring从 input、messages、contents、prompt、system 汇总出的文本
request.last_user_messagestring最后一条 role=user 消息文本
request.systemstring顶层 system,或 role=system/developer 的系统提示文本
request.has_imagebool请求体中是否出现 image、image_url 或 images
request.has_audiobool请求体中是否出现 audio、input_audio 或 audio_url
request.has_toolsbool请求体中是否出现 tools、tool_choice、function_call 或 functions
request.tool_countnumbertools 或 functions 数组长度
request.streambool顶层 stream 布尔值
request.temperaturenumber顶层 temperature 数值

用户和密钥变量 ​

变量类型说明
user.balancenumber当前用户余额
user.idnumber当前用户 ID
user.groupstring当前用户分组名
user.is_adminbool当前用户是否为管理员
api_key.namestring当前 API 密钥名称
api_key.quota_limitnumber当前 API 密钥额度上限,没有上限时为 0
api_key.quota_remainingnumber当前 API 密钥剩余额度,没有正数上限时为 0

预留变量 ​

变量类型当前值
channel.namestring渠道选择前固定为空字符串
judge.outputstring未来 judge action 使用,当前不可执行

完整示例 ​

静态别名 ​

text
call "gpt-4o-mini"

适合把对外模型名和内部真实模型名解耦。

按上下文长度路由 ​

text
route {
  when request.total_estimated_tokens <= 4000 => call "gpt-4o-mini"
  when request.total_estimated_tokens <= 32000 => call "gpt-4o"
  otherwise => call "claude-sonnet-4"
}

多模态和工具调用路由 ​

text
route {
  when request.has_audio == true => call "gpt-4o-audio"
  when request.has_image == true => call "gpt-4o"
  when request.has_tools == true => call "gpt-4o"
  otherwise => call "gpt-4o-mini"
}

文本关键词和正则路由 ​

text
route {
  when request.last_user_message matches "(?i)code|debug|stack trace" => call "claude-sonnet-4"
  when request.text contains "invoice" => call "gpt-4o"
  when request.system starts_with "You are a translator" => call "gpt-4o-mini"
  otherwise => call "gpt-4o-mini"
}

概率 A/B 路由 ​

text
switch {
  weight 95 => call "gpt-4o-mini"
  weight 5 => call "gpt-4o"
}

适合灰度强模型、成本实验或新模型小流量验证。

route 嵌套 switch ​

text
route {
  when request.has_image == true => call "gpt-4o"
  when request.input_tokens > 32000 => call "claude-sonnet-4"
  otherwise => switch {
    weight 90 => call "gpt-4o-mini"
    weight 10 => call "gpt-4o"
  }
}

这类写法适合先处理硬约束,再对普通请求做概率分流。

额度和余额感知 ​

text
route {
  when api_key.quota_remaining < 0.5 => call "gpt-4o-mini"
  when user.balance < 1 => call "gpt-4o-mini"
  when user.group == "enterprise" => call "claude-sonnet-4"
  otherwise => call "gpt-4o"
}

管理员专用强模型 ​

text
route {
  when user.is_admin == true => call "claude-sonnet-4"
  otherwise => call "gpt-4o-mini"
}

小数概率和兜底 ​

text
switch {
  probability 0.70 => call "gpt-4o-mini"
  probability 0.20 => call "gpt-4o"
  otherwise => call "claude-sonnet-4"
}

这里前两个分支合计 0.90,otherwise 获得剩余约 0.10。

保存校验规则 ​

管理接口保存或校验时会检查:

  • DSL 非空。
  • 语法完整,没有未闭合块、非法字符或非法字面量。
  • 根节点必须是一个 action。
  • 引用的模型必须存在。
  • 引用的模型必须是真实模型,不能是另一个元模型。
  • 元模型不能引用自己。
  • route 必须有且只有一个 otherwise。
  • route 的 otherwise 后不能再出现 when。
  • switch 至少有一个带权重分支。
  • switch 权重必须大于 0。
  • switch 不能有重复 otherwise。
  • switch 的 otherwise 后不能再出现带权重分支。
  • parallel 和 judge 当前会被拒绝,因为运行时未实现。
  • 价格不能为负数。
  • billing_mode 只能是 actual 或 meta,空值会按 actual 处理。
  • provider 为空时按 meta 处理,provider_name 为空时按 Meta Module 处理。
  • expose_referenced_models 只影响公开目录是否下发底层模型列表,不影响 DSL 校验和运行。

常见错误:

输入或场景错误
空 DSLDSL is required
route 缺少 otherwiseroute requires an otherwise branch
route 里 otherwise 后继续写 whenwhen branch cannot follow otherwise
switch 没有权重分支switch requires at least one weighted branch
switch 权重为 0switch weight must be a positive number
call "not-a-model"Referenced model not found: not-a-model
元模型 meta-smart 内写 call "meta-smart"Meta model cannot reference itself
引用另一个元模型Meta model cannot reference another meta model yet: ...
保存 parallelparallel meta model execution is not implemented yet
保存 judgejudge meta model execution is not implemented yet

安全边界 ​

DSL 不能读写文件、读取环境变量、访问网络、查询数据库、执行 shell、定义函数、定义循环或递归调用元模型。它只能读取内置运行时变量,并返回一个真实模型名。

API 密钥行为需要特别注意:

  • 密钥的 allowed_models 先校验元模型名。
  • 元模型解析到真实模型后,不会再次用该密钥的 allowed_models 校验真实模型名。
  • 这样管理员可以只暴露一个受控的元模型名,而不直接暴露所有底层模型名。
  • 真实模型仍必须能通过当前用户的渠道绑定、渠道启用状态、模型启用状态和余额额度检查。

推荐工作流 ​

  1. 先在模型管理中确认所有真实模型已经存在并启用。
  2. 新建元模型名,例如 meta-smart。
  3. 选择计费模式。默认使用 actual 更容易和真实模型成本对齐。
  4. 编写 DSL,先用 POST /api/meta-models/validate 或前端校验按钮验证。
  5. 保存并启用元模型。
  6. 用普通 API 请求调用该元模型名,查看用量日志和真实上游表现。
  7. 对概率路由做灰度时,先使用小比例权重,并持续观察成本和错误率。

形式文法 ​

text
program          = { option separator } action { separator } ;
option           = "option" identifier "=" literal ;
action           = call | route | switch | parallel | judge ;
call             = "call" string ;
route            = "route" "{" { when_branch separator } otherwise_branch { separator } "}" ;
when_branch      = "when" expression "=>" action ;
otherwise_branch = "otherwise" "=>" action ;
switch           = "switch" "{" { switch_branch separator } [ switch_otherwise separator ] "}" ;
switch_branch    = switch_weight number "=>" action ;
switch_weight    = "weight" | "chance" | "probability" ;
switch_otherwise = "otherwise" "=>" action ;
parallel         = "parallel" "{" call { separator call } { separator } "}" [ "synthesize" string ] ;
judge            = "judge" string "{" [ "prompt" string separator ] route { separator } "}" ;
expression       = identifier operator literal ;
operator         = "==" | "!=" | "<" | "<=" | ">" | ">="
                 | "contains" | "not_contains" | "starts_with" | "ends_with" | "matches" ;
literal          = string | number | boolean ;
boolean          = "true" | "false" ;
separator        = ";" | "," ;

基于 MIT 协议发布(社区版)