- AI 应用
- API网关
- LLM 网关
- 后端
- 前端
【免费下载链接】axonhub
⚡️ Open-source AI Gateway — Use any SDK to call 100+ LLMs. Built-in failover, load balancing, cost control & end-to-end tracing.
本篇指南围绕 AxonHub 的API Key Profile(API Key 配置档)展开,讲解如何通过它为单个 API Key 配置模型重命名映射、渠道限制与模型白名单,并在多个 Profile 之间即时切换。你将掌握 Profile 在请求处理管线中的位置、
modelMappings/channelTags/modelIDs三类核心配置的完整用法,以及它们背后的源码实现与校验规则,可直接用于接入层模型兼容、多客户端统一模型名和灰度放量等场景。
什么是 API Key Profile?
API Key Profile是绑定在某个 API Key 上的一组请求级配置,主要提供四项能力:
- 映射模型(Map models):改写客户端请求中的模型名,让请求按你指定的模型去处理;
- 限制渠道(Restrict channels):将该 API Key 限定到指定的渠道(Channel)范围;
- 限制模型(Restrict models):将该 API Key 限定到指定的模型白名单;
- 切换 Profile(Switch profiles):同一个 API Key 可以创建多个 Profile,并随时切换当前生效的一个。
简单来说,API Key Profile 在请求入口处决定了请求首先被当作哪个模型来处理——它是整个请求处理链路中第一次(也是最早一次)对模型名进行干预的位置。
从数据模型上看,Profile 保存在 API Key 的Profiles字段中,结构定义在 internal/objects/apikey.go:
type APIKeyProfiles struct { ActiveProfile string `json:"activeProfile"` // 当前生效的 Profile 名 Profiles []APIKeyProfile `json:"profiles"` // Profile 列表 } type APIKeyProfile struct { Name string `json:"name"` // Profile 名称,同一 API Key 内唯一 ModelMappings []ModelMapping `json:"modelMappings"` // 模型映射规则 ChannelIDs []int `json:"channelIDs,omitempty"` // 允许的渠道 ID 列表 ChannelTags []string `json:"channelTags,omitempty"` // 允许的渠道标签 ChannelTagsMatchMode ChannelTagsMatchMode `json:"channelTagsMatchMode,omitempty"` // 标签匹配模式 ModelIDs []string `json:"modelIDs,omitempty"` // 模型白名单 Quota *APIKeyQuota `json:"quota,omitempty"` // 可选的 Profile 级配额 LoadBalanceStrategy *string `json:"loadBalanceStrategy,omitempty"` // 可选:覆盖负载均衡策略 TraceStickyMode *string `json:"traceStickyMode,omitempty"` // 可选:覆盖 Trace 粘性模式 }其中每条映射规则ModelMapping定义在 internal/objects/channel.go,只有两个字段:
type ModelMapping struct { From string `json:"from"` // 客户端请求中的模型名 To string `json:"to"` // AxonHub 实际使用的模型名 }API Key Profile 在请求流程中的位置
API Key Profile 的模型映射是整个请求处理三层模型设置管线中的第一步。关于完整流程,参见 请求处理指南。
简单概括:
API Key Profile 重命名 → 模型关联(Model Association)选择渠道 → 渠道重命名 → 发往上游
在 请求处理指南 中,三层模型设置的定位如下:
| 层级 | 配置位置 | 作用 | 通俗类比 |
|---|---|---|---|
| 第 1 层 | API Key Profile | 改写客户端请求的模型名 | “请求什么” |
| 第 2 层 | 模型关联 | 决定由哪个渠道处理请求 | “去哪里” |
| 第 3 层 | 渠道配置 | 改写发往上游的模型名 | “上游叫什么” |
实际请求在到达渠道选择之前,会先经过apply-model-mapping中间件,其实现位于 internal/server/orchestrator/model_mapper.go:
func (m *apiKeyModelMappingMiddleware) OnInboundLlmRequest(ctx context.Context, llmRequest *llm.Request) (*llm.Request, error) { // 保存客户端原始请求模型名 if m.RequestModel == "" { m.RequestModel = llmRequest.Model } // 若存在生效的 Profile,则应用模型映射 if m.inbound.state.APIKey != nil { originalModel := llmRequest.Model mappedModel := m.inbound.state.ModelMapper.MapModel(ctx, m.inbound.state.APIKey, originalModel) if mappedModel != originalModel { llmRequest.Model = mappedModel } } return llmRequest, nil }值得注意的一个细节:映射不仅发生在入站请求方向。在出站响应方向,中间件会调用ReplaceResponseModel把响应体中的模型名替换回客户端最初请求的模型名(见 model_mapper.go 与 ReplaceResponseModel),从而保证客户端看到的模型名始终是它自己请求的那个,映射过程对客户端完全透明。
常见使用场景
场景一:客户端工具写死了模型名
很多 AI 工具在内部写死了模型名(例如某些 CLI 或 IDE 插件固定请求claude-sonnet-4-5)。如果希望它们实际走其他模型,不需要改工具,只需在 Profile 中做模型映射:
{ "modelMappings": [ {"from": "claude-sonnet-4-5", "to": "anthropic/claude-3.5-sonnet"} ] }场景二:统一多个客户端的模型名
如果多个客户端分别请求gpt4、gpt-4-turbo,但后端统一使用gpt-4o,可以在一个 Profile 中配置多条映射,将不同写法统一收拢:
{ "modelMappings": [ {"from": "gpt4", "to": "gpt-4o"}, {"from": "gpt-4-turbo", "to": "gpt-4o"} ] }场景三:将 Profile 限制到系统的一部分
如果只想让该 Profile 走带production标签的渠道,且只允许请求gpt-4o与claude-3-sonnet两个模型,可以同时使用渠道标签与模型白名单:
{ "channelTags": ["production"], "modelIDs": ["gpt-4o", "claude-3-sonnet"] }配置步骤
第 1 步:打开 Profile 配置界面
- 登录 AxonHub 管理界面;
- 进入API Keys(API Key 管理)页面;
- 找到要配置的 API Key;
- 打开该 Key 的Actions(操作)菜单;
- 选择Profiles或Configure进入 Profile 配置弹窗(对应前端组件 apikeys-profiles-dialog.tsx)。
第 2 步:创建 Profile
- 点击Add Profile(添加 Profile);
- 填写 Profile 名称;
- 配置模型映射、渠道限制或模型限制。
第 3 步:配置模型映射
每条映射包含:
- From:客户端请求中的模型名;
- To:AxonHub 实际应使用的模型名。
支持的匹配方式有两种:
精确匹配
{"from": "gpt-4", "to": "claude-3-opus"}正则匹配
{"from": "gpt-.*", "to": "claude-3-sonnet"}关于正则的底层行为,源码 internal/pkg/xregexp/match.go 给出了精确的实现语义,值得展开说明:
- 当
from不含任何正则特殊字符时,走精确匹配分支,pattern == str才算命中(match.go); - 当
from为*时,视为匹配任意模型(match.go); - 其余情况使用
regexp2编译为自动锚定的正则(等价于^(?:pattern)$,见 ensureAnchored),这意味着gpt-.*只会匹配完整模型名而非任意子串; - 所有模式都会经过全局缓存(
globalCache),高频请求不会反复编译正则(match.go)。
上述行为均有测试用例覆盖,例如 internal/server/orchestrator/model_mapper_test.go 验证了精确匹配、通配符*、正则特殊字符、非法正则(如[invalid)返回false等场景。
第 4 步:设置生效的 Profile
- 在Active Profile(生效 Profile)下拉中选择一个 Profile;
- 点击Save保存;
- 变更立即生效,无需重启服务。
“立即生效”背后有两点保障:
- 后端
UpdateAPIKeyProfiles更新成功后立即调用invalidateAPIKeyCaches失效该 Key 的缓存(见 internal/server/biz/api_key.go); - 每次请求解析生效 Profile 时,通过
GetActiveProfile实时按名称在 Profile 列表中查找(见 internal/ent/extra.go):
func (a *APIKey) GetActiveProfile() *objects.APIKeyProfile { if a == nil || a.Profiles == nil || a.Profiles.ActiveProfile == "" { return nil } for i := range a.Profiles.Profiles { if a.Profiles.Profiles[i].Name == a.Profiles.ActiveProfile { return &a.Profiles.Profiles[i] } } return nil }规则匹配顺序
模型映射按声明顺序逐条求值,第一个命中的规则生效,后续规则不再参与匹配。该逻辑实现于 internal/server/orchestrator/model_mapper.go 的applyModelMapping:
func (m *ModelMapper) applyModelMapping(mappings []objects.ModelMapping, model string) string { for _, mapping := range mappings { if m.matchesMapping(mapping.From, model) { return mapping.To // 命中即返回,不再继续 } } return model // 全部未命中则保持原模型名 }因此请遵循:把更具体的规则放在前面,更通用的规则放在后面。例如先写精确的gpt-4,再写兜底的gpt-.*,避免通用规则提前吞掉特定请求。
如果所有规则都未命中,请求模型名保持不变(原样返回),不会报错。
深入理解:渠道限制与模型白名单的执行位置
除模型映射外,Profile 的channelTags、channelIDs与modelIDs分别在请求管线的不同环节生效:
渠道限制:候选渠道过滤
在select-candidates阶段,请求会先应用项目级 Profile(作为上界过滤),再应用API Key 级 Profile(在项目范围内进一步收窄),实现位于 internal/server/orchestrator/select_candidates.go:
// 项目级 Profile 过滤(上界) if projectProfile := project.GetActiveProfile(); projectProfile != nil { if len(projectProfile.ChannelIDs) > 0 { selector = WithSelectedChannelsSelector(selector, projectProfile.ChannelIDs) } if len(projectProfile.ChannelTags) > 0 { selector = WithChannelTagsFilterSelector(selector, projectProfile.ChannelTags, projectProfile.ChannelTagsMatchMode) } } // API Key 级 Profile 过滤(在项目范围内进一步收窄) if profile := inbound.state.APIKey.GetActiveProfile(); profile != nil { if len(profile.ChannelIDs) > 0 { selector = WithSelectedChannelsSelector(selector, profile.ChannelIDs) } if len(profile.ChannelTags) > 0 { selector = WithChannelTagsFilterSelector(selector, profile.ChannelTags, profile.ChannelTagsMatchMode) } }渠道标签匹配由MatchChannelTags统一实现(internal/objects/apikey.go),支持三种channelTagsMatchMode:
| 模式 | 语义 |
|---|---|
any(默认) | 渠道标签与允许标签任一匹配即可通过 |
all | 渠道必须同时具备所有允许标签才可通过 |
none | 渠道标签不得包含任何允许标签才可通过 |
该枚举在前端表单中同样被校验,见 frontend/src/features/apikeys/data/schema.ts(z.enum(['any', 'all', 'none']))。这一机制正是“优先使用 channelTags 而非硬编码 channelIDs”这一最佳实践的底层支撑——标签让渠道范围的调整无需改动 Profile。
模型限制:入口模型访问控制
modelIDs白名单在check-api-key-model-access中间件中执行(internal/server/orchestrator/model_access.go):若当前生效 Profile 配置了modelIDs,请求模型不在白名单内,则直接以ErrInvalidModel拒绝请求(注意:此时比较的是经过模型映射之后的模型名,即“该 Profile 最终允许哪些模型”)。
profile := inbound.state.APIKey.GetActiveProfile() if profile == nil || len(profile.ModelIDs) == 0 { return llmRequest, nil // 未配置白名单则不做限制 } allowed := slices.Contains(profile.ModelIDs, llmRequest.Model) if !allowed { return nil, fmt.Errorf("%w: %s", biz.ErrInvalidModel, llmRequest.Model) }该行为的测试用例见 internal/server/orchestrator/model_access_test.go。
进阶:Profile 还能覆盖什么?
从 internal/objects/apikey.go 的数据结构可以看到,Profile 还支持可选配置:
quota:Profile 级配额(请求数 / 总 Token / 成本,支持all_time、past_duration、calendar_duration三种周期),在enforceQuota阶段按生效 Profile 检查(internal/server/orchestrator/quota.go);loadBalanceStrategy/traceStickyMode:Profile 可覆盖默认的负载均衡策略与 Trace 粘性模式(internal/server/orchestrator/retry.go)。
这些字段在保存时都会经过严格校验(internal/server/biz/api_key.go 中的validateProfileFilters、validateProfileRoutingPolicies、validateProfileQuota)。
FAQ
Q:为什么模型映射没有生效?
按以下顺序排查:
- 是否选中了正确的 Active Profile?如果
activeProfile为空或指向不存在的 Profile,MapModel会直接返回原始模型名(见 model_mapper.go)。后端在保存时会强制校验activeProfile必须存在于 Profile 列表中(internal/server/biz/api_key.go)。 - 模型名是否真的匹配?精确匹配要求完全一致;正则匹配是全名锚定匹配(等价
^(?:...)$),gpt-.*不会匹配my-gpt-4这类带前缀的字符串。 - 正则写法是否正确?非法正则(如
[invalid)会被缓存为编译失败,命中恒为false(见 xregexp/match.go 与对应测试 model_mapper_test.go)。
Q:Profile 名称有什么要求?
- 在同一个 API Key 内必须唯一(校验时按大小写不敏感处理,
Profile与profile视为重复,见 internal/server/biz/api_key.go); - 不能为空(空名称会被拒绝);
- 建议使用有意义的名称。
Q:应该创建多少个 Profile?
没有硬性数量限制,但建议保持精简、易于管理。需要不同行为(不同模型映射、不同渠道范围、不同配额)时再新建 Profile,通过切换 Active Profile 即可即时变更行为,无需新建 API Key。
Q:保存 Profile 时报 “duplicate profile name” 怎么办?
Profile 名称在同一 API Key 内不区分大小写地要求唯一,请检查是否已有同名(或仅大小写不同)的 Profile,重命名后重试。
最佳实践
- 使用描述性名称:例如
production、openrouter-mapping,让人一眼看出 Profile 的用途; - 具体规则在前:映射按顺序首个命中生效,把精确规则放在通用正则之前;
- 启用前先测试:用测试请求验证映射结果与渠道范围符合预期后再切换为正式生效;
- 优先使用渠道标签:用
channelTags代替硬编码的channelIDs,后续调整渠道范围时无需修改 Profile; - 充分利用白名单做隔离:给外部客户、灰度环境等分别配置
modelIDs白名单,从入口杜绝越权模型请求(该拦截在check-api-key-model-access阶段执行,早于渠道选择)。
相关文档
- 模型管理指南 —— 配置模型关联(选择渠道)
- 渠道管理指南 —— 配置上游渠道
- 负载均衡指南 —— 理解渠道选择与故障转移
- 请求处理指南 —— 查看完整请求流程
- AI 应用
- API网关
- LLM 网关
- 后端
- 前端
【免费下载链接】axonhub
⚡️ Open-source AI Gateway — Use any SDK to call 100+ LLMs. Built-in failover, load balancing, cost control & end-to-end tracing.
相关推荐
JSDoc注释规范详解:@param、@return、@type等15个必会标签一次学会
JSDoc注释规范详解:@param、@return、@type等15个必会标签一次学会 JSDoc 是 JavaScript 开发者最常用的 API 文档生成
开发工具文档AxonHub 接入 OpenCode 实战指南:Anthropic 兼容端点、模型 Profile 路由与全链路追踪
AxonHub 接入 OpenCode 实战指南:Anthropic 兼容端点、模型 Profile 路由与全链路追踪 导读 本文是一份面向开发者的实操指南,讲
AI 应用API网关LLM 网关后端前端TypeScript 映射类型(Mapped Types)实战指南:用 keyof 构建类型变换、修饰符与 key 重映射
TypeScript 映射类型(Mapped Types)实战指南:用 keyof 构建类型变换、修饰符与 key 重映射 映射类型(Mapped Types)
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考