feat: 拆分 usage 记录的请求体/响应体为客户端侧与提供商侧

将 request_body/response_body 语义明确为客户端原始请求体和提供商原始响应体,
新增 provider_request_body(格式转换后发给提供商的请求体)和 client_response_body
(格式转换后返回给客户端的响应体),支持跨格式转换场景下分别查看两侧数据。

- 数据库新增 provider_request_body/client_response_body 及对应压缩字段
- 全链路(telemetry/recording/handler/stream_context)传递新字段
- 维护调度器同步支持新字段的压缩与清理
- 前端请求详情抽屉支持请求体/响应体/响应头的客户端/提供商视图切换
This commit is contained in:
fawney19
2026-02-21 01:56:28 +08:00
parent 4c5dac603f
commit 314e4a497d
23 changed files with 449 additions and 106 deletions

View File

@@ -392,15 +392,19 @@ class Usage(Base):
# 完整请求和响应记录
request_headers = Column(JSON, nullable=True) # 客户端请求头
request_body = Column(JSON, nullable=True) # 请求体7天内未压缩
request_body = Column(JSON, nullable=True) # 客户端原始请求体7天内未压缩
provider_request_headers = Column(JSON, nullable=True) # 向提供商发送的请求头
provider_request_body = Column(JSON, nullable=True) # 发给提供商的请求体(格式转换后)
response_headers = Column(JSON, nullable=True) # 提供商响应头
response_body = Column(JSON, nullable=True) # 提供商原始响应体7天内未压缩
client_response_headers = Column(JSON, nullable=True) # 返回给客户端的响应头
response_body = Column(JSON, nullable=True) # 响应体7天内未压缩
client_response_body = Column(JSON, nullable=True) # 返回给客户端的响应体(格式转换后
# 压缩存储字段7天后自动压缩到这里
request_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的请求体
response_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的响应
request_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的客户端请求体
provider_request_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的提供商请求
response_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的提供商响应体
client_response_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的客户端响应体
# 元数据
request_metadata = Column(JSON, nullable=True) # 存储额外信息
@@ -421,7 +425,7 @@ class Usage(Base):
provider_api_key = relationship("ProviderAPIKey")
def get_request_body(self) -> Any:
"""获取请求体(自动解压)"""
"""获取客户端原始请求体(自动解压)"""
if self.request_body is not None:
return self.request_body
if self.request_body_compressed is not None:
@@ -430,8 +434,18 @@ class Usage(Base):
return decompress_json(self.request_body_compressed)
return None
def get_provider_request_body(self) -> Any:
"""获取发给提供商的请求体(自动解压)"""
if self.provider_request_body is not None:
return self.provider_request_body
if self.provider_request_body_compressed is not None:
from src.utils.compression import decompress_json
return decompress_json(self.provider_request_body_compressed)
return None
def get_response_body(self) -> Any:
"""获取响应体(自动解压)"""
"""获取提供商原始响应体(自动解压)"""
if self.response_body is not None:
return self.response_body
if self.response_body_compressed is not None:
@@ -440,6 +454,16 @@ class Usage(Base):
return decompress_json(self.response_body_compressed)
return None
def get_client_response_body(self) -> Any:
"""获取返回给客户端的响应体(自动解压)"""
if self.client_response_body is not None:
return self.client_response_body
if self.client_response_body_compressed is not None:
from src.utils.compression import decompress_json
return decompress_json(self.client_response_body_compressed)
return None
class UserQuota(Base):
"""用户配额历史记录"""

View File

@@ -126,15 +126,19 @@ class Usage(Base):
# 完整请求和响应记录
request_headers = Column(JSON, nullable=True) # 客户端请求头
request_body = Column(JSON, nullable=True) # 请求体7天内未压缩
request_body = Column(JSON, nullable=True) # 客户端原始请求体7天内未压缩
provider_request_headers = Column(JSON, nullable=True) # 向提供商发送的请求头
provider_request_body = Column(JSON, nullable=True) # 发给提供商的请求体(格式转换后)
response_headers = Column(JSON, nullable=True) # 提供商响应头
response_body = Column(JSON, nullable=True) # 提供商原始响应体7天内未压缩
client_response_headers = Column(JSON, nullable=True) # 返回给客户端的响应头
response_body = Column(JSON, nullable=True) # 响应体7天内未压缩
client_response_body = Column(JSON, nullable=True) # 返回给客户端的响应体(格式转换后
# 压缩存储字段7天后自动压缩到这里
request_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的请求体
response_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的响应
request_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的客户端请求体
provider_request_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的提供商请求
response_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的提供商响应体
client_response_body_compressed = Column(LargeBinary, nullable=True) # gzip压缩的客户端响应体
# 元数据
request_metadata = Column(JSON, nullable=True) # 存储额外信息
@@ -155,7 +159,7 @@ class Usage(Base):
provider_api_key = relationship("ProviderAPIKey")
def get_request_body(self) -> Any:
"""获取请求体(自动解压)"""
"""获取客户端原始请求体(自动解压)"""
if self.request_body is not None:
return self.request_body
if self.request_body_compressed is not None:
@@ -164,8 +168,18 @@ class Usage(Base):
return decompress_json(self.request_body_compressed)
return None
def get_provider_request_body(self) -> Any:
"""获取发给提供商的请求体(自动解压)"""
if self.provider_request_body is not None:
return self.provider_request_body
if self.provider_request_body_compressed is not None:
from src.utils.compression import decompress_json
return decompress_json(self.provider_request_body_compressed)
return None
def get_response_body(self) -> Any:
"""获取响应体(自动解压)"""
"""获取提供商原始响应体(自动解压)"""
if self.response_body is not None:
return self.response_body
if self.response_body_compressed is not None:
@@ -174,6 +188,16 @@ class Usage(Base):
return decompress_json(self.response_body_compressed)
return None
def get_client_response_body(self) -> Any:
"""获取返回给客户端的响应体(自动解压)"""
if self.client_response_body is not None:
return self.client_response_body
if self.client_response_body_compressed is not None:
from src.utils.compression import decompress_json
return decompress_json(self.client_response_body_compressed)
return None
class RequestCandidate(Base):
"""请求候选记录 - 追踪所有候选(包括未使用的)"""