From 7d02cded992d5e8d9c048b16a651608970fad9a1 Mon Sep 17 00:00:00 2001 From: iomgaa Date: Thu, 16 Jul 2026 06:05:55 -0400 Subject: [PATCH] fix: wrap diagnosis run_log with StepsJsonRunLog (restore algo #7 traces) --- app/harness/runner.py | 4 +- tests/unit/test_runner_diag_tree_inject.py | 64 ++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/app/harness/runner.py b/app/harness/runner.py index 068601d..f7bc167 100644 --- a/app/harness/runner.py +++ b/app/harness/runner.py @@ -2168,13 +2168,15 @@ class Runner: self, run_id: str, *, question_ids: list[str] | None = None ) -> DiagnosisResult: """执行两阶段诊断。""" + from app.harness.baseline_run_log import StepsJsonRunLog from app.harness.log import RunLogImpl from app.harness.workspace import VersionedSkillStore from app.question_gen import load_benchmark from core.evolution.diagnose import run_diagnosis questions = load_benchmark(self._paths.questions_dir) - run_log = RunLogImpl(str(self._paths.db_path)) + # traces 表空时(如训练 rollout 只落 steps_json)从 steps_json 重建轨迹,恢复算法 #7 + run_log = StepsJsonRunLog(RunLogImpl(str(self._paths.db_path))) skill_store = VersionedSkillStore(self._paths.skills_dir) diagnose_prompts = self._load_diagnose_prompts() diff --git a/tests/unit/test_runner_diag_tree_inject.py b/tests/unit/test_runner_diag_tree_inject.py index 2d579fc..404b649 100644 --- a/tests/unit/test_runner_diag_tree_inject.py +++ b/tests/unit/test_runner_diag_tree_inject.py @@ -147,6 +147,70 @@ def _fake_question(question_id: str, video_id: str) -> object: ) +@pytest.mark.asyncio +async def test_diagnosis_reads_traces_from_steps_json( + runner_with_real_store: Runner, +) -> None: + """traces 表为空但 predictions.steps_json 有轨迹时,诊断仍拿到非空 traces。 + + 构造一条只写 steps_json、不写 traces 表的 predictions 行;patch run_diagnosis + 捕获传入的 run_log,直接 await 其 get_traces 断言经 StepsJsonRunLog 从 + steps_json 重建出非空轨迹(算法 #7 恢复)。 + """ + from app.harness.inference import PREDICTIONS_SCHEMA + from app.harness.log import HarnessLog + + steps_json = json.dumps( + [ + { + "thought": "先看整体", + "tool_call": {"tool": "search_tree", "args": {"query": "开场"}}, + "tool_output": "命中 L2 节点 A", + } + ], + ensure_ascii=False, + ) + with HarnessLog(str(runner_with_real_store._paths.db_path), "infer_adhoc") as log: + log.create_table("predictions", PREDICTIONS_SCHEMA) + log.create_table("traces", {"video_id": "TEXT", "question_id": "TEXT", "step": "INTEGER"}) + log.insert( + "predictions", + { + "video_id": _REAL_VIDEO_ID, + "question_id": _REAL_QUESTION_ID, + "task_type": "Action Reasoning", + "prediction": "A", + "answer": "B", + "evidence": "", + "reasoning": "", + "steps_used": 1, + "prompt_tokens": 0, + "completion_tokens": 0, + "stop_reason": "finished", + "steps_json": steps_json, + }, + ) + + captured: dict[str, object] = {} + + async def _fake_run_diagnosis(**kwargs: object) -> DiagnosisResult: + captured["run_log"] = kwargs["run_log"] + return _empty_diagnosis_result() + + with patch( + "core.evolution.diagnose.run_diagnosis", + new=AsyncMock(side_effect=_fake_run_diagnosis), + ): + await runner_with_real_store._run_diagnosis( + "infer_adhoc", question_ids=[_REAL_QUESTION_ID] + ) + + run_log = captured["run_log"] + traces = await run_log.get_traces("infer_adhoc", question_ids=[_REAL_QUESTION_ID]) + assert traces, "traces 表空时应从 steps_json 重建出非空轨迹" + assert traces[0]["tool_name"] == "search_tree" + + @pytest.mark.asyncio async def test_run_diagnosis_full_scan_loads_all_video_trees( runner_with_real_store: Runner,