跳到主要内容
版本:1.5.0

指标与用量事件

AISIX AI 网关会公开聚合指标和可导出的用量事件。这些信号共同反映服务健康状况、流量趋势,以及每次请求尝试对应的模型、路由和策略结果。

选择遥测来源​

请先选择与运维问题匹配的来源;需要深入调查问题时,再按请求、模型或服务提供方关联各类信号。

运维问题首选来源提供的信息
各网关实例的流量是否健康?Prometheus 指标请求速率、延迟分布、Token 和成本计数、策略结果、路由健康、缓存行为和导出器投递健康。
单个请求发生了什么?访问日志结构化请求字段,包括状态、延迟、模型、模型服务提供方、请求 ID,以及可用时的路由结果。
调用应用可以观察到什么?响应头受支持路由上的请求关联、缓存结果、重试时机和选中目标提示。
请求记录可以存储到哪里、如何分析或用于核算?用量事件通过可观测性导出器投递的逐次尝试结果和消耗记录。

抓取 Prometheus 指标​

AISIX 默认通过专用指标监听器在 /metrics 提供 Prometheus 指标。你可以通过启动期可观测性设置修改路径或禁用该端点。

该端点设计上不做认证。请保持专用指标监听器私有。

在启动配置中配置 Prometheus 暴露:

config.yaml
observability:
metrics:
prometheus:
enabled: true
path: "/metrics"

专用监听器默认绑定到 0.0.0.0:9090。如果 Prometheus 应从其他接口或端口抓取指标,请设置不同的监听地址。

抓取默认指标端点:

curl -sS "http://127.0.0.1:9090/metrics"
产生流量后才会出现流量指标

AISIX 会在每次抓取时发布配置状态。其他指标族会在首次观测时注册,因此流量指标可能不会在启动后立即出现。请先发送一次模型请求,再检查 aisix_requests_total 和 aisix_tokens_consumed_total 等序列。

AISIX 使用 aisix_ 前缀输出原生指标名称。使用直方图序列计算跨网关实例的延迟百分位数,并使用请求计数器分析成功率和路由情况。精确指标名称、标签范围和 PromQL 示例请参见指标参考。

通过 observability.metrics.labels 可以选择各指标族输出的标签。配置示例、默认值和完整变量列表参见指标标签与变量。

区分请求计数与尝试计数​

请求是一次调用方交互。重试或故障转移可能让一个请求产生多次用量事件尝试。部分尝试会在目标级限流或请求组装阶段停止,尚未调用模型服务提供方,因此 AISIX 在不同信号中对尝试的计量方式也不同:

单位统计位置一个样本或一行代表什么
请求aisix_proxy_requests_total、aisix_llm_requests_total一次客户端请求,状态是调用方实际收到的状态码。
尝试aisix_deployment_requests_total 及部署指标族对某一个目标模型的一次上游调用。
发出尝试aisix_usage_events_emitted_total一次用量事件的发出尝试,在投递队列接受或拒绝它之前计数。
尝试用量事件记录,以及基于其构建的用量日志一次已记录的处理尝试,包括在上游调用前停止的尝试。同一请求的各次尝试共享 request_id,并按 attempt_index 排序。

如果某个目标返回 502,而后备目标成功,请求计数器只记录最终返回给调用方的 200。部署计数器和用量事件则会保留失败的尝试。因此,用量日志中的 5xx 尝试数可能远高于请求指标;两者计量的单位不同。

请按各查询真正回答的问题选用:

# 最终以服务端错误结束的请求——调用方实际经历的情况。
sum(increase(aisix_proxy_requests_total{status=~"5.."}[24h]))

# 发生过故障转移、但最终仍以服务端错误结束的请求。
sum(increase(aisix_proxy_requests_total{status=~"5..", is_fallback="true"}[24h]))

# 失败的上游尝试,按目标聚合——包含后续被故障转移救回的那些。
# 这是 5xx 用量日志行数在尝试层面的对应视图,范围限于通过模型组下发的端点。
sum(increase(aisix_deployment_failure_responses_total[24h])) by (model)

# 成功救回请求的故障转移,按模型组和实际到达的目标聚合。
sum(increase(aisix_routing_successful_fallbacks_total[24h])) by (model, fallback_model)

