Files
Aether/_deprecated_py_src/services/request/result.py
fawney19 1d9c77522a refactor: 移除 Python 后端源码,全面迁移至 Rust gateway 架构
- 删除全部 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)
2026-04-03 16:26:16 +08:00

377 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
统一的请求结果和元数据结构
设计原则:
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__()