> 原始 Markdown 孪生体（构建期从源 Markdown 生成）。渲染页：https://docs.gatellm.io/zh-CN/howto/configure-identity-model-mapping · 文档索引：https://docs.gatellm.io/zh-CN/llms.txt


# 配置身份作用域模型映射

当你希望**不同客户请求同一个客户端模型名时被路由到不同上游**时，用身份作用域模型映射：在访问密钥或密钥组上声明 `from → to` 规则，网关会在入口闸之后、路由之前自动重写模型名，随后由出口闸校验目标。例如让 `team-a` 组的 `claude-opus-5` 实际走通义千问 `qwen3.7-max`，而没有该规则的客户照常走原路由。规则还可按请求头分流：带 `X-Tenant: vip` 的 `claude-opus-5` 走优质线路，不带走普通线路。

入口：控制台 → **访问密钥**（Access Keys / Key Groups 页签，仅 admin 可改映射）。

> **入口闸 / 出口闸**（下文反复出现）：两道白名单校验。**入口闸**查「客户端请求的模型名」是否在该身份的 `models` / `load_balancers` 白名单里，不在就拒；**出口闸**查「实际要路由到的模型」（含映射重写后的目标）是否在对应白名单里。两道都过，请求才放行。本页映射规则本身不发权限，目标仍要过出口闸。

## 它和 switch-route / 脚本 useModel 的区别

| 机制 | 作用范围 | 配置方式 | 触发依据 | 目标过访问控制？ |
|------|---------|---------|---------|----------------|
| `switch-route` | 全局 | 声明式 | 请求体内容 | 否（管理员改路） |
| 脚本 `context.useModel()` | 全局 | 编程式 | 脚本逻辑 | 否（管理员改路） |
| **身份模型映射** | **按 key / group** | **声明式** | **请求身份 + 请求头** | **是（出口闸）** |

三者都是「模型名交换」，但只有身份映射按租户生效、且**目标必须在该客户端的权限白名单里**——配一条映射规则不等于发放权限。`switch-route` / `useModel` 的目标是管理员强制改路，不受客户端权限约束（二者签名也不接身份上下文）。

## 配置步骤

1. 打开 **访问密钥** 页，编辑目标访问密钥或访问密钥组。
2. 在表单底部找到 **模型映射（Model Mappings）** 区域，点 **添加规则**。
3. 每行选两个字段：**源模型**（`from`，客户端请求的名字）与 **目标模型**（`to`，实际路由的名字）。`to` 下拉只列出该身份出口闸有权到达的名字；`from` 是可输入下拉——网关里真实存在的模型/负载均衡/别名可下拉选，虚构接入名（access label，网关里不存在、客户端习惯发的标签）靠输入。group 级输入的 `from` 保存时自动并入该组 `models` 清单（入口闸放行）；key 级 `from` 若不在该 key 所属任何组清单，行内会警告「入口闸将拒」。UI 里能配出来的规则，运行时出口闸一定放行。可加多行，点行尾删除图标移除。
4. （可选）点 **添加请求头条件**，给规则加一个 `when`：**请求头名**可输入或下拉选、**操作符**下拉选，按操作符决定是否填**条件值**（输入框）。详见下文「头条件」。
5. 保存。规则即时热重载生效，无需重启。

> 没碰过映射区域就保存，等于「保留现有规则」；只有在该区域做过增删改，才会把当前列表写回（清空所有行 = 显式清空映射）。新身份（尚未保存）的下拉为空——先保存身份及其模型 / 负载均衡白名单，再回来配映射，下拉才会填充。

## 通配符

`from` 与 `to` 各至多一个 `*`。`from` 的 `*` 捕获中间片段，`to` 的 `*` 回填它：

- `claude-opus-5 → qwen3.7-max`：精确匹配，整名替换。
- `claude-* → qwen-*`：`claude-opus-5` 变成 `qwen-opus-5`，`claude-sonnet-4` 变成 `qwen-sonnet-4`。
- `to` 含 `*` 时 `from` 必须含 `*`；`from` 与 `to` 都不能有两个 `*`。否则保存时报 422。

