From 92497d35a599c4685cff22185b93136ad340405a Mon Sep 17 00:00:00 2001 From: Mplan Date: Mon, 29 Jun 2026 23:52:14 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=98=E9=87=8F=E5=87=BD=E6=95=B0=E9=87=8D?= =?UTF-8?q?=E5=91=BD=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 6 ++--- service.py | 10 +++---- tests/test_api.py | 66 +++++++++++++++++++++++------------------------ 3 files changed, 41 insertions(+), 41 deletions(-) diff --git a/app.py b/app.py index de7b104..873a2cf 100644 --- a/app.py +++ b/app.py @@ -13,7 +13,7 @@ from models import ( SendMessageRequest, SendMessageResponse, ) -from service import ChatService, DeepSeekUpstreamError +from service import ChatProviderError, ChatService from storage import JsonSessionStorage, SessionNotFoundError, SessionStorageError @@ -23,7 +23,7 @@ async def encode_sse( try: async for event in events: yield f"data: {json.dumps(event, ensure_ascii=False)}\n\n" - except (DeepSeekUpstreamError, SessionStorageError) as exc: + except (ChatProviderError, SessionStorageError) as exc: error = {"type": "error", "detail": str(exc)} yield f"data: {json.dumps(error, ensure_ascii=False)}\n\n" yield "data: [DONE]\n\n" @@ -109,7 +109,7 @@ def create_app( return JSONResponse(content=response.model_dump(mode="json")) except SessionNotFoundError as exc: raise HTTPException(status_code=404, detail="session not found") from exc - except DeepSeekUpstreamError as exc: + except ChatProviderError as exc: raise HTTPException(status_code=502, detail=str(exc)) from exc except SessionStorageError as exc: raise HTTPException(status_code=500, detail=str(exc)) from exc diff --git a/service.py b/service.py index 7865178..2de31a5 100644 --- a/service.py +++ b/service.py @@ -7,7 +7,7 @@ from models import Message, SendMessageResponse, Session, TokenUsage from storage import JsonSessionStorage -class DeepSeekUpstreamError(Exception): +class ChatProviderError(Exception): pass @@ -41,10 +41,10 @@ class ChatService: ) assistant_content = completion.choices[0].message.content except (APIError, IndexError, AttributeError) as exc: - raise DeepSeekUpstreamError("DeepSeek request failed") from exc + raise ChatProviderError("Upstream chat request failed") from exc if assistant_content is None: - raise DeepSeekUpstreamError("DeepSeek returned an empty response") + raise ChatProviderError("Upstream chat provider returned an empty response") assistant_message = await self._save_exchange( session, content, user_created_at, assistant_content @@ -94,11 +94,11 @@ class ChatService: finally: await stream.close() except (APIError, AttributeError, TypeError) as exc: - raise DeepSeekUpstreamError("DeepSeek stream failed") from exc + raise ChatProviderError("Upstream chat stream failed") from exc assistant_content = "".join(parts) if not assistant_content: - raise DeepSeekUpstreamError("DeepSeek returned an empty response") + raise ChatProviderError("Upstream chat provider returned an empty response") assistant_message = await self._save_exchange( session, content, user_created_at, assistant_content diff --git a/tests/test_api.py b/tests/test_api.py index bd91e5a..c1a9c02 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -80,8 +80,8 @@ class FakeClient: @pytest.fixture -def client_and_deepseek(tmp_path: Path) -> tuple[TestClient, FakeClient]: - deepseek = FakeClient() +def client_and_provider(tmp_path: Path) -> tuple[TestClient, FakeClient]: + provider = FakeClient() settings = Settings( api_key=None, base_url="https://api.deepseek.com", @@ -89,9 +89,9 @@ def client_and_deepseek(tmp_path: Path) -> tuple[TestClient, FakeClient]: default_system_prompt="default prompt", data_dir=tmp_path, ) - app = create_app(settings=settings, client=deepseek) # type: ignore[arg-type] + app = create_app(settings=settings, client=provider) # type: ignore[arg-type] with TestClient(app) as client: - yield client, deepseek + yield client, provider def create_session(client: TestClient, **body: str) -> str: @@ -101,9 +101,9 @@ def create_session(client: TestClient, **body: str) -> str: def test_create_session_uses_default_prompt( - client_and_deepseek: tuple[TestClient, FakeClient], tmp_path: Path + client_and_provider: tuple[TestClient, FakeClient], tmp_path: Path ) -> None: - client, _ = client_and_deepseek + client, _ = client_and_provider session_id = create_session(client) data = json.loads((tmp_path / f"{session_id}.json").read_text()) @@ -112,9 +112,9 @@ def test_create_session_uses_default_prompt( def test_create_session_without_body( - client_and_deepseek: tuple[TestClient, FakeClient] + client_and_provider: tuple[TestClient, FakeClient] ) -> None: - client, _ = client_and_deepseek + client, _ = client_and_provider response = client.post("/sessions") @@ -123,9 +123,9 @@ def test_create_session_without_body( def test_create_session_accepts_custom_prompt( - client_and_deepseek: tuple[TestClient, FakeClient] + client_and_provider: tuple[TestClient, FakeClient] ) -> None: - client, _ = client_and_deepseek + client, _ = client_and_provider response = client.post("/sessions", json={"system_prompt": "回答中文"}) assert response.status_code == 201 @@ -133,9 +133,9 @@ def test_create_session_accepts_custom_prompt( def test_multi_turn_request_contains_full_history( - client_and_deepseek: tuple[TestClient, FakeClient], tmp_path: Path + client_and_provider: tuple[TestClient, FakeClient], tmp_path: Path ) -> None: - client, deepseek = client_and_deepseek + client, provider = client_and_provider session_id = create_session(client, system_prompt="system") first = client.post( @@ -146,7 +146,7 @@ def test_multi_turn_request_contains_full_history( ) assert first.status_code == second.status_code == 200 - assert deepseek.completions.calls[1]["messages"] == [ + assert provider.completions.calls[1]["messages"] == [ {"role": "system", "content": "system"}, {"role": "user", "content": "first"}, {"role": "assistant", "content": "reply-1"}, @@ -169,9 +169,9 @@ def test_multi_turn_request_contains_full_history( def test_invalid_session_and_blank_message( - client_and_deepseek: tuple[TestClient, FakeClient] + client_and_provider: tuple[TestClient, FakeClient] ) -> None: - client, _ = client_and_deepseek + client, _ = client_and_provider missing = client.post( "/sessions/00000000-0000-0000-0000-000000000000/messages", @@ -187,12 +187,12 @@ def test_invalid_session_and_blank_message( def test_upstream_failure_does_not_change_history( - client_and_deepseek: tuple[TestClient, FakeClient], tmp_path: Path + client_and_provider: tuple[TestClient, FakeClient], tmp_path: Path ) -> None: - client, deepseek = client_and_deepseek + client, provider = client_and_provider session_id = create_session(client) before = (tmp_path / f"{session_id}.json").read_text() - deepseek.completions.error = APIConnectionError(request=object()) # type: ignore[arg-type] + provider.completions.error = APIConnectionError(request=object()) # type: ignore[arg-type] response = client.post( f"/sessions/{session_id}/messages", json={"content": "hello"} @@ -203,9 +203,9 @@ def test_upstream_failure_does_not_change_history( def test_corrupt_session_returns_500( - client_and_deepseek: tuple[TestClient, FakeClient], tmp_path: Path + client_and_provider: tuple[TestClient, FakeClient], tmp_path: Path ) -> None: - client, _ = client_and_deepseek + client, _ = client_and_provider session_id = create_session(client) (tmp_path / f"{session_id}.json").write_text("not-json") @@ -217,9 +217,9 @@ def test_corrupt_session_returns_500( def test_streaming_response_is_sse_and_persists_complete_message( - client_and_deepseek: tuple[TestClient, FakeClient], tmp_path: Path + client_and_provider: tuple[TestClient, FakeClient], tmp_path: Path ) -> None: - client, deepseek = client_and_deepseek + client, provider = client_and_provider session_id = create_session(client, system_prompt="system") response = client.post( @@ -241,8 +241,8 @@ def test_streaming_response_is_sse_and_persists_complete_message( ] assert json.loads(payloads[-2])["usage"]["total_tokens"] == 14 assert payloads[-1] == "[DONE]" - assert deepseek.completions.calls[0]["stream"] is True - assert deepseek.completions.calls[0]["stream_options"] == { + assert provider.completions.calls[0]["stream"] is True + assert provider.completions.calls[0]["stream_options"] == { "include_usage": True } @@ -255,12 +255,12 @@ def test_streaming_response_is_sse_and_persists_complete_message( def test_streaming_failure_emits_error_and_does_not_persist( - client_and_deepseek: tuple[TestClient, FakeClient], tmp_path: Path + client_and_provider: tuple[TestClient, FakeClient], tmp_path: Path ) -> None: - client, deepseek = client_and_deepseek + client, provider = client_and_provider session_id = create_session(client) before = (tmp_path / f"{session_id}.json").read_text() - deepseek.completions.stream_error = APIConnectionError( # type: ignore[arg-type] + provider.completions.stream_error = APIConnectionError( # type: ignore[arg-type] request=object() ) @@ -276,9 +276,9 @@ def test_streaming_failure_emits_error_and_does_not_persist( def test_legacy_messages_without_timestamp_remain_usable( - client_and_deepseek: tuple[TestClient, FakeClient], tmp_path: Path + client_and_provider: tuple[TestClient, FakeClient], tmp_path: Path ) -> None: - client, _ = client_and_deepseek + client, _ = client_and_provider session_id = create_session(client) path = tmp_path / f"{session_id}.json" data = json.loads(path.read_text()) @@ -296,13 +296,13 @@ def test_legacy_messages_without_timestamp_remain_usable( def test_same_session_concurrent_messages_are_serialized(tmp_path: Path) -> None: async def scenario() -> None: - deepseek = FakeClient() - deepseek.completions.delay = 0.01 + provider = FakeClient() + provider.completions.delay = 0.01 storage = JsonSessionStorage(tmp_path) session = await storage.create("system") service = ChatService( storage=storage, - client=deepseek, # type: ignore[arg-type] + client=provider, # type: ignore[arg-type] model="deepseek-v4-flash", ) @@ -311,7 +311,7 @@ def test_same_session_concurrent_messages_are_serialized(tmp_path: Path) -> None service.generate_response(session.session_id, "second"), ) - assert deepseek.completions.calls[1]["messages"] == [ + assert provider.completions.calls[1]["messages"] == [ {"role": "system", "content": "system"}, {"role": "user", "content": "first"}, {"role": "assistant", "content": "reply-1"},