mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +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:
110
tests/services/test_format_conversion_health_buckets.py
Normal file
110
tests/services/test_format_conversion_health_buckets.py
Normal file
@@ -0,0 +1,110 @@
|
||||
from contextlib import asynccontextmanager
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from src.core.api_format import APIFormat
|
||||
from src.services.orchestration.error_classifier import ErrorClassifier
|
||||
from src.services.request.executor import RequestExecutor
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def _noop_async_cm():
|
||||
yield
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_executor_records_health_by_provider_format() -> None:
|
||||
db = MagicMock()
|
||||
|
||||
concurrency_manager = MagicMock()
|
||||
concurrency_manager.get_current_concurrency = AsyncMock(return_value=(0, 0))
|
||||
concurrency_manager.get_key_rpm_count = AsyncMock(return_value=1)
|
||||
concurrency_manager.rpm_guard = MagicMock(return_value=_noop_async_cm())
|
||||
|
||||
adaptive_manager = MagicMock()
|
||||
|
||||
provider = MagicMock()
|
||||
provider.id = "p1"
|
||||
provider.name = "p1"
|
||||
|
||||
endpoint = MagicMock()
|
||||
endpoint.id = "e1"
|
||||
endpoint.api_format = "OPENAI"
|
||||
|
||||
key = MagicMock()
|
||||
key.id = "k1"
|
||||
key.api_key = "encrypted"
|
||||
key.rpm_limit = 10
|
||||
key.learned_rpm_limit = None
|
||||
key.cache_ttl_minutes = 0
|
||||
|
||||
candidate = MagicMock()
|
||||
candidate.provider = provider
|
||||
candidate.endpoint = endpoint
|
||||
candidate.key = key
|
||||
candidate.is_cached = False
|
||||
|
||||
async def request_func(_provider, _endpoint, _key, _candidate): # noqa: ANN001
|
||||
return {"ok": True}
|
||||
|
||||
with patch("src.services.request.executor.RequestCandidateService.mark_candidate_started"), patch(
|
||||
"src.services.request.executor.RequestCandidateService.mark_candidate_success"
|
||||
), patch("src.services.request.executor.get_adaptive_reservation_manager") as mock_res_mgr, patch(
|
||||
"src.services.request.executor.health_monitor.record_success"
|
||||
) as record_success:
|
||||
mock_res_mgr.return_value.calculate_reservation.return_value = MagicMock(
|
||||
ratio=0.0, phase="stable", confidence=1.0
|
||||
)
|
||||
|
||||
executor = RequestExecutor(db=db, concurrency_manager=concurrency_manager, adaptive_manager=adaptive_manager)
|
||||
await executor.execute(
|
||||
candidate=candidate,
|
||||
candidate_id="c1",
|
||||
candidate_index=0,
|
||||
user_api_key=MagicMock(user_id="u1", id="ak1"),
|
||||
request_func=request_func,
|
||||
request_id="r1",
|
||||
api_format=APIFormat.CLAUDE, # client_format
|
||||
model_name="m",
|
||||
is_stream=False,
|
||||
)
|
||||
|
||||
record_success.assert_called()
|
||||
assert record_success.call_args.kwargs["api_format"] == "OPENAI"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_error_classifier_records_failure_by_provider_format() -> None:
|
||||
db = MagicMock()
|
||||
classifier = ErrorClassifier(db=db, cache_scheduler=None, adaptive_manager=MagicMock())
|
||||
|
||||
provider = MagicMock()
|
||||
provider.name = "p1"
|
||||
|
||||
endpoint = MagicMock()
|
||||
endpoint.id = "e1"
|
||||
endpoint.api_format = "OPENAI"
|
||||
|
||||
key = MagicMock()
|
||||
key.id = "k1"
|
||||
|
||||
with patch("src.services.orchestration.error_classifier.health_monitor.record_failure") as record_failure:
|
||||
await classifier.handle_retriable_error(
|
||||
error=RuntimeError("boom"),
|
||||
provider=provider,
|
||||
endpoint=endpoint,
|
||||
key=key,
|
||||
affinity_key="aff",
|
||||
api_format=APIFormat.CLAUDE, # client_format
|
||||
global_model_id="gm1",
|
||||
captured_key_concurrent=None,
|
||||
elapsed_ms=None,
|
||||
request_id="r1",
|
||||
attempt=1,
|
||||
max_attempts=2,
|
||||
)
|
||||
|
||||
record_failure.assert_called()
|
||||
assert record_failure.call_args.kwargs["api_format"] == "OPENAI"
|
||||
|
||||
Reference in New Issue
Block a user