Files
Aether/_deprecated_py_src/services/request/result.py

377 lines
12 KiB
Python
Raw Normal View History

2025-12-10 20:52:44 +08:00
"""
统一的请求结果和元数据结构
设计原则
1. RequestMetadata: 描述请求执行的上下文ProviderEndpointKeyAPI格式等
2. RequestResult: 封装请求的完整结果成功/失败响应元数据费用等
3. 确保 api_format 在整个链路中始终可用
使用场景
- ProviderService 创建 RequestMetadata
- TaskService 在异常时补充 RequestMetadata
2025-12-10 20:52:44 +08:00
- ChatHandlerBase 使用 RequestResult 记录 Usage
- ChatAdapterBase 使用 RequestResult 处理异常响应
"""
from __future__ import annotations
from collections.abc import AsyncIterator
2025-12-10 20:52:44 +08:00
from dataclasses import dataclass, field
from enum import Enum
from typing import Any
2025-12-10 20:52:44 +08:00
class RequestStatus(Enum):
"""请求状态"""
SUCCESS = "success"
FAILED = "failed"
PARTIAL = "partial" # 流式请求部分成功
CANCELLED = "cancelled" # 客户端主动断开连接
2025-12-10 20:52:44 +08:00
@dataclass
class RequestMetadata:
"""
请求元数据 - 描述请求执行的上下文
必填字段
- api_format: API 格式必须在请求开始时就确定
- provider: Provider 名称
- model: 模型名称
可选字段
- provider_id, provider_endpoint_id, provider_api_key_id: Provider 追踪信息
- provider_request_headers, provider_response_headers: 请求/响应头
- attempt_id: 请求尝试 ID
- original_model: 用户请求的原始模型名映射前
"""
# 必填字段 - 在请求开始时就应该确定
api_format: str
provider: str = "unknown"
model: str = "unknown"
# 结构化格式维度(从 Adapter 层透传,优先于从 api_format 字符串解析)
api_family: str | None = None # 协议族: claude, openai, gemini
endpoint_kind: str | None = None # 端点类型: chat, cli, video
2025-12-10 20:52:44 +08:00
# Provider 追踪信息
provider_id: str | None = None
provider_endpoint_id: str | None = None
provider_api_key_id: str | None = None
2025-12-10 20:52:44 +08:00
# 请求/响应头
provider_request_headers: dict[str, str] = field(default_factory=dict)
provider_response_headers: dict[str, str] = field(default_factory=dict)
2025-12-10 20:52:44 +08:00
# 其他元数据
attempt_id: str | None = None
original_model: str | None = None # 用户请求的原始模型名(用于价格计算)
2025-12-10 20:52:44 +08:00
# Provider 响应元数据(存储 provider 返回的额外信息,如 Gemini 的 modelVersion
response_metadata: dict[str, Any] = field(default_factory=dict)
2025-12-10 20:52:44 +08:00
def with_provider_info(
self,
provider: str,
provider_id: str,
provider_endpoint_id: str,
provider_api_key_id: str,
) -> RequestMetadata:
2025-12-10 20:52:44 +08:00
"""返回包含 Provider 信息的新 RequestMetadata"""
return RequestMetadata(
api_format=self.api_format,
provider=provider,
model=self.model,
api_family=self.api_family,
endpoint_kind=self.endpoint_kind,
2025-12-10 20:52:44 +08:00
provider_id=provider_id,
provider_endpoint_id=provider_endpoint_id,
provider_api_key_id=provider_api_key_id,
provider_request_headers=self.provider_request_headers,
provider_response_headers=self.provider_response_headers,
attempt_id=self.attempt_id,
original_model=self.original_model,
response_metadata=self.response_metadata,
)
def with_response_headers(self, headers: dict[str, str]) -> RequestMetadata:
2025-12-10 20:52:44 +08:00
"""返回包含响应头的新 RequestMetadata"""
return RequestMetadata(
api_format=self.api_format,
provider=self.provider,
model=self.model,
api_family=self.api_family,
endpoint_kind=self.endpoint_kind,
2025-12-10 20:52:44 +08:00
provider_id=self.provider_id,
provider_endpoint_id=self.provider_endpoint_id,
provider_api_key_id=self.provider_api_key_id,
provider_request_headers=self.provider_request_headers,
provider_response_headers=headers,
attempt_id=self.attempt_id,
original_model=self.original_model,
response_metadata=self.response_metadata,
)
@dataclass
class UsageInfo:
"""Token 使用量信息"""
input_tokens: int = 0
output_tokens: int = 0
cache_creation_input_tokens: int = 0
cache_read_input_tokens: int = 0
@dataclass
class CostInfo:
"""费用信息"""
input_cost_usd: float = 0.0
output_cost_usd: float = 0.0
cache_creation_cost_usd: float = 0.0
cache_read_cost_usd: float = 0.0
cache_cost_usd: float = 0.0
total_cost_usd: float = 0.0
# 实际费用(乘以 rate_multiplier 后)
actual_input_cost_usd: float = 0.0
actual_output_cost_usd: float = 0.0
actual_cache_creation_cost_usd: float = 0.0
actual_cache_read_cost_usd: float = 0.0
actual_total_cost_usd: float = 0.0
@dataclass
class RequestResult:
"""
请求结果 - 封装请求的完整结果
用于
- 成功请求包含响应数据使用量费用
- 失败请求包含错误信息状态码
- 流式请求包含流生成器和元数据
"""
# 状态
status: RequestStatus
# 元数据(必须存在)
metadata: RequestMetadata
# 响应相关
response_data: Any | None = None # 成功时的响应数据
stream: AsyncIterator[str] | None = None # 流式响应
2025-12-10 20:52:44 +08:00
# 使用量和费用
usage: UsageInfo = field(default_factory=UsageInfo)
cost: CostInfo = field(default_factory=CostInfo)
# 错误信息
status_code: int = 200
error_message: str | None = None
error_type: str | None = None
2025-12-10 20:52:44 +08:00
# 计时
response_time_ms: int = 0
# 请求信息(用于记录)
is_stream: bool = False
request_headers: dict[str, str] = field(default_factory=dict)
request_body: dict[str, Any] = field(default_factory=dict)
2025-12-10 20:52:44 +08:00
@property
def is_success(self) -> bool:
return self.status == RequestStatus.SUCCESS
@property
def is_failed(self) -> bool:
return self.status == RequestStatus.FAILED
@property
def is_cancelled(self) -> bool:
return self.status == RequestStatus.CANCELLED
2025-12-10 20:52:44 +08:00
@classmethod
def success(
cls,
metadata: RequestMetadata,
response_data: Any,
usage: UsageInfo,
response_time_ms: int,
is_stream: bool = False,
) -> RequestResult:
2025-12-10 20:52:44 +08:00
"""创建成功的请求结果"""
return cls(
status=RequestStatus.SUCCESS,
metadata=metadata,
response_data=response_data,
usage=usage,
status_code=200,
response_time_ms=response_time_ms,
is_stream=is_stream,
)
@classmethod
def failed(
cls,
metadata: RequestMetadata,
status_code: int,
error_message: str,
error_type: str,
response_time_ms: int,
is_stream: bool = False,
) -> RequestResult:
2025-12-10 20:52:44 +08:00
"""创建失败的请求结果"""
return cls(
status=RequestStatus.FAILED,
metadata=metadata,
status_code=status_code,
error_message=error_message,
error_type=error_type,
response_time_ms=response_time_ms,
is_stream=is_stream,
)
@classmethod
def cancelled(
cls,
metadata: RequestMetadata,
response_time_ms: int,
usage: UsageInfo | None = None,
is_stream: bool = False,
) -> RequestResult:
"""创建客户端取消的请求结果"""
return cls(
status=RequestStatus.CANCELLED,
metadata=metadata,
status_code=499,
error_message="client_disconnected",
error_type="client_disconnected",
response_time_ms=response_time_ms,
usage=usage or UsageInfo(),
is_stream=is_stream,
)
2025-12-10 20:52:44 +08:00
@classmethod
def from_exception(
cls,
exception: Exception,
api_format: str,
model: str,
response_time_ms: int,
is_stream: bool = False,
) -> RequestResult:
2025-12-10 20:52:44 +08:00
"""从异常创建失败的请求结果"""
# 尝试从异常中提取 metadata
existing_metadata = getattr(exception, "request_metadata", None)
def get_meta_value(meta: Any, key: str, default: Any | None = None) -> Any:
2025-12-10 20:52:44 +08:00
"""从 metadata 中提取值,支持字典和对象两种形式"""
if meta is None:
return default
if isinstance(meta, dict):
return meta.get(key, default)
return getattr(meta, key, default)
if existing_metadata:
# 如果异常已有 metadata使用它但确保 api_format 存在
metadata = RequestMetadata(
api_format=get_meta_value(existing_metadata, "api_format") or api_format,
provider=get_meta_value(existing_metadata, "provider", "unknown") or "unknown",
model=get_meta_value(existing_metadata, "model", model) or model,
api_family=get_meta_value(existing_metadata, "api_family"),
endpoint_kind=get_meta_value(existing_metadata, "endpoint_kind"),
2025-12-10 20:52:44 +08:00
provider_id=get_meta_value(existing_metadata, "provider_id"),
provider_endpoint_id=get_meta_value(existing_metadata, "provider_endpoint_id"),
provider_api_key_id=get_meta_value(existing_metadata, "provider_api_key_id"),
provider_request_headers=get_meta_value(
existing_metadata, "provider_request_headers", {}
),
2025-12-10 20:52:44 +08:00
provider_response_headers=get_meta_value(
existing_metadata, "provider_response_headers", {}
),
attempt_id=get_meta_value(existing_metadata, "attempt_id"),
original_model=get_meta_value(existing_metadata, "original_model"),
response_metadata=get_meta_value(existing_metadata, "response_metadata", {}),
)
else:
# 创建最小的 metadata
metadata = RequestMetadata(
api_format=api_format,
provider="unknown",
model=model,
)
# 确定状态码和错误类型
from src.core.exceptions import (
ProviderAuthException,
ProviderNotAvailableException,
ProviderRateLimitException,
ProviderTimeoutException,
)
if isinstance(exception, ProviderAuthException):
status_code = 503
error_type = "provider_auth_error"
elif isinstance(exception, ProviderRateLimitException):
status_code = 429
error_type = "rate_limit_exceeded"
elif isinstance(exception, ProviderTimeoutException):
status_code = 504
error_type = "timeout_error"
elif isinstance(exception, ProviderNotAvailableException):
status_code = 503
error_type = "provider_unavailable"
else:
status_code = 500
error_type = "internal_error"
# 构建错误消息:优先使用友好的 message 属性
# upstream_response 仅用于调试/链路追踪,不作为客户端错误消息
error_message = getattr(exception, "message", None)
if not error_message or not isinstance(error_message, str):
error_message = str(exception)
2025-12-10 20:52:44 +08:00
return cls(
status=RequestStatus.FAILED,
metadata=metadata,
status_code=status_code,
error_message=error_message,
2025-12-10 20:52:44 +08:00
error_type=error_type,
response_time_ms=response_time_ms,
is_stream=is_stream,
)
class StreamWithMetadata:
"""带元数据的流式响应包装器"""
def __init__(
self,
stream: AsyncIterator[str],
metadata: RequestMetadata,
response_headers_container: dict[str, Any] | None = None,
2025-12-10 20:52:44 +08:00
):
self.stream = stream
self.metadata = metadata
self.response_headers_container = response_headers_container
self._metadata_updated = False
def update_metadata_with_response_headers(self) -> None:
2025-12-10 20:52:44 +08:00
"""使用实际的响应头更新元数据"""
if self.response_headers_container and "headers" in self.response_headers_container:
if not self._metadata_updated:
self.metadata = self.metadata.with_response_headers(
self.response_headers_container["headers"]
)
self._metadata_updated = True
def __aiter__(self) -> None:
2025-12-10 20:52:44 +08:00
return self.stream
async def __anext__(self) -> None:
2025-12-10 20:52:44 +08:00
return await self.stream.__anext__()