mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 09:50:21 +08:00
- 删除全部 Python 源码 (src/) 及 Alembic 迁移脚本,归档至 _deprecated_py_src/ - 重构 Rust gateway ai_pipeline: 拆分 planner/finalize 模块,新增 contracts/adaptation 层 - 重组 handlers 模块为 admin/public/proxy/internal/shared 子模块结构 - 新增 executor 模块,引入 Rust 原生数据库迁移 (aether-data/migrations) - 简化 CI/Docker 构建流程,移除 base image 二级构建,统一为单一 app image - 移除 Python 相关基础设施文件 (entrypoint.sh, gunicorn_conf.py, Dockerfile.base)
377 lines
12 KiB
Python
377 lines
12 KiB
Python
"""
|
||
统一的请求结果和元数据结构
|
||
|
||
设计原则:
|
||
1. RequestMetadata: 描述请求执行的上下文(Provider、Endpoint、Key、API格式等)
|
||
2. RequestResult: 封装请求的完整结果(成功/失败、响应、元数据、费用等)
|
||
3. 确保 api_format 在整个链路中始终可用
|
||
|
||
使用场景:
|
||
- ProviderService 创建 RequestMetadata
|
||
- TaskService 在异常时补充 RequestMetadata
|
||
- ChatHandlerBase 使用 RequestResult 记录 Usage
|
||
- ChatAdapterBase 使用 RequestResult 处理异常响应
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from collections.abc import AsyncIterator
|
||
from dataclasses import dataclass, field
|
||
from enum import Enum
|
||
from typing import Any
|
||
|
||
|
||
class RequestStatus(Enum):
|
||
"""请求状态"""
|
||
|
||
SUCCESS = "success"
|
||
FAILED = "failed"
|
||
PARTIAL = "partial" # 流式请求部分成功
|
||
CANCELLED = "cancelled" # 客户端主动断开连接
|
||
|
||
|
||
@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
|
||
|
||
# Provider 追踪信息
|
||
provider_id: str | None = None
|
||
provider_endpoint_id: str | None = None
|
||
provider_api_key_id: str | None = None
|
||
|
||
# 请求/响应头
|
||
provider_request_headers: dict[str, str] = field(default_factory=dict)
|
||
provider_response_headers: dict[str, str] = field(default_factory=dict)
|
||
|
||
# 其他元数据
|
||
attempt_id: str | None = None
|
||
original_model: str | None = None # 用户请求的原始模型名(用于价格计算)
|
||
|
||
# Provider 响应元数据(存储 provider 返回的额外信息,如 Gemini 的 modelVersion)
|
||
response_metadata: dict[str, Any] = field(default_factory=dict)
|
||
|
||
def with_provider_info(
|
||
self,
|
||
provider: str,
|
||
provider_id: str,
|
||
provider_endpoint_id: str,
|
||
provider_api_key_id: str,
|
||
) -> RequestMetadata:
|
||
"""返回包含 Provider 信息的新 RequestMetadata"""
|
||
return RequestMetadata(
|
||
api_format=self.api_format,
|
||
provider=provider,
|
||
model=self.model,
|
||
api_family=self.api_family,
|
||
endpoint_kind=self.endpoint_kind,
|
||
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:
|
||
"""返回包含响应头的新 RequestMetadata"""
|
||
return RequestMetadata(
|
||
api_format=self.api_format,
|
||
provider=self.provider,
|
||
model=self.model,
|
||
api_family=self.api_family,
|
||
endpoint_kind=self.endpoint_kind,
|
||
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 # 流式响应
|
||
|
||
# 使用量和费用
|
||
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
|
||
|
||
# 计时
|
||
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)
|
||
|
||
@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
|
||
|
||
@classmethod
|
||
def success(
|
||
cls,
|
||
metadata: RequestMetadata,
|
||
response_data: Any,
|
||
usage: UsageInfo,
|
||
response_time_ms: int,
|
||
is_stream: bool = False,
|
||
) -> RequestResult:
|
||
"""创建成功的请求结果"""
|
||
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:
|
||
"""创建失败的请求结果"""
|
||
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,
|
||
)
|
||
|
||
@classmethod
|
||
def from_exception(
|
||
cls,
|
||
exception: Exception,
|
||
api_format: str,
|
||
model: str,
|
||
response_time_ms: int,
|
||
is_stream: bool = False,
|
||
) -> RequestResult:
|
||
"""从异常创建失败的请求结果"""
|
||
# 尝试从异常中提取 metadata
|
||
existing_metadata = getattr(exception, "request_metadata", None)
|
||
|
||
def get_meta_value(meta: Any, key: str, default: Any | None = None) -> Any:
|
||
"""从 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"),
|
||
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", {}
|
||
),
|
||
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)
|
||
|
||
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,
|
||
)
|
||
|
||
|
||
class StreamWithMetadata:
|
||
"""带元数据的流式响应包装器"""
|
||
|
||
def __init__(
|
||
self,
|
||
stream: AsyncIterator[str],
|
||
metadata: RequestMetadata,
|
||
response_headers_container: dict[str, Any] | None = None,
|
||
):
|
||
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:
|
||
"""使用实际的响应头更新元数据"""
|
||
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:
|
||
return self.stream
|
||
|
||
async def __anext__(self) -> None:
|
||
return await self.stream.__anext__()
|