# 用量事件的发出尝试,以及被交接队列拒绝的事件。
sum(increase(aisix_usage_events_emitted_total{status_code="5xx"}[24h]))
sum(increase(aisix_usage_event_drops_total[24h]))

# 某个成员被限流拒绝的次数。`status` 携带原始 HTTP 状态码,
# 因此无需扫描整个状态族即可定位单一故障模式。
sum(increase(aisix_usage_events_emitted_total{user_id="<member-id>", status="429"}[24h]))

# 哪些队列交接被拒绝了。队列侧的 reason 与发出计数器携带相同的模型与
# Provider Key 标签,因此该差值在按模型的粒度上同样成立,而不只是总量。
sum(increase(aisix_usage_event_drops_total{reason=~"sink_.*"}[24h])) by (model, provider_key_name)

# 网关已记录但未能投递到控制面的用量。这些样本只携带成员标签,
# 因此按成员分组,而不是按模型分组。
sum(increase(aisix_usage_event_drops_total{reason=~"send_failed|retry_budget_exhausted"}[24h])) by (reason, user_name)

比较相同的数据范围​

在将两个总数视为不一致之前,请先检查它们的覆盖范围:

差异原因检查方法
抓取覆盖范围请求计数器不带环境标签。只有 Prometheus 抓取了服务该环境的每个网关,单环境用量总数才可与之比较。按 job 和 instance 对原始计数器分组,再与正在运行的网关实例核对。
时间覆盖范围increase(...[24h]) 只使用该区间内存在的样本。重启或更短的 Prometheus 保留期可能造成用量日志查询中没有的缺口。在相同时间范围内绘制原始计数器。
投递覆盖范围aisix_usage_event_drops_total 既统计因接收端被禁用、队列已满或关闭而无法进入投递队列的事件(sink_disabled、sink_full、sink_closed),也统计工作线程已接受、但未能投递到控制面的事件(send_failed、retry_budget_exhausted)。导出器侧的丢失不计入该指标。队列侧丢弃按 model 分组;投递侧丢弃按 user_name 分组——它们不带模型和 Provider Key 归因。监控控制面投递的 telemetry batch failed (events dropped) 和导出器投递的 sink delivery dropped。控制面在已接受的批次中拒绝的事件则由 aisix_usage_events_rejected_total 统计。
没有上游调用目标级限流和请求组装失败仍会产生用量事件,但没有部署计数器样本。组装失败包括凭证不可用、缺少 model_name,以及 api_base 缺失或格式错误。配置错误的目标会产生失败请求,但没有部署失败,因为模型服务提供方从未被调用。

部署指标族覆盖通过模型组分发的端点,而用量事件也覆盖直连模型。请逐项隔离差异,不要将全部差值都解释为网关侧拒绝。

导出用量事件​

用量事件是受支持代理路径输出的逐次尝试记录。发生重试或故障转移的请求会生成多条具有相同 request_id 的事件,并按 attempt_index 排序。因此统计这些记录得到的是尝试数而非请求数——在拿记录条数与请求指标作比较之前,请先阅读区分请求计数与尝试计数。/v1/messages/count_tokens 以及支持的端点中列出的单次调用端点遵循同样的规则:每次导致重试或故障转移的失败尝试都会产生一条自己的零 Token 事件。

每条事件都携带 occurred_at,即网关记录该事件的时间,格式为带恰好三位小数的 RFC 3339 UTC 时间戳,例如 2026-09-24T12:42:25.123Z。对象存储、阿里云 SLS 和 Datadog 按原样接收该字符串,OTLP 导出器则在其纳秒时间戳中携带同一时间。因此,同一请求的各次尝试,以及在同一秒内完成的事件,都可以精确到毫秒排序。RFC 3339 解析器都接受带小数的形式;只接受整秒(…:SSZ)的解析器需要更新。

用量事件不能从本地端点读取。请在可观测性导出器中配置导出器,将其投递到 OTLP/HTTP、对象存储、阿里云 SLS 或 Datadog。

每条事件都会包含请求结果、消耗详情、调用方请求的模型别名,以及网关能够观测到时处理该次尝试的解析模型。在 /v1/chat/completions 上,每条事件还会携带 cache_status(disabled、miss、hit 或 bypass);由响应缓存返回的响应会把命中层记录在 cache_hit_layer(exact 或 semantic),语义命中还会把匹配相似度记录在 cache_similarity。流式响应从不被缓存,因此即使环境中启用了缓存策略,流式请求的事件也会上报 cache_status="disabled"。

