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)
This commit is contained in:
fawney19
2026-04-03 16:26:16 +08:00
parent 8f26e1a31f
commit 1d9c77522a
868 changed files with 1735 additions and 2433 deletions

View File

@@ -0,0 +1,255 @@
"""
Handler 基础工具函数
"""
from __future__ import annotations
import gzip
import json
from typing import TYPE_CHECKING, Any
from fastapi.responses import JSONResponse, Response
from src.config.constants import StreamDefaults
from src.core.api_format import filter_response_headers
from src.core.api_format.headers import get_header_value
from src.core.exceptions import EmbeddedErrorException, ProviderNotAvailableException
from src.core.http_compression import accepts_gzip, normalize_content_encoding
from src.core.logger import logger
if TYPE_CHECKING:
from src.core.api_format.conversion.registry import FormatConversionRegistry
def get_format_converter_registry() -> FormatConversionRegistry:
"""
获取格式转换注册表(线程安全)
该函数确保 normalizers 已注册后再返回全局注册表实例。
register_default_normalizers 内部已有双重检查锁,可安全多次调用。
"""
from src.core.api_format.conversion.registry import (
format_conversion_registry,
register_default_normalizers,
)
register_default_normalizers()
return format_conversion_registry
def build_sse_headers(extra_headers: dict[str, str] | None = None) -> dict[str, str]:
"""
构建 SSEtext/event-stream推荐响应头用于减少代理缓冲带来的卡顿/成段输出。
说明:
- Cache-Control: no-transform 可避免部分代理对流做压缩/改写导致缓冲
- X-Accel-Buffering: no 可显式提示 Nginx 关闭缓冲(即使全局已关闭也无害)
"""
headers: dict[str, str] = {
"Cache-Control": "no-cache, no-transform",
"X-Accel-Buffering": "no",
}
if extra_headers:
headers.update(extra_headers)
return headers
def filter_proxy_response_headers(headers: dict[str, str] | None) -> dict[str, str]:
"""
过滤上游响应头中不应透传给客户端的字段。
主要用于“解析/转换后再返回”的场景:
- 非流式:我们会 `resp.json()` 后再由 `JSONResponse` 重新序列化
- 流式:我们会解析/重组 SSE 行再输出
如果透传上游的 `content-length/content-encoding/...`,会导致客户端解码失败或等待更多字节。
"""
return filter_response_headers(headers)
def resolve_client_content_encoding(
original_headers: dict[str, str],
hinted_content_encoding: str | None = None,
) -> str | None:
"""解析客户端请求体编码(优先使用上层透传值)。"""
if hinted_content_encoding is not None:
return normalize_content_encoding(hinted_content_encoding)
return normalize_content_encoding(get_header_value(original_headers, "content-encoding"))
def resolve_client_accept_encoding(
original_headers: dict[str, str],
hinted_accept_encoding: str | None = None,
) -> str | None:
"""解析客户端 Accept-Encoding优先使用上层透传值"""
if isinstance(hinted_accept_encoding, str):
normalized_hint = hinted_accept_encoding.strip()
if normalized_hint:
return normalized_hint
header_value = get_header_value(original_headers, "accept-encoding")
normalized_header = header_value.strip()
return normalized_header or None
def build_json_response_for_client(
*,
status_code: int,
content: Any,
headers: dict[str, str] | None,
client_accept_encoding: str | None,
) -> Response:
"""根据客户端 Accept-Encoding 返回普通或 gzip 压缩 JSON 响应。"""
response_headers = dict(headers or {})
response_headers.setdefault("content-type", "application/json")
if not accepts_gzip(client_accept_encoding):
return JSONResponse(status_code=status_code, content=content, headers=response_headers)
json_bytes = json.dumps(content, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
compressed_bytes = gzip.compress(json_bytes, compresslevel=6)
cleaned_headers = {
key: value
for key, value in response_headers.items()
if key.lower() not in {"content-length", "content-encoding", "vary"}
}
cleaned_headers["Content-Encoding"] = "gzip"
existing_vary = next((v for k, v in response_headers.items() if k.lower() == "vary"), "")
vary_values = [part.strip() for part in str(existing_vary).split(",") if part.strip()]
if not any(part.lower() == "accept-encoding" for part in vary_values):
vary_values.append("Accept-Encoding")
if vary_values:
cleaned_headers["Vary"] = ", ".join(vary_values)
return Response(
status_code=status_code,
content=compressed_bytes,
headers=cleaned_headers,
media_type="application/json",
)
def check_html_response(line: str) -> bool:
"""
检查行是否为 HTML 响应base_url 配置错误的常见症状)
Args:
line: 要检查的行内容
Returns:
True 如果检测到 HTML 响应
"""
lower_line = line.lstrip().lower()
return lower_line.startswith("<!doctype") or lower_line.startswith("<html")
def ensure_stream_buffer_limit(
buffer: bytes,
*,
request_id: str,
provider_name: str | None = None,
) -> None:
"""防止上游单行流式数据过大导致内存失控。"""
total_limit = StreamDefaults.MAX_STREAM_BUFFER_TOTAL_BYTES
if len(buffer) > total_limit:
raise ProviderNotAvailableException(
"上游流式响应异常:总缓冲区超过安全上限",
provider_name=provider_name,
upstream_status=502,
upstream_response=(
f"stream buffer total overflow: {len(buffer)} bytes > {total_limit}, "
f"request_id={request_id}"
),
)
limit = StreamDefaults.MAX_STREAM_BUFFER_BYTES
if len(buffer) <= limit:
return
# 允许 chunk 中存在大量完整行;只限制“最后一行未闭合缓冲”的体积。
trailing_line = buffer.rsplit(b"\n", 1)[-1]
if len(trailing_line) <= limit:
return
raise ProviderNotAvailableException(
"上游流式响应异常:单行数据超过安全上限",
provider_name=provider_name,
upstream_status=502,
upstream_response=f"stream buffer overflow: {len(buffer)} bytes > {limit}, request_id={request_id}",
)
def check_prefetched_response_error(
prefetched_chunks: list,
parser: Any,
request_id: str,
provider_name: str,
endpoint_id: str | None,
base_url: str | None,
) -> None:
"""
检查预读的响应是否为非 SSE 格式的错误响应HTML 或纯 JSON 错误)
某些代理可能返回:
1. HTML 页面base_url 配置错误)
2. 纯 JSON 错误(无换行或多行 JSON
Args:
prefetched_chunks: 预读的字节块列表
parser: 响应解析器(需要有 is_error_response 和 parse_response 方法)
request_id: 请求 ID用于日志
provider_name: Provider 名称
endpoint_id: Endpoint ID
base_url: Endpoint 的 base_url
Raises:
ProviderNotAvailableException: 如果检测到 HTML 响应
EmbeddedErrorException: 如果检测到 JSON 错误响应
"""
if not prefetched_chunks:
return
try:
prefetched_bytes = b"".join(prefetched_chunks)
stripped = prefetched_bytes.lstrip()
# 去除 BOM
if stripped.startswith(b"\xef\xbb\xbf"):
stripped = stripped[3:]
# HTML 响应(通常是 base_url 配置错误导致返回网页)
lower_prefix = stripped[:32].lower()
if lower_prefix.startswith(b"<!doctype") or lower_prefix.startswith(b"<html"):
endpoint_short = endpoint_id[:8] + "..." if endpoint_id else "N/A"
logger.error(
f" [{request_id}] 检测到 HTML 响应,可能是 base_url 配置错误: "
f"Provider={provider_name}, Endpoint={endpoint_short}, "
f"base_url={base_url}"
)
raise ProviderNotAvailableException(
"上游服务返回了非预期的响应格式",
provider_name=provider_name,
upstream_status=200,
upstream_response=stripped.decode("utf-8", errors="replace")[:500],
)
# 纯 JSON可能无换行/多行 JSON
if stripped.startswith(b"{") or stripped.startswith(b"["):
payload_str = stripped.decode("utf-8", errors="replace").strip()
data = json.loads(payload_str)
if isinstance(data, dict) and parser.is_error_response(data):
parsed = parser.parse_response(data, 200)
logger.warning(
f" [{request_id}] 检测到 JSON 错误响应: "
f"Provider={provider_name}, "
f"error_type={parsed.error_type}, "
f"embedded_status={parsed.embedded_status_code}, "
f"message={parsed.error_message}"
)
raise EmbeddedErrorException(
provider_name=provider_name,
error_code=parsed.embedded_status_code,
error_message=parsed.error_message,
error_status=parsed.error_type,
)
except json.JSONDecodeError:
pass