mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
refactor: 全局适配 ApiFamily/EndpointKind 结构化标识体系
将新的 (ApiFamily, EndpointKind) / `family:kind` 签名体系应用到整个代码库: - API Handlers: 所有 adapter/handler 使用新的签名格式 - Services: provider, model, usage, cache, auth 等服务层适配 - Database: ProviderEndpoint 新增 api_family/endpoint_kind 字段 - Frontend: Provider 管理、Usage 表格等组件适配 - Tests: 更新所有相关测试用例
This commit is contained in:
@@ -85,7 +85,7 @@ class TestConvertSseLineBasic:
|
||||
|
||||
def test_empty_line_returns_empty_list(self) -> None:
|
||||
handler = MockCliHandler()
|
||||
ctx = StreamContext(model="test", api_format="OPENAI")
|
||||
ctx = StreamContext(model="test", api_format="openai:chat")
|
||||
|
||||
result = handler._convert_sse_line(ctx, "", [])
|
||||
|
||||
@@ -93,7 +93,7 @@ class TestConvertSseLineBasic:
|
||||
|
||||
def test_whitespace_line_returns_line(self) -> None:
|
||||
handler = MockCliHandler()
|
||||
ctx = StreamContext(model="test", api_format="OPENAI")
|
||||
ctx = StreamContext(model="test", api_format="openai:chat")
|
||||
|
||||
result = handler._convert_sse_line(ctx, " ", [])
|
||||
|
||||
@@ -101,7 +101,7 @@ class TestConvertSseLineBasic:
|
||||
|
||||
def test_done_marker_returns_as_is(self) -> None:
|
||||
handler = MockCliHandler()
|
||||
ctx = StreamContext(model="test", api_format="OPENAI")
|
||||
ctx = StreamContext(model="test", api_format="openai:chat")
|
||||
|
||||
result = handler._convert_sse_line(ctx, "data: [DONE]", [])
|
||||
|
||||
@@ -109,7 +109,7 @@ class TestConvertSseLineBasic:
|
||||
|
||||
def test_non_data_line_passthrough(self) -> None:
|
||||
handler = MockCliHandler()
|
||||
ctx = StreamContext(model="test", api_format="OPENAI")
|
||||
ctx = StreamContext(model="test", api_format="openai:chat")
|
||||
|
||||
result = handler._convert_sse_line(ctx, "event: message_start", [])
|
||||
|
||||
@@ -117,7 +117,7 @@ class TestConvertSseLineBasic:
|
||||
|
||||
def test_invalid_json_passthrough(self) -> None:
|
||||
handler = MockCliHandler()
|
||||
ctx = StreamContext(model="test", api_format="OPENAI")
|
||||
ctx = StreamContext(model="test", api_format="openai:chat")
|
||||
|
||||
result = handler._convert_sse_line(ctx, "data: {invalid json}", [])
|
||||
|
||||
@@ -130,9 +130,9 @@ class TestConvertSseLineWithMockConverter:
|
||||
def test_same_format_returns_original(self) -> None:
|
||||
"""同格式无需转换"""
|
||||
handler = MockCliHandler()
|
||||
ctx = StreamContext(model="test", api_format="OPENAI")
|
||||
ctx.provider_api_format = "OPENAI"
|
||||
ctx.client_api_format = "OPENAI"
|
||||
ctx = StreamContext(model="test", api_format="openai:chat")
|
||||
ctx.provider_api_format = "openai:chat"
|
||||
ctx.client_api_format = "openai:chat"
|
||||
|
||||
chunk = {"choices": [{"delta": {"content": "hello"}}]}
|
||||
line = f"data: {json.dumps(chunk)}"
|
||||
@@ -144,14 +144,14 @@ class TestConvertSseLineWithMockConverter:
|
||||
|
||||
def test_state_initialization(self) -> None:
|
||||
"""测试状态自动初始化
|
||||
|
||||
|
||||
流式转换状态应使用用户请求的原始模型名(ctx.model),
|
||||
而非映射后的模型名(ctx.mapped_model),确保返回给客户端的响应使用原始模型名。
|
||||
"""
|
||||
handler = MockCliHandler()
|
||||
ctx = StreamContext(model="gpt-4", api_format="OPENAI")
|
||||
ctx.provider_api_format = "OPENAI"
|
||||
ctx.client_api_format = "OPENAI"
|
||||
ctx = StreamContext(model="gpt-4", api_format="openai:chat")
|
||||
ctx.provider_api_format = "openai:chat"
|
||||
ctx.client_api_format = "openai:chat"
|
||||
ctx.mapped_model = "claude-3-5-sonnet" # 映射后的模型名(发给上游的)
|
||||
ctx.request_id = "req_123"
|
||||
|
||||
@@ -181,9 +181,9 @@ class TestConvertSseLineOneInManyOut:
|
||||
def test_openai_to_claude_conversion(self) -> None:
|
||||
"""测试 OpenAI -> Claude 流式转换"""
|
||||
handler = MockCliHandler()
|
||||
ctx = StreamContext(model="gpt-4", api_format="OPENAI")
|
||||
ctx.provider_api_format = "OPENAI"
|
||||
ctx.client_api_format = "CLAUDE"
|
||||
ctx = StreamContext(model="gpt-4", api_format="openai:chat")
|
||||
ctx.provider_api_format = "openai:chat"
|
||||
ctx.client_api_format = "claude:chat"
|
||||
ctx.mapped_model = "claude-3-5-sonnet"
|
||||
ctx.request_id = "req_test"
|
||||
|
||||
@@ -205,9 +205,9 @@ class TestConvertSseLineOneInManyOut:
|
||||
def test_claude_to_openai_conversion(self) -> None:
|
||||
"""测试 Claude -> OpenAI 流式转换"""
|
||||
handler = MockCliHandler()
|
||||
ctx = StreamContext(model="claude-3-5-sonnet", api_format="CLAUDE")
|
||||
ctx.provider_api_format = "CLAUDE"
|
||||
ctx.client_api_format = "OPENAI"
|
||||
ctx = StreamContext(model="claude-3-5-sonnet", api_format="claude:chat")
|
||||
ctx.provider_api_format = "claude:chat"
|
||||
ctx.client_api_format = "openai:chat"
|
||||
ctx.mapped_model = "gpt-4"
|
||||
ctx.request_id = "msg_test"
|
||||
|
||||
@@ -225,9 +225,9 @@ class TestConvertSseLineOneInManyOut:
|
||||
def test_multiple_chunks_state_persistence(self) -> None:
|
||||
"""测试多个 chunk 之间状态持久化"""
|
||||
handler = MockCliHandler()
|
||||
ctx = StreamContext(model="gpt-4", api_format="OPENAI")
|
||||
ctx.provider_api_format = "OPENAI"
|
||||
ctx.client_api_format = "CLAUDE"
|
||||
ctx = StreamContext(model="gpt-4", api_format="openai:chat")
|
||||
ctx.provider_api_format = "openai:chat"
|
||||
ctx.client_api_format = "claude:chat"
|
||||
ctx.mapped_model = "claude-3-5-sonnet"
|
||||
|
||||
# 第一个 chunk
|
||||
@@ -249,7 +249,7 @@ class TestStreamContextIntegration:
|
||||
|
||||
def test_stream_conversion_state_reset_on_retry(self) -> None:
|
||||
"""测试重试时重置流式转换状态"""
|
||||
ctx = StreamContext(model="test", api_format="OPENAI")
|
||||
ctx = StreamContext(model="test", api_format="openai:chat")
|
||||
ctx.stream_conversion_state = StreamState(model="test", message_id="123")
|
||||
|
||||
ctx.reset_for_retry()
|
||||
@@ -258,7 +258,7 @@ class TestStreamContextIntegration:
|
||||
|
||||
def test_stream_conversion_state_field_exists(self) -> None:
|
||||
"""测试 StreamContext 有 stream_conversion_state 字段"""
|
||||
ctx = StreamContext(model="test", api_format="OPENAI")
|
||||
ctx = StreamContext(model="test", api_format="openai:chat")
|
||||
|
||||
assert hasattr(ctx, "stream_conversion_state")
|
||||
assert ctx.stream_conversion_state is None
|
||||
|
||||
@@ -3,7 +3,7 @@ from src.api.handlers.base.stream_context import StreamContext
|
||||
|
||||
|
||||
def test_collected_text_append_and_property() -> None:
|
||||
ctx = StreamContext(model="test-model", api_format="OPENAI")
|
||||
ctx = StreamContext(model="test-model", api_format="openai:chat")
|
||||
assert ctx.collected_text == ""
|
||||
|
||||
ctx.append_text("hello")
|
||||
@@ -13,7 +13,7 @@ def test_collected_text_append_and_property() -> None:
|
||||
|
||||
|
||||
def test_reset_for_retry_clears_state() -> None:
|
||||
ctx = StreamContext(model="test-model", api_format="OPENAI")
|
||||
ctx = StreamContext(model="test-model", api_format="openai:chat")
|
||||
ctx.append_text("x")
|
||||
ctx.update_usage(input_tokens=10, output_tokens=5)
|
||||
ctx.parsed_chunks.append({"type": "chunk"})
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
from typing import Any
|
||||
|
||||
from src.api.handlers.base.response_parser import ParsedChunk, ParsedResponse, ResponseParser, StreamStats
|
||||
from src.api.handlers.base.response_parser import (
|
||||
ParsedChunk,
|
||||
ParsedResponse,
|
||||
ResponseParser,
|
||||
StreamStats,
|
||||
)
|
||||
from src.api.handlers.base.stream_context import StreamContext
|
||||
from src.api.handlers.base.stream_processor import StreamProcessor
|
||||
from src.utils.sse_parser import SSEEventParser
|
||||
@@ -21,7 +26,7 @@ class DummyParser(ResponseParser):
|
||||
|
||||
|
||||
def test_process_line_strips_newlines_and_finalizes_event() -> None:
|
||||
ctx = StreamContext(model="test-model", api_format="OPENAI")
|
||||
ctx = StreamContext(model="test-model", api_format="openai:chat")
|
||||
processor = StreamProcessor(request_id="test-request", default_parser=DummyParser())
|
||||
sse_parser = SSEEventParser()
|
||||
|
||||
@@ -29,4 +34,3 @@ def test_process_line_strips_newlines_and_finalizes_event() -> None:
|
||||
processor._process_line(ctx, sse_parser, "\n")
|
||||
|
||||
assert ctx.has_completion is True
|
||||
|
||||
|
||||
@@ -33,9 +33,9 @@ async def _empty_async_iter():
|
||||
async def test_create_response_stream_converts_claude_to_openai() -> None:
|
||||
register_default_normalizers()
|
||||
|
||||
ctx = StreamContext(model="test-model", api_format="OPENAI")
|
||||
ctx.client_api_format = "OPENAI"
|
||||
ctx.provider_api_format = "CLAUDE"
|
||||
ctx = StreamContext(model="test-model", api_format="openai:chat")
|
||||
ctx.client_api_format = "openai:chat"
|
||||
ctx.provider_api_format = "claude:chat"
|
||||
ctx.needs_conversion = True
|
||||
|
||||
processor = StreamProcessor(request_id="test-request", default_parser=DummyParser())
|
||||
|
||||
@@ -28,9 +28,9 @@ async def _iter_bytes(chunks: list[bytes]) -> AsyncIterator[bytes]:
|
||||
async def test_stream_processor_converts_gemini_json_lines_without_data_prefix() -> None:
|
||||
register_default_normalizers()
|
||||
|
||||
ctx = StreamContext(model="gemini-test", api_format="OPENAI")
|
||||
ctx.provider_api_format = "GEMINI"
|
||||
ctx.client_api_format = "OPENAI"
|
||||
ctx = StreamContext(model="gemini-test", api_format="openai:chat")
|
||||
ctx.provider_api_format = "gemini:chat"
|
||||
ctx.client_api_format = "openai:chat"
|
||||
ctx.needs_conversion = True
|
||||
ctx.request_id = "req_test"
|
||||
ctx.mapped_model = "gemini-test"
|
||||
@@ -62,7 +62,7 @@ async def test_stream_processor_converts_gemini_json_lines_without_data_prefix()
|
||||
|
||||
processor = StreamProcessor(
|
||||
request_id="req_test",
|
||||
default_parser=get_parser_for_format("OPENAI"),
|
||||
default_parser=get_parser_for_format("openai:chat"),
|
||||
)
|
||||
|
||||
out = b""
|
||||
|
||||
@@ -17,4 +17,3 @@ def test_is_done_event_false_when_no_candidates_or_reason() -> None:
|
||||
parser = GeminiStreamParser()
|
||||
assert parser.is_done_event({}) is False
|
||||
assert parser.is_done_event({"candidates": [{}]}) is False
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ API Pipeline 测试
|
||||
- 审计日志记录
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from datetime import datetime, timezone
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
|
||||
from src.api.base.pipeline import ApiRequestPipeline
|
||||
@@ -142,7 +142,11 @@ class TestPipelineAuditLogging:
|
||||
) as mock_log:
|
||||
with patch("time.time", return_value=1001.0):
|
||||
pipeline._record_audit_event(
|
||||
mock_context, mock_adapter, success=False, status_code=500, error="Internal error"
|
||||
mock_context,
|
||||
mock_adapter,
|
||||
success=False,
|
||||
status_code=500,
|
||||
error="Internal error",
|
||||
)
|
||||
|
||||
mock_log.assert_called_once()
|
||||
|
||||
Reference in New Issue
Block a user