延迟字段会区分服务提供方耗时和调用方可见耗时:

字段范围
upstream_latency_ms一次上游尝试所花费的时间,不包括请求解析、安全护栏、路由、重试延迟和先前尝试。
upstream_ttft_ms从一次上游尝试开始到首个流式帧的时间,无论帧类型为何——包括 response.created、仅含角色的 chat 增量等元数据起始帧,与调用方侧代理的统计口径一致。非流式请求、错误和缓存命中时省略或为零。
downstream_latency_ms从收到请求到向下游投递的时间:非流式响应为完整响应,流式响应为首个 Token 或转发帧,A2A 和 /v1/audio/speech 则为整个流。它包括网关处理、重试及重试延迟和输出暂存,仅出现在最后一次尝试中。

如果调用方放弃请求,仍会生成用量事件,因为上游可能已经执行工作并计费。这些事件的状态码为 499、error_class 为 client_disconnected 而不是 200,因此可以作为一个类别筛选;流式传输中途断开时,只统计断开前已到达的 Token。统计成功请求时请使用 status_code = 200,以排除被放弃的请求。

用量日志记录了什么​

用量事件是请求的记录,而不是计费条目。只要 AISIX 能够将请求归因到某个调用方,就会为其写入用量事件,包括从未到达模型服务提供方的请求,以及没有任何可计费内容的请求(这类请求记录为零 Token)。可以用下表区分「没有记录」和「记录了一次拒绝」:

结果是否记录记录内容
成功是状态 200、解析出的目标,以及上报的消耗量。
Rerank 成功但没有 Token 数是状态 200,零 Token。例如 Cohere 的 rerank 模型上报的是搜索单元而不是 Token,AISIX 不对搜索单元定价。
模型服务提供方不支持该端点是状态 501,零 Token,没有上游调用。当解析出的模型服务提供方缺少对应能力时,AISIX 会在文本补全、Embeddings、图像生成和视频提交上自行返回该状态。
缓存命中是状态 200,回放所存响应的 Token 数量,成本为零,cache_status="hit",cache_hit_layer 为 exact 或 semantic,语义命中还带 cache_similarity。由于没有联系任何上游,它不携带 provider_request_id,也没有路由目标,但 provider_model_version 会标明产出所存响应的那个模型。只有非流式请求会被缓存,因此缓存命中与流式响应不会同时出现。
模型未找到是状态 404、error_class="model_not_found",model_id 为空。
被限流拒绝是状态 429、error_class="rate_limit_exceeded"。调用方 Key 级、模型级和限流策略的拒绝都是这一形态,三者都在下发前完成检查。
预算耗尽是状态 429、error_class="billing_error"。
安全护栏拦截是状态 422、error_class="content_filter",guardrail_blocked 为 true。
上游错误或超时是每次失败的尝试各一条事件,携带该次尝试的 attempt_index、attempt_kind、attempt_model、error_class 和 error_message。如果后续某次尝试成功,则它的成功事件即最终事件;如果所有尝试都失败,则最后一次失败尝试自身的事件即最终事件,不会额外再写一条。
调用方在响应头写出前断开是状态 499、error_class="client_disconnected"、error_message="client closed the request before the response head was written",Token 数量和成本均为零。
响应正文在被读取前即被丢弃是状态和类别相同,error_message="client closed the request before the response body was streamed"。由于上游已经应答,事件会标明处理它的目标。
调用方在流式传输中途断开是状态和类别相同,error_message="client closed the request while the response was streaming",携带断开前已投递的 Token。
流的 200 响应头发出后上游失败是同一失败若发生在响应头之前会得到的状态,例如 502 或 504;error_class 和 error_message 取自该失败本身。参见响应头发出后失败的流。
认证失败否只写指标。被拒绝的凭证也不会产生访问日志,因此没有可供按 request_id 关联的记录。
请求正文解析错误或超过大小限制否只写访问日志和指标。
未认证的请求否不记录任何内容,这也正是健康检查与发现类路由保持静默的原因。
/livez、/readyz、/v1/models、/v1/videos/{id} 和 /v1/videos/{id}/content、OAuth 受保护资源发现路由,以及 /a2a/{agent}/.well-known/agent-card.json否这些路由在任何结果下都不产生用量事件。视频作业的工作量已由提交请求计量。

