跳到主要内容
版本:1.9.0

成本归属标签

成本归属标签让组织按自己的维度归属用量和成本,例如部门、事业部、子公司、项目或成本中心。组织先声明标签键,再在调用方 API Key、团队和成员上设置 key=value,或者从调用方的 JWT 读取取值。每个请求按其运行时解析出的标签值归属。之后可以在 成本分析 页面按这些值拆分支出,用标签预算为其封顶,并让限流策略按标签匹配和拆分流量。

标签在 AISIX Enterprise 和 AISIX Cloud 中的行为一致。

前提条件​

开始之前,请准备以下内容:

  • 声明标签键需要组织的 owner 或 admin 角色,或拥有 tag_keys 写权限的自定义角色。在某个资源上设置标签,需要该资源的写权限。
  • 每个需要归属的环境都使用会在用量上记录标签的网关。1.6.x 及更早版本的网关不会记录,参见网关版本要求。
  • 使用 Admin API 示例时,需要控制面地址和具有写权限的 Admin Token:
# AISIX_CP 包含 /api,末尾不带斜杠。
# 本地 AISIX Enterprise 快速入门使用 http://localhost:8080/api。
export AISIX_CP="YOUR_AISIX_CONTROL_PLANE_API_URL"
export AISIX_TOKEN="YOUR_ADMIN_TOKEN"
export TEAM_ID="YOUR_TEAM_ID"
export ENV_ID="YOUR_ENVIRONMENT_ID"

声明标签键​

标签键必须先在组织中声明,资源才能带上它。预先声明可以统一用词,避免 department 同时被写成 dept 或 Department。

  1. 在组织导航中打开 标签键,选择 新建标签键。
  2. 输入 键,例如 department。
  3. 可以填写 显示名称 和 描述。
  4. 选择 声明标签键。

通过 Admin API 声明标签键:

curl -sS -X POST "${AISIX_CP}/tag_keys" \
-H "Authorization: Bearer ${AISIX_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"key": "department",
"display_name": "Department",
"description": "Cost center the usage is charged to."
}'

标签键遵循以下规则:

  • 以小写字母开头,只包含小写字母、数字和下划线,最多 63 个字符:^[a-z][a-z0-9_]{0,62}$。
  • 声明后不能修改键本身。显示名称和描述可以随时编辑。
  • 每个组织最多声明 32 个键。超出时返回 400,错误码为 TAG_KEY_LIMIT;声明已存在的键返回 409。
  • 所有成员都可以读取已声明的键,写入需要 tag_keys 写权限。

删除标签键​

只要有预算、限流策略或信任服务提供方的 tag_claims 在使用某个标签键,删除就会被拒绝,返回 409,错误码为 TAG_KEY_IN_USE。错误信息会列出它们,请先移除。

否则,删除会把该键从所有带有它的调用方 API Key、团队和成员上移除,之后的请求不再归属到它。已记录的用量保留当时的标签,因此已删除键的值仍会出现在已存的历史数据中。只要所选时间范围内的用量带有该键,成本分析 就会继续提供它,并标记为 已删除。

在 API Key、团队和成员上设置标签​

可以在三类资源上设置标签。每个资源只带自己的标签,形式为标签键到标签值的对象:

资源控制台Admin API
调用方 API Key环境 API 密钥 下 Key 的创建和编辑表单中的 成本归属标签 部分POST /environments/{environment_id}/api_keys 和 PATCH /environments/{environment_id}/api_keys/{api_key_id}
团队团队 下团队的创建和编辑表单POST /teams 和 PATCH /teams/{team_id}
成员成员 中成员行的 标签 面板,以及 AISIX Enterprise 中的 创建用户 表单POST /members 和 PATCH /members/{user_id}

控制台提供已声明的键供选择,并提示组织中该键已在使用的值。同样的建议值也可以通过 GET /tag_keys/{key}/values 获取,它列出最多 500 个去重值:调用方 API Key、团队和成员当前设置的值,以及最近 30 天用量归属到的值,从 JWT Claim 读取的值就出现在后者中。来自用量的值只填满配置值留下的名额,并且只有同时拥有 usage 读权限的调用方才能看到。组织很大时这部分查询可能超时,响应中就只有配置值。此时仍可手动输入值。

标签值遵循以下规则:

  • 值是 1 到 128 个字符的自由文本。
  • 值不能包含换行等控制字符,也不能以空白字符(包括不换行空格和全角空格)开头或结尾。控制面会以 400 拒绝这样的值。
  • 资源 tags 中的每个键都必须已在组织中声明。