> `from` 可输入任意值（具体名、通配符模式如 `claude-*`、虚构接入名）；`to` 只能从下拉选（该身份出口闸有权到达的具体名字，不含通配符）。若需 `to` 含通配符的规则，经配置文件 / API 直接配置——会正常生效与回显，但无法在控制台 `to` 下拉里新建。

> **两套通配符方言（注意区分）**：
>
> | 写在哪 | `*` 的含义 | 例子 |
> |--------|-----------|------|
> | 映射规则 `from` | **中间片段捕获** | `claude-*-5` 匹配 `claude-opus-5`（捕获 `opus`） |
> | 密钥组白名单 `models` / `load_balancers` | 仅精确名、`"*"` 全选、**尾部 `*` 前缀匹配**；不支持中间 `*` | `claude-*` 匹配所有 `claude-` 开头；`claude-*-5` 只当字面量 |
>
> 故 `from = "claude-*-5"` 的规则，要让入口闸放行，白名单里须写**具体名**（如 `claude-opus-5`）或尾部通配（`claude-*`）。白名单写 `claude-*-5` 会被当字面量、只精确匹配字符串本身。这是 fail-closed：写不进白名单的 `from` 名会在入口闸 403。

## 头条件（按请求头分流）

给规则加 `when` 即可让同一个 `from` 按请求头走不同目标。操作符五选一：

| 操作符 | 语义 | 是否填条件值 |
|--------|------|------------|
| `exact` | 头值精确相等 | 是 |
| `prefix` | 头值以该前缀开头 | 是 |
| `regex` | 头值匹配正则 | 是 |
| `exists` | 该头存在（不看值） | 否 |
| `absent` | 该头不存在 | 否 |

头名大小写不敏感，条件值大小写敏感。条件不满足时**跳过这条规则**，继续在同一个桶里找下一条 `from` 匹配的规则——所以把**带条件的规则放前面、无条件规则兜底**：

```text
1. claude-opus-5 → glm-4.6        when X-Tenant exact vip      # 带头条件的具体规则放前
2. claude-opus-5 → qwen3.7-max                                 # 无条件兜底
```

带 `X-Tenant: vip` 的请求命中规则 1 → `glm-4.6`；不带的跳过规则 1、命中规则 2 → `qwen3.7-max`。

> 请求头名是**可输入下拉**：内置常见头 ∪ 现有请求头访问规则用过的头 ∪ 本身份已用过的头作为候选建议，也可直接键入候选里没有的任意头名。运行时对头名**没有白名单**——某条规则引用的头在请求里缺失时，该规则被**跳过**（不会 403），继续看同桶下一条规则。

> `when` 针对的是**发起请求那一刻**携带的请求头。少数反向推断落点（DashScope 异步任务状态查询 `GET /v1/services/{model}/tasks/{task_id}`）在映射处拿不到原始请求头，传空查询——此时只有**无条件规则**会匹配，带头条件的规则被跳过。这是 fail-closed：若唯一匹配的规则带了 `when` 且未被满足，模型名不重写、按原名解析（可能 404）。出口闸照常运行。

## 优先级

键级规则 > 组级规则（**按该访问密钥 groups 列表的顺序**）> 透传。访问密钥自身的规则先看；没命中再依次看它所属各组的规则；都没命中则模型名不变。桶内**顺序优先**：第一条 `from` 匹配且头条件满足的规则胜出（first-match-wins），故带条件的规则须放在无条件规则之前。

**组与组之间的顺序 = 该访问密钥 groups 列表的顺序，且这一顺序在两次保存之间恒定。** 多个组都对同一个源名（`from`）配了映射时，列表里靠前的组胜出——网关不会把各组的规则合并成一张大表，而是逐组尝试、第一个产生匹配的组即返回、后面的组整组不看。由于「逐组串行、首匹配组即返回」，**组的顺序优先于桶内顺序**：例如组 A 的规则是 `[m→X（仅 vip）, m→Y（无条件）]`、组 B 是 `[m→Z（无条件）]`，当请求不带 vip 时结果是 **Y** 而非 Z（A 桶内跳过第一条后命中第二条，整组返回，B 轮不到）。