缓存命中是用所存响应作答的,因此它的事件描述的是调用方寻址的条目,而不是任何目标:

  • provider_model_version 标明产出所存响应的那个模型,它读自缓存条目而非本次请求,与当初那次调用自身事件上的值相同。对模型组而言,这是该条记录上唯一能指出产出方的字段。仅当所存响应本身没有携带模型名时它才为空。
  • provider_request_id 保持为空。它是客户用来与模型服务提供方控制台对账的标识,而本次请求并未到达任何模型服务提供方。
  • 在 routing 或 semantic 条目的命中上,由 Provider Key 派生的字段——provider_kind、provider_featured、branded_provider、pk_label 和 byo_label——为空,provider 上报 unknown。1.2.0 及更早版本的网关会用该请求路由策略排在首位的那个目标来填充它们,从而标注了一个从未运行过的目标,而且同一条缓存条目的两次命中还可能标注不同的目标。由此带来一个变化:按 Provider Key 标签筛选请求日志时不再返回模型组的缓存命中,而按模型服务提供方统计的请求面板会把它们计入 provider="unknown",旁边的 cache_status="hit" 说明了原因。
  • 直连模型的命中仍保留该模型自身的模型服务提供方、Provider Key 和上游模型,它们是该模型的静态属性,而不代表发生过下发。

该请求的访问日志遵循相同规则,参见解读缓存判定结果。

被取消请求的 requested_model 是调用方寻址的条目——对于路由请求即模型组名称——model_id 则是它已经确定的目标。只有在没有任何尝试落定、且调用方寻址的条目本身就是模型组时,model_id 才为空:模型组本身不产生费用,其标识符永远不会写入该字段。而在任何尝试开始前被取消的直连模型仍会记录自己的标识符,这与 model_not_found 事件采用的约定一致。被取消的请求也不携带安全护栏归因,即使某次尝试已经应答也不携带 Token 数量,因为这两者都产生于网关来不及执行的响应处理阶段。

调用方不指定模型的路由——/mcp、/a2a/{agent}、透传命名空间、升级前的 /v1/realtime,以及文件、批处理和微调相关端点——同样会写入这条 499 记录。它们的模型字段为空,由各自的归因字段代替:passthrough_route_name,mcp_server_name 与 mcp_tool_name,或 a2a_agent_name 与 a2a_method、a2a_operation。该记录仍然携带 api_key_id、调用方身份、auth_type、operation 和 inbound_protocol。

响应头发出后失败的流​

流式响应会在上游产出答案之前就发出 200 状态行,因此之后到达的失败无法再改变客户端收到的内容。用量记录在流结束时写入,它报告的是流实际如何结束,而不是客户端响应行上的 200:

流的结束方式记录的状态
上游断开连接,或发送了 AISIX 无法解码的内容502
流中途读取上游超时504
上游在流内发送了携带 4xx 状态的错误事件该状态。Anthropic 的流内错误按 Anthropic 为该错误类型记载的状态映射,因此 rate_limit_error 记录为 429。
上游在流内发送了其他错误事件,包括 5xx 状态,以及不携带 HTTP 状态的 Responses API error 或 response.failed 事件502
桥接的 /v1/responses 上游流不含任何内容、推理、工具调用或结束原因502,error_class="stream_aborted",消息说明上游返回了空流
调用方在流结束前离开499,error_class="client_disconnected"
流正常结束200

即使客户端收到的是 200,error_class 和 error_message 描述的也是失败本身。上游失败与调用方断开同时发生时,记录的是上游失败:转发传输错误的中继会中止连接,这与调用方离开看起来完全一样。

以上规则适用于 /v1/chat/completions(包括流式 ensemble 的 judge,panel 成员的记录仍为 200)、原生与转换路径上的 /v1/responses 和 /v1/messages、流式 /v1/audio/transcriptions、/v1/audio/speech,以及透传路由。携带可识别信封(OpenAI Chat Completions、Completions 或 Responses,以及 Anthropic Messages)的透传路由会像类型化端点一样读取流内错误事件;raw 路由没有可识别的错误信封,只记录传输失败和读取超时。

