本文档描述 onr(open-next-router)使用的 Provider 配置 DSL。
本仓库的 DSL 主入口通常是 config/onr.conf,而这个文件一般会再 include 进 config/providers/*.conf。
默认情况下 ONR 会加载 config/onr.conf,而这个文件通常会先 include modes/*.conf;,再 include providers;,把全局预设和 provider 配置一起纳入进来。
如果需要,也仍然可以通过配置项 providers.dir 或环境变量 ONR_PROVIDERS_DIR 强制走目录模式。
- 1. 基本约定
- 2. include(复用片段)
- 3. 顶层结构
- 4. match 规则(选择逻辑)
- 5. phase/block 列表(defaults 与 match 中都可写)
- 6. 可用的 Context(表达式变量)
- 7. 指令参考(nginx 风格)
- 8. 内置变量参考
- 一个 provider 一个文件:
config/providers/<provider>.conf - 文件名必须与 provider 名一致:例如
config/providers/openai.conf内必须是provider "openai" { ... } - provider 名匹配不区分大小写:匹配时会统一转小写;配置中建议使用小写
- 分号
;必须写:所有语句以;结束 - 只有 block 才使用花括号
{}:指令本身不使用{}(nginx 风格) - 推荐书写风格:每行一条指令(便于 diff 与可读性)
- 支持注释:
#、//
推荐格式示例:
upstream_config {
base_url = "https://api.example.com";
}
match api = "chat.completions" {
upstream {
set_path "/v1/chat/completions";
set_query stream "true";
}
response {
resp_passthrough;
}
}语法:
include relative/or/absolute/path.conf;
include providers;
include providers/*.conf;- 相对路径以「当前文件所在目录」为基准
- 如果 include 的目标是一个目录,会按文件名字典序展开该目录下全部
*.conf - 支持 glob 模式,展开结果同样按字典序处理
- 目前仍兼容带引号写法,但更推荐使用 nginx 风格的不带引号写法
- 最大递归深度:20
- 检测循环 include:出现循环会报错并跳过该 provider 文件加载
syntax "next-router/0.1";
provider "openai" {
defaults { ... }
match api = "chat.completions" stream = true { ... }
match api = "chat.completions" stream = false { ... }
match api = "embeddings" { ... }
}目前仅用于占位与版本标识(syntax "next-router/0.1";),解析器不会做强校验,但建议保留。
语法:
provider "<name>" { ... }语法(支持的条件):
match api = "<api-name>" stream = true { ... }
match api = "<api-name>" stream = false { ... }
match api = "<api-name>" { ... }- 当前仅解析
api与stream match api必须是下面列出的受支持 API 之一;未知 API 会在校验/加载阶段直接报错- 只会命中第一条匹配的 match(按文件中出现顺序)
- 建议:更具体的规则放前面,更泛的规则放后面
- 当某个 provider 被选中(DSL enabled)但 没有任何 match 命中 时,请求会直接被拒绝(HTTP 400),避免静默回退导致行为不透明。
当前支持的 api(与 OpenAI 风格端点对齐):
completionschat.completionsresponsesclaude.messagesembeddingsgemini.generateContent(Gemini 原生:POST /v1beta/models/{model}:generateContent)gemini.streamGenerateContent(Gemini 原生:POST /v1beta/models/{model}:streamGenerateContent?alt=sse)images.generationsimages.editsaudio.speechaudio.transcriptionsaudio.translations
可用 block:
upstream_config { ... }auth { ... }request { ... }upstream { ... }response { ... }error { ... }metrics { ... }
合并规则(非常重要):
defaults先应用,再应用命中的match,但不是所有 phase 都按同一种方式合并metrics、request、balance中的大多数单值字段按“字段级继承 + 显式覆盖”处理:match里写了的字段覆盖defaultsmatch里没写的字段继续继承defaults- 例如:
defaults.metrics写了finish_reason_extract openai_chat_completions;,某个match.metrics只写usage_extract custom;,则最终同时生效usage_extract custom;与finish_reason_extract openai_chat_completions;
auth/request里的 header 操作,以及response/error里的json_*、sse_json_del_if等列表型指令,通常按“defaults 在前,match 追加在后”处理;若存在同一对象上的后续冲突,通常以后出现的指令结果为准response/error里的单值指令(如主op/mode)仍可被match覆盖upstream不按通用“整块 merge”处理:defaults.upstream_config主要提供base_url默认值;具体路由动作(如set_path、query 改写)来自命中的match.upstreammodels在 v0.1 只有defaults,没有match级覆盖- 总结:不要把
defaults/match理解成“整个 block 替换”,应以各 phase 的实际合并行为为准
phase 边界规则(非常重要):
request负责“上游请求内容的构造”,包括请求体变换、header 操作,以及供后续路由表达式使用的 model 映射。upstream只负责“上游目标的路由”,也就是 path、query、以及与 base_url 相关的目标选择。- 不要把 header 或 body 的变更语义放进
upstream。
语法:
upstream_config {
base_url = "https://api.example.com";
}说明:
base_url是必填,同时也是默认值- 若渠道(DB)配置了
base_url,则优先使用渠道配置;只有当渠道base_url为空时,才使用这里的默认值
用途:声明鉴权头的“形式”。token/value 不需要写在 conf 中,统一取渠道的 key(等价于 $channel.key)。
auth { auth_bearer; }效果:Authorization: Bearer <channel.key>
auth { auth_header_key "x-api-key"; }
auth { auth_header_key api_key; }效果:<Header-Name>: <channel.key>
auth {
oauth_mode openai; # openai|gemini|qwen|claude|iflow|antigravity|kimi|custom
oauth_refresh_token $channel.key;
auth_oauth_bearer;
}oauth_mode:开启运行时 OAuth token 交换。auth_oauth_bearer;:注入Authorization: Bearer <oauth.access_token>。- 内置 mode 会提供默认 token endpoint 与请求格式。
custom模式必须显式配置oauth_token_url,并至少有一条oauth_form。- 可选覆盖项:
oauth_token_url <expr>;oauth_client_id <expr>;oauth_client_secret <expr>;oauth_refresh_token <expr>;(默认$channel.key)oauth_scope <expr>;oauth_audience <expr>;oauth_method <GET|POST>;oauth_content_type <form|json>;oauth_form <key> <expr>;(可多条)oauth_token_path <jsonpath>;(默认$.access_token)oauth_expires_in_path <jsonpath>;(默认$.expires_in)oauth_token_type_path <jsonpath>;(默认$.token_type)oauth_timeout_ms <int>;(默认5000)oauth_refresh_skew_sec <int>;(默认300)oauth_fallback_ttl_sec <int>;(默认1800)
v0.1:
requestphase 负责“上游请求内容的构造”,同时承担请求头操作、请求体(JSON)变换,以及供后续路由表达式使用的 model 映射。
request {
set_header "x-trace-id" "trace-123";
set_header "x-foo" concat("a-", $request.model_mapped);
}说明:
- 支持多条;按顺序执行
- 同一 header 多次
set_header:后写的覆盖先写的
request {
del_header "x-remove-me";
}说明:
- 支持多条;按顺序执行
defaults的操作在前,match的操作追加在后,因此 match 更容易覆盖 defaults(例如 defaults 先 set,match 再 del)
request {
pass_header "anthropic-beta";
}说明:
- 将原始客户端请求中的某个 header 复制到上游请求。
- 如果源 header 不存在,则为 no-op。
- 支持多条;与
set_header、del_header一样按声明顺序执行。 - 如果同一个 header 先
pass_header,后续又被set_header或del_header,则以后出现的指令结果为准。
request {
filter_header_values "anthropic-beta" "context-1m-*" "fast-mode-*";
filter_header_values "x-feature-flags" "exp-*" "debug" separator=";";
}说明:
- 用于过滤某个上游请求 header 中的“列表型值”。
- 语法:
filter_header_values <header> <pattern>... [separator="<sep>"]; - 推荐风格:pattern 统一写成连续位置参数,不要使用逗号分隔参数的写法。
- 默认分隔符是
,。 - 运行时行为:
- 读取当前上游请求 header 值
- 按
separator分割 - 对每一项执行
strings.TrimSpace - 删除匹配任一 pattern 的项
- 如果结果为空,则删除整个 header
- 否则将剩余项重新拼接
- 输出格式会被规范化:
- 如果
separator == ",",使用", "连接 - 否则使用
"<sep> "连接,例如"; "
- 如果
- pattern 使用简单的
*通配语义;不支持正则表达式。
request {
model_map "gpt-4o-mini" "gpt4o-mini-prod";
model_map "gpt-4o-mini" $request.model;
}说明:
- 用途:把
$request.model映射为$request.model_mapped,供set_path/set_query/set_header等表达式使用(例如 Azure deployments 路径拼接) - 支持多条;按“from 模型名”精确匹配
- 同一 from 多次出现:后写的覆盖先写的
request {
model_map_default $request.model;
}说明:
- 当没有任何
model_map <from> ...;命中时,使用该默认表达式作为$request.model_mapped - 如未配置该指令:
$request.model_mapped默认等于$request.model
json_set / json_replace / json_set_if_absent / json_del / json_rename / json_wrap_input_text / json_set_header_values / json_filter_values / json_del_with_condition(可多条)
request {
json_set "$.stream" true;
json_replace "$.model" $request.model;
json_set_if_absent "$.instructions" "";
json_set "$.user" "alice";
json_rename "$.max_tokens" "$.max_completion_tokens";
json_wrap_input_text "$.input";
json_set_header_values "$.anthropic_beta" "anthropic-beta";
json_filter_values "$.anthropic_beta" "computer-use-2025-01-24";
after_req_map {
json_set "$.anthropic_version" "bedrock-2023-05-31";
json_del_with_condition "$.tools" "type" "web_search*" "web_fetch*";
}
json_del "$.tools";
}说明:
- 用途:对“已生成的上游请求 JSON”做轻量变换(在旧 adaptor 的
ConvertRequest之后执行) - JSONPath(v0.1)仅支持对象路径:
$.a.b.c(不支持数组下标[]) json_set的值表达式支持:true/false/null、整数、字符串字面量、变量/concatjson_set:设置字段;不存在的对象路径会自动创建json_replace:仅当目标路径已存在时替换字段;路径不存在时为 no-op,不会创建缺失对象或字段json_set_if_absent:仅当路径不存在时写入;已存在值会保留json_wrap_input_text:当路径值是字符串时,将其包装成 OpenAI Responsesinputmessage 列表;路径不存在或值已经是数组时为 no-op,其他类型会报错json_set_header_values:从原始下游用户请求 header 中读取条目并写成 JSON 字符串数组,不读取 request header 规则准备发送给上游的 header。默认按逗号拆分,可用separator="<sep>"覆盖;不接受额外过滤参数,需要过滤时在后面显式配置json_filter_valuesjson_filter_values:过滤已有 JSON 字符串数组,只保留允许的值或通配符匹配项json_del_with_condition:当对象字段匹配允许值或通配符时,删除该对象或数组中的匹配对象after_req_map { ... }:在req_map之后执行内部 JSON 操作;如果没有配置req_map,则在普通请求 JSON 操作之后执行
request { req_map <mode>; }内置请求映射(非流式 JSON 变换)。若写多条指令,最后一条生效。
v0.1 内置:
openai_chat_to_openai_responses:OpenAI-compatiblechat.completions请求 JSON → OpenAI/responses请求 JSONanthropic_to_openai_chat:Anthropic/v1/messages请求 JSON → OpenAIchat.completions请求 JSONgemini_to_openai_chat:GeminigenerateContent请求 JSON → OpenAIchat.completions请求 JSONopenai_chat_to_gemini_generate_content:OpenAIchat.completions请求 JSON → GeminigenerateContent请求 JSONopenai_chat_to_anthropic_messages:OpenAIchat.completions请求 JSON → Anthropic/v1/messages请求 JSON
upstream phase 只负责上游目标路由。
它应只承载 path / query / base_url 相关的选择逻辑,不应承载请求头或请求体的变更语义。
upstream {
set_path "/v1/chat/completions";
}说明:
- 设置上游请求路径(会覆盖原路径)
upstream {
set_query "api-version" "2024-10-01";
set_query stream "true";
# 内置变量不要加引号(字符串字面量内不会做变量展开):
# ✅ set_query key $channel.key;
# ❌ set_query key "$channel.key"; # 会变成普通字符串 "$channel.key"
}说明:
- 支持多条
- 同一 key 多次
set_query:后写的覆盖先写的 - **重要:**内置变量(例如
$channel.key、$request.model_mapped)仅在作为“裸表达式”使用时才会展开;如果放进双引号里,会被当作普通字符串字面量,不会展开。
upstream {
del_query "foo";
del_query bar;
}说明:
- 支持多条
- 执行顺序:先执行所有
del_query,再执行所有set_query
该 phase 会选择响应策略。对 resp_passthrough / resp_map / sse_parse 这类策略指令,如写多条,最后一条生效。sse_collect 是独立的非流式前置聚合步骤,可以后接 resp_map。
response { resp_passthrough; }用途:上游响应已经是 OpenAI-compatible,直接透传。
response { resp_map <mode>; }用途:非流式响应映射(例如把某供应商 JSON 映射为 OpenAI chat.completions)。
response { sse_parse <mode>; }用途:流式 SSE 映射(例如把某供应商 SSE 映射为 OpenAI stream chunks)。
response { sse_collect <mode>; }用途:把上游 text/event-stream 聚合成同上游协议的非流式 JSON 对象,适用于下游非流式、但上游返回 SSE 的路由。
sse_collect不做跨协议转换。- 聚合后可继续执行可选的
resp_map和响应 JSON 操作。 resp_map不是必需的;没有resp_map时,ONR 直接返回聚合后的同协议 JSON。sse_collect不能和sse_parse或resp_passthrough组合使用。sse_collect只允许用于stream = false的 match。
支持的模式:
openai_responses:OpenAI/Azure Responses SSE → Responses JSONanthropic_messages:Anthropic Messages SSE → Message JSONgemini_generate_content:GeministreamGenerateContentSSE →GenerateContentResponseJSON
mode 的可用值取决于内置实现;当前 v0.1 已内置:
anthropic_to_openai_chat(resp_map):Anthropic/v1/messagesJSON → OpenAIchat.completionsJSONanthropic_to_openai_chunks(sse_parse):Anthropic/v1/messagesSSE → OpenAI stream chunksopenai_to_anthropic_messages(resp_map):OpenAI-compatiblechat.completionsJSON → Anthropic/v1/messagesJSONopenai_to_anthropic_chunks(sse_parse):OpenAI-compatiblechat.completionsSSE → Anthropic/v1/messagesSSEopenai_to_gemini_chat/openai_to_gemini_generate_content(resp_map):OpenAI-compatiblechat.completionsJSON → GeminigenerateContentJSONgemini_to_openai_chat(resp_map):GeminigenerateContentJSON → OpenAIchat.completionsJSONopenai_to_gemini_chunks(sse_parse):OpenAI-compatiblechat.completionsSSE → Gemini SSEgemini_to_openai_chat_chunks(sse_parse):Gemini SSE → OpenAIchat.completionsSSE chunksopenai_responses_to_openai_chat(resp_map):OpenAI/Azure/responsesJSON → OpenAIchat.completionsJSONopenai_responses_to_openai_chat_chunks(sse_parse):OpenAI/Azure/responsesSSE → OpenAIchat.completionsSSE chunks
这些指令会对下游响应体做**尽力而为(best-effort)**的 JSON 变换:
response {
json_del "$.usage";
json_set "$.foo" "bar";
json_replace "$.model" $request.model;
json_set_if_absent "$.bar" "baz";
json_rename "$.a" "$.b";
}语义:
- 非流式 JSON:对整个响应体的 JSON 对象做变换
- 流式 SSE(
text/event-stream):对每个 SSE event 拼接后的data:JSON 对象做变换 - 非 JSON / 非对象负载:保持原样透传
- 执行顺序:按配置块内出现顺序依次执行
json_set会创建缺失路径;json_replace只替换已存在路径,适合替换上游响应中的model字段而不污染其它事件。- 流式 SSE 中,
json_set、json_replace、json_set_if_absent、json_del、json_rename可追加event="<name|name2>",按 SSEevent:名过滤执行。 - response JSON 操作可追加
max_count=<n>限制单条指令在一次响应处理周期内的最大生效次数;默认max_count=0表示不限次数。
response {
resp_passthrough;
json_replace "$.message.model" $request.model event="message_start" max_count=1;
json_replace "$.response.model" $request.model event="response.created|response.completed|response.incomplete";
}event="..."只影响 SSE JSON event;非流式 JSON 响应没有 event 上下文,带 event 的响应 JSON 操作会跳过。max_count按单条指令计数:非流式 JSON 最多处理一次对象;SSE 会跨整个 stream 累计。只有实际修改成功才计数,例如json_replace路径不存在时不计数。- 响应 JSON 操作不支持
event_optional=true;需要兼容缺失event:的上游时,应使用不带 event 的指令或在上游映射阶段补齐 event。
限制(v0.1):
- JSONPath 仅支持对象路径
$.a.b.c(不支持数组下标)
response {
# 若 SSE event 的 JSON payload 满足 $.type == "message_delta",则仅在该 event 中删除 $.usage
sse_json_del_if "$.type" "message_delta" "$.usage";
}- 仅对
text/event-stream生效 - 条件要求
<cond_path>取到的值是字符串,且必须完全等于<equals> - 规则按顺序执行,且在
json_*响应操作之前执行
语法:
error { error_map openai; }说明:
- 允许的
mode(加载期白名单校验):openai/common/passthrough - 如写多条,最后一条生效
passthrough:跳过错误归一化,直接把上游错误响应透传给客户端
usage_mode "shared_openai" {
usage_fact input token path="$.usage.input_tokens";
usage_fact output token path="$.usage.output_tokens";
}说明:
usage_mode是顶层指令,用来给整个 providers 配置集合声明一个可复用的全局 usage 预设。- 推荐把这类全局 DSL 预设单独放在
config/modes/usage_modes.conf这类文件里,再由config/onr.conf通过include modes/*.conf;在include providers;之前先引入。 - 它可以单独放在一个没有
provider {}的.conf文件里;这类文件在config/providers/中是合法的,但不会出现在 provider 列表里。 usage_mode块内支持和metrics相同的 usage 指令:usage_extract、usage_root、usage_fact、*_tokens_path、*_tokens_expr。usage_mode内部也可以继续通过usage_extract <other_mode>;引用另一个usage_mode,用于组合更大的预设;递归引用会报错。- 在同一个 providers 目录或合并后的 providers 文件中,
usage_mode名字是全局唯一的;重名会在校验期报错。 - 本仓库默认的
config/modes/usage_modes.conf会定义openai_chat_completions、openai_prompt_completion、openai_responses、openai_responses_stream、anthropic_messages、anthropic_messages_stream、gemini_generate_content、gemini_generate_content_stream这类按 API / 路径拆分的全局usage_mode预设;如果你在 DSL 里声明同名usage_mode,就会覆盖这份默认预设。 - 执行时,
usage_extract <custom_name>;会先解析到对应的usage_mode,再编译成与 builtin mode 相同的最终 usage plan。
finish_reason_mode "anthropic_messages_stream" {
finish_reason_path "$.delta.stop_reason";
finish_reason_path "$.message.stop_reason" fallback=true;
}说明:
finish_reason_mode是顶层指令,用来给整个 providers 配置集合声明一个可复用的全局 finish_reason 预设。- 推荐把这类全局 DSL 预设单独放在
config/modes/finish_reason_modes.conf这类文件里,再由config/onr.conf通过include modes/*.conf;在include providers;之前先引入。 - 它也可以单独放在一个没有
provider {}的.conf文件里。 finish_reason_mode块内支持和metrics中 finish reason 提取相同的指令:finish_reason_extract、finish_reason_path。finish_reason_mode内部也可以继续通过finish_reason_extract <other_mode>;引用另一个finish_reason_mode,用于组合更大的预设;递归引用会报错。- 在同一个 providers 目录或合并后的 providers 文件中,
finish_reason_mode名字是全局唯一的;重名会在校验期报错。 - 本仓库默认的
config/modes/finish_reason_modes.conf会定义openai_chat_completions、openai_completions、openai_responses、anthropic_messages、anthropic_messages_stream、gemini_generate_content、gemini_generate_content_stream这类更具体的全局finish_reason_mode预设;如果你在 DSL 里声明同名finish_reason_mode,就会覆盖这份默认预设。
models_mode "openai" {
path "/v2/models";
}说明:
models_mode也可以作为顶层指令,用来给整个 providers 配置集合声明一个可复用的全局 models 预设。- 推荐把这类全局 DSL 预设单独放在
config/modes/models_modes.conf这类文件里,再由config/onr.conf通过include modes/*.conf;在include providers;之前先引入。 - 它也可以单独放在一个没有
provider {}的.conf文件里。 models_mode块内支持和modelsphase 相同的指令:models_mode、method、path、id_path、id_regex、id_allow_regex、set_header、del_header。models_mode内部也可以继续通过models_mode <other_mode>;引用另一个models_mode,用于组合更大的预设;递归引用会报错。- 在同一个 providers 目录或合并后的 providers 文件中,
models_mode名字是全局唯一的;重名会在校验期报错。 - 本仓库默认的
config/modes/models_modes.conf会定义openai和gemini这几个全局models_mode预设;如果你在 DSL 里声明同名models_mode,就会覆盖这份默认预设。
balance_mode "openai" {
usage_path "/v9/dashboard/billing/usage";
}说明:
balance_mode也可以作为顶层指令,用来给整个 providers 配置集合声明一个可复用的全局 balance 预设。- 推荐把这类全局 DSL 预设单独放在
config/modes/balance_modes.conf这类文件里,再由config/onr.conf通过include modes/*.conf;在include providers;之前先引入。 - 它也可以单独放在一个没有
provider {}的.conf文件里。 balance_mode块内支持和balancephase 相同的指令:balance_mode、method、path、balance_path、balance_expr、used_path、used_expr、balance_unit、subscription_path、usage_path、set_header、del_header。balance_mode内部也可以继续通过balance_mode <other_mode>;引用另一个balance_mode,用于组合更大的预设;递归引用会报错。- 在同一个 providers 目录或合并后的 providers 文件中,
balance_mode名字是全局唯一的;重名会在校验期报错。 - 本仓库默认的
config/modes/balance_modes.conf会定义openai这个全局balance_mode预设;如果你在 DSL 里声明同名balance_mode,就会覆盖这份默认预设。
usage_mode "shared_openai" {
usage_extract custom;
usage_fact input token path="$.usage.input_tokens";
usage_fact output token path="$.usage.output_tokens";
}
provider "openai" {
defaults {
metrics { usage_extract shared_openai; }
}
}
metrics { usage_extract openai_chat_completions; }
metrics { usage_extract anthropic_messages; }
metrics { usage_extract gemini_generate_content; }
metrics { usage_extract custom; }说明:
custom:使用受限 JSONPath 子集从响应 JSON 中提取(可选加减表达式,见下)- 其他任意 mode 名:用户自定义的全局
usage_mode - 仓库默认配置现在只保留更具体的 API / 路径级预设,例如
openai_chat_completions、openai_prompt_completion、openai_responses、openai_responses_stream、anthropic_messages、anthropic_messages_stream、gemini_generate_content、gemini_generate_content_stream。 openai/anthropic/gemini这类泛化名字不再是特殊的 DSL 内置usage_extractmode;如果你想继续用这些名字,需要自己显式定义对应的全局usage_mode预设。- 用户自定义
usage_mode也会先完成解析,再编译进同一套统一 fact-based 执行计划。 - 在
metrics里,如果声明了usage_fact、*_tokens_path或*_tokens_expr,但没有写usage_extract,ONR 会自动按usage_extract custom;处理。仅声明usage_root不会触发 usage 提取,因为它只是给usage_fact提供读取根对象。 - 若在代码侧做 introspection,建议区分三层:
- declared:用户显式写的
usage_fact - compiled:最终真正参与执行的统一 usage plan
- declared:用户显式写的
旧的泛化 provider mode 迁移到 custom 时,可参考以下近似写法:
openai等价思路:
metrics {
usage_extract custom;
usage_fact input token path="$.usage.prompt_tokens";
usage_fact input token path="$.usage.input_tokens" fallback=true;
usage_fact output token path="$.usage.completion_tokens";
usage_fact output token path="$.usage.output_tokens" fallback=true;
usage_fact cache_read token path="$.usage.prompt_tokens_details.cached_tokens";
usage_fact cache_read token path="$.usage.input_tokens_details.cached_tokens" fallback=true;
usage_fact cache_read token path="$.usage.cached_tokens" fallback=true;
# 可选:仅当上游真实返回分模态 cached token 时配置。
usage_fact cache_read token path="$.usage.cache_details.text.cached_tokens" attr.modality="text";
usage_fact cache_read token path="$.usage.cache_details.image.cached_tokens" attr.modality="image";
usage_fact cache_read token path="$.usage.cache_details.audio.cached_tokens" attr.modality="audio";
}anthropic等价思路:
metrics {
usage_fact input token path="$.usage.input_tokens";
usage_fact input token path="$.usage.cache_read_input_tokens";
usage_fact input token path="$.usage.cache_creation_input_tokens";
usage_fact output token path="$.usage.output_tokens";
usage_fact cache_read token path="$.usage.cache_read_input_tokens";
usage_fact cache_write token path="$.usage.cache_creation.ephemeral_5m_input_tokens" attr.ttl="5m";
usage_fact cache_write token path="$.usage.cache_creation.ephemeral_1h_input_tokens" attr.ttl="1h";
usage_fact cache_write token path="$.usage.cache_creation_input_tokens" fallback=true;
}gemini等价思路:
metrics {
usage_extract custom;
usage_fact input token path='$.usageMetadata.promptTokensDetails[?(@.modality=="TEXT")].tokenCount';
usage_fact input token path="$.usageMetadata.promptTokenCount" fallback=true;
usage_fact input.image token path='$.usageMetadata.promptTokensDetails[?(@.modality=="IMAGE")].tokenCount';
usage_fact input.video token path='$.usageMetadata.promptTokensDetails[?(@.modality=="VIDEO")].tokenCount';
usage_fact input.audio token path='$.usageMetadata.promptTokensDetails[?(@.modality=="AUDIO")].tokenCount';
# 可选:仅当上游真实返回分模态 cached token 时配置。
usage_fact cache_read token path='$.usageMetadata.cacheTokensDetails[?(@.modality=="TEXT")].tokenCount' attr.modality="text";
usage_fact cache_read token path='$.usageMetadata.cacheTokensDetails[?(@.modality=="IMAGE")].tokenCount' attr.modality="image";
usage_fact cache_read token path='$.usageMetadata.cacheTokensDetails[?(@.modality=="AUDIO")].tokenCount' attr.modality="audio";
usage_fact cache_read token path='$.usageMetadata.cacheTokensDetails[?(@.modality=="VIDEO")].tokenCount' attr.modality="video";
usage_fact output token path="$.usageMetadata.candidatesTokenCount";
usage_fact output token path="$.usageMetadata.thoughtsTokenCount";
usage_fact output.image token path='$.usageMetadata.candidatesTokensDetails[?(@.modality=="IMAGE")].tokenCount';
}注意:
gemini:当前默认预设行为已经可以用custom配置完整平替;input token通常优先取TEXT模态,再 fallback 到promptTokenCountanthropic:ONR 现在将input视为包含 cache 的有效输入总量,因此cache_read_input_tokens与cache_creation_input_tokens也应并入inputopenai:上述配置只覆盖核心 token / cache 提取;图片、音频、tool usage 以及分模态 cache 等 API-specific supplemental facts 仍需额外显式写usage_fact- 分模态 cache 复用现有
cache_read token维度,并通过attr.modality="text|image|audio|video"标识真实模态。只有当上游明确返回分模态 cached token 时才应配置这些字段;不要把一个总 cached token 字段按比例拆分后上报为分模态事实。 - Gemini 的
candidatesTokenCount是候选输出总量。下游计费应将output.image token等分模态输出事实视为普通 output 的组成分量,避免同一批 token 被重复收费。 total_tokens默认会由input + output自动聚合;通常不建议再显式配置total_tokens_expr,避免引入多个事实源- 当前 Gemini 原生 usage 字段按驼峰命名处理:
usageMetadata.promptTokenCount/candidatesTokenCount/thoughtsTokenCount/totalTokenCount
Anthropic 流式场景的 custom 写法示意:
metrics {
usage_fact input token path="$.message.usage.input_tokens" event="message_start";
usage_fact input token path="$.message.usage.cache_read_input_tokens" event="message_start";
usage_fact input token path="$.message.usage.cache_creation_input_tokens" event="message_start";
usage_fact input token path="$.usage.cache_read_input_tokens" event="message_delta";
usage_fact input token path="$.usage.cache_creation_input_tokens" event="message_delta";
usage_fact output token path="$.usage.output_tokens" event="message_delta";
usage_fact cache_read token path="$.message.usage.cache_read_input_tokens" event="message_start";
usage_fact cache_read token path="$.usage.cache_read_input_tokens" event="message_delta";
usage_fact cache_write token path="$.message.usage.cache_creation.ephemeral_5m_input_tokens" attr.ttl="5m" event="message_start";
usage_fact cache_write token path="$.usage.cache_creation.ephemeral_5m_input_tokens" attr.ttl="5m" event="message_delta";
usage_fact cache_write token path="$.message.usage.cache_creation.ephemeral_1h_input_tokens" attr.ttl="1h" event="message_start";
usage_fact cache_write token path="$.usage.cache_creation.ephemeral_1h_input_tokens" attr.ttl="1h" event="message_delta";
usage_fact cache_write token path="$.message.usage.cache_creation_input_tokens" fallback=true event="message_start";
usage_fact cache_write token path="$.usage.cache_creation_input_tokens" fallback=true event="message_delta";
}- 这类配置可以覆盖 Anthropic stream 的主要 usage 事件
event="..."可以把usage_fact限制在指定的 SSEevent:名上,只在流式提取时生效event_optional=true可与event="..."搭配使用:如果上游没有返回event:,这条规则会退化为普通 chunk 匹配;如果有event:,仍然必须匹配指定名字- 相比旧的泛化
anthropicmode,主要差异在于配置更长、更容易漏掉某一种事件路径
OpenAI supplemental facts 的 custom 补充示意:
# responses: web search tool calls
metrics {
usage_extract custom;
usage_fact input token path="$.usage.input_tokens";
usage_fact output token path="$.usage.output_tokens";
usage_fact server_tool.web_search call count_path="$.output[*]" type="web_search_call" status="completed";
}
# images.generations
metrics {
usage_extract custom;
usage_fact input token path="$.usage.input_tokens";
usage_fact output token path="$.usage.output_tokens";
usage_fact image.generate image count_path="$.data[*]";
}
# audio.speech
metrics {
usage_extract custom;
usage_fact audio.tts second source=derived path="$.audio_duration_seconds";
}- 旧的泛化 OpenAI 行为本质上仍然可以用
usage_fact补齐 - 但这些规则与具体 API 强相关,所以不像 Gemini/Anthropic 核心 token 提取那样能用一小段通用配置统一覆盖
metrics {
usage_extract custom;
input_tokens_path "$.usage.input_tokens";
output_tokens_path "$.usage.output_tokens";
cache_read_tokens_path "$.usage.cache_read_input_tokens";
cache_write_tokens_path "$.usage.cache_creation_input_tokens";
}支持的 JSONPath 子集:
$.a.b.c$.items[0].x$.items[*].x(对数组中的数值求和)$.items[?(@.field=="VALUE")].x(对过滤后命中的数值求和)
说明:
- 当前 filter 仅支持数组过滤
- 当前仅支持单条件等值匹配:
== - 当前仅支持字符串字面量比较
- 例如:
$.usageMetadata.promptTokensDetails[?(@.modality=="AUDIO")].tokenCount
- 当路径写在 DSL 双引号字符串里时,filter 里的内部双引号需要转义,例如:
usage_fact input.audio token path='$.usageMetadata.promptTokensDetails[?(@.modality=="AUDIO")].tokenCount';多条与覆盖规则:
- 这些字段都是“可选覆盖项”,同字段重复出现时以后者为准
metrics {
usage_extract custom;
usage_root path="$.usage";
usage_fact input token path="$.input_tokens";
usage_fact output token path="$.output_tokens";
}usage_root用于先从响应 JSON / SSE event JSON 中提取 usage 子树。- 配置了
usage_root后,未显式写source的usage_fact默认从合并后的 usage 子树读取;未配置usage_root时仍兼容旧行为,默认从原始response读取。 - 可以配置多条
usage_root,命中的 usage 对象会合并到同一个结构里,后续非零字段可补齐或覆盖前面的零值字段。 - 流式提取时,ONR 会先在每个 chunk 中合并
usage_root,等 stream 结束后再从合并结果执行默认 source /source=usage的usage_fact;显式source=response/request/derived的事实仍在 chunk 阶段按自己的 source 与 event 执行。 event="a|b"可用于匹配多个 SSE 事件名。event_optional=true可选,需要与event="..."一起使用,用于兼容上游有时不带 SSEevent:的情况。usage_root不支持name,统一合并为一个 usage 对象。- 需要继续从原始响应读取的事实应显式写
source="response",例如:
usage_fact server_tool.web_search call source="response" count_path="$.output[*]" type="web_search_call" status="completed";metrics {
usage_extract custom;
usage_root path="$.usage";
usage_fact input token path="$.input_tokens";
usage_fact output token path="$.output_tokens";
usage_fact cache_read token path="$.cache_read_input_tokens";
usage_fact cache_read token path="$.cache_details.image_cached_tokens" attr.modality="image";
usage_fact cache_write token path="$.cache_creation.ephemeral_5m_input_tokens" attr.ttl="5m";
usage_fact cache_write token path="$.cache_creation.ephemeral_1h_input_tokens" attr.ttl="1h";
usage_fact cache_write token path="$.cache_creation_input_tokens" fallback=true;
}usage_fact用于把不同来源的用量统一抽成规范化事实- 在
metrics里,只写usage_fact而省略usage_extract,等价于usage_extract custom; - 命名 preset 现在也会先编译成同一套内部 fact-based 执行计划,再叠加显式
usage_fact - 第一批支持
path/count_path/sum_path/expr event="..."可选,用于把usage_fact限制在指定 SSE 事件,例如message_start/message_delta;也支持response.completed|response.incomplete这种多事件写法event_optional=true可选,需要与event="..."一起使用,用于兼容“有时有 event、有时没有 event”的上游attr.ttl用于区分 Anthropic 的5m/1hcache writeattr.modality="text|image|audio|video"可用于cache_read token,表示上游真实返回的分模态 cached token。Relay 会用它生成openai_cache/gemini_cache这类 provider cache detail 字段。- 同一
dimension + unit可声明多条usage_fact;所有命中的非 fallback 规则会按声明顺序累计求和 fallback=true用于在更具体的事实不存在时回退到总量字段source在配置了usage_root时默认是usage,否则默认是responsesource=usage表示从usage_root合并后的 usage 对象读取;source=response表示从当前响应 JSON 提取;source=request表示从当前请求 JSON 提取;source=derived表示从 ONR 运行时派生出的聚合结果提取- 当前不支持除
usage/response/request/derived之外的其他source dimension是 registry 中的扁平命名空间字符串,.只是名称的一部分,不表示嵌套结构- 当前支持的
dimension与dimension + unit固定 registry,完整列表见后文usage_fact指令说明 - 仓库内置的 OpenAI API-specific 预设通常会补齐这些 canonical facts:
openai_images_generations -> image.generate imageopenai_images_edits -> image.edit imageopenai_audio_transcriptions -> audio.stt secondopenai_audio_translations -> audio.translate secondopenai_audio_speech -> audio.tts secondopenai_responses -> server_tool.web_search call
补充示例:
metrics {
usage_extract openai_responses;
usage_fact server_tool.web_search call count_path="$.output[*]" type="web_search_call" status="completed";
}metrics {
usage_extract openai_images_generations;
usage_fact image.generate image count_path="$.data[*]";
usage_fact image.generate image source=request expr="$.n" fallback=true;
}metrics {
usage_extract openai_audio_speech;
usage_fact audio.tts second source=derived path="$.audio.tts.seconds";
}metrics {
usage_extract custom;
usage_fact audio.tts second path='$.usage.details[?(@.modality=="AUDIO")].seconds';
}这部分已拆到根仓文档,便于把 Gemini 方案细节与 DSL 语法说明分开维护。
详见:ONR 生成类 Gemini 多模态 Token Facts
这里保留一条最常用的最小示例:
metrics {
usage_extract custom;
usage_fact input token path='$.usageMetadata.promptTokensDetails[?(@.modality=="TEXT")].tokenCount';
usage_fact input token path="$.usageMetadata.promptTokenCount" fallback=true;
usage_fact input.image token path='$.usageMetadata.promptTokensDetails[?(@.modality=="IMAGE")].tokenCount';
usage_fact input.video token path='$.usageMetadata.promptTokensDetails[?(@.modality=="VIDEO")].tokenCount';
usage_fact input.audio token path='$.usageMetadata.promptTokensDetails[?(@.modality=="AUDIO")].tokenCount';
usage_fact output token path="$.usageMetadata.candidatesTokenCount";
usage_fact output token path="$.usageMetadata.thoughtsTokenCount";
usage_fact output.image token path='$.usageMetadata.candidatesTokensDetails[?(@.modality=="IMAGE")].tokenCount';
}-
范围:仅适用于
gemini.generateContent/gemini.streamGenerateContent -
口径:
input token优先取TEXT模态,再 fallback 到promptTokenCount -
这类场景是 token 型 usage,不涉及 Live / realtime 的
second型 fact -
现在可以直接用 filter JSONPath 从
promptTokensDetails按modality取值 -
ONR builtin 与显式
usage_fact现在都支持这套 Gemini 多模态 token 语义
后续范围说明:
- Gemini Live / Realtime 的
input.audio second、output.audio second、input.vision second - 其他厂商 realtime 接口的通用 meter-based facts
- 需要
source=derived的 runtime 聚合场景
用于从响应 JSON 中提取 finish_reason(用于日志 / 计费上报等)。
metrics { finish_reason_extract openai_chat_completions; }
metrics { finish_reason_extract anthropic_messages; }
metrics { finish_reason_extract gemini_generate_content; }
metrics { finish_reason_path "$.choices[0].finish_reason"; }说明:
custom:必须提供finish_reason_path(JSONPath 子集),从该路径提取 finish_reason- 其他任意 mode 名:用户自定义的全局
finish_reason_mode finish_reason_mode在块内如果省略finish_reason_extract,但已经声明了finish_reason_path,ONR 会自动按custom处理。- 在
metrics里,只写finish_reason_path而省略finish_reason_extract,等价于finish_reason_extract custom; - 仓库默认配置现在只保留更具体的 API / 路径级预设,例如
openai_chat_completions、openai_completions、openai_responses、openai_responses_stream、anthropic_messages、anthropic_messages_stream、gemini_generate_content、gemini_generate_content_stream。 openai/anthropic/gemini这类泛化名字不再是特殊的 DSL 内置finish_reason_extractmode;如果你想继续用这些名字,需要自己显式定义对应的全局finish_reason_mode预设。
这些 path-specific 预设对应的提取路径:
openaichat.completions/completions:读取$.choices[*].finish_reason(取第一个非空)responses非流式:读取$.incomplete_details.reasonresponses流式 SSE 包装层:读取$.response.incomplete_details.reason
anthropic- 依次读取
$.stop_reason、$.delta.stop_reason、$.message.stop_reason
- 依次读取
gemini- 优先读取
$.candidates[*].finishReason - 若为空则回退到
$.candidates[*].finish_reason
- 优先读取
等效 custom 配置示例:
- OpenAI Chat/Completions:
metrics {
finish_reason_path "$.choices[0].finish_reason";
}- OpenAI Responses 原始 reason:
metrics {
finish_reason_extract custom;
finish_reason_path "$.incomplete_details.reason";
}这和当前内置 openai 在非流式 responses 下是等价的。
- OpenAI Responses SSE 包装层:
metrics {
finish_reason_extract custom;
finish_reason_path "$.incomplete_details.reason";
finish_reason_path "$.response.incomplete_details.reason" fallback=true;
}这可以复刻当前内置 openai 对非流式和 response.incomplete 流式事件的覆盖范围。
- Anthropic 非流式:
metrics {
finish_reason_extract custom;
finish_reason_path "$.stop_reason";
}- Anthropic 流式
message_delta:
metrics {
finish_reason_extract custom;
finish_reason_path "$.delta.stop_reason";
}- Anthropic 流式
message_start兜底:
metrics {
finish_reason_extract custom;
finish_reason_path "$.message.stop_reason";
}- Gemini:
metrics {
finish_reason_extract custom;
finish_reason_path "$.candidates[0].finishReason";
}如果上游返回的是 snake_case,可改为 $.candidates[0].finish_reason。
多条与覆盖规则:
finish_reason_extract在同一个metricsblock 内只能出现一次finish_reason_path可作为覆盖项;同字段重复出现时以后者为准
metrics {
finish_reason_extract openai_chat_completions;
finish_reason_path "$.choices[0].finish_reason";
}- 作为兜底:当某些 provider 把 finish_reason 暴露在自定义位置时,可用该字段覆盖提取路径。
- 允许声明多条
finish_reason_path。 fallback=true表示只有在前面的非 fallback 路径都没有提取到非空值时,这条路径才会生效。event="<name>"可把某条路径限定在特定 SSE event 上,例如response.completed或message_delta。event_optional=true允许这条带事件的规则在“没有 event 上下文”的普通 JSON 提取场景继续作为回退规则生效。
示例:
metrics {
finish_reason_extract custom;
finish_reason_path "$.delta.stop_reason" event="message_delta" event_optional=true;
finish_reason_path "$.message.stop_reason" event="message_start" event_optional=true fallback=true;
}在 <expr> 位置可以引用以下变量:
$channel.base_url
渠道配置的 base_url(字符串)。
$channel.key
渠道配置的 key(鉴权 token,字符串)。
$request.model
原始请求的模型名(字符串)。
$request.model_mapped
映射后的模型名(字符串)。默认等于 $request.model,可通过 request { model_map ...; model_map_default ...; } 修改。
表达式形态(v0.1):
- 字符串字面量:
"abc" - 变量引用:
$channel.key - 连接:
concat("Bearer ", $channel.key)
注意:除上述最小能力外,v0.1 不支持更复杂的函数/运算。
本节按 nginx 文档风格为每条指令给出:Syntax / Default / Context,并补充 Multiple(是否支持多条)与要点说明。
约定:Context 用 DSL 中的 block 名表示(例如
auth/request/upstream/response/error/metrics/file)。
Syntax: syntax "<version>";
Default: —
Context: file
Multiple: no
- 目前主要用于占位与版本标识,建议保留。
Syntax: include "<path>";
Default: —
Context: file
Multiple: yes
<path>可为相对路径或绝对路径;相对路径以当前文件所在目录为基准。- include 在解析前做纯文本展开;最大深度 20;会检测循环引用。
Syntax: usage_mode "<name>" { ... }
Syntax: finish_reason_mode "<name>" { ... }
Syntax: models_mode "<name>" { ... }
Syntax: balance_mode "<name>" { ... }
Default: —
Context: file
Multiple: yes
- 用于声明可复用的顶层
metrics/models/balance预设块。 - 推荐分别放在
config/modes/usage_modes.conf、config/modes/finish_reason_modes.conf、config/modes/models_modes.conf与config/modes/balance_modes.conf。
provider/defaults/match/...是 block,不属于“指令”;但这里给出 nginx 文档风格摘要,方便查阅。
Syntax: provider "<name>" { ... }
Default: —
Context: file
Multiple: yes
- 文件名必须与
<name>一致:config/providers/<name>.conf。 - 名称匹配会统一转小写;建议
<name>与文件名都使用小写。
Syntax: defaults { ... }
Default: —
Context: provider
Multiple: no
- 作为 provider 的默认配置;命中 match 后会基于 defaults 叠加/覆盖。
Syntax: match api = "<api-name>" [stream = true|false] { ... }
Default: —
Context: provider
Multiple: yes
- 只会命中第一条匹配的 match(按出现顺序)。
Syntax: upstream_config { base_url = "<url>"; }
Default: —
Context: defaults
Multiple: no
base_url必填,且必须是字符串字面量(固定 URL)。- 同时
base_url仍是默认值:当渠道(DB)配置了base_url时优先使用渠道配置;仅当渠道base_url为空时才使用此处值。
Syntax: auth_bearer;
Default: —
Context: auth
Multiple: yes
- 效果:设置
Authorization: Bearer <channel.key>。 - 不支持配置 token/value(固定取渠道 key)。
Syntax: auth_header_key <Header-Name>;
Default: —
Context: auth
Multiple: yes
- 效果:设置
<Header-Name>: <channel.key>。 <Header-Name>支持标识符或字符串(建议用字符串以支持-)。- 不支持配置 token/value(固定取渠道 key)。
Syntax: oauth_mode <mode>;
Default: —
Context: auth
Multiple: yes(最后一条生效)
<mode>可选:openai|gemini|qwen|claude|iflow|antigravity|kimi|custom。- 含义:开启运行时 OAuth token 交换。
Syntax: auth_oauth_bearer;
Default: —
Context: auth
Multiple: yes
- 效果:设置
Authorization: Bearer <oauth.access_token>。
Syntax:
oauth_token_url <expr>;
oauth_client_id <expr>;
oauth_client_secret <expr>;
oauth_refresh_token <expr>;
oauth_scope <expr>;
oauth_audience <expr>;
oauth_method <GET|POST>;
oauth_content_type <form|json>;
oauth_form <key> <expr>;
oauth_token_path <jsonpath>;
oauth_expires_in_path <jsonpath>;
oauth_token_type_path <jsonpath>;
oauth_timeout_ms <int>;
oauth_refresh_skew_sec <int>;
oauth_fallback_ttl_sec <int>;
Default: 内置 mode 有默认值;custom 模式需显式配置关键字段
Context: auth
Multiple:
- oauth_form: yes
- 其他: yes(最后一条生效)
custom模式必填:oauth_token_url- 至少一条
oauth_form
Syntax: set_header <Header-Name> <expr>;
Default: —
Context: request
Multiple: yes
- 支持多条;按顺序执行;同名 header 多次 set 时后者覆盖前者。
Syntax: del_header <Header-Name>;
Default: —
Context: request
Multiple: yes
- 支持多条;按顺序执行。
- 合并规则:
defaults的操作先执行,match的操作追加在后执行。
Syntax: pass_header <Header-Name>;
Default: —
Context: request
Multiple: yes
- 将原始客户端请求中的某个 header 复制到上游请求。
- 如果源 header 不存在,则为 no-op。
Syntax: filter_header_values <Header-Name> <pattern>... [separator="<sep>"];
Default: separator=","
Context: request
Multiple: yes
- 用于过滤上游请求 header 中的列表型值。
- 执行过程为:按
separator分割,对每项做 trim,删除匹配项,再重组剩余项。 - 如果过滤后没有剩余项,则删除整个 header。
- 输出连接格式会被规范化:
,使用", ",其他分隔符使用"<sep> "。
Syntax: model_map <from-model> <expr>;
Default: —
Context: request
Multiple: yes
- 将
<from-model>(精确匹配$request.model)映射为$request.model_mapped。 <from-model>支持标识符或字符串(建议用字符串以避免特殊字符问题)。- 同一
<from-model>多次出现:后写的覆盖先写的。
Syntax: model_map_default <expr>;
Default: $request.model
Context: request
Multiple: yes
- 当没有任何
model_map命中时,使用该表达式作为$request.model_mapped。 - 多次出现时,以最后一条为准。
Syntax: json_set <jsonpath> <expr> [event="<name|name2>"] [max_count=<n>];
Default: —
Context: request/response
Multiple: yes
- 设置 JSON 字段;不存在的对象路径会自动创建。
- JSONPath(v0.1)仅支持对象路径:
$.a.b.c(不支持数组下标[])。 <expr>在此处支持:true/false/null、整数、字符串字面量、变量/concat。event="..."仅在response的 SSE JSON 操作中有效,用于按 SSEevent:名过滤执行。max_count=<n>仅在response中有效;0表示不限次数,n > 0表示本指令在一次响应处理周期内最多实际修改n次。
Syntax: json_replace <jsonpath> <expr> [event="<name|name2>"] [max_count=<n>];
Default: —
Context: request/response
Multiple: yes
- 仅当目标路径已存在时替换 JSON 字段;路径不存在时为 no-op。
- 不会创建缺失的父对象或叶子字段。
- JSONPath(v0.1)仅支持对象路径:
$.a.b.c(不支持数组下标[])。 <expr>支持同json_set。event="..."仅在response的 SSE JSON 操作中有效,用于按 SSEevent:名过滤执行。max_count=<n>仅在response中有效,语义同json_set。
Syntax: json_set_if_absent <jsonpath> <expr> [event="<name|name2>"] [max_count=<n>];
Default: —
Context: request/response
Multiple: yes
- 仅当路径不存在时设置字段。
- 若路径已存在(包括值为
null),则保留原值不覆盖。 event="..."仅在response的 SSE JSON 操作中有效。max_count=<n>仅在response中有效,语义同json_set。
Syntax: json_del <jsonpath> [event="<name|name2>"] [max_count=<n>];
Default: —
Context: request/response
Multiple: yes
- 删除字段;字段不存在时为 no-op。
- JSONPath(v0.1)仅支持对象路径:
$.a.b.c(不支持数组下标[])。 event="..."仅在response的 SSE JSON 操作中有效。max_count=<n>仅在response中有效,语义同json_set。
Syntax: json_rename <from-jsonpath> <to-jsonpath> [event="<name|name2>"] [max_count=<n>];
Default: —
Context: request/response
Multiple: yes
- 将字段从
<from-jsonpath>移动到<to-jsonpath>;源字段不存在时为 no-op。 - JSONPath(v0.1)仅支持对象路径:
$.a.b.c(不支持数组下标[])。 event="..."仅在response的 SSE JSON 操作中有效。max_count=<n>仅在response中有效,语义同json_set。
Syntax: json_wrap_input_text <jsonpath>;
Default: —
Context: request
Multiple: yes
- 将
<jsonpath>指向的字符串值包装成 OpenAI Responsesinputmessage 列表。 - 路径不存在时为 no-op;路径值已经是数组时为 no-op,避免二次包装。
- 路径值为对象、数字、布尔值或
null时会报错,避免把异常请求静默转发给上游。 - JSONPath(v0.1)仅支持对象路径:
$.a.b.c(不支持数组下标[])。
示例:
request {
json_wrap_input_text "$.input";
}输入:
{
"input": "Generate an image of gray tabby cat hugging an otter with an orange scarf"
}输出:
{
"input": [
{
"role": "user",
"content": [
{
"type": "input_text",
"text": "Generate an image of gray tabby cat hugging an otter with an orange scarf"
}
]
}
]
}Syntax: json_del_with_condition <jsonpath> <field> <pattern>...;
Default: —
Context: request
Multiple: yes
- 当
<jsonpath>指向对象时,如果<field>匹配任一 pattern,则删除该对象。 - 当
<jsonpath>指向数组时,删除数组中<field>匹配任一 pattern 的对象元素。 - 匹配不区分大小写,支持
*通配符。 - 如果数组元素全部被删除,则移除该数组字段。
示例:
request {
after_req_map {
json_del_with_condition "$.tools" "type" "web_search*" "web_fetch*";
json_del_with_condition "$.tool_choice" "type" "web_search*" "web_fetch*";
}
}Syntax: set_path <path-or-expr>;
Default: —
Context: upstream
Multiple: yes
- 设置上游请求路径(覆盖原 path)。
Syntax: set_query <key> <expr>;
Default: —
Context: upstream
Multiple: yes
- 支持多条;同一 key 多次 set 时后者覆盖前者。
Syntax: del_query <key>;
Default: —
Context: upstream
Multiple: yes
- 支持多条。
- 执行顺序:先执行所有
del_query,再执行所有set_query。
该 phase 语义是“选择一个响应策略”。如写多条,最后一条生效。
Syntax: resp_passthrough;
Default: —
Context: response
Multiple: yes
- 透传上游响应(上游已是 OpenAI-compatible)。
Syntax: resp_map <mode>;
Default: —
Context: response
Multiple: yes
- 非流式响应映射;
mode取决于内置实现。
Syntax: sse_parse <mode>;
Default: —
Context: response
Multiple: yes
- 流式 SSE 映射;
mode取决于内置实现。
Syntax: sse_collect <mode>;
Default: —
Context: response
Multiple: yes
- 把上游 SSE 聚合成同协议非流式 JSON,再可选执行
resp_map。 - 支持的
mode:openai_responses、anthropic_messages、gemini_generate_content。 - 只允许用于
stream = false的 match;不能和sse_parse或resp_passthrough组合。
Syntax: error_map <mode>;
Default: —
Context: error
Multiple: yes
- 允许的
mode(加载期白名单校验):openai/common/passthrough。 passthrough:跳过错误归一化,直接透传上游错误响应。
Syntax: usage_extract <mode>;
Default: —
Context: metrics
Multiple: no
- 目前支持:
openai/anthropic/gemini/custom。
Syntax: usage_root path="$.usage" [event="a|b"] [event_optional=true];
Default: —
Context: metrics, usage_mode
Multiple: yes
path必填,必须以$.开头。event可选,只对流式 SSE 提取有意义,支持用|配置多个事件名。event_optional=true必须与event同时出现。- 多条
usage_root命中时会合并成一个 usage 对象。 usage_root不支持name。
Syntax: usage_fact <dimension> <unit> path|count_path|sum_path|expr ...;
Default: —
Context: metrics
Multiple: yes
- 仅建议配合
usage_extract custom;使用。 - 第一批支持固定 registry 维度,不开放任意自定义维度。
- 支持
path/count_path/sum_path/expr四类原语。 count_path可搭配type/status过滤。path/sum_path/expr中的 JSONPath 现在支持受限 filter 子集:$.items[?(@.field=="VALUE")].x
- 支持常量属性,如
attr.ttl="5m"/attr.ttl="1h"/attr.modality="image"。 attr.modality对cache_read token有特殊含义,用于表达上游真实返回的分模态 cached token。支持值为text/image/audio/video;OpenAI cache detail 使用 text/image/audio,Gemini cache detail 使用 text/image/audio/video。- 同一
dimension + unit可以出现多条规则;所有命中的非 fallback 规则会累计求和。 fallback=true表示在同一dimension + unit没有更具体事实时再生效。source在配置了usage_root时默认是usage,否则默认是response;当前支持usage/response/request/derived。usage/response/request/derived之外的其他source会在校验阶段直接报错。dimension是扁平字符串键,.只是名称的一部分,不表示嵌套。- 实时多模态的 meter-based facts(例如
input.audio second)属于计划中的目标能力;若文档其他位置出现相关示例,应视为预览写法,不代表当前 registry 已开放。 path/count_path/sum_path/expr既可以用双引号,也可以用单引号包裹。- 当使用双引号字符串时,filter 内部的双引号需要写成
\"。 - 当使用单引号字符串时,可以直接写:
path='$.usageMetadata.promptTokensDetails[?(@.modality=="AUDIO")].tokenCount'
- 当前支持的
dimension:inputoutputinput.imageinput.videoinput.audiooutput.imageoutput.audiooutput.videocache_readcache_writeserver_tool.web_searchserver_tool.file_searchimage.generateimage.editimage.variationaudio.ttsaudio.sttaudio.translate
- 当前固定 registry 包括:
input tokenoutput tokeninput.image tokeninput.video tokeninput.audio tokenoutput.image tokenoutput.audio tokenoutput.video tokencache_read tokencache_write tokenserver_tool.web_search callserver_tool.file_search callimage.generate imageimage.edit imageimage.variation imageaudio.tts secondaudio.stt secondaudio.translate second
Syntax: finish_reason_extract <mode>;
Default: —
Context: metrics
Multiple: no
- 目前支持:
openai/anthropic/gemini/custom。 - 内置语义:
openai:chat.completions/completions:choices[*].finish_reasonresponses非流式:incomplete_details.reasonresponses流式:response.incomplete_details.reason
anthropic:按stop_reason -> delta.stop_reason -> message.stop_reasongemini:按candidates[*].finishReason -> candidates[*].finish_reason
custom现在可以通过多条finish_reason_path复刻有序 fallback;只要是纯路径查找类场景,就可以完整替代内置模式。
Syntax: finish_reason_path <jsonpath> [fallback=true|false] [event="<name>"] [event_optional=true|false];
Default: —
Context: metrics
Multiple: yes
- 可选覆盖项;当使用
finish_reason_extract custom;时为必填。 - 支持在路径后追加
fallback=true|false、event="<name>"、event_optional=true|false元数据。 event适用于 SSE 按事件名筛选的场景;命中事件时才会尝试这条finish_reason_path。event_optional=true仅可和event一起使用,表示当运行时没有提供事件名上下文时,仍允许这条规则按普通 JSON 匹配回退执行。- JSONPath 子集:
$.a.b.c/$.items[0].x/$.items[*].x/$.items[?(@.field=="VALUE")].x([*]或 filter 命中多项时取第一个非空字符串)。
Syntax: input_tokens_expr = <expr>;
Default: —
Context: metrics
Multiple: yes
- 建议仅配合
usage_extract custom;使用;同字段多次出现时后者覆盖前者。 - 这是兼容层写法:执行前会被编译成等价的内部 fact-based 规则。
<expr>为受限表达式:只允许+/-、JSONPath、整数常量;不支持括号/乘除/函数。- JSONPath 子集与
*_tokens_path相同:$.a.b.c/$.items[0].x/$.items[*].x/$.items[?(@.field=="VALUE")].x([*]或 filter 命中多项时对数组求和)。 - 取不到/非数值按
0处理。
Syntax: output_tokens_expr = <expr>;
Default: —
Context: metrics
Multiple: yes
- 规则同
input_tokens_expr。
Syntax: cache_read_tokens_expr = <expr>;
Default: —
Context: metrics
Multiple: yes
- 规则同
input_tokens_expr。
Syntax: cache_write_tokens_expr = <expr>;
Default: —
Context: metrics
Multiple: yes
- 规则同
input_tokens_expr。
Syntax: total_tokens_expr = <expr>;
Default: input_tokens_expr + output_tokens_expr
Context: metrics
Multiple: yes
- 规则同
input_tokens_expr。 - 若未显式声明,则默认按
input_tokens_expr + output_tokens_expr计算。 - 不推荐:显式配置
total_tokens_expr会额外引入一个独立的 total 事实源,容易与由input/output聚合出的 total 产生口径分歧。 - 也是兼容层写法:执行前会被编译到统一的内部 usage plan。
Syntax: input_tokens_path <jsonpath>;
Default: —
Context: metrics
Multiple: yes
- 建议仅配合
usage_extract custom;使用;同字段多次出现时后者覆盖前者。 - 等价于
input_tokens_expr = <jsonpath>;的简写(仅能写单个 JSONPath,不支持加减)。 - 同样属于兼容层写法:执行前会被编译成等价的内部 fact-based 规则。
Syntax: output_tokens_path <jsonpath>;
Default: —
Context: metrics
Multiple: yes
- 建议仅配合
usage_extract custom;使用;同字段多次出现时后者覆盖前者。 - 等价于
output_tokens_expr = <jsonpath>;的简写(仅能写单个 JSONPath,不支持加减)。 - 同样属于兼容层写法:执行前会被编译成等价的内部 fact-based 规则。
Syntax: cache_read_tokens_path <jsonpath>;
Default: —
Context: metrics
Multiple: yes
- 建议仅配合
usage_extract custom;使用;同字段多次出现时后者覆盖前者。 - 等价于
cache_read_tokens_expr = <jsonpath>;的简写(仅能写单个 JSONPath,不支持加减)。 - 同样属于兼容层写法:执行前会被编译成等价的内部 fact-based 规则。
Syntax: cache_write_tokens_path <jsonpath>;
Default: —
Context: metrics
Multiple: yes
- 建议仅配合
usage_extract custom;使用;同字段多次出现时后者覆盖前者。 - 等价于
cache_write_tokens_expr = <jsonpath>;的简写(仅能写单个 JSONPath,不支持加减)。 - 同样属于兼容层写法:执行前会被编译成等价的内部 fact-based 规则。
Syntax: balance_mode <mode>;
Default: —
Context: balance
Multiple: no
- 支持:
openai/custom。 - 其他任意 mode 名:会先解析为顶层全局
balance_mode预设,再统一执行。 - 在
balance里,如果省略balance_mode,但已经声明了path、balance_path、balance_expr、used_path、used_expr等自定义查询字段,ONR 会自动按balance_mode custom;处理。
Syntax: method <GET|POST>;
Default: GET
Context: balance
Multiple: yes
Syntax: path <path-or-url>;
Default: —
Context: balance
Multiple: yes
balance_mode custom时必填。- 支持绝对 URL 或相对 provider
base_url的路径。
Syntax: balance_expr = <expr>;
Syntax: used_expr = <expr>;
Default: —
Context: balance
Multiple: yes
- 受限表达式:只支持 JSONPath / 数值常量 +
+-。
Syntax: balance_path <jsonpath>;
Syntax: used_path <jsonpath>;
Default: —
Context: balance
Multiple: yes
- 在 custom 模式下,如果未设置
balance_expr,则balance_path必填。
Syntax: balance_unit <string>;
Default: USD
Context: balance
Multiple: yes
- 仅支持:
USD/CNY。
Syntax: set_header <Header-Name> <expr>;
Syntax: del_header <Header-Name>;
Default: —
Context: balance
Multiple: yes
Syntax: subscription_path <path-or-url>;
Syntax: usage_path <path-or-url>;
Default: OpenAI dashboard 默认路径
Context: balance
Multiple: yes
balance_mode openai的可选覆盖项。
Syntax: models_mode <mode>;
Default: —
Context: models
Multiple: no
- 支持:
openai/gemini/custom - 其他任意 mode 名:会先解析为顶层全局
models_mode预设,再统一执行
Syntax: method <GET|POST>;
Default: GET
Context: models
Multiple: yes
Syntax: path <path-or-url>;
Default: 与 mode 相关
Context: models
Multiple: yes
models_mode openai:默认/v1/modelsmodels_mode gemini:默认/v1beta/modelsmodels_mode custom:必填- 如果省略
models_mode,但已经声明了path、id_path、id_regex、id_allow_regex等自定义查询字段,ONR 会自动按models_mode custom;处理。
Syntax: id_path <jsonpath>;
Default: 与 mode 相关
Context: models
Multiple: yes
models_mode openai:默认$.data[*].idmodels_mode gemini:默认$.models[*].namemodels_mode custom:至少需要一个id_path
Syntax: id_regex <regex>;
Syntax: id_allow_regex <regex>;
Default: —
Context: models
Multiple: yes
id_regex用于重写模型 ID(优先取第 1 个捕获组)id_allow_regex用于重写后的白名单过滤
Syntax: set_header <Header-Name> <expr>;
Syntax: del_header <Header-Name>;
Default: —
Context: models
Multiple: yes
本节列出 v0.1 可在 <expr> 中使用的内置变量(均为字符串)。
说明:变量在运行期求值;当某变量在当前请求上下文中为空时,会展开为空字符串。
$channel.base_url
渠道(DB)配置的 base_url。在 upstream_config { base_url = "..."; } 的默认行为中会作为“渠道优先”来源:渠道非空则优先用渠道。
$channel.key
渠道(DB)配置的 key(鉴权 token)。auth_bearer; 与 auth_header_key ...; 会固定使用该值作为 token/value。
$request.model
来自客户端请求的模型名。
$request.model_mapped
映射后的模型名。默认等于 $request.model;可通过 model_map 与 model_map_default 修改。
request {
set_header "x-upstream-model" $request.model_mapped;
}
auth {
# Authorization: Bearer <channel.key>
auth_bearer;
}
upstream {
# 示例:把 model 拼进 path(注意这只是字符串拼接示例)
set_path concat("/v1/", $request.model_mapped, "/chat/completions");
}