> 这一顺序在配置不变时是确定的（每次请求命中的都是同一个组）。但当前**没有**针对「多个组（或键与组之间）对同一 `from` 配了规则」的冲突校验或告警——谁先生效完全由 groups 列表顺序这一隐含约定决定。需要确定性时，最稳的做法是让同一个 `from` 只在一个地方配规则（或直接配在访问密钥上，键级永远最先、最明确）；若必须分散在多个组，就把应优先的组放在该键 groups 列表更靠前的位置。

## 三条必须知道的运维契约

1. **映射目标必须在白名单，且按真实种类写对清单**。出口闸在映射原语内部对真实目标名判型：目标是普通模型查组的 `models`、目标是负载均衡查组的 `load_balancers`。目标不在对应清单里 → 403。故配 `claude-opus-5 → my-lb`（`my-lb` 是负载均衡）时，把 `my-lb` 写进组的 `load_balancers`。

2. **源名（`from`）必须在白名单**。入口闸对路由表里存在的名字按种类查对应清单（`load_balancers = ["*"]` 只放行负载均衡名，不顺带放行普通模型名）；对虚构客户端名（网关里不存在的接入标签）两张清单取并集回退，故虚构名写 `models` 或 `load_balancers` 均可。在 **group 级**配映射规则时，输入的虚构 `from` 保存时**自动并入该组 `models` 清单**（入口闸放行）；在 **key 级**配规则时，key 无独立清单，虚构 `from` 须手动写进该 key 所属某组的 `models` 或 `load_balancers`（写哪张均可），控制台会在 `from` 不在所属组清单时警告。这是显式授权动作，不是绕过。

3. **`/v1/models` 列表不反映映射**。列表只列配置里真实存在的模型，映射的源名（如 `claude-opus-5`、`gpt-4o`）通常不在其中，所以下拉框里看不到它——但客户端直接请求该名字能成功。这是预期行为。

## 统计与计费看到的是哪个名字？

**映射后的最终模型名**。映射对统计与计费完全透明——日志、统计页、账单里记录的是目标模型（如 `qwen3.7-max`），不会出现源名行。若你需要按客户视角的源名做归因，请在客户端侧自行记录。

## 如何在日志里确认映射生效

看日志页模型列的 **`MM`** 标签：命中映射规则的请求模型名前会挂 `MM`，鼠标悬停显示 `原名 → 映射名`（如 `gpt-4o → glm-4.6`），日志详情页「概览」区同处可见映射来源条。无需翻请求体——标签即凭证。映射目标是负载均衡名时，同一行 `MM` + `LB` 两标签都在；同时被脚本 `useModel` 改路则 `MM` + `SW` 都在。

## 排错

- **保存报 422「invalid model mapping rule」**：检查该行 from/to 是否为空、`from` 是否多于一个 `*`、`to` 有 `*` 而 `from` 没有；若带 `when`，检查头名是否为空、`exact`/`prefix`/`regex` 是否填了值、`exists`/`absent` 是否误填了值、`regex` 能否编译。
- **请求 403**：要么是源名（`from`）不在该身份任一清单里（入口闸拒），要么是目标（`to`）不在对应种类的清单里（出口闸拒）。403 错误体里写的是**客户端原名**。
- **请求 404 但服务端 warn 了真实目标名**：映射目标模型 / 负载均衡不存在或已禁用；返回给客户端的消息已脱敏回源名。
- **头条件没分流**：确认带条件的规则排在无条件规则**之前**；确认客户端确实带了那个头且值匹配。
- **映射没生效**：确认规则配在正确的身份上（键级 vs 组级），且客户端确实以该身份认证；优先级被更高优先级的规则覆盖时也会「看起来没生效」。

**下一步**：[访问密钥与密钥组字段](/zh-CN/reference/access-keys-groups-fields.md) 看 `model_mappings` 字段定义与白名单清单；[访问控制设计](/zh-CN/practices/access-control.md) 看白名单访问控制的整体思路；[日志查看器](/zh-CN/console/logs-viewer.md) 看 `MM` / `SW` / `LB` 标签怎么读。