以下结果不变:安全护栏拦截保持其自身状态,已产出内容但未带结束原因就结束的流仍为 200,正常结束的流仍为 200。

由于这些流现在被记录为失败,控制台的成功率会相应下降,它们也会退出只统计 2xx 请求的延迟分位数。aisix_usage_events_emitted_total 上的 status 和 status_code 标签同样随之变化,请求的访问日志行与其用量事件保持一致。成本、预算和限流不依赖状态,因此不受影响。

1.4.0 及更早版本的网关会把大多数此类流记录为 200,error_class 和 error_message 为空。在其转换路径的 /v1/messages 上,上游失败被记录为 499;上游断开的流式转录也是如此。

备注

由于这些取消记录此前并不存在,环境的请求数现在会包含被放弃的请求,升级后成功率可能下降。控制面的这两个数值都由用量事件推导而来。延迟分位数不受影响,因为它们本来就只统计成功的请求。

访问日志、用量事件与 Logs 页面​

这三种信号计量的对象不同,因此发生重试的请求产生的数量也不同:

信号每个请求的数量在哪里查看
访问日志无论结果如何,恰好一条网关的标准错误流
用量事件每次尝试一条,外加该请求的最终事件可观测性导出器以及控制面
Logs 页面记录每条用量事件一行AISIX Cloud 请求日志

请通过 request_id 关联它们,调用方也会以 x-aisix-request-id 收到该值。在同一个请求内部,按 attempt_index 对各次尝试排序,并通过 attempt_kind 区分重试与故障转移。由于三者数量不同,拿记录条数与请求指标作比较就是在比较两种不同的单位——请先阅读区分请求计数与尝试计数。

在高于 1.2.0 的网关上,流式请求的访问日志在流结束时与最终用量事件一同写出,因此两者的 status、error_kind/error_class 和失败消息一致。

用量事件如何送达控制面​

这条链路只存在于托管模式。网关在内存中对用量事件排队,由一个工作线程分批投递到控制面。队列容量为 16,384 条;队列满时,网关会丢弃当前正在发出的事件而不是让它等待,因为遥测不能拖慢请求路径。此类丢弃会增加 aisix_usage_event_drops_total{reason},reason 取值 sink_full、sink_closed 或 sink_disabled 说明原因,同时写入 usage event dropped 日志。

工作线程在累积 100 条事件或每隔 5 秒时(以先到者为准)刷新一次,这也是刚完成的请求需要几秒才会出现的原因。同一时刻只有一个批次在途,批次按顺序发出。

投递失败的批次会被重发——前提是控制面已表明它能识别自己已经存储过的批次,不会重复计数。首次重发等待 1 秒,此后每次尝试等待时间翻倍,上限 30 秒。因此,控制面的一次中断不再意味着丢失该时间窗内记录的用量。首次失败会写入 telemetry batch failed; re-sending (the control plane de-duplicates by batch id) 日志,之后成功投递的批次会写入 telemetry batch delivered after re-sending 日志。

重发是有上限的,因为到达过晚的用量记录已经进不了它所属的计费窗口:

  • 批次在其最早一条事件发生 30 分钟后被放弃——不是在首次尝试 30 分钟后。轮到某个批次时它已经超过该时限的,会被直接放弃,连一次尝试都不做。
  • 批次在控制面作出应答的 8 次连续失败之后也会被放弃。完全没有应答的失败——连接错误或超时,也就是中断所产生的情况——不计入该上限;这类失败由上面的时限来约束。
  • 应答表明该批次永远不可接受时,批次立即结束,不再重发。
  • 未表明具备去重能力的控制面,每个批次仍然只尝试一次,与重发能力出现之前完全一致。
  • 关闭时,手上的批次只做最后一次尝试,不会等待退避间隔。

被放弃的批次中,每条事件都会计入 aisix_usage_event_drops_total{reason}:send_failed(从未重发就被放弃)或 retry_budget_exhausted(超过时限,或 8 次被应答的失败),并写入带 reason、attempts 和 batch_id 的 telemetry batch failed (events dropped) 日志。这些样本只携带成员标签(user_id、user_name);model 和 Provider Key 标签为 unknown,因为工作线程持有的是事件本身,而不是发出该事件的处理器所用的标签集。

