From 70320d7cfaa199b9d3696994e84184eab7479304 Mon Sep 17 00:00:00 2001 From: iomgaa Date: Mon, 6 Jul 2026 22:33:48 -0400 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E6=B7=BB=E5=8A=A0=20LLMResponse?= =?UTF-8?q?=20frozen=20dataclass?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 含 content/thinking/ttft_ms/max_inter_token_ms/call_id 等 11 个字段。 Co-Authored-By: Claude Opus 4.6 (1M context) --- core/types.py | 23 ++++++++++++++++ tests/unit/test_core_types.py | 51 +++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 tests/unit/test_core_types.py diff --git a/core/types.py b/core/types.py index 28ca3cb..fb1cf61 100644 --- a/core/types.py +++ b/core/types.py @@ -1 +1,24 @@ """跨模块共享类型。""" +from __future__ import annotations + +from dataclasses import dataclass + + +@dataclass(frozen=True) +class LLMResponse: + """LLM/VLM 调用的统一返回值。 + + 由 adapters 层生成,core 层消费。frozen=True 确保响应不可变。 + """ + + content: str + thinking: str + model: str + provider: str + prompt_tokens: int + completion_tokens: int + latency_ms: int + ttft_ms: float | None + max_inter_token_ms: float | None + cache_hit: bool + call_id: str diff --git a/tests/unit/test_core_types.py b/tests/unit/test_core_types.py new file mode 100644 index 0000000..45947e2 --- /dev/null +++ b/tests/unit/test_core_types.py @@ -0,0 +1,51 @@ +"""core/types.py 单元测试。""" +from __future__ import annotations + +import pytest + +from core.types import LLMResponse + + +class TestLLMResponse: + @pytest.fixture() + def sample_response(self) -> LLMResponse: + return LLMResponse( + content="回答内容", + thinking="思考过程", + model="deepseek-v4-pro", + provider="deepseek", + prompt_tokens=100, + completion_tokens=50, + latency_ms=1200, + ttft_ms=350.0, + max_inter_token_ms=45.0, + cache_hit=False, + call_id="test-uuid-001", + ) + + def test_frozen_prevents_mutation(self, sample_response: LLMResponse) -> None: + with pytest.raises(AttributeError): + sample_response.content = "篡改" + + def test_all_fields_accessible(self, sample_response: LLMResponse) -> None: + assert sample_response.content == "回答内容" + assert sample_response.thinking == "思考过程" + assert sample_response.model == "deepseek-v4-pro" + assert sample_response.provider == "deepseek" + assert sample_response.prompt_tokens == 100 + assert sample_response.completion_tokens == 50 + assert sample_response.latency_ms == 1200 + assert sample_response.ttft_ms == 350.0 + assert sample_response.max_inter_token_ms == 45.0 + assert sample_response.cache_hit is False + assert sample_response.call_id == "test-uuid-001" + + def test_cache_hit_response_has_none_ttft(self) -> None: + resp = LLMResponse( + content="cached", thinking="", model="m", provider="p", + prompt_tokens=0, completion_tokens=0, latency_ms=1, + ttft_ms=None, max_inter_token_ms=None, cache_hit=True, call_id="c", + ) + assert resp.ttft_ms is None + assert resp.max_inter_token_ms is None + assert resp.cache_hit is True