阶段G第一部分: 链路透视接线——运行详情页接事件账本(探索轨迹/模型回合/成本)与派发链父子导航(看板测试绿+真实运行页走查通过)

This commit is contained in:
zizi 2026-08-23 16:24:18 +08:00
parent befb4714c0
commit ec6b1b9c23
2 changed files with 126 additions and 1 deletions

View File

@ -2548,9 +2548,94 @@ def _run_turn_panels_html(run_id: str, candidates: list, terminal_state) -> str:
)
def _agent_events_html(run_id):
"""链路透视 · 事件账本:探索轨迹(读了什么)与模型回合(example_agent_event)。"""
if not table_exists("example_agent_event"):
return empty_gap("事件账本 · 待建表", "事件账本表 example_agent_event 尚未建立。")
cols, rows = q(
"SELECT seq,event_type,tool_name,status,actual_model_id,input_tokens,output_tokens,cost_usd,details "
"FROM example_agent_event WHERE run_id=%s ORDER BY seq",
(run_id,),
)
events = _row_dicts(cols, rows)
if not events:
return empty_gap("事件账本 · 该运行暂无记录", "框架派发运行后,探索轨迹与模型回合在此逐条可见。")
def _details_text(value):
if value is None:
return "—"
text = value if isinstance(value, str) else json.dumps(value, ensure_ascii=False)
return esc(text[:300] + ("…" if len(text) > 300 else ""))
tool_events = [e for e in events if str(e["event_type"]).startswith("tool.")]
model_events = [e for e in events if str(e["event_type"]).startswith("model.")]
tool_rows = "".join(
f"<tr><td class='num'>{esc(e['seq'])}</td><td><code>{esc(e['event_type'])}</code></td>"
f"<td><code>{esc(e['tool_name'])}</code></td><td>{chip(e['status'])}</td>"
f"<td>{_details_text(e['details'])}</td></tr>"
for e in tool_events
)
model_rows = "".join(
f"<tr><td class='num'>{esc(e['seq'])}</td><td><code>{esc(e['event_type'])}</code></td>"
f"<td><code>{esc(e['actual_model_id'])}</code></td><td>{chip(e['status'])}</td>"
f"<td class='num'>{fmt_num(e['input_tokens'])}</td><td class='num'>{fmt_num(e['output_tokens'])}</td>"
f"<td class='num'>{fmt_num(e['cost_usd'])}</td></tr>"
for e in model_events
)
tool_table = (
"<div class='hd'>探索轨迹(工具事件)</div><table><thead>"
"<tr><th class='num'>序</th><th>事件类型</th><th>工具</th><th>状态</th><th>参数/明细</th></tr></thead>"
f"<tbody>{tool_rows}</tbody></table>" if tool_events
else "<div class='hd'>探索轨迹(工具事件)</div><div class='empty'>本运行未发生工具探索。</div>"
)
model_table = (
"<div class='hd'>模型回合</div><table><thead>"
"<tr><th class='num'>序</th><th>事件类型</th><th>实际模型编码</th><th>状态</th>"
"<th class='num'>输入令牌</th><th class='num'>输出令牌</th><th class='num'>成本美元</th></tr></thead>"
f"<tbody>{model_rows}</tbody></table>" if model_events
else "<div class='hd'>模型回合</div><div class='empty'>本运行无模型回合事件。</div>"
)
return f"<div class='card'>{tool_table}{model_table}</div>"
def _dispatch_chain_html(run_id, trigger_detail):
"""链路透视 · 派发链:经 trigger_detail 关联的生产/评测父运行与派发子运行。"""
parent_ref = None
if isinstance(trigger_detail, dict):
parent_ref = trigger_detail.get("productionRunId") or trigger_detail.get("evaluationRunId")
_, child_rows = q(
"SELECT run_id, terminal_state, trigger_detail->>'stage' AS stage "
"FROM example_run WHERE deleted=false AND "
"(trigger_detail->>'productionRunId'=%s OR trigger_detail->>'evaluationRunId'=%s) "
"ORDER BY started_at",
(run_id, run_id),
)
if not parent_ref and not child_rows:
return ""
parts = ["<div class='card'><div class='hd'>派发链(父子运行)</div>"]
if parent_ref:
parts.append(
f"<p class='note'>父运行:<a href='/runs/{esc(str(parent_ref))}'><code>{esc(str(parent_ref))}</code></a></p>"
)
if child_rows:
parts.append(
"<table><thead><tr><th>派发子运行</th><th>终态</th><th>阶段</th></tr></thead><tbody>"
+ "".join(
f"<tr><td><a href='/runs/{esc(c[0])}'><code>{esc(c[0])}</code></a></td>"
f"<td>{chip(c[1])}</td><td>{esc(c[2] or '—')}</td></tr>" for c in child_rows
)
+ "</tbody></table>"
)
parts.append("</div>")
return "".join(parts)
def view_run_detail(run_id):
_, rr = q(
"""SELECT r.run_id,r.work_id,r.target_chapter,r.trigger_source,r.started_at,r.finished_at,r.terminal_state,
r.trigger_detail,
w.title AS work_title,c.id AS chapter_id,c.order_no AS chapter_order,c.title AS chapter_title
FROM example_run r
LEFT JOIN muse_content_work w ON w.id=r.work_id
@ -2560,7 +2645,7 @@ def view_run_detail(run_id):
)
if not rr:
return layout("运行", "<h1>运行不存在</h1>", "/runs")
rid, wid, chap, trig, started, finished, state, work_title, chapter_id, chapter_order, chapter_title = rr[0]
rid, wid, chap, trig, started, finished, state, trigger_detail, work_title, chapter_id, chapter_order, chapter_title = rr[0]
rc_cols, rc_rows = q(
"""SELECT id,sample_id,revision,stage_kind,adapter_role,attempt,loop_seq,candidate_version,is_error,
stop_reason,terminal_reason,safe_summary,raw_content_id,requested_model_id,actual_model_id,
@ -2758,9 +2843,12 @@ def view_run_detail(run_id):
error_items.append("<div class='error-item'><div class='value'>运行终态为失败,但回执和质量表没有提供更细错误记录。</div></div>")
error_html = ("<div class='error-panel'><div class='hd'>错误与诊断</div>" + "".join(error_items) + "</div>") if error_items else ""
turn_html = _run_turn_panels_html(rid, candidates, state)
events_html = _agent_events_html(rid)
chain_html = _dispatch_chain_html(rid, trigger_detail)
body = (f"<div class='crumb'><a href='/runs'>运行记录</a> / <code>{esc(rid)}</code></div>"
f"<h1>运行详情</h1>{info}{error_html}{turn_html}"
f"<h2>回执链</h2>{rc_html}<h2>质量评判</h2>{q_html}<h2>模型调用</h2>{call_html}"
f"<h2>链路透视</h2>{chain_html}{events_html}"
f"<h2>候选与决策</h2>{cand}{dec}<h2>证据与上下文</h2>{raw_html}{raw_detail_html}{freeze_html}")
return layout("运行详情", body, "/runs")

View File

@ -0,0 +1,37 @@
# 阶段 G 第一部分:链路透视接线——事件账本与派发链上看板
日期:2026-08-23
状态:已完成(看板测试绿 + 真实运行页走查通过)
上游事实:两阶段写手、抽取、盲评派发运行已在 `example_agent_event` 与 `example_run` 留下真实数据;§11 矩阵中"探索轨迹与事件链""依赖清单"此前为接线缺口。
## 1. 意图
把框架派发链的运行时证据接上看板:运行详情页新增"链路透视"区块,回放一次运行的探索轨迹(读了什么)、模型回合(成本)与派发链父子关系。只读库,不拼命令,不写库。
## 2. 改动台账
| 文件 | 动作 | 原因 |
|---|---|---|
| `dashboard/server.py` | 修改 | `view_run_detail` 主查询补 `trigger_detail`;新增 `_agent_events_html`(探索轨迹 + 模型回合,读 `example_agent_event`)与 `_dispatch_chain_html`(经 `trigger_detail.productionRunId/evaluationRunId` 关联父子运行);页面加"链路透视"区块 |
## 3. 验证
- `dashboard/test_server_display.py` 全绿(展示层中文语义与编码合同)。
- 真实页走查(只读看板):
- `…-writer-v9`:探索轨迹含 `read_chapter_text`×8、`read_fine_outline`×4 等,模型回合带令牌/成本,"派发链"回链父运行。
- 生产父运行 `…-42beb650`:列出子运行 explore-v9 / writer-v9。
- 盲评运行 `…-30f33c50c7`:列出 judge-v1 / judge-v2。
## 4. §11 对照(本部分消解的缺口)
| 内容 | 之前 | 现在 |
|---|---|---|
| 探索轨迹与事件链(example_agent_event) | 看板未接(缺口) | 运行详情页"链路透视"可见 |
| 依赖清单(本次运行实际读了什么) | 看板未接(缺口) | 以工具事件(工具名+参数明细)呈现 |
| 派发链父子关系 | 无视图 | "派发链"区块双向可导航 |
## 5. 未决(阶段 G 后续)
- 任务包(spec)与生产 artifacts 读侧迁到运行目录/库投影的看板呈现(§11"生产 artifacts"行的读侧迁移)。
- 待审视图增强:候选详情页直达其生产运行与检查报告(现经 /runs 手工跳转)。
- 五视图整体走查与 §11 全矩阵验收。