批次重发期间,新事件仍会不断进入队列,而队列是唯一承载它们的地方。因此,用量仍会在两种情况下丢失:队列被填满,此时新发出的事件按 sink_full 丢弃;以及中断时长超过上述上限。请对 aisix_usage_event_drops_total 的任何增长告警,并按 reason 拆分以区分这两种情况。

控制面也可能接受一个批次,却拒绝其中个别未通过逐字段校验的事件,例如 ID 格式错误或状态码超出 HTTP 范围。这些事件会被丢弃而不会重发,因为控制面会再次拒绝它们。1.4.0 之后版本的网关会把被拒绝的数量累加到 aisix_usage_events_rejected_total,并记录带 batch_id、count 和 rejected 的 control plane rejected usage events in an accepted telemetry batch (events dropped) 日志。该计数器没有标签,因为控制面只返回一个数量;这些事件也不计入 aisix_usage_event_drops_total。1.4.0 及更早版本的网关会把任何成功应答都视为全部送达。

因此,缺失的记录可能属于本来就不产生记录的请求(见上表),可能是在上述环节之一丢失的事件(丢弃计数器的 reason 可以区分),也可能是被控制面拒绝的事件(由 aisix_usage_events_rejected_total 统计)。

区分请求类型​

每条事件都携带 operation,用于标识匹配端点所执行的工作类型。inbound_protocol 和模型名称都无法可靠地区分对话、图像、视频或其他操作,因此按请求类型聚合流量时请使用该字段。

其取值是一个适合索引、分组和绘图的固定集合:

取值端点
chat/v1/chat/completions
messages/v1/messages
count_tokens/v1/messages/count_tokens
responses/v1/responses
completions/v1/completions
embeddings/v1/embeddings
rerank/v1/rerank
image_generation/v1/images/generations
image_edit/v1/images/edits
transcription/v1/audio/transcriptions
translation/v1/audio/translations
speech/v1/audio/speech
video_generationPOST /v1/videos
realtime/v1/realtime
files、batches、fine_tuning文件、批处理和微调管理端点
batch_completion网关自己对已完成批处理作业的计量,在作业完成时记录,而非在提交时记录
mcp、a2aMCP 网关和 A2A 网关
passthrough透传路由

查询该字段时,请注意以下行为:

  • 它描述的是请求,不是结果。 失败的请求、被护栏拒绝的请求,携带的取值与成功请求相同。它是这类记录上唯一能说明端点的字段。
  • 它以请求为作用域。 发生重试或故障转移的请求会为每次尝试生成一条事件,且每次尝试携带相同取值,因此按 operation 统计事件数得到的是尝试数。参见区分请求计数与尝试计数。
  • 轮询视频作业不是视频生成。 只有提交(POST /v1/videos)会产生用量事件,查询作业状态或下载结果都不会。因此 video_generation 的计数是被请求生成的视频数量,而不是围绕这些视频发出的请求数量。

由于 operation 属于元数据,导出器即使在不接收提示词内容的 metadata_only 模式下也会保留它。在阿里云 SLS 中,该字段会作为独立列写入。请为查询使用的字段启用分析,然后按操作类型聚合流量:

* | SELECT operation, COUNT(*) AS calls, SUM(prompt_tokens + completion_tokens) AS tokens GROUP BY operation ORDER BY calls DESC

要筛选某一类流量,请直接按操作过滤,例如 operation: video_generation。不要从模型推断操作类型:同一个模型可以服务多个端点,而 requested_model 标识的是配置的别名或模型组,不是请求类型。

将事件归因到成员​

每条事件使用 user_id 标识请求发生时拥有调用方 API Key 的组织成员:

情况记录行为
调用方 API Key 没有关联成员省略 user_id。所有权必须显式分配,不会从 Key 的创建者推断。
一个成员使用多个凭证事件可以有不同的 api_key_id,但共享同一个 user_id。这包括同一成员通过 API Key 和 OIDC 调用、且两者解析到不同 Key 的情况。
Key 被重新分配或删除既有事件保留原始 user_id。重新分配后,后续请求归属于新所有者;删除 Key 不会清除早期归属。
事件产生时的网关尚不支持该字段不记录成员,因此成员筛选只覆盖升级后的流量。

