mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
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:
25
_deprecated_py_src/api/handlers/openai_cli/__init__.py
Normal file
25
_deprecated_py_src/api/handlers/openai_cli/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""OpenAI CLI handler package (lazy exports)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from importlib import import_module
|
||||
from typing import Any
|
||||
|
||||
_LAZY_EXPORTS: dict[str, tuple[str, str]] = {
|
||||
"OpenAICliAdapter": (".adapter", "OpenAICliAdapter"),
|
||||
"OpenAICompactAdapter": (".adapter", "OpenAICompactAdapter"),
|
||||
"OpenAICliMessageHandler": (".handler", "OpenAICliMessageHandler"),
|
||||
}
|
||||
|
||||
__all__ = list(_LAZY_EXPORTS.keys())
|
||||
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
if name not in _LAZY_EXPORTS:
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
module_name, attr_name = _LAZY_EXPORTS[name]
|
||||
module = import_module(module_name, __name__)
|
||||
value = getattr(module, attr_name)
|
||||
globals()[name] = value
|
||||
return value
|
||||
138
_deprecated_py_src/api/handlers/openai_cli/adapter.py
Normal file
138
_deprecated_py_src/api/handlers/openai_cli/adapter.py
Normal file
@@ -0,0 +1,138 @@
|
||||
"""
|
||||
OpenAI CLI Adapter - 基于通用 CLI Adapter 基类的简化实现
|
||||
|
||||
继承 CliAdapterBase,只需配置 FORMAT_ID 和 HANDLER_CLASS。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from src.api.base.context import ApiRequestContext
|
||||
from src.api.handlers.base.cli_adapter_base import CliAdapterBase, register_cli_adapter
|
||||
from src.api.handlers.base.cli_handler_base import CliMessageHandlerBase
|
||||
from src.config.settings import config
|
||||
from src.core.api_format import ApiFamily, EndpointKind
|
||||
from src.core.provider_types import ProviderType
|
||||
from src.utils.url_utils import is_codex_url
|
||||
|
||||
|
||||
@register_cli_adapter
|
||||
class OpenAICliAdapter(CliAdapterBase):
|
||||
"""
|
||||
OpenAI CLI API 适配器
|
||||
|
||||
处理 /v1/responses 端点的请求。
|
||||
"""
|
||||
|
||||
FORMAT_ID = "openai:cli"
|
||||
API_FAMILY = ApiFamily.OPENAI
|
||||
name = "openai.cli"
|
||||
|
||||
@property
|
||||
def HANDLER_CLASS(self) -> type[CliMessageHandlerBase]:
|
||||
"""延迟导入 Handler 类避免循环依赖"""
|
||||
from src.api.handlers.openai_cli.handler import OpenAICliMessageHandler
|
||||
|
||||
return OpenAICliMessageHandler
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
allowed_api_formats: list[str] | None = None,
|
||||
*,
|
||||
compact: bool = False,
|
||||
):
|
||||
super().__init__(allowed_api_formats)
|
||||
self._compact = compact
|
||||
|
||||
async def handle(self, context: ApiRequestContext) -> Any:
|
||||
"""处理 CLI API 请求。"""
|
||||
if self._compact:
|
||||
from src.services.provider.adapters.codex.context import (
|
||||
CodexRequestContext,
|
||||
set_codex_request_context,
|
||||
)
|
||||
|
||||
# Keep compact routing state out of the request body. Transport/policy layers
|
||||
# read this request-scoped flag directly when legacy compact fallback is needed.
|
||||
set_codex_request_context(CodexRequestContext(is_compact=True))
|
||||
|
||||
body = await context.ensure_json_body_async()
|
||||
# compact 端点永远非流式
|
||||
body.pop("stream", None)
|
||||
return await super().handle(context)
|
||||
|
||||
@classmethod
|
||||
def build_endpoint_url(
|
||||
cls,
|
||||
base_url: str,
|
||||
request_data: dict[str, Any],
|
||||
model_name: str | None = None,
|
||||
*,
|
||||
compact: bool = False,
|
||||
provider_type: str | None = None,
|
||||
) -> str:
|
||||
"""构建OpenAI CLI API端点URL(使用 Responses API)
|
||||
|
||||
对于 Codex OAuth 端点(如 chatgpt.com/backend-api/codex),直接追加 /responses;
|
||||
对于标准 OpenAI API,使用 /v1/responses。
|
||||
compact=True 时追加 /compact 后缀。
|
||||
|
||||
provider_type 优先:仅当 provider_type 为 codex 时才使用 Codex 路由规则;
|
||||
未传入 provider_type 时回退到 URL 模式匹配(兼容旧调用方)。
|
||||
"""
|
||||
suffix = "/responses/compact" if compact else "/responses"
|
||||
base_url = base_url.rstrip("/")
|
||||
# 判断是否按 Codex 规则构建 URL
|
||||
is_codex = (
|
||||
(provider_type or "").lower() == ProviderType.CODEX
|
||||
if provider_type
|
||||
else is_codex_url(base_url)
|
||||
)
|
||||
if is_codex:
|
||||
return f"{base_url}{suffix}"
|
||||
# 标准 OpenAI API
|
||||
if base_url.endswith("/v1"):
|
||||
return f"{base_url}{suffix}"
|
||||
else:
|
||||
return f"{base_url}/v1{suffix}"
|
||||
|
||||
# build_request_body 使用基类实现
|
||||
# OpenAI CLI normalizer 会自动添加 instructions 字段
|
||||
|
||||
@classmethod
|
||||
def build_request_body(
|
||||
cls,
|
||||
request_data: dict[str, Any] | None = None,
|
||||
*,
|
||||
base_url: str | None = None,
|
||||
provider_type: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""构建测试请求体。"""
|
||||
from src.api.handlers.base.request_builder import build_test_request_body
|
||||
|
||||
del base_url, provider_type
|
||||
return build_test_request_body(cls.FORMAT_ID, request_data)
|
||||
|
||||
@classmethod
|
||||
def get_cli_user_agent(cls) -> str | None:
|
||||
"""获取OpenAI CLI User-Agent"""
|
||||
return config.internal_user_agent_openai_cli
|
||||
|
||||
|
||||
__all__ = ["OpenAICliAdapter"]
|
||||
|
||||
|
||||
@register_cli_adapter
|
||||
class OpenAICompactAdapter(OpenAICliAdapter):
|
||||
"""OpenAI Compact Responses adapter (/v1/responses/compact)."""
|
||||
|
||||
FORMAT_ID = "openai:compact"
|
||||
ENDPOINT_KIND = EndpointKind.COMPACT
|
||||
name = "openai.compact"
|
||||
|
||||
def __init__(self, allowed_api_formats: list[str] | None = None):
|
||||
super().__init__(allowed_api_formats=allowed_api_formats, compact=True)
|
||||
|
||||
|
||||
__all__.append("OpenAICompactAdapter")
|
||||
226
_deprecated_py_src/api/handlers/openai_cli/handler.py
Normal file
226
_deprecated_py_src/api/handlers/openai_cli/handler.py
Normal file
@@ -0,0 +1,226 @@
|
||||
"""
|
||||
OpenAI CLI Message Handler - 基于通用 CLI Handler 基类的简化实现
|
||||
|
||||
继承 CliMessageHandlerBase,只需覆盖格式特定的配置和事件处理逻辑。
|
||||
代码量从原来的 900+ 行减少到 ~100 行。
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from src.api.handlers.base.cli_handler_base import (
|
||||
CliMessageHandlerBase,
|
||||
StreamContext,
|
||||
)
|
||||
from src.core.api_format import ApiFamily, EndpointKind
|
||||
|
||||
|
||||
class OpenAICliMessageHandler(CliMessageHandlerBase):
|
||||
"""
|
||||
OpenAI CLI Message Handler - 处理 OpenAI CLI Responses API 格式
|
||||
|
||||
使用新三层架构 (Provider -> ProviderEndpoint -> ProviderAPIKey)
|
||||
通过 TaskService/FailoverEngine 实现自动故障转移、健康监控和并发控制
|
||||
|
||||
响应格式特点:
|
||||
- 使用 output[] 数组而非 content[]
|
||||
- 使用 output_text 类型而非普通 text
|
||||
- 流式事件:response.output_text.delta, response.output_text.done
|
||||
|
||||
模型字段:请求体顶级 model 字段
|
||||
"""
|
||||
|
||||
FORMAT_ID = "openai:cli"
|
||||
API_FAMILY = ApiFamily.OPENAI
|
||||
ENDPOINT_KIND = EndpointKind.CLI
|
||||
|
||||
def extract_model_from_request(
|
||||
self,
|
||||
request_body: dict[str, Any],
|
||||
path_params: dict[str, Any] | None = None, # noqa: ARG002
|
||||
) -> str:
|
||||
"""
|
||||
从请求中提取模型名 - OpenAI 格式实现
|
||||
|
||||
OpenAI API 的 model 在请求体顶级字段。
|
||||
|
||||
Args:
|
||||
request_body: 请求体
|
||||
path_params: URL 路径参数(OpenAI 不使用)
|
||||
|
||||
Returns:
|
||||
模型名
|
||||
"""
|
||||
model = request_body.get("model")
|
||||
return str(model) if model else "unknown"
|
||||
|
||||
def apply_mapped_model(
|
||||
self,
|
||||
request_body: dict[str, Any],
|
||||
mapped_model: str,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
OpenAI CLI (Responses API) 的 model 在请求体顶级字段。
|
||||
|
||||
Args:
|
||||
request_body: 原始请求体
|
||||
mapped_model: 映射后的模型名
|
||||
|
||||
Returns:
|
||||
更新了 model 字段的请求体
|
||||
"""
|
||||
result = dict(request_body)
|
||||
result["model"] = mapped_model
|
||||
return result
|
||||
|
||||
def _process_event_data(
|
||||
self,
|
||||
ctx: StreamContext,
|
||||
event_type: str,
|
||||
data: dict[str, Any],
|
||||
) -> None:
|
||||
"""
|
||||
处理 OpenAI CLI 格式的 SSE 事件
|
||||
|
||||
事件类型:
|
||||
- response.output_text.delta: 文本增量
|
||||
- response.completed: 响应完成(包含 usage)
|
||||
|
||||
跨格式转换时(如 provider=claude:chat),原始事件数据是 Provider 格式而非 OpenAI CLI 格式。
|
||||
此时先调用基类方法通过 Provider 格式解析器提取 usage,再执行 OpenAI CLI 特定的处理逻辑。
|
||||
"""
|
||||
# 跨格式转换时:原始事件是 Provider 格式(如 Claude),
|
||||
# 基类 _process_event_data 会自动选择正确的 Provider 解析器提取 usage/text
|
||||
if ctx.provider_api_format and ctx.provider_api_format != ctx.client_api_format:
|
||||
super()._process_event_data(ctx, event_type, data)
|
||||
return
|
||||
|
||||
# 以下是同格式(openai:cli)的处理逻辑
|
||||
|
||||
# 提取 response_id
|
||||
if not ctx.response_id:
|
||||
response_obj = data.get("response")
|
||||
if isinstance(response_obj, dict) and response_obj.get("id"):
|
||||
ctx.response_id = response_obj["id"]
|
||||
elif "id" in data:
|
||||
ctx.response_id = data["id"]
|
||||
|
||||
# 处理文本增量
|
||||
if event_type in ["response.output_text.delta", "response.outtext.delta"]:
|
||||
delta = data.get("delta")
|
||||
if isinstance(delta, str):
|
||||
ctx.append_text(delta)
|
||||
elif isinstance(delta, dict) and "text" in delta:
|
||||
ctx.append_text(delta["text"])
|
||||
|
||||
# 处理完成事件
|
||||
elif event_type == "response.completed":
|
||||
ctx.has_completion = True
|
||||
response_obj = data.get("response")
|
||||
if isinstance(response_obj, dict):
|
||||
ctx.final_response = response_obj
|
||||
|
||||
usage_obj = response_obj.get("usage")
|
||||
if isinstance(usage_obj, dict):
|
||||
ctx.final_usage = usage_obj
|
||||
ctx.input_tokens = usage_obj.get("input_tokens", 0)
|
||||
ctx.output_tokens = usage_obj.get("output_tokens", 0)
|
||||
|
||||
details = usage_obj.get("input_tokens_details")
|
||||
if isinstance(details, dict):
|
||||
ctx.cached_tokens = details.get("cached_tokens", 0)
|
||||
|
||||
# 如果没有收集到文本,从 output 中提取
|
||||
if not ctx.collected_text and "output" in response_obj:
|
||||
for output_item in response_obj.get("output", []):
|
||||
if output_item.get("type") != "message":
|
||||
continue
|
||||
for content_item in output_item.get("content", []):
|
||||
if content_item.get("type") == "output_text":
|
||||
text = content_item.get("text", "")
|
||||
if text:
|
||||
ctx.append_text(text)
|
||||
|
||||
# 备用:从顶层 usage 提取
|
||||
usage_obj = data.get("usage")
|
||||
if isinstance(usage_obj, dict) and not ctx.final_usage:
|
||||
ctx.final_usage = usage_obj
|
||||
ctx.input_tokens = usage_obj.get("input_tokens", 0)
|
||||
ctx.output_tokens = usage_obj.get("output_tokens", 0)
|
||||
|
||||
details = usage_obj.get("input_tokens_details")
|
||||
if isinstance(details, dict):
|
||||
ctx.cached_tokens = details.get("cached_tokens", 0)
|
||||
|
||||
# 备用:从 response 字段提取
|
||||
response_obj = data.get("response")
|
||||
if isinstance(response_obj, dict) and not ctx.final_response:
|
||||
ctx.final_response = response_obj
|
||||
|
||||
def _extract_response_metadata(
|
||||
self,
|
||||
response: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
从 OpenAI 响应中提取元数据
|
||||
|
||||
提取 model、status、response_id 等字段作为元数据。
|
||||
|
||||
Args:
|
||||
response: OpenAI API 响应
|
||||
|
||||
Returns:
|
||||
提取的元数据字典
|
||||
"""
|
||||
metadata: dict[str, Any] = {}
|
||||
|
||||
# 提取模型名称(实际使用的模型)
|
||||
if "model" in response:
|
||||
metadata["model"] = response["model"]
|
||||
|
||||
# 提取响应 ID
|
||||
if "id" in response:
|
||||
metadata["response_id"] = response["id"]
|
||||
|
||||
# 提取状态
|
||||
if "status" in response:
|
||||
metadata["status"] = response["status"]
|
||||
|
||||
# 提取对象类型
|
||||
if "object" in response:
|
||||
metadata["object"] = response["object"]
|
||||
|
||||
# 提取系统指纹(如果存在)
|
||||
if "system_fingerprint" in response:
|
||||
metadata["system_fingerprint"] = response["system_fingerprint"]
|
||||
|
||||
return metadata
|
||||
|
||||
def _finalize_stream_metadata(self, ctx: StreamContext) -> None:
|
||||
"""
|
||||
从流上下文中提取最终元数据
|
||||
|
||||
在流传输完成后调用,从收集的事件中提取元数据。
|
||||
|
||||
Args:
|
||||
ctx: 流上下文
|
||||
"""
|
||||
# 从 response_id 提取响应 ID
|
||||
if ctx.response_id:
|
||||
ctx.response_metadata["response_id"] = ctx.response_id
|
||||
|
||||
# 从 final_response 提取更多元数据
|
||||
if ctx.final_response and isinstance(ctx.final_response, dict):
|
||||
if "model" in ctx.final_response:
|
||||
ctx.response_metadata["model"] = ctx.final_response["model"]
|
||||
if "status" in ctx.final_response:
|
||||
ctx.response_metadata["status"] = ctx.final_response["status"]
|
||||
if "object" in ctx.final_response:
|
||||
ctx.response_metadata["object"] = ctx.final_response["object"]
|
||||
if "system_fingerprint" in ctx.final_response:
|
||||
ctx.response_metadata["system_fingerprint"] = ctx.final_response[
|
||||
"system_fingerprint"
|
||||
]
|
||||
|
||||
# 如果没有从响应中获取到 model,使用上下文中的
|
||||
if "model" not in ctx.response_metadata and ctx.model:
|
||||
ctx.response_metadata["model"] = ctx.model
|
||||
Reference in New Issue
Block a user