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:
fawney19
2026-01-22 01:48:56 +08:00
parent 99388bfa33
commit cc5db20c58
25 changed files with 1818 additions and 547 deletions

View File

@@ -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",
]

View File

@@ -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],
)