按 user_id 筛选可包含成员拥有的所有凭证;按 api_key_id 筛选则只查看一个凭证。

在控制台的 Logs 页面中,Member 筛选器提供该能力,旁边的 Status 筛选器可接受状态族(4xx)、具体状态码(429)或区间(500-599)。二者组合即可用一次查询回答「该成员最近 24 小时内哪些请求被限流了」这类问题。CSV 导出同时包含 user_id 和成员名称。

记录的 Token 计数​

用量事件按上游上报的原样记录 Token 计数。调用方读到的 usage 块会按其使用的协议适配,而事件保留的是上游自己的口径,因此两者可能不同。

事件携带 total_tokens,即上游自己上报的总数,原样记录:

上游total_tokens 的来源
Vertex AI 适配器上的 GeminitotalTokenCount
OpenAI 兼容上游和 Azure OpenAIusage.total_tokens
Responses APIusage.total_tokens
Amazon Bedrock ConversetotalTokens

上游没有上报总数时省略该字段,例如 Anthropic 从不上报总数。网关从不用自己计算的和来填充它。当网关把多份用量报告合并为一个数值时(例如同一个流的多个用量帧),只有每份报告都带有总数,事件才会保留总数。1.4.0 及更早版本的网关不记录该字段。

各计数之间不做校正,因此 reasoning_tokens 可能大于 completion_tokens,cached_prompt_tokens 也可能大于 prompt_tokens。控制面如何依据 total_tokens 区分两种推理计数口径,请参阅推理 Token 如何计费。

对于 Vertex AI 适配器上的 Gemini 思考模型,Gemini 会把思考 Token(thoughtsTokenCount)报告在回答 Token(candidatesTokenCount)之外。只要提示词 + 补全 + 推理 Token 之和等于 totalTokenCount,事件就把 completion_tokens 记为回答 Token,reasoning_tokens 记为思考 Token,total_tokens 记为 totalTokenCount。如果 Gemini 没有上报总数,或上报的总数与这些计数对不上,事件会像 1.4.0 及更早版本的网关那样,把思考 Token 计入 completion_tokens。无论哪种情况,调用方的响应都不变:OpenAI 形态的响应仍把思考 Token 计入 completion_tokens,Token 限流和 Prometheus Token 指标也是如此。详见 Google Vertex AI。

在 1.4.0 之后版本的网关上,/v1/messages 和 /v1/completions 会与 /v1/chat/completions 完全一样地记录 reasoning_tokens 和 total_tokens,因此同一次上游调用无论经由这些端点中的哪一个发起,记录的计数都相同。1.4.0 及更早版本的网关在这两个端点上不记录推理计数。

导出器收到的就是这些记录下来的计数。对象存储和阿里云 SLS 原样携带事件,包括 total_tokens。OTLP 和 Datadog 则按 OpenTelemetry 约定报告 gen_ai.usage.output_tokens,详见导出遥测中的 Token 计数。

OpenAI 缓存写入 Token​

AISIX 可以识别 Chat Completions usage.prompt_tokens_details 或 Responses usage.input_tokens_details 中的 cache_write_tokens。它会将该原始值保留为用量事件的可选 cache_write_tokens 字段,包括通过 /v1/messages 或 /v1/responses 桥接的请求,以及支持协议感知的透传路由。此功能要求网关版本高于 1.1.0。

用量事件字段
{
"prompt_tokens": 101,
"completion_tokens": 11,
"cached_prompt_tokens": 19,
"cache_write_tokens": 37
}

上游未提供时省略此字段;明确返回 0 时保留零值。AISIX Cloud 的日志详情、用量事件 API 和 JSON/CSV 导出均保留这一区别,CSV 用空单元格表示缺失值。对于使用字段前缀的日志后端,沿用其原有前缀,例如 Datadog 中为 aisix.cache_write_tokens。

此字段与 Anthropic 的可累加字段 cache_creation_tokens 相互独立,不增加输入/输出 Token 总量,也不改变现有计费计算。上例的输入加输出仍为 112。现有缓存计数器不会因此重命名或合并。

下一步​

配置可观测性导出器,把用量事件发送到外部收集器、日志目标、对象存储或数据仓库工作流。使用访问日志与请求关联调查单个请求;构建 Prometheus 仪表板或告警时,请使用指标参考。