Files
Aether/_deprecated_py_src/api/base/context.py

360 lines
13 KiB
Python
Raw Normal View History

2025-12-10 20:52:44 +08:00
from __future__ import annotations
perf: 优化请求鉴权链路并批量化统计/调度查询 - 为 Pipeline/Context 增加按需读取请求体能力,支持 async 懒加载 JSON body - 为 chat/cli/video/claude/openai-cli 适配器关闭默认预读,减少无效 body 读取与超时风险 - 将本地登录、JWT 用户加载、API Key 鉴权迁移到线程池隔离会话执行,避免阻塞事件循环 - 为 API Key 鉴权返回结构化余额结果,并在主请求会话中重新绑定 user/api_key 后再校验状态、过期和锁定信息 - 为 management/user token 前缀认证引入独立会话与结果回绑,避免跨会话对象写入失效 - 为 Usage 余额检查补充结构化返回,统一透出 remaining 与欠费/不可用文案映射 - 为用户与管理端活跃请求查询增加 maintain_status 开关,避免轮询指定 id 时误触发状态修复 - 重写 user_me usage 汇总逻辑,支持 group_by=None 的粗粒度聚合 - 修正 provider 维度成功率与平均响应时间统计,基于 success_count 和成功响应耗时汇总计算 - 前端 Usage 轮询由 setInterval 改为串行 setTimeout,避免并发轮询叠加 - 为 StatsAggregator 增加按本地日期批量计算百分位能力,替代逐天 fan-out 查询 - 为混合统计查询合并连续实时日期区间,并批量读取 StatsDaily,减少逐日查询次数 - 为用户日统计增加批量聚合入口,替代逐用户循环聚合 - 为系统配置导出改用 selectinload 预加载 provider 关联数据,减少 N+1 查询 - 为管理员用户列表增加钱包批量查询,避免逐用户回表 - 为调度器增加 provider 轻量引用预过滤,先按 allowed_providers 缩小范围再加载完整 provider 图 - 为 CandidateBuilder 增加 provider refs/provider_ids 查询能力,保留分页顺序 - 为模型缓存增加 provider_model_mappings 索引缓存与 model_mappings 规则缓存,减少重复全量扫描 - 为请求候选中间态改为 flush/batch commit,降低 pending/streaming 状态切换的事务往返 - 为钱包访问结果补充 balance_snapshot,并抽取余额快照复用逻辑 - 补充 pipeline、auth、admin users、user_me usage、stats aggregator、model cache、 scheduler、wallet、request candidate 等回归与契约测试
2026-03-09 22:57:23 +08:00
import asyncio
import gzip
2025-12-10 20:52:44 +08:00
import json
import time
import uuid
from dataclasses import dataclass, field
from typing import Any
2025-12-10 20:52:44 +08:00
from fastapi import HTTPException, Request
from sqlalchemy.orm import Session
perf: 优化请求鉴权链路并批量化统计/调度查询 - 为 Pipeline/Context 增加按需读取请求体能力,支持 async 懒加载 JSON body - 为 chat/cli/video/claude/openai-cli 适配器关闭默认预读,减少无效 body 读取与超时风险 - 将本地登录、JWT 用户加载、API Key 鉴权迁移到线程池隔离会话执行,避免阻塞事件循环 - 为 API Key 鉴权返回结构化余额结果,并在主请求会话中重新绑定 user/api_key 后再校验状态、过期和锁定信息 - 为 management/user token 前缀认证引入独立会话与结果回绑,避免跨会话对象写入失效 - 为 Usage 余额检查补充结构化返回,统一透出 remaining 与欠费/不可用文案映射 - 为用户与管理端活跃请求查询增加 maintain_status 开关,避免轮询指定 id 时误触发状态修复 - 重写 user_me usage 汇总逻辑,支持 group_by=None 的粗粒度聚合 - 修正 provider 维度成功率与平均响应时间统计,基于 success_count 和成功响应耗时汇总计算 - 前端 Usage 轮询由 setInterval 改为串行 setTimeout,避免并发轮询叠加 - 为 StatsAggregator 增加按本地日期批量计算百分位能力,替代逐天 fan-out 查询 - 为混合统计查询合并连续实时日期区间,并批量读取 StatsDaily,减少逐日查询次数 - 为用户日统计增加批量聚合入口,替代逐用户循环聚合 - 为系统配置导出改用 selectinload 预加载 provider 关联数据,减少 N+1 查询 - 为管理员用户列表增加钱包批量查询,避免逐用户回表 - 为调度器增加 provider 轻量引用预过滤,先按 allowed_providers 缩小范围再加载完整 provider 图 - 为 CandidateBuilder 增加 provider refs/provider_ids 查询能力,保留分页顺序 - 为模型缓存增加 provider_model_mappings 索引缓存与 model_mappings 规则缓存,减少重复全量扫描 - 为请求候选中间态改为 flush/batch commit,降低 pending/streaming 状态切换的事务往返 - 为钱包访问结果补充 balance_snapshot,并抽取余额快照复用逻辑 - 补充 pipeline、auth、admin users、user_me usage、stats aggregator、model cache、 scheduler、wallet、request candidate 等回归与契约测试
2026-03-09 22:57:23 +08:00
from starlette.requests import ClientDisconnect
2025-12-10 20:52:44 +08:00
perf: 优化请求鉴权链路并批量化统计/调度查询 - 为 Pipeline/Context 增加按需读取请求体能力,支持 async 懒加载 JSON body - 为 chat/cli/video/claude/openai-cli 适配器关闭默认预读,减少无效 body 读取与超时风险 - 将本地登录、JWT 用户加载、API Key 鉴权迁移到线程池隔离会话执行,避免阻塞事件循环 - 为 API Key 鉴权返回结构化余额结果,并在主请求会话中重新绑定 user/api_key 后再校验状态、过期和锁定信息 - 为 management/user token 前缀认证引入独立会话与结果回绑,避免跨会话对象写入失效 - 为 Usage 余额检查补充结构化返回,统一透出 remaining 与欠费/不可用文案映射 - 为用户与管理端活跃请求查询增加 maintain_status 开关,避免轮询指定 id 时误触发状态修复 - 重写 user_me usage 汇总逻辑,支持 group_by=None 的粗粒度聚合 - 修正 provider 维度成功率与平均响应时间统计,基于 success_count 和成功响应耗时汇总计算 - 前端 Usage 轮询由 setInterval 改为串行 setTimeout,避免并发轮询叠加 - 为 StatsAggregator 增加按本地日期批量计算百分位能力,替代逐天 fan-out 查询 - 为混合统计查询合并连续实时日期区间,并批量读取 StatsDaily,减少逐日查询次数 - 为用户日统计增加批量聚合入口,替代逐用户循环聚合 - 为系统配置导出改用 selectinload 预加载 provider 关联数据,减少 N+1 查询 - 为管理员用户列表增加钱包批量查询,避免逐用户回表 - 为调度器增加 provider 轻量引用预过滤,先按 allowed_providers 缩小范围再加载完整 provider 图 - 为 CandidateBuilder 增加 provider refs/provider_ids 查询能力,保留分页顺序 - 为模型缓存增加 provider_model_mappings 索引缓存与 model_mappings 规则缓存,减少重复全量扫描 - 为请求候选中间态改为 flush/batch commit,降低 pending/streaming 状态切换的事务往返 - 为钱包访问结果补充 balance_snapshot,并抽取余额快照复用逻辑 - 补充 pipeline、auth、admin users、user_me usage、stats aggregator、model cache、 scheduler、wallet、request candidate 等回归与契约测试
2026-03-09 22:57:23 +08:00
from src.config.settings import config
from src.core.api_format.headers import get_header_value
from src.core.http_compression import is_gzip_content_encoding, normalize_content_encoding
2025-12-10 20:52:44 +08:00
from src.core.logger import logger
from src.models.database import ApiKey, ManagementToken, User
from src.utils.perf import PerfRecorder
from src.utils.request_utils import (
get_request_identity_metadata,
update_request_state,
)
def _snapshot_optional_str(value: Any) -> str | None:
return value.strip() if isinstance(value, str) and value.strip() else None
def _snapshot_optional_float(value: Any) -> float | None:
if value is None:
return None
if isinstance(value, bool):
return None
if isinstance(value, (int, float)):
return float(value)
if isinstance(value, str):
raw = value.strip()
if not raw:
return None
try:
return float(raw)
except ValueError:
return None
return None
@dataclass(frozen=True)
class _ContextBuildSnapshot:
request_id: str
start_time: float
request_method: str
request_path: str
client_ip: str
user_agent: str
original_headers: dict[str, str]
request_content_type: str | None
query_params: dict[str, str]
path_params: dict[str, Any]
prefetched_balance_remaining: float | None
gateway_execution_path: str | None
rate_limit_scope: str | None
tx_committed_by_route: bool
client_content_encoding: str | None
client_accept_encoding: str | None
perf_metrics: dict[str, Any] | None
@classmethod
def from_request(cls, request: Request) -> _ContextBuildSnapshot:
request_state = getattr(request, "state", None)
original_headers = dict(request.headers)
identity = get_request_identity_metadata(request)
request_id = identity.request_id or str(uuid.uuid4())[:8]
accept_encoding = get_header_value(original_headers, "accept-encoding")
if isinstance(accept_encoding, str):
accept_encoding = accept_encoding.strip() or None
perf_metrics = getattr(request.state, "perf_metrics", None)
perf_payload = perf_metrics if isinstance(perf_metrics, dict) and perf_metrics else None
return cls(
request_id=request_id,
start_time=time.time(),
request_method=request.method,
request_path=request.url.path,
client_ip=identity.client_ip,
user_agent=identity.user_agent,
original_headers=original_headers,
request_content_type=get_header_value(original_headers, "content-type"),
query_params=dict(request.query_params),
path_params=dict(getattr(request, "path_params", {}) or {}),
prefetched_balance_remaining=_snapshot_optional_float(
getattr(request_state, "prefetched_balance_remaining", None)
),
gateway_execution_path=_snapshot_optional_str(
getattr(request_state, "gateway_execution_path", None)
),
rate_limit_scope=_snapshot_optional_str(
getattr(request_state, "rate_limit_scope", None)
),
tx_committed_by_route=getattr(request_state, "tx_committed_by_route", False) is True,
client_content_encoding=normalize_content_encoding(
get_header_value(original_headers, "content-encoding")
),
client_accept_encoding=accept_encoding,
perf_metrics=perf_payload,
)
def _apply_context_state_markers(
request: Request,
*,
request_id: str,
user: User | None,
api_key: ApiKey | None,
) -> None:
update_request_state(request, request_id=request_id)
if user:
update_request_state(request, user_id=user.id)
if api_key:
update_request_state(request, api_key_id=api_key.id)
2025-12-10 20:52:44 +08:00
@dataclass
class ApiRequestContext:
"""统一的API请求上下文贯穿Pipeline与格式适配器。"""
request: Request
db: Session
user: User | None
api_key: ApiKey | None
2025-12-10 20:52:44 +08:00
request_id: str
start_time: float
request_method: str
request_path: str
2025-12-10 20:52:44 +08:00
client_ip: str
user_agent: str
original_headers: dict[str, str]
query_params: dict[str, str]
request_content_type: str | None = None
perf_metrics: dict[str, Any] | None = None
2025-12-10 20:52:44 +08:00
raw_body: bytes | None = None
json_body: dict[str, Any] | None = None
balance_remaining: float | None = None
prefetched_balance_remaining: float | None = None
2025-12-10 20:52:44 +08:00
mode: str = "standard" # standard / proxy
api_format_hint: str | None = None
2025-12-10 20:52:44 +08:00
# URL 路径参数(如 Gemini API 的 /v1beta/models/{model}:generateContent
path_params: dict[str, Any] = field(default_factory=dict)
2025-12-10 20:52:44 +08:00
# Management Token用于管理 API 认证)
management_token: ManagementToken | None = None
2025-12-10 20:52:44 +08:00
# 供适配器扩展的状态存储
extra: dict[str, Any] = field(default_factory=dict)
audit_metadata: dict[str, Any] = field(default_factory=dict)
2025-12-10 20:52:44 +08:00
# 高频轮询端点日志抑制标志
quiet_logging: bool = False
gateway_execution_path: str | None = None
rate_limit_scope: str | None = None
tx_committed_by_route: bool = False
client_content_encoding: str | None = None
client_accept_encoding: str | None = None
def _get_perf_metrics(self) -> dict[str, Any] | None:
if isinstance(self.perf_metrics, dict):
return self.perf_metrics
perf_metrics = getattr(self.request.state, "perf_metrics", None)
if isinstance(perf_metrics, dict):
self.perf_metrics = perf_metrics
return perf_metrics
return None
def sync_runtime_state_from_request(self) -> None:
request_state = getattr(self.request, "state", None)
self.prefetched_balance_remaining = _snapshot_optional_float(
getattr(request_state, "prefetched_balance_remaining", self.prefetched_balance_remaining)
)
self.gateway_execution_path = _snapshot_optional_str(
getattr(request_state, "gateway_execution_path", self.gateway_execution_path)
)
self.rate_limit_scope = _snapshot_optional_str(
getattr(request_state, "rate_limit_scope", self.rate_limit_scope)
)
self.tx_committed_by_route = (
getattr(request_state, "tx_committed_by_route", self.tx_committed_by_route) is True
)
perf: 优化请求鉴权链路并批量化统计/调度查询 - 为 Pipeline/Context 增加按需读取请求体能力,支持 async 懒加载 JSON body - 为 chat/cli/video/claude/openai-cli 适配器关闭默认预读,减少无效 body 读取与超时风险 - 将本地登录、JWT 用户加载、API Key 鉴权迁移到线程池隔离会话执行,避免阻塞事件循环 - 为 API Key 鉴权返回结构化余额结果,并在主请求会话中重新绑定 user/api_key 后再校验状态、过期和锁定信息 - 为 management/user token 前缀认证引入独立会话与结果回绑,避免跨会话对象写入失效 - 为 Usage 余额检查补充结构化返回,统一透出 remaining 与欠费/不可用文案映射 - 为用户与管理端活跃请求查询增加 maintain_status 开关,避免轮询指定 id 时误触发状态修复 - 重写 user_me usage 汇总逻辑,支持 group_by=None 的粗粒度聚合 - 修正 provider 维度成功率与平均响应时间统计,基于 success_count 和成功响应耗时汇总计算 - 前端 Usage 轮询由 setInterval 改为串行 setTimeout,避免并发轮询叠加 - 为 StatsAggregator 增加按本地日期批量计算百分位能力,替代逐天 fan-out 查询 - 为混合统计查询合并连续实时日期区间,并批量读取 StatsDaily,减少逐日查询次数 - 为用户日统计增加批量聚合入口,替代逐用户循环聚合 - 为系统配置导出改用 selectinload 预加载 provider 关联数据,减少 N+1 查询 - 为管理员用户列表增加钱包批量查询,避免逐用户回表 - 为调度器增加 provider 轻量引用预过滤,先按 allowed_providers 缩小范围再加载完整 provider 图 - 为 CandidateBuilder 增加 provider refs/provider_ids 查询能力,保留分页顺序 - 为模型缓存增加 provider_model_mappings 索引缓存与 model_mappings 规则缓存,减少重复全量扫描 - 为请求候选中间态改为 flush/batch commit,降低 pending/streaming 状态切换的事务往返 - 为钱包访问结果补充 balance_snapshot,并抽取余额快照复用逻辑 - 补充 pipeline、auth、admin users、user_me usage、stats aggregator、model cache、 scheduler、wallet、request candidate 等回归与契约测试
2026-03-09 22:57:23 +08:00
async def ensure_raw_body_async(self) -> bytes:
"""按需读取原始请求体,避免所有请求都在 Pipeline 阶段预读。"""
if self.raw_body is not None:
return self.raw_body
perf_metrics = self._get_perf_metrics()
perf: 优化请求鉴权链路并批量化统计/调度查询 - 为 Pipeline/Context 增加按需读取请求体能力,支持 async 懒加载 JSON body - 为 chat/cli/video/claude/openai-cli 适配器关闭默认预读,减少无效 body 读取与超时风险 - 将本地登录、JWT 用户加载、API Key 鉴权迁移到线程池隔离会话执行,避免阻塞事件循环 - 为 API Key 鉴权返回结构化余额结果,并在主请求会话中重新绑定 user/api_key 后再校验状态、过期和锁定信息 - 为 management/user token 前缀认证引入独立会话与结果回绑,避免跨会话对象写入失效 - 为 Usage 余额检查补充结构化返回,统一透出 remaining 与欠费/不可用文案映射 - 为用户与管理端活跃请求查询增加 maintain_status 开关,避免轮询指定 id 时误触发状态修复 - 重写 user_me usage 汇总逻辑,支持 group_by=None 的粗粒度聚合 - 修正 provider 维度成功率与平均响应时间统计,基于 success_count 和成功响应耗时汇总计算 - 前端 Usage 轮询由 setInterval 改为串行 setTimeout,避免并发轮询叠加 - 为 StatsAggregator 增加按本地日期批量计算百分位能力,替代逐天 fan-out 查询 - 为混合统计查询合并连续实时日期区间,并批量读取 StatsDaily,减少逐日查询次数 - 为用户日统计增加批量聚合入口,替代逐用户循环聚合 - 为系统配置导出改用 selectinload 预加载 provider 关联数据,减少 N+1 查询 - 为管理员用户列表增加钱包批量查询,避免逐用户回表 - 为调度器增加 provider 轻量引用预过滤,先按 allowed_providers 缩小范围再加载完整 provider 图 - 为 CandidateBuilder 增加 provider refs/provider_ids 查询能力,保留分页顺序 - 为模型缓存增加 provider_model_mappings 索引缓存与 model_mappings 规则缓存,减少重复全量扫描 - 为请求候选中间态改为 flush/batch commit,降低 pending/streaming 状态切换的事务往返 - 为钱包访问结果补充 balance_snapshot,并抽取余额快照复用逻辑 - 补充 pipeline、auth、admin users、user_me usage、stats aggregator、model cache、 scheduler、wallet、request candidate 等回归与契约测试
2026-03-09 22:57:23 +08:00
perf_sampled = isinstance(perf_metrics, dict) and bool(perf_metrics)
body_start = PerfRecorder.start(force=perf_sampled)
body_size = 0
try:
self.raw_body = await asyncio.wait_for(
self.request.body(), timeout=config.request_body_timeout
)
body_size = len(self.raw_body or b"")
except TimeoutError as exc:
timeout_sec = int(config.request_body_timeout)
logger.error("读取请求体超时({}s),可能客户端未发送完整请求体", timeout_sec)
raise HTTPException(
status_code=408,
detail=f"Request timeout: body not received within {timeout_sec} seconds",
) from exc
except ClientDisconnect:
logger.warning(
"[Context] 客户端在读取请求体期间断开连接: {} {}",
self.request_method,
self.request_path,
perf: 优化请求鉴权链路并批量化统计/调度查询 - 为 Pipeline/Context 增加按需读取请求体能力,支持 async 懒加载 JSON body - 为 chat/cli/video/claude/openai-cli 适配器关闭默认预读,减少无效 body 读取与超时风险 - 将本地登录、JWT 用户加载、API Key 鉴权迁移到线程池隔离会话执行,避免阻塞事件循环 - 为 API Key 鉴权返回结构化余额结果,并在主请求会话中重新绑定 user/api_key 后再校验状态、过期和锁定信息 - 为 management/user token 前缀认证引入独立会话与结果回绑,避免跨会话对象写入失效 - 为 Usage 余额检查补充结构化返回,统一透出 remaining 与欠费/不可用文案映射 - 为用户与管理端活跃请求查询增加 maintain_status 开关,避免轮询指定 id 时误触发状态修复 - 重写 user_me usage 汇总逻辑,支持 group_by=None 的粗粒度聚合 - 修正 provider 维度成功率与平均响应时间统计,基于 success_count 和成功响应耗时汇总计算 - 前端 Usage 轮询由 setInterval 改为串行 setTimeout,避免并发轮询叠加 - 为 StatsAggregator 增加按本地日期批量计算百分位能力,替代逐天 fan-out 查询 - 为混合统计查询合并连续实时日期区间,并批量读取 StatsDaily,减少逐日查询次数 - 为用户日统计增加批量聚合入口,替代逐用户循环聚合 - 为系统配置导出改用 selectinload 预加载 provider 关联数据,减少 N+1 查询 - 为管理员用户列表增加钱包批量查询,避免逐用户回表 - 为调度器增加 provider 轻量引用预过滤,先按 allowed_providers 缩小范围再加载完整 provider 图 - 为 CandidateBuilder 增加 provider refs/provider_ids 查询能力,保留分页顺序 - 为模型缓存增加 provider_model_mappings 索引缓存与 model_mappings 规则缓存,减少重复全量扫描 - 为请求候选中间态改为 flush/batch commit,降低 pending/streaming 状态切换的事务往返 - 为钱包访问结果补充 balance_snapshot,并抽取余额快照复用逻辑 - 补充 pipeline、auth、admin users、user_me usage、stats aggregator、model cache、 scheduler、wallet、request candidate 等回归与契约测试
2026-03-09 22:57:23 +08:00
)
raise HTTPException(
status_code=499,
detail="Client closed request",
)
finally:
body_duration = PerfRecorder.stop(
body_start,
"pipeline_body_read",
labels={"mode": self.mode},
log_hint=f"size={body_size}",
)
if isinstance(perf_metrics, dict):
pipeline_metrics = perf_metrics.setdefault("pipeline", {})
pipeline_metrics["body_read_ms"] = int((body_duration or 0) * 1000)
pipeline_metrics["body_bytes"] = int(body_size)
return self.raw_body or b""
async def ensure_json_body_async(self) -> dict[str, Any]:
"""异步懒加载 JSON 请求体。"""
await self.ensure_raw_body_async()
return self.ensure_json_body()
def ensure_json_body(self) -> dict[str, Any]:
2025-12-10 20:52:44 +08:00
"""确保请求体已解析为JSON并返回。"""
if self.json_body is not None:
return self.json_body
if not self.raw_body:
raise HTTPException(status_code=400, detail="请求体不能为空")
perf_metrics = self._get_perf_metrics()
perf_sampled = isinstance(perf_metrics, dict) and bool(perf_metrics)
parse_start = PerfRecorder.start(force=perf_sampled)
def _record_parse_duration(duration: float | None) -> None:
if duration is None:
return
if not isinstance(perf_metrics, dict):
return
perf_metrics.setdefault("pipeline", {})["json_parse_ms"] = int(duration * 1000)
body_to_parse = self.raw_body
content_encoding = self.client_content_encoding or normalize_content_encoding(
get_header_value(self.original_headers, "content-encoding")
)
if is_gzip_content_encoding(content_encoding):
try:
body_to_parse = gzip.decompress(body_to_parse)
except OSError as exc:
parse_duration = PerfRecorder.stop(
parse_start,
"pipeline_json_parse",
labels={"mode": self.mode},
)
_record_parse_duration(parse_duration)
logger.warning("gzip 请求体解压失败: {}", exc)
raise HTTPException(status_code=400, detail="gzip 请求体解压失败") from exc
2025-12-10 20:52:44 +08:00
try:
self.json_body = json.loads(body_to_parse.decode("utf-8"))
parse_duration = PerfRecorder.stop(
parse_start,
"pipeline_json_parse",
labels={"mode": self.mode},
)
_record_parse_duration(parse_duration)
2025-12-10 20:52:44 +08:00
except json.JSONDecodeError as exc:
parse_duration = PerfRecorder.stop(
parse_start,
"pipeline_json_parse",
labels={"mode": self.mode},
)
_record_parse_duration(parse_duration)
2025-12-10 20:52:44 +08:00
logger.warning(f"解析JSON失败: {exc}")
raise HTTPException(status_code=400, detail="请求体必须是合法的JSON") from exc
return self.json_body
def add_audit_metadata(self, **values: Any) -> None:
"""向审计日志附加字段(会自动过滤 None"""
for key, value in values.items():
if value is not None:
self.audit_metadata[key] = value
def extend_audit_metadata(self, data: dict[str, Any]) -> None:
2025-12-10 20:52:44 +08:00
"""批量附加审计字段。"""
for key, value in data.items():
if value is not None:
self.audit_metadata[key] = value
@classmethod
def build(
cls,
request: Request,
db: Session,
user: User | None,
api_key: ApiKey | None,
raw_body: bytes | None = None,
2025-12-10 20:52:44 +08:00
mode: str = "standard",
api_format_hint: str | None = None,
path_params: dict[str, Any] | None = None,
) -> ApiRequestContext:
2025-12-10 20:52:44 +08:00
"""创建上下文实例并提前读取必要的元数据。"""
snapshot = _ContextBuildSnapshot.from_request(request)
_apply_context_state_markers(
request,
request_id=snapshot.request_id,
user=user,
api_key=api_key,
)
2025-12-10 20:52:44 +08:00
context = cls(
request=request,
db=db,
user=user,
api_key=api_key,
request_id=snapshot.request_id,
start_time=snapshot.start_time,
request_method=snapshot.request_method,
request_path=snapshot.request_path,
client_ip=snapshot.client_ip,
user_agent=snapshot.user_agent,
original_headers=snapshot.original_headers,
request_content_type=snapshot.request_content_type,
query_params=snapshot.query_params,
perf_metrics=snapshot.perf_metrics,
2025-12-10 20:52:44 +08:00
raw_body=raw_body,
prefetched_balance_remaining=snapshot.prefetched_balance_remaining,
2025-12-10 20:52:44 +08:00
mode=mode,
api_format_hint=api_format_hint,
path_params=dict(path_params or snapshot.path_params),
gateway_execution_path=snapshot.gateway_execution_path,
rate_limit_scope=snapshot.rate_limit_scope,
tx_committed_by_route=snapshot.tx_committed_by_route,
client_content_encoding=snapshot.client_content_encoding,
client_accept_encoding=snapshot.client_accept_encoding,
2025-12-10 20:52:44 +08:00
)
if snapshot.perf_metrics is not None:
context.extra["perf"] = snapshot.perf_metrics
2025-12-10 20:52:44 +08:00
return context