在 Admin API 中,PATCH 会整体替换 tags 对象,请发送要保留的完整集合。发送 null 或 {} 清空所有标签,不传 tags 则保持不变。例如,以下请求为团队设置两个标签:

curl -sS -X PATCH "${AISIX_CP}/teams/${TEAM_ID}" \
-H "Authorization: Bearer ${AISIX_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"tags": {
"department": "rnd",
"subsidiary": "emea"
}
}'

成员的标签属于其在本组织中的成员资格,而不属于其账号。admin 可以修改成员的标签;修改成员角色仍然需要 owner。通过邀请、单点登录或 SCIM 目录同步加入的成员一开始没有标签。由目录同步管理的成员,其标签也可以在控制台和 Admin API 中编辑。目录同步既不读取也不写入标签,因此标签不会从身份提供方同步。如果要在请求时从身份提供方取值,参见从 JWT Claim 读取标签。

请求如何解析标签​

请求通过身份认证时,网关根据认证所用的调用方 API Key 解析其标签;对于 JWT 调用方,还会读取已验证的 Token。对每个标签键,取最近的值:

  1. 对于 JWT 调用方,取信任服务提供方的 tag_claims 从 Token 读取的值。参见从 JWT Claim 读取标签。
  2. 否则,取调用方 API Key 自己的值。
  3. 否则,取该 Key 绑定的团队上设置的值。
  4. 否则,取拥有该 Key 的成员上设置的值。

只有 Key 自己的团队绑定和成员绑定会参与解析,成员所属的其他团队不提供任何标签。每个请求的每个标签键只有一个值,不同的键可以组合,所以一个请求可以从 Key 取 department、从团队取 business_group、从成员取 subsidiary。请求不能自带标签,网关从不从请求头或请求体中读取标签。

例如,组织声明了 department、business_group 和 subsidiary,并做了如下设置:

资源标签
团队 platformdepartment=infra、business_group=cloud
成员 Alicedepartment=rnd、subsidiary=emea
Key K1,绑定到 platform,所有者为 Alicedepartment=ml
Key K2,绑定到 platform,所有者为 Alice无
Key K3,所有者为 Alice,没有团队无

请求解析出的标签如下:

Keydepartmentbusiness_groupsubsidiary
K1ml,来自 Keycloud,来自团队emea,来自成员
K2infra,来自团队cloud,来自团队emea,来自成员
K3rnd,来自成员未设置emea,来自成员

从 JWT Claim 读取标签​

对于使用 JWT 认证的调用方,信任服务提供方可以从已验证的 Token 中得到标签。它的 tag_claims 字段把标签键映射到 Claim 路径,网关在每个请求上读取这些 Claim。用量分析、导出、标签预算和标签限流都使用读取结果,因此无需逐个给调用方 API Key 打标签,也无需与身份提供方保持同步。

读取到的值只作用于当前请求,不会写回调用方 API Key、团队或成员,因此这些资源的页面不会显示它。

Claim 必须在到达网关的 JWT 里。只有身份提供方的 userinfo 端点才返回的字段读取不到,需要在身份提供方上配置,把它放进 Token。对于使用交互式登录的 MCP 客户端,Claim 必须在 ID Token 里。

配置标签 Claim​

先声明每个标签键,再在环境的 OIDC 身份提供方 视图中创建或编辑服务提供方。在 来自 Token 声明的标签(可选) 下选择 添加标签,选择已声明的键并填写 Claim 路径。列表中服务提供方所在的行会显示当前的映射。

通过 Admin API 设置同样的映射时,在创建或更新服务提供方时使用 tag_claims:

export OIDC_PROVIDER_ID="YOUR_OIDC_PROVIDER_ID"

curl -sS -X PATCH "${AISIX_CP}/environments/${ENV_ID}/oidc_providers/${OIDC_PROVIDER_ID}" \
-H "Authorization: Bearer ${AISIX_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"tag_claims": {
"department": "department",
"subsidiary": "org.subsidiary"
}
}'

PATCH 会替换整个 tag_claims 对象。发送 null 或 {} 可移除所有映射,省略该字段则保持不变。

以下情况控制面返回 400,错误码为 INVALID_REQUEST:键未在组织中声明或不符合标签键格式、条目超过 32 条、Claim 路径为空。被映射引用的键要先移除映射才能删除。

Claim 如何变成标签值​

网关先把 Claim 路径当作一个完整的顶层 Claim 名读取,所以 https://example.com/department 这样的名字可以使用。Token 中没有这个名字的 Claim 时,再按点号逐层访问嵌套对象,所以 org.subsidiary 读取 org 内的 subsidiary。

然后按下表转换 Claim 的值:

Claim 值标签值
"rnd"rnd,原样使用
10421042,数字转为文本
["rnd", "ml", "rnd"]ml,rnd,元素去重后按文本排序,再用逗号拼接
["rnd"]rnd,元素本身
true、null、{} 或 []缺失
["rnd", true],即数组中含有字符串和数字以外的元素缺失

结果必须符合与其他标签值相同的规则:1 到 128 个字符,不含控制字符,首尾没有空白。对于数组,每个元素和拼接结果都要满足这条规则。不符合规则的值按缺失处理,不会被截断或去除空白。

一个数组只产生一个标签值,而不是多个。groups Claim 为 ["ml", "rnd"] 的用户带有的值是 ml,rnd,所以 groups=rnd 上的预算不会计入该用户的请求。元素内的逗号不做转义,所以 ["a,b"] 和 ["a", "b"] 产生相同的值。

回落和默认值​

标签 Claim 按标签键逐个解析,优先于调用方 API Key、团队和成员上的值。Claim 缺失时,只有该键回落到后面几层。

要让缺少该 Claim 的请求仍然可见,可以在调用方 API Key 上设置默认值,例如 department=unassigned。这些请求会在成本分析中归到这个值下,也可以为它设置预算。如果某个标签键在所有来源上都没有值,请求就不受该键上的预算或限流策略约束。

取值变化时​

身份提供方上的值变化后,要等签发新 Token 才生效。已记录的用量保留其运行时带有的值,因此人员调动部门不会重新归属历史用量。

通过 MCP 交互式登录登录的用户使用登录时记录的 Claim,刷新会话也不会更新,只有重新登录才会得到新值。服务提供方的 tag_claims 本身的变更,在这两种情况下都对之后的请求生效。

选择低基数的 Claim​

建议映射取值不多的 Claim,例如部门或成本中心。每个不同的值都是成本分析中的一个分组,也是按该标签分组的限流策略中的一个计数器。

多人共用一把调用方 API Key 时,避免映射到每人一个值的字段,例如邮箱。用量按 Key 和标签集合汇总,这样会明显增加汇总数据量。这个值还会进入用量记录和导出,请注意隐私影响。

共用 Key 时的行为​

同一把调用方 API Key 上,不同请求从 Claim 读取到的标签可以不同,这会带来两个可见的影响:

  • 同一把 Key 上,标签预算可能拒绝一个 Token 而放行另一个。aisix_budget_* 指标按 Key 打标签,所以反映的是最近一次请求的判定。
  • 已完成 batch 的用量带有观察到任务完成的那个调用方的标签,而不是提交 batch 的调用方的标签。

排查标签 Claim​

要查看 Claim 产生了什么值,可以在 成本分析 中按该标签键分组,或者在请求日志中展开请求所在的行,查看 标签。如果请求显示的是来自 Key、团队或成员的值,或者落在 未设置 中,说明没有从 Claim 得到值。

具体原因只出现在网关的 debug 日志中。通过 log_level 或 RUST_LOG 为 aisix::auth 目标开启 debug 日志,例如 info,aisix::auth=debug,然后重启网关。此后每个请求上每个被丢弃的 Claim 都会写一行 claim yields no tag value,带有 provider、tag_key 和 reason。这行日志不包含取值本身。

reason含义
claim_absentToken 中该路径上没有 Claim。
value_type值是布尔、null 或对象。
empty_array值是空数组。
array_element_type数组元素不是字符串或数字。
length值、数组元素或拼接结果不是 1 到 128 个字符。
control_character值、数组元素或拼接结果含有控制字符。
surrounding_whitespace值、数组元素或拼接结果的首尾有空白。

归属在请求运行时确定​

网关会把解析出的标签和 Key 的团队记录在该请求的每条用量事件上,包括重试、故障转移以及网关自己的拒绝。控制面随事件一起存储它们,并按记录的内容归属成本。

因此,标签的变更只影响之后的请求。修改 Key、团队或成员的标签,修改信任服务提供方的 tag_claims 或它读取的 Claim 值,把 Key 绑定到其他团队,或删除标签键,都不会重新归属已经记录的用量。在设置标签之前记录的用量,以及不记录标签的网关上报的用量,在该标签上都没有值,显示为 未设置。

按标签分析成本​

组织级的 成本分析 页面一次按一个维度,对组织内所有环境的用量分组。页面分为两部分:筛选 选定要分析的用量,统计 对其进行拆分。建议按这个顺序操作:先筛选用量,再对剩下的用量分组,然后下钻。例如,先筛选 business_group=cloud,再按 department 标签分组,查看该业务组的花费在各部门之间如何分布。

