写第一个脚本变换
GateLLM 内置 JavaScript 脚本引擎(基于 QuickJS),可对请求和响应的 JSON body 做自定义变换。脚本运行在沙箱中,有严格资源限制,安全且可控。
本章带你写第一个脚本:基本结构 → 挂载方式 → 配置方式 → Context API 简介。完整 API 表见 脚本 API 参考。
什么时候用脚本
request_payload 覆盖约 90% 的简单场景(简单字段增删改),switch-route 处理纯路由决策(检测图片/联网→切模型),脚本处理剩下的复杂逻辑(跨字段计算、响应脱敏)。完整决策表与选型见 请求改写与路由:怎么选。
⚠️ 纯路由决策优先用
switch-route:「检测图片/联网→切模型」这类纯路由(同协议)用原生switch-route规则,零脚本开销。脚本路径已默认开启懒投影,不再因整段物化 body 而 OOM;但script_error_mode = log-and-continue下脚本抛错会被静默跳过,故脚本应保证不抛错。仅当切换是跨协议、或判定超出「字段存在 / 小值相等」能表达的范围时,才用 before 槽脚本(读客户端原始格式)。完整反模式与耗时参考见 脚本性能与内存。
快速开始
基本结构
每个脚本定义一个 transform 函数,接收 body 和 context,必须返回 body:
function transform(body, context) {
// 读取和修改 body
// 读取 context 获取元数据(只读)
return body; // 必须!
}| 参数 | 可修改 | 说明 |
|---|---|---|
body | 是 | JSON 请求体或响应体 |
context | 否 | 请求/响应元数据(模型名、headers 等) |
挂载方式
脚本可以挂在上游或模型上:
| 级别 | 作用范围 | 适用场景 |
|---|---|---|
| 上游 | 经过此上游的所有模型 | 协议适配、公共字段注入 |
| 模型 | 仅此模型 | 模型特定调整、参数调优 |
配置方式
内联脚本(短脚本直接写在输入框里)——控制台 → 模型详情(或上游)→ 请求/响应脚本 → 在对应槽(request_transform_after / request_transform_before / response_transform)黏贴:
function transform(body, context) { body.temperature = 0.7; return body; }文件脚本(较大脚本引用外部 .js 文件):控制台界面的脚本编辑器是纯内联的,没有「选文件」控件——{file} 引用无法在 UI 里创建,只能通过 Console API(PUT 上游 / 模型)带 request_transform_before: {"file": "scripts/my-transform.js"} 设置;UI 会原样保留已设置的 {file} 引用,直到你在该槽输入内联内容覆盖它。
Context 对象
context 提供当前请求/响应的元数据,只读:
请求和响应阶段均可用
| 字段 | 类型 | 说明 |
|---|---|---|
context.clientModel | string | 客户端看到的模型名 |
context.upstreamModel | string | 发送给上游的模型 ID |
context.upstreamName | string | 上游分组名 |
context.sourceProtocol | string | 客户端协议(如 "OpenAIChatCompletions") |
context.targetProtocol | string | 上游协议(如 "Anthropic") |
context.stream | boolean | 是否流式请求 |
context.headers | object | 请求 headers(敏感头已过滤) |
context.query | object | URL 查询参数 |
context.requestId | string | 唯一请求 ID |
context.clientIp | string | 客户端 IP |
context.accessKeyName | string | 访问密钥名 |
context.accessKeyGroup | string | 访问密钥分组 |
仅响应阶段可用
| 字段 | 类型 | 说明 |
|---|---|---|
context.responseStatus | number | 上游 HTTP 状态码 |
context.responseHeaders | object | 上游响应 headers |
context.elapsedMs | number | 耗时(毫秒) |
安全:
authorization、cookie、x-api-key等敏感 headers 在脚本可见之前已被移除。
完整字段表见 脚本 API 参考。
请求脚本的两个槽(before / after)
请求侧脚本不再是一个脚本加位置开关,而是两个独立槽,各自可空、可同时配置:
| 槽 | 字段 | 运行时机 | 看到的 body | useModel 能力 |
|---|---|---|---|---|
| 翻译后 | request_transform_after | 协议翻译之后 | 上游协议格式 | 仅同协议切换 |
| 翻译前 | request_transform_before | 协议翻译之前 | 客户端原始格式 | 支持跨协议切换 |
request_transform_after:在协议转换后运行,body 已是上游格式。适合简单的字段修改、上游方言改写。request_transform_before:在协议转换前运行,body 是客户端原始格式。适合需要检测客户端输入(如图片、搜索、plan 模式标记)并切换路由的场景。- 两个槽可同时配置:同一路由的 before 与 after 各自独立合并(model 覆盖 upstream),两个脚本都会运行——before 先跑(可触发跨协议 立即
useModel切换),after 后跑。注意:一旦 before 槽触发了 立即useModel切换路由,原路由的 after 槽不再执行(整套 pipeline 换成目标路由的);after 槽声明的useModel是同协议 延后切换。response_transform无槽概念,保持单字段。
执行顺序
before 槽与 after 槽各自独立运行;同一路由两者都配时,before 先在翻译前阶段跑,after 在翻译后阶段跑。下面分别给出两槽在管线中的位置。
请求侧 — after 槽(翻译后)
客户端请求 → 协议转换 → 自动归一化 → request_payload 规则 → 上游脚本 → 模型脚本 → 缓存注入 → 发送到上游请求侧 — before 槽(翻译前)
客户端请求 → 上游脚本 → 模型脚本 → (useModel swap) → 协议转换 → 自动归一化 → request_payload 规则 → 缓存注入 → 发送到上游响应侧(仅非流式)
上游响应 → 响应脚本 → 反向协议转换 → 返回客户端流式请求的响应脚本暂不支持。
第一个示例:覆盖模型名
function transform(body, context) {
body.model = context.upstreamModel;
return body;
}挂在模型或上游的 request_transform_after 字段。请求发送到上游前,body 的 model 字段被替换为上游真实模型 ID。
切换模型:context.useModel
脚本可以动态切换请求路由到的模型:
function transform(body, context) {
// 检测多模态输入(图片),切换到多模态模型
const msgs = body.messages || [];
const hasImage = msgs.some(m =>
Array.isArray(m.content) && m.content.some(c => c.type === "image_url"));
if (hasImage) {
context.useModel("gpt-4o-vision");
}
return body;
}context.useModel vs body.model =:
| 操作 | 效果 |
|---|---|
body.model = "X" | 仅改请求体的 model 字段,路由不变 |
context.useModel("X") | 完整路由切换(base_url、协议、凭证一起换) |
用 body.model 改同端点内的模型名;用 context.useModel 做跨端点/跨协议切换。
通过 Console API 测试
部署前先测试脚本($CONSOLE_TOKEN 的获取见 脚本配置 → 脚本测试):
curl -X POST http://localhost:7890/console/api/scripts/test \
-H "Authorization: Bearer $CONSOLE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"script": "function transform(body, context) { body.model = context.upstreamModel; return body; }",
"body": {"model": "gpt-4o", "messages": []},
"context": {
"clientModel": "gpt-4o",
"upstreamModel": "gpt-4o-2024-05-13",
"upstreamName": "openai",
"sourceProtocol": "openai",
"targetProtocol": "openai",
"stream": false
}
}'响应:
{
"result_body": {"model": "gpt-4o-2024-05-13", "messages": []},
"execution_time_ms": 0.12
}错误处理
script_error_mode
| 模式 | 行为 | 适用场景 |
|---|---|---|
log-and-continue(默认) | 脚本报错时记录 warn 日志,请求以原始 body 继续 | 脚本失败不应阻断请求 |
log-and-reject | 脚本报错时拒绝请求,返回网关错误 | 脚本正确性至关重要(如安全脱敏) |
提前返回时必须返回 body
function transform(body, context) {
// ✅ 正确
if (!body.messages) return body;
// ❌ 错误:return 无值 → 脚本静默失败
if (!body.messages) return;
body.messages[0].content = "modified";
return body;
}常见问题
Q:脚本在控制台界面哪里配? 上游详情页和模型详情页都有「请求脚本」「响应脚本」输入框。也可以直接通过 Console API 设置。
Q:脚本改了 body.model,为什么路由没变?body.model 只改请求体的字段,不改路由。要切换路由用 context.useModel("模型名")。
Q:流式请求的响应脚本生效吗? 暂不支持流式响应的脚本。请求脚本对流式请求有效,但响应脚本仅在非流式时生效。
Q:脚本报错了但请求正常通过? 默认的 log-and-continue 模式下,脚本报错不会阻断请求。查看 warn 日志了解报错原因。如需脚本必须成功,改用 log-and-reject。
Q:context.headers 里为什么没有 Authorization? 安全设计:authorization、cookie、x-api-key 等敏感 headers 在脚本可见之前已被移除,防止凭证泄露。
Q:Before 和 After 脚本位置怎么选?
- 只改上游格式 body 的字段 → After(默认)
- 需要检测客户端原始格式(如不同协议的图片字段不同)→ Before
- 需要跨协议 useModel 切换 → Before
- 同协议下「检测图片/联网→切模型」这类纯路由决策 → 不用脚本,用原生
switch-route规则(零脚本开销首选;脚本路径因懒投影已不再整段物化 body,但纯路由决策仍无需脚本)
下一步:脚本 API 参考 看完整内置函数表;脚本配置 看运行限制与配置;用脚本做协议翻译修正 看实战示例。
