mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +08:00
feat: 实现跨 API 格式自动转换功能
- 新增端点级 format_acceptance_config 配置,控制是否接受跨格式请求 - 重构 EndpointFormDialog 为卡片式布局,支持内联编辑和格式转换开关 - StreamProcessor 实现流式响应的跨格式转换,支持 OpenAI/Claude/Gemini 互转 - CacheAwareScheduler 按端点格式筛选候选,同格式优先于跨格式 - 健康度/熔断按 Provider 端点格式分桶,而非客户端请求格式 - 新增 format_conversion_total 和 format_conversion_duration_seconds 指标 - 新增全局配置 format_conversion_enabled 控制总开关 - Input 组件新增 size="sm" 尺寸选项
This commit is contained in:
@@ -13,9 +13,12 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Union
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
from typing import TYPE_CHECKING, Any, Dict, Generator, Optional, Tuple, Union
|
||||
|
||||
from src.core.logger import logger
|
||||
from src.core.metrics import format_conversion_duration_seconds, format_conversion_total
|
||||
|
||||
from .exceptions import FormatConversionError
|
||||
|
||||
@@ -23,6 +26,35 @@ if TYPE_CHECKING:
|
||||
from .state import GeminiStreamConversionState, StreamConversionState
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _track_conversion_metrics(
|
||||
direction: str, source: str, target: str
|
||||
) -> Generator[None, None, None]:
|
||||
"""
|
||||
跟踪转换指标的上下文管理器
|
||||
|
||||
Args:
|
||||
direction: 转换方向(request/response/stream)
|
||||
source: 源格式(大写)
|
||||
target: 目标格式(大写)
|
||||
|
||||
Yields:
|
||||
None - 执行转换逻辑
|
||||
"""
|
||||
start = time.perf_counter()
|
||||
status = "success"
|
||||
try:
|
||||
yield
|
||||
except Exception:
|
||||
status = "error"
|
||||
raise
|
||||
finally:
|
||||
format_conversion_total.labels(direction, source, target, status).inc()
|
||||
format_conversion_duration_seconds.labels(direction, source, target).observe(
|
||||
time.perf_counter() - start
|
||||
)
|
||||
|
||||
|
||||
class FormatConverterRegistry:
|
||||
"""
|
||||
格式转换器注册表
|
||||
@@ -118,7 +150,9 @@ class FormatConverterRegistry:
|
||||
logger.debug(f"[ConverterRegistry] 请求转换成功: {source_format} -> {target_format}")
|
||||
return converted
|
||||
except Exception as e:
|
||||
logger.error(f"[ConverterRegistry] 请求转换失败: {source_format} -> {target_format}: {e}")
|
||||
logger.error(
|
||||
f"[ConverterRegistry] 请求转换失败: {source_format} -> {target_format}: {e}"
|
||||
)
|
||||
return request
|
||||
|
||||
def convert_response(
|
||||
@@ -160,7 +194,9 @@ class FormatConverterRegistry:
|
||||
logger.debug(f"[ConverterRegistry] 响应转换成功: {source_format} -> {target_format}")
|
||||
return converted
|
||||
except Exception as e:
|
||||
logger.error(f"[ConverterRegistry] 响应转换失败: {source_format} -> {target_format}: {e}")
|
||||
logger.error(
|
||||
f"[ConverterRegistry] 响应转换失败: {source_format} -> {target_format}: {e}"
|
||||
)
|
||||
return response
|
||||
|
||||
def convert_stream_chunk(
|
||||
@@ -196,14 +232,16 @@ class FormatConverterRegistry:
|
||||
result: list[Dict[str, Any]] = converter.convert_stream_chunk(chunk, state)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"[ConverterRegistry] 流式块转换失败: {source_format} -> {target_format}: {e}")
|
||||
logger.error(
|
||||
f"[ConverterRegistry] 流式块转换失败: {source_format} -> {target_format}: {e}"
|
||||
)
|
||||
return [chunk]
|
||||
|
||||
# 降级到普通响应转换(作为单个事件返回)
|
||||
if hasattr(converter, "convert_response"):
|
||||
try:
|
||||
result = converter.convert_response(chunk)
|
||||
return [result]
|
||||
converted: Dict[str, Any] = converter.convert_response(chunk)
|
||||
return [converted]
|
||||
except Exception:
|
||||
return [chunk]
|
||||
|
||||
@@ -284,8 +322,11 @@ class FormatConverterRegistry:
|
||||
Raises:
|
||||
FormatConversionError: 转换失败时抛出
|
||||
"""
|
||||
source_upper = source_format.upper()
|
||||
target_upper = target_format.upper()
|
||||
|
||||
# 同格式无需转换
|
||||
if source_format.upper() == target_format.upper():
|
||||
if source_upper == target_upper:
|
||||
return request
|
||||
|
||||
converter = self.get_converter(source_format, target_format)
|
||||
@@ -293,16 +334,19 @@ class FormatConverterRegistry:
|
||||
raise FormatConversionError(source_format, target_format, "未找到转换器")
|
||||
|
||||
if not hasattr(converter, "convert_request"):
|
||||
raise FormatConversionError(source_format, target_format, "转换器缺少 convert_request 方法")
|
||||
raise FormatConversionError(
|
||||
source_format, target_format, "转换器缺少 convert_request 方法"
|
||||
)
|
||||
|
||||
try:
|
||||
converted: Dict[str, Any] = converter.convert_request(request)
|
||||
logger.debug(f"[ConverterRegistry] 请求转换成功: {source_format} -> {target_format}")
|
||||
return converted
|
||||
except FormatConversionError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise FormatConversionError(source_format, target_format, str(e)) from e
|
||||
with _track_conversion_metrics("request", source_upper, target_upper):
|
||||
try:
|
||||
converted: Dict[str, Any] = converter.convert_request(request)
|
||||
logger.debug(f"[ConverterRegistry] 请求转换成功: {source_format} -> {target_format}")
|
||||
return converted
|
||||
except FormatConversionError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise FormatConversionError(source_format, target_format, str(e)) from e
|
||||
|
||||
def convert_response_strict(
|
||||
self,
|
||||
@@ -316,7 +360,10 @@ class FormatConverterRegistry:
|
||||
Raises:
|
||||
FormatConversionError: 转换失败时抛出
|
||||
"""
|
||||
if source_format.upper() == target_format.upper():
|
||||
source_upper = source_format.upper()
|
||||
target_upper = target_format.upper()
|
||||
|
||||
if source_upper == target_upper:
|
||||
return response
|
||||
|
||||
converter = self.get_converter(source_format, target_format)
|
||||
@@ -324,16 +371,19 @@ class FormatConverterRegistry:
|
||||
raise FormatConversionError(source_format, target_format, "未找到转换器")
|
||||
|
||||
if not hasattr(converter, "convert_response"):
|
||||
raise FormatConversionError(source_format, target_format, "转换器缺少 convert_response 方法")
|
||||
raise FormatConversionError(
|
||||
source_format, target_format, "转换器缺少 convert_response 方法"
|
||||
)
|
||||
|
||||
try:
|
||||
converted: Dict[str, Any] = converter.convert_response(response)
|
||||
logger.debug(f"[ConverterRegistry] 响应转换成功: {source_format} -> {target_format}")
|
||||
return converted
|
||||
except FormatConversionError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise FormatConversionError(source_format, target_format, str(e)) from e
|
||||
with _track_conversion_metrics("response", source_upper, target_upper):
|
||||
try:
|
||||
converted: Dict[str, Any] = converter.convert_response(response)
|
||||
logger.debug(f"[ConverterRegistry] 响应转换成功: {source_format} -> {target_format}")
|
||||
return converted
|
||||
except FormatConversionError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise FormatConversionError(source_format, target_format, str(e)) from e
|
||||
|
||||
def convert_stream_chunk_strict(
|
||||
self,
|
||||
@@ -357,7 +407,10 @@ class FormatConverterRegistry:
|
||||
Raises:
|
||||
FormatConversionError: 转换失败时抛出
|
||||
"""
|
||||
if source_format.upper() == target_format.upper():
|
||||
source_upper = source_format.upper()
|
||||
target_upper = target_format.upper()
|
||||
|
||||
if source_upper == target_upper:
|
||||
return [chunk]
|
||||
|
||||
converter = self.get_converter(source_format, target_format)
|
||||
@@ -369,13 +422,16 @@ class FormatConverterRegistry:
|
||||
source_format, target_format, "转换器缺少 convert_stream_chunk 方法"
|
||||
)
|
||||
|
||||
try:
|
||||
result: list[Dict[str, Any]] = converter.convert_stream_chunk(chunk, state)
|
||||
return result
|
||||
except FormatConversionError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise FormatConversionError(source_format, target_format, f"流式块转换失败: {e}") from e
|
||||
with _track_conversion_metrics("stream", source_upper, target_upper):
|
||||
try:
|
||||
result: list[Dict[str, Any]] = converter.convert_stream_chunk(chunk, state)
|
||||
return result
|
||||
except FormatConversionError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise FormatConversionError(
|
||||
source_format, target_format, f"流式块转换失败: {e}"
|
||||
) from e
|
||||
|
||||
|
||||
# 全局单例
|
||||
@@ -387,4 +443,3 @@ __all__ = [
|
||||
"converter_registry",
|
||||
"FormatConversionError",
|
||||
]
|
||||
|
||||
|
||||
@@ -65,3 +65,18 @@ model_mapping_conflict_total = Counter(
|
||||
"model_mapping_conflict_total",
|
||||
"Total number of mapping conflicts detected (same name maps to multiple GlobalModels)",
|
||||
)
|
||||
|
||||
# ==================== API 格式转换 ====================
|
||||
|
||||
format_conversion_total = Counter(
|
||||
"format_conversion_total",
|
||||
"Total number of format conversions",
|
||||
["direction", "source_format", "target_format", "status"], # status: success/error
|
||||
)
|
||||
|
||||
format_conversion_duration_seconds = Histogram(
|
||||
"format_conversion_duration_seconds",
|
||||
"Duration of format conversions in seconds",
|
||||
["direction", "source_format", "target_format"],
|
||||
buckets=[0.0005, 0.001, 0.0025, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user