筛选 部分在前:

  • 时间范围:最近 24 小时、7 天、30 天,或自定义范围。页面按整点小时读取,开始时间向下取整,结束时间向上取整。
  • 筛选条件:环境、团队、成员、模型,以及任意多个标签 key=value 筛选。不同标签键的筛选必须同时满足,同一个键的多个值满足其一即可。从 API Key 行下钻时,会把该 Key 加为筛选条件。每个生效的筛选条件都以标签块的形式显示在控件下方,清除筛选 可一次清除全部筛选。

统计 部分随后对筛选后的用量进行拆分:

  • 分组方式:标签键、团队、成员、API Key、模型、环境、日期或目标模型。参见对比模型的目标模型。按日期分组时显示按天的柱状图,其他分组显示横向条形图。
  • 排序依据:成本、总 Token、输入 Token、输出 Token、请求数或成功请求数,分组按从大到小排列。
  • 刷新:重新加载数据,同时重新加载标签键以及团队、成员和环境的名称。

选择某一行即可下钻:该行的分组会成为筛选条件,页面随后选择筛选尚未固定的下一个分组方式。表格上方的面包屑可以回到之前的任意一步。未设置 行汇总在该分组上没有值的用量,不能下钻;日期行也不能下钻。表格最多列出 50 个分组,其余的汇总在 其他 N 个分组 行中,因此列出的分组加上该行等于合计。

按团队或标签键分组时,预算(本期) 列显示对应团队预算或标签预算的本期已花费和额度。按 API Key 分组时,每个 Key 行都可以跳转到该 Key 所在环境的请求日志,并已按该 Key 筛选。

分组方式、标签筛选和下钻中提供的标签键,是组织已声明的键,加上所选时间范围内用量带有、但当前已不再声明的键。后者是在这些用量记录之后被删除的键,会标记为 已删除,仍然可以用于分组和筛选。取值建议来自调用方 API Key、团队和成员当前带有的值以及最近的用量,已删除的键没有取值建议:请手动输入值,或从分组行下钻。如果一个标签键都没有,分组方式和筛选的下拉框会显示提示,并链接到标签键设置页,标签筛选在声明标签键之前不可用。

团队、成员、API Key 或环境在其用量记录之后被删除,仍会单独成行。分组行、图表标签、面包屑和筛选标签块会显示其 ID 的前 8 个字符,并带有 已删除 徽标;已不在组织中的成员显示 已移出。选择徽标可以查看完整 ID 并复制。名称不可用 徽标表示控制台无法确认该资源是否仍然存在,例如团队列表加载失败,并不代表该资源已被删除。

控制面升级后,在升级前记录的用量补入期间,页面显示与 用量 视图相同的回填提示,回填完成前数据并不完整。成本使用组织币种。

所有有用量读取权限的成员都可以打开成本分析。自动化可以通过 GET /usage_analysis 读取相同的数据,并通过 GET /usage_analysis/tag_keys 读取某个时间范围内用量带有的标签键(包括已删除的键),参见 AISIX Enterprise / Cloud Admin API 参考。

对比模型的目标模型​

模型行按调用方指定的名称分组。要查看模型组的流量在其背后每个模型上的情况,请在模型行上选择 查看目标模型,或在 分组方式 中选择 目标模型。每一行显示目标模型的 Provider Key、计费金额和 平台成本。平台成本是用量按目标模型自身价格计算的成本,不含模型组价格和低峰折扣。没有价格的部分会以未定价尝试次数显示在金额旁边。

这些计数是每个目标模型上的尝试次数,包含失败的尝试和回退,因此各目标模型相加可能超过该模型的请求数。直接调用某个模型的用量记在该模型自己的行,包括由网关缓存直接返回的响应。调用模型组却没有到达任何目标模型的用量(例如由网关缓存直接返回的响应,或在选中目标模型之前就失败的请求)落在 未设置 行。目标模型行和平台成本只覆盖引入它们的控制面版本之后记录的用量,所以更早的时间范围在这里会少于模型那一行。目标模型无法按团队、成员、API Key 或标签筛选,这些筛选生效时 查看目标模型 和该分组不可用。

自动化可以通过 GET /usage_analysis?group_by=target_model 读取相同的数据,model=<name> 可限定到一个模型组。它的权限与其他用量读取相同。与 team_id、member_id、api_key_id 或 tag 同用会返回 400。每个分组包含:

字段含义
key目标模型 ID。调用模型组却没有到达任何目标模型的用量(例如缓存命中)为 null。
name目标模型当前的名称,模型不再存在后为 null。
environment_id目标模型所属的环境。模型不再存在后不返回。
provider、provider_key_name目标模型的服务提供方密钥的厂商 ID 和显示名称,模型或密钥不再存在后为 null。
cost_usd计费金额,币种为组织币种。
prompt_tokens、completion_tokens、total_tokens这些尝试的 Token 数量。
request_count该目标模型上的尝试次数,每次失败的尝试和回退都算。
success_count该目标模型上成功的尝试次数。
upstream_cost平台成本之和,所有尝试都未定价时为 null。
unpriced_request_count该目标模型上 upstream_cost 为 null 的尝试次数。没有到达任何目标模型的尝试不计入。

remainder 和 total 也带 upstream_cost 和 unpriced_request_count。

按标签设置预算​

标签预算为组织内所有环境中归属到某个标签值的支出封顶。在预算视图中选择 标签 范围,再选择标签键并填写值。在 Admin API 中,将 scope 设为 tag,scope_ref 设为 <key>=<value>,例如 department=rnd。键必须已声明,值为第一个 = 之后的全部内容。limit_cents 以组织币种的最小单位计。

curl -sS -X POST "${AISIX_CP}/budgets" \
-H "Authorization: Bearer ${AISIX_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"display_name": "R&D monthly",
"scope": "tag",
"scope_ref": "department=rnd",
"limit_cents": 50000,
"period": "month",
"hard_stop": true
}'

标签预算的周期、到达限额即阻断(hard stop)和告警规则与其他预算相同,每个标签值只能有一个预算。支出按每个请求用量上记录的标签计入。达到限额的阻断型标签预算会以 429 拒绝之后解析出同一标签值的请求,错误码为 budget_exceeded,其中 scope 为 tag,scope_ref 为该 key=value。

只要组织任一环境中注册的网关运行的是不记录标签的版本(即 1.6.x 及更早版本),创建标签预算就会被拒绝,返回 422,错误码为 DP_INCOMPATIBLE,因为这类网关的用量不会计入该预算。

按标签限流​

条件式限流策略在条件维度和 group_by 中都接受 tag:<key>,键必须已声明。例如,以下策略为每个部门分别设置每分钟 600 个请求的限额:

curl -sS -X POST "${AISIX_CP}/environments/${ENV_ID}/rate_limits" \
-H "Authorization: Bearer ${AISIX_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "per-department-rpm",
"group_by": ["tag:department"],
"limits": { "rpm": 600 }
}'

解析出 department=rnd 的两个调用方 API Key 共用一个计数器,解析出 department=sales 的 Key 有自己的计数器。请求在该标签上没有值时,既不命中针对它的条件,也不命中取反后的条件;按该标签分组的策略也不约束它。

只要该环境中有注册的网关运行 1.6.x 及更早版本,保存使用 tag: 的策略就会被拒绝,返回 422,错误码为 DP_INCOMPATIBLE,因为这些版本无法加载它。

在用量事件和请求日志中读取标签​

每条用量事件都带有 tags(请求解析出的标签)和 team_id(调用方 API Key 绑定的团队),为空时省略。可观测性导出器会把它们发送到外部系统,请求日志在行详情和导出中显示它们。各导出器中的字段名见将事件归因到团队和标签。标签不会成为 Prometheus 指标标签。

网关版本要求​

标签由网关解析并记录。1.6.x 及更早版本的网关会忽略它们:

  • 它能加载带标签的调用方 API Key、团队和成员,但会忽略 tags 字段。控制台会为这些网关显示部分兼容提示。
  • 它的用量不带标签,也不带 team_id,因此在成本分析中显示为 未设置。
  • 它会丢弃使用 tag: 的限流策略。

从 JWT Claim 读取标签需要 1.9.0 或更高版本的网关。更早版本的网关能加载信任服务提供方,但会忽略 tag_claims,因此只按 Key、团队和成员归属请求。环境中注册了这类网关时,带 tag_claims 的服务提供方仍可保存成功:控制台会显示警告,Admin API 响应的 warnings 中会有一条错误码为 field_ignored、字段为 tag_claims 的记录。请先升级控制面,再升级网关。

如上所述,只要注册了这类网关,控制面就会拒绝保存标签预算和标签限流策略。这项检查只能看到保存预算或策略时已经注册的网关。

已知缺口

如果有网关回滚到 1.6.x,或者在标签限流策略或标签预算保存之后又有 1.6.x 网关加入,那么标签限流在这台网关上不生效,它的用量也不会计入标签预算。受影响环境的概览页面会显示被拒绝的配置。将该网关升级即可恢复限流和计数。

下一步​

使用预算和预算告警与通知为标签支出封顶并进行监控。要将带标签的用量与环境级合计对照,参见用量上报。