ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AxonHub API Key Profile 实战指南:模型映射、访问限制与 Profile 切换

AxonHub API Key Profile 实战指南:模型映射、访问限制与 Profile 切换
  • 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.

项目地址:https://gitcode.com/gh_mirrors/ax/axonhub
点击查看免费下载

本篇指南围绕 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 配置界面

  1. 登录 AxonHub 管理界面;
  2. 进入API Keys(API Key 管理)页面;
  3. 找到要配置的 API Key;
  4. 打开该 Key 的Actions(操作)菜单;
  5. 选择Profiles或Configure进入 Profile 配置弹窗(对应前端组件 apikeys-profiles-dialog.tsx)。

第 2 步:创建 Profile

  1. 点击Add Profile(添加 Profile);
  2. 填写 Profile 名称;
  3. 配置模型映射、渠道限制或模型限制。

第 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

  1. 在Active Profile(生效 Profile)下拉中选择一个 Profile;
  2. 点击Save保存;
  3. 变更立即生效,无需重启服务。

“立即生效”背后有两点保障:

  • 后端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:为什么模型映射没有生效?

按以下顺序排查:

  1. 是否选中了正确的 Active Profile?如果activeProfile为空或指向不存在的 Profile,MapModel会直接返回原始模型名(见 model_mapper.go)。后端在保存时会强制校验activeProfile必须存在于 Profile 列表中(internal/server/biz/api_key.go)。
  2. 模型名是否真的匹配?精确匹配要求完全一致;正则匹配是全名锚定匹配(等价^(?:...)$),gpt-.*不会匹配my-gpt-4这类带前缀的字符串。
  3. 正则写法是否正确?非法正则(如[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,重命名后重试。

最佳实践

  1. 使用描述性名称:例如production、openrouter-mapping,让人一眼看出 Profile 的用途;
  2. 具体规则在前:映射按顺序首个命中生效,把精确规则放在通用正则之前;
  3. 启用前先测试:用测试请求验证映射结果与渠道范围符合预期后再切换为正式生效;
  4. 优先使用渠道标签:用channelTags代替硬编码的channelIDs,后续调整渠道范围时无需修改 Profile;
  5. 充分利用白名单做隔离:给外部客户、灰度环境等分别配置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.

项目地址:https://gitcode.com/gh_mirrors/ax/axonhub
点击查看免费下载

相关推荐

上一篇:VALL-E代码实现原理:深入理解AR与NAR解码器的设计思想
下一篇:lz4命令速查:常用参数与示例

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进