feat(core): 追加 GeneratedQuestion frozen dataclass
跨层共享类型,被 core/evolution/ 和 app/harness/、app/question_gen/ 使用。 frozen=True + tuple 字段确保不可变。无默认值(显式传入)。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,3 +23,31 @@ class LLMResponse:
|
|||||||
max_inter_token_ms: float | None
|
max_inter_token_ms: float | None
|
||||||
cache_hit: bool
|
cache_hit: bool
|
||||||
call_id: str
|
call_id: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class GeneratedQuestion:
|
||||||
|
"""单条生成/加载的题目。
|
||||||
|
|
||||||
|
跨层共享类型,被 core/evolution/ 和 app/harness/、app/question_gen/ 使用。
|
||||||
|
frozen=True 确保题目不可变。
|
||||||
|
|
||||||
|
属性:
|
||||||
|
question_id: 题目唯一标识。
|
||||||
|
video_id: 所属视频标识。
|
||||||
|
task_type: 题型(如 "Action Reasoning")。
|
||||||
|
question: 题目文本。
|
||||||
|
options: 选项元组(如 ("A. ...", "B. ...", "C. ...", "D. ..."))。
|
||||||
|
answer: 正确答案字母(如 "B")。
|
||||||
|
source_nodes: 来源节点 ID 元组。
|
||||||
|
difficulty: 难度等级。
|
||||||
|
"""
|
||||||
|
|
||||||
|
question_id: str
|
||||||
|
video_id: str
|
||||||
|
task_type: str
|
||||||
|
question: str
|
||||||
|
options: tuple[str, ...]
|
||||||
|
answer: str
|
||||||
|
source_nodes: tuple[str, ...]
|
||||||
|
difficulty: str
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
"""core/types.py 单元测试。"""
|
"""core/types.py 单元测试。"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from core.types import LLMResponse
|
from core.types import GeneratedQuestion, LLMResponse
|
||||||
|
|
||||||
|
|
||||||
class TestLLMResponse:
|
class TestLLMResponse:
|
||||||
@@ -42,10 +43,58 @@ class TestLLMResponse:
|
|||||||
|
|
||||||
def test_cache_hit_response_has_none_ttft(self) -> None:
|
def test_cache_hit_response_has_none_ttft(self) -> None:
|
||||||
resp = LLMResponse(
|
resp = LLMResponse(
|
||||||
content="cached", thinking="", model="m", provider="p",
|
content="cached",
|
||||||
prompt_tokens=0, completion_tokens=0, latency_ms=1,
|
thinking="",
|
||||||
ttft_ms=None, max_inter_token_ms=None, cache_hit=True, call_id="c",
|
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.ttft_ms is None
|
||||||
assert resp.max_inter_token_ms is None
|
assert resp.max_inter_token_ms is None
|
||||||
assert resp.cache_hit is True
|
assert resp.cache_hit is True
|
||||||
|
|
||||||
|
|
||||||
|
class TestGeneratedQuestion:
|
||||||
|
@pytest.fixture()
|
||||||
|
def sample_question(self) -> GeneratedQuestion:
|
||||||
|
return GeneratedQuestion(
|
||||||
|
question_id="719-1",
|
||||||
|
video_id="B7Hh0PY1kks",
|
||||||
|
task_type="Action Reasoning",
|
||||||
|
question="What are the differing motivations?",
|
||||||
|
options=("A. Option 1", "B. Option 2", "C. Option 3", "D. Option 4"),
|
||||||
|
answer="B",
|
||||||
|
source_nodes=(),
|
||||||
|
difficulty="medium",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_frozen_prevents_mutation(self, sample_question: GeneratedQuestion) -> None:
|
||||||
|
with pytest.raises(AttributeError):
|
||||||
|
sample_question.question = "篡改"
|
||||||
|
|
||||||
|
def test_all_fields_accessible(self, sample_question: GeneratedQuestion) -> None:
|
||||||
|
assert sample_question.question_id == "719-1"
|
||||||
|
assert sample_question.video_id == "B7Hh0PY1kks"
|
||||||
|
assert sample_question.task_type == "Action Reasoning"
|
||||||
|
assert sample_question.question == "What are the differing motivations?"
|
||||||
|
assert sample_question.options == (
|
||||||
|
"A. Option 1",
|
||||||
|
"B. Option 2",
|
||||||
|
"C. Option 3",
|
||||||
|
"D. Option 4",
|
||||||
|
)
|
||||||
|
assert sample_question.answer == "B"
|
||||||
|
assert sample_question.source_nodes == ()
|
||||||
|
assert sample_question.difficulty == "medium"
|
||||||
|
|
||||||
|
def test_options_is_tuple(self, sample_question: GeneratedQuestion) -> None:
|
||||||
|
assert isinstance(sample_question.options, tuple)
|
||||||
|
|
||||||
|
def test_source_nodes_is_tuple(self, sample_question: GeneratedQuestion) -> None:
|
||||||
|
assert isinstance(sample_question.source_nodes, tuple)
|
||||||
|
|||||||
Reference in New Issue
Block a user