"""RunLog 包装器:traces 表空时从 predictions.steps_json 重建轨迹。 用于对 infer_adhoc 这类 traces 未落表、轨迹在 steps_json 的历史 run 跑离线诊断。 """ from __future__ import annotations from typing import Any from app.harness.steps_json_traces import steps_json_to_trace_rows class StepsJsonRunLog: """委托内层 RunLog;get_traces 空表时回退 steps_json。 实现 core/evolution/protocols.py 的 RunLog Protocol(duck-typing)。 对 traces 已落表的正常 run 完全透传;仅当底层 traces 为空时, 才从 predictions.steps_json 经 steps_json_to_trace_rows 重建轨迹行。 """ def __init__(self, inner: Any) -> None: """构造包装器。 参数: inner: 内层 RunLog 实现(如 app/harness/log.py::RunLogImpl), 需提供 get_predictions / get_traces 两个 async 方法。 """ self._inner = inner async def get_predictions( self, run_id: str, *, question_ids: list[str] | None = None ) -> list[dict[str, Any]]: """透传内层预测查询。 参数: run_id: 运行标识。 question_ids: 可选的题目 ID 过滤列表。 返回: 内层返回的预测记录字典列表,原样透传。 """ return await self._inner.get_predictions(run_id, question_ids=question_ids) async def get_traces( self, run_id: str, *, question_ids: list[str] | None = None ) -> list[dict[str, Any]]: """查询轨迹;底层 traces 表空时从 steps_json 回退重建。 参数: run_id: 运行标识。 question_ids: 可选的题目 ID 过滤列表。 返回: 轨迹行字典列表。 关键实现细节: - 内层 traces 非空 → 原样返回,不触发回退(正常 run 路径)。 - 内层 traces 为空 → 拉取同一过滤条件下的 predictions, 逐题经 steps_json_to_trace_rows 展开为轨迹行并拼接。 - steps_json 缺失时以空串传入,由下游确定性返回 []。 """ inner_rows = await self._inner.get_traces(run_id, question_ids=question_ids) if inner_rows: return inner_rows preds = await self._inner.get_predictions(run_id, question_ids=question_ids) rows: list[dict[str, Any]] = [] for p in preds: rows.extend( steps_json_to_trace_rows(p["video_id"], p["question_id"], p.get("steps_json") or "") ) return rows