框架(看板): 运行记录空间补全——按作品/按角色双维 + 运行详情下钻
- /runs 落地页(运行列表+常驻双维入口+候选决策占位);/runs/by-work、/runs/by-role 服务端下拉筛选(无JS) - /runs/<run_id> 运行详情:运行信息+回执链(样本·revision哈希链)+质量评判+模型调用+候选与决策,沿来源下钻起点 - do_GET 解析 query 参数;合同 §9 待建清单收敛(运行详情/双维已建成)
This commit is contained in:
parent
b936de5bef
commit
338e3a7dbb
@ -89,7 +89,7 @@
|
||||
|
||||
1. 落库表分两类:**运行注册 / 运行回执 / 质量评判(98)与模型调用(97)已建**,但写路径未接通,视图暂空;候选 / 用户决策 / raw 全文 / 规划冻结(99/100/101)尚未建。没有它们 §4.2 的视图就是空的。
|
||||
2. `db/ddl/96` 授权快照表**决定不启用**(领域索引 §9,单用户本地不做多租户授权),DDL 留存不 apply。
|
||||
3. 看板首版已建成(总览 / 作品 / 知识库 / 智能体 / 运行记录五空间,只读 HTTP + §4.1 部分视图 + 待落库占位格;智能体空间读 102 登记表);待建 = 运行详情下钻、作品/章工作区标签、沿来源下钻链。页面与信息架构见 §10。
|
||||
3. 看板首版已建成(总览 / 作品 / 知识库 / 智能体 / 运行记录五空间,只读 HTTP + §4.1 部分视图 + 待落库占位格;智能体空间读 102 登记表;运行记录含按作品/按角色双维 + 运行详情下钻);待建 = 作品/章工作区标签、沿来源下钻链(raw 下钻依赖 101)。页面与信息架构见 §10。
|
||||
|
||||
## 10. 页面与信息架构(产品形态 SoT)
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ import html
|
||||
import re
|
||||
import traceback
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
import psycopg
|
||||
|
||||
@ -435,71 +436,125 @@ def view_knowledge():
|
||||
return layout("知识库", body, "/knowledge")
|
||||
|
||||
|
||||
# ── 运行记录(05 流程 + 06 质量:运行/候选/决策/质量/模型调用;智能体空间待 102 登记表建好后另加)────────
|
||||
def view_runs_page():
|
||||
# 运行注册(98 已建)
|
||||
if table_exists("example_run"):
|
||||
_, runs = q("SELECT run_id, work_id, target_chapter, trigger_source, terminal_state, started_at "
|
||||
"FROM example_run WHERE deleted=false ORDER BY started_at DESC LIMIT 50")
|
||||
else:
|
||||
runs = None
|
||||
if runs:
|
||||
trs = "".join(
|
||||
f"<tr><td><code>{esc(r[0])}</code></td><td>{esc(r[1])}</td><td class='num'>{esc(r[2])}</td>"
|
||||
f"<td>{esc(r[3])}</td><td>{chip(r[4])}</td><td>{esc(r[5])}</td></tr>" for r in runs)
|
||||
run_html = (f"<div class='card'><div class='hd'>运行(最近 50)</div><table><thead>"
|
||||
f"<tr><th>run_id</th><th>作品</th><th class='num'>章</th><th>触发</th><th>终态</th><th>开始</th></tr>"
|
||||
f"</thead><tbody>{trs}</tbody></table></div>")
|
||||
else:
|
||||
run_html = empty_gap("暂无运行记录", "运行注册表已就位,等创作链(写作/检测/审核)落库后,每次运行登记一行。")
|
||||
# ── 运行记录(05 流程 + 06 质量:运行列表按作品/按角色双维 + 运行详情下钻)────────
|
||||
_RUN_COLS = "<table><thead><tr><th>run_id</th><th>作品</th><th class='num'>章</th><th>触发</th><th>终态</th><th>开始</th></tr></thead><tbody>"
|
||||
|
||||
# 质量评判(98 已建)
|
||||
if table_exists("example_quality_result"):
|
||||
_, qres = q("SELECT judge_kind, dimension, scale_version, conclusion, failure_class, candidate_sha256 "
|
||||
"FROM example_quality_result ORDER BY create_time DESC LIMIT 50")
|
||||
if qres:
|
||||
trs = "".join(
|
||||
f"<tr><td>{chip(r[0])}</td><td>{esc(r[1])}</td><td>{esc(r[2])}</td>"
|
||||
f"<td>{chip(r[3])}</td><td>{esc(r[4])}</td><td><code>{esc((r[5] or '')[:12])}</code></td></tr>" for r in qres)
|
||||
q_html = (f"<div class='card'><div class='hd'>质量评判(最近 50 · 有评判就记录就可见)</div><table><thead>"
|
||||
f"<tr><th>类型</th><th>维度</th><th>量表版本</th><th>结论</th><th>失败类别</th><th>候选</th></tr>"
|
||||
f"</thead><tbody>{trs}</tbody></table></div>")
|
||||
else:
|
||||
q_html = empty_gap("暂无质量评判", "检测/评分/审核/实验落库后逐条记录(绑候选哈希 + 量表版本)。")
|
||||
else:
|
||||
q_html = empty_gap("质量评判 · 待落库", "质量结果表尚未建。")
|
||||
|
||||
# 候选与决策(99 待建)
|
||||
cand_html = (empty_gap("待审候选 · 待落库", "候选表尚未建,AI 候选正文目前无持久化登记。")
|
||||
if not table_exists("example_candidate")
|
||||
else "<div class='empty'>候选表已建 · 暂无候选</div>")
|
||||
dec_html = (empty_gap("用户决策 · 待落库", "决策记录表尚未建,接受/合并/丢弃暂以 git 提交留痕。")
|
||||
if not table_exists("example_user_decision")
|
||||
else "<div class='empty'>决策表已建 · 暂无决策</div>")
|
||||
def _run_rows(runs):
|
||||
return "".join(
|
||||
f"<tr><td><a href='/runs/{esc(r[0])}'><code>{esc(r[0])}</code></a></td><td>{esc(r[1])}</td>"
|
||||
f"<td class='num'>{esc(r[2])}</td><td>{esc(r[3])}</td><td>{chip(r[4])}</td><td>{esc(r[5])}</td></tr>"
|
||||
for r in runs)
|
||||
|
||||
# 模型调用明细(97 已建)
|
||||
if table_exists("example_llm_call"):
|
||||
_, calls = q("SELECT call_time, caller, requested_model_id, actual_model_id, out_tokens, cost_usd, stop_reason "
|
||||
"FROM example_llm_call ORDER BY call_time DESC LIMIT 50")
|
||||
if calls:
|
||||
trs = "".join(
|
||||
f"<tr><td>{esc(c[0])}</td><td>{esc(c[1])}</td><td>{esc(c[2])}</td><td>{esc(c[3])}</td>"
|
||||
f"<td class='num'>{fmt_num(c[4])}</td><td class='num'>${float(c[5] or 0):.5f}</td><td>{esc(c[6])}</td></tr>" for c in calls)
|
||||
call_html = (f"<div class='card'><div class='hd'>模型调用明细(最近 50 · 逐次记录)</div>"
|
||||
f"<table><thead><tr><th>时间</th><th>调用方</th><th>请求模型</th><th>实际模型</th>"
|
||||
f"<th class='num'>输出token</th><th class='num'>成本</th><th>结束</th></tr></thead>"
|
||||
f"<tbody>{trs}</tbody></table></div>")
|
||||
else:
|
||||
call_html = empty_gap("暂无逐次调用明细", "调用明细表已就位,模型调用落库后逐次记录(提示词全文受访问控制,单独存 raw)。")
|
||||
else:
|
||||
call_html = empty_gap("模型调用明细 · 待落库", "调用明细表尚未建。")
|
||||
|
||||
body = (f"<h1>运行记录<span class='sub'>agentic 的操作与产出——运行、候选、决策、质量评判、模型调用 · 流程 05 · 质量 06(智能体空间待 102 登记表建好后另加)</span></h1>"
|
||||
f"<h2>运行</h2>{run_html}<h2>质量评判</h2>{q_html}"
|
||||
f"<h2>待审候选</h2>{cand_html}<h2>用户决策</h2>{dec_html}<h2>模型调用</h2>{call_html}")
|
||||
def _all_runs(limit=50):
|
||||
_, runs = q("SELECT run_id, work_id, target_chapter, trigger_source, terminal_state, started_at "
|
||||
"FROM example_run WHERE deleted=false ORDER BY started_at DESC LIMIT %s", (limit,))
|
||||
return runs
|
||||
|
||||
|
||||
def _selector(label, name, options, sel):
|
||||
"""服务端渲染的下拉筛选(GET 表单,无 JS)。options=[(值,显示名)]。"""
|
||||
opts = "".join(f"<option value='{esc(v)}'{' selected' if str(v) == str(sel) else ''}>{esc(t)}</option>"
|
||||
for v, t in options)
|
||||
return (f"<form method='get' style='margin:14px 0;display:flex;gap:8px;align-items:center'>"
|
||||
f"<label style='color:var(--muted)'>{esc(label)}</label>"
|
||||
f"<select name='{name}'><option value=''>— 全部 —</option>{opts}</select>"
|
||||
f"<button type='submit'>查看</button></form>")
|
||||
|
||||
|
||||
def view_runs():
|
||||
runs = _all_runs()
|
||||
entries = ("<p class='note'>按维度查看:<a href='/runs/by-work'>按作品看运行</a> · "
|
||||
"<a href='/runs/by-role'>按角色看运行</a></p>")
|
||||
runs_html = (f"<div class='card'><div class='hd'>运行(最近 50)</div>"
|
||||
+ _RUN_COLS + _run_rows(runs) + "</tbody></table></div>") if runs else \
|
||||
empty_gap("暂无运行记录", "创作链落库后,每次运行登记一行(example_run)。")
|
||||
cand = (empty_gap("待审候选 · 待落库", "候选表(99)未建,AI 候选正文暂无持久化登记。")
|
||||
if not table_exists("example_candidate") else "<div class='empty'>候选表已建 · 暂无候选</div>")
|
||||
dec = (empty_gap("用户决策 · 待落库", "决策记录表(99)未建,接受/合并/丢弃暂以 git 提交留痕。")
|
||||
if not table_exists("example_user_decision") else "<div class='empty'>决策表已建 · 暂无决策</div>")
|
||||
body = (f"<h1>运行记录<span class='sub'>agentic 的操作与产出 · 流程 05 · 质量 06</span></h1>"
|
||||
f"{entries}{runs_html}<h2>待审候选</h2>{cand}<h2>用户决策</h2>{dec}")
|
||||
return layout("运行记录", body, "/runs")
|
||||
|
||||
|
||||
def view_runs_by_work(params):
|
||||
sel = (params.get("work") or [""])[0]
|
||||
_, works = q("SELECT id, title FROM muse_content_work WHERE deleted=false ORDER BY id")
|
||||
selector = _selector("选作品:", "work", [(w[0], w[1]) for w in works], sel)
|
||||
if sel:
|
||||
_, runs = q("SELECT run_id, work_id, target_chapter, trigger_source, terminal_state, started_at "
|
||||
"FROM example_run WHERE work_id=%s AND deleted=false ORDER BY started_at DESC LIMIT 50", (sel,))
|
||||
else:
|
||||
runs = _all_runs()
|
||||
tbl = (_RUN_COLS + _run_rows(runs) + "</tbody></table>") if runs else "<div class='empty'>暂无运行</div>"
|
||||
body = (f"<div class='crumb'><a href='/runs'>运行记录</a> / 按作品</div>"
|
||||
f"<h1>按作品看运行</h1>{selector}<div class='card'>{tbl}</div>")
|
||||
return layout("按作品看运行", body, "/runs")
|
||||
|
||||
|
||||
def view_runs_by_role(params):
|
||||
sel = (params.get("role") or [""])[0]
|
||||
_, roles = q("SELECT role, display_name FROM example_agent_role WHERE deleted=false ORDER BY role")
|
||||
selector = _selector("选角色:", "role", [(r[0], r[1] or r[0]) for r in roles], sel)
|
||||
if sel:
|
||||
_, runs = q("SELECT DISTINCT n.run_id, n.work_id, n.target_chapter, n.trigger_source, n.terminal_state, n.started_at "
|
||||
"FROM example_run_receipt r JOIN example_run n ON n.run_id=r.run_id "
|
||||
"WHERE r.adapter_role=%s AND n.deleted=false ORDER BY n.started_at DESC LIMIT 50", (sel,))
|
||||
else:
|
||||
runs = _all_runs()
|
||||
tbl = (_RUN_COLS + _run_rows(runs) + "</tbody></table>") if runs else "<div class='empty'>该角色暂无运行</div>"
|
||||
body = (f"<div class='crumb'><a href='/runs'>运行记录</a> / 按角色</div>"
|
||||
f"<h1>按角色看运行</h1>{selector}<div class='card'>{tbl}</div>")
|
||||
return layout("按角色看运行", body, "/runs")
|
||||
|
||||
|
||||
def view_run_detail(run_id):
|
||||
_, rr = q("SELECT run_id, work_id, target_chapter, trigger_source, started_at, finished_at, terminal_state "
|
||||
"FROM example_run WHERE run_id=%s AND deleted=false", (run_id,))
|
||||
if not rr:
|
||||
return layout("运行", "<h1>运行不存在</h1>", "/runs")
|
||||
rid, wid, chap, trig, started, finished, state = rr[0]
|
||||
info = (f"<div class='card'><table>"
|
||||
f"<tr><td style='width:110px;color:var(--muted)'>run_id</td><td><code>{esc(rid)}</code></td></tr>"
|
||||
f"<tr><td style='color:var(--muted)'>作品</td><td>{esc(wid)}</td></tr>"
|
||||
f"<tr><td style='color:var(--muted)'>目标章</td><td>{esc(chap)}</td></tr>"
|
||||
f"<tr><td style='color:var(--muted)'>触发</td><td>{esc(trig)}</td></tr>"
|
||||
f"<tr><td style='color:var(--muted)'>终态</td><td>{chip(state)}</td></tr>"
|
||||
f"<tr><td style='color:var(--muted)'>起止</td><td>{esc(started)} → {esc(finished)}</td></tr>"
|
||||
f"</table></div>")
|
||||
_, rc = q("SELECT sample_id, revision, stage_kind, adapter_role, attempt, loop_seq, candidate_version, is_error "
|
||||
"FROM example_run_receipt WHERE run_id=%s ORDER BY sample_id, revision", (run_id,))
|
||||
rc_html = ("<div class='card'><div class='hd'>回执链(按样本 · revision 哈希链)</div><table><thead>"
|
||||
"<tr><th>样本</th><th class='num'>rev</th><th>阶段</th><th>角色</th><th class='num'>attempt</th><th class='num'>补证</th><th>候选版本</th><th>结果</th></tr></thead><tbody>"
|
||||
+ "".join(f"<tr><td><code>{esc(r[0])}</code></td><td class='num'>{esc(r[1])}</td><td>{esc(r[2])}</td><td>{esc(r[3])}</td>"
|
||||
f"<td class='num'>{esc(r[4])}</td><td class='num'>{esc(r[5])}</td><td>{esc(r[6])}</td><td>{chip('failed' if r[7] else 'passed')}</td></tr>" for r in rc)
|
||||
+ "</tbody></table></div>") if rc else empty_gap("暂无回执", "该运行的回执落库后在此显示(revision 哈希链)。")
|
||||
_, qres = q("SELECT judge_kind, dimension, scale_version, score, conclusion, failure_class "
|
||||
"FROM example_quality_result WHERE run_id=%s ORDER BY create_time", (run_id,))
|
||||
q_html = ("<div class='card'><div class='hd'>质量评判</div><table><thead>"
|
||||
"<tr><th>类型</th><th>维度</th><th>量表版本</th><th class='num'>分</th><th>结论</th><th>失败类别</th></tr></thead><tbody>"
|
||||
+ "".join(f"<tr><td>{chip(u[0])}</td><td>{esc(u[1])}</td><td>{esc(u[2])}</td><td class='num'>{esc(u[3])}</td><td>{chip(u[4])}</td><td>{esc(u[5])}</td></tr>" for u in qres)
|
||||
+ "</tbody></table></div>") if qres else empty_gap("暂无质量评判", "")
|
||||
_, calls = q("SELECT call_time, caller, requested_model_id, actual_model_id, out_tokens, cost_usd "
|
||||
"FROM example_llm_call WHERE run_id=%s ORDER BY call_time", (run_id,))
|
||||
call_html = ("<div class='card'><div class='hd'>模型调用</div><table><thead>"
|
||||
"<tr><th>时间</th><th>调用方</th><th>请求模型</th><th>实际模型</th><th class='num'>输出token</th><th class='num'>成本</th></tr></thead><tbody>"
|
||||
+ "".join(f"<tr><td>{esc(c[0])}</td><td>{esc(c[1])}</td><td>{esc(c[2])}</td><td>{esc(c[3])}</td>"
|
||||
f"<td class='num'>{fmt_num(c[4])}</td><td class='num'>${float(c[5] or 0):.5f}</td></tr>" for c in calls)
|
||||
+ "</tbody></table></div>") if calls else empty_gap("暂无模型调用", "")
|
||||
cand = (empty_gap("待审候选 · 待落库", "候选表(99)未建。") if not table_exists("example_candidate")
|
||||
else "<div class='empty'>候选表已建 · 该运行暂无候选</div>")
|
||||
dec = (empty_gap("用户决策 · 待落库", "决策记录表(99)未建。") if not table_exists("example_user_decision")
|
||||
else "<div class='empty'>决策表已建 · 该运行暂无决策</div>")
|
||||
body = (f"<div class='crumb'><a href='/runs'>运行记录</a> / <code>{esc(rid)}</code></div>"
|
||||
f"<h1>运行详情</h1>{info}"
|
||||
f"<h2>回执链</h2>{rc_html}<h2>质量评判</h2>{q_html}<h2>模型调用</h2>{call_html}"
|
||||
f"<h2>候选与决策</h2>{cand}{dec}")
|
||||
return layout(f"运行 · {rid}", body, "/runs")
|
||||
|
||||
|
||||
# ── 智能体(07:读 102 登记表 example_agent_role / example_skill;权威在 Git 侧,登记脚本同步)────
|
||||
def _fmt_list(v):
|
||||
"""JSONB 列表 → 逗号串;非列表原样转义。"""
|
||||
@ -586,26 +641,30 @@ def view_skill(name):
|
||||
|
||||
# ── 路由 ─────────────────────────────────────────────────────────────────────
|
||||
_ROUTES = [
|
||||
(re.compile(r"^/$"), lambda m: view_home()),
|
||||
(re.compile(r"^/works$"), lambda m: view_works()),
|
||||
(re.compile(r"^/works/(\d+)$"), lambda m: view_work(int(m.group(1)))),
|
||||
(re.compile(r"^/works/(\d+)/ch/(\d+)$"), lambda m: view_chapter(int(m.group(1)), int(m.group(2)))),
|
||||
(re.compile(r"^/knowledge$"), lambda m: view_knowledge()),
|
||||
(re.compile(r"^/agents$"), lambda m: view_agents()),
|
||||
(re.compile(r"^/agents/role/([\w-]+)$"), lambda m: view_agent_role(m.group(1))),
|
||||
(re.compile(r"^/agents/skill/([\w-]+)$"), lambda m: view_skill(m.group(1))),
|
||||
(re.compile(r"^/runs$"), lambda m: view_runs_page()),
|
||||
(re.compile(r"^/$"), lambda m, p: view_home()),
|
||||
(re.compile(r"^/works$"), lambda m, p: view_works()),
|
||||
(re.compile(r"^/works/(\d+)$"), lambda m, p: view_work(int(m.group(1)))),
|
||||
(re.compile(r"^/works/(\d+)/ch/(\d+)$"), lambda m, p: view_chapter(int(m.group(1)), int(m.group(2)))),
|
||||
(re.compile(r"^/knowledge$"), lambda m, p: view_knowledge()),
|
||||
(re.compile(r"^/agents$"), lambda m, p: view_agents()),
|
||||
(re.compile(r"^/agents/role/([\w-]+)$"), lambda m, p: view_agent_role(m.group(1))),
|
||||
(re.compile(r"^/agents/skill/([\w-]+)$"), lambda m, p: view_skill(m.group(1))),
|
||||
(re.compile(r"^/runs$"), lambda m, p: view_runs()),
|
||||
(re.compile(r"^/runs/by-work$"), lambda m, p: view_runs_by_work(p)),
|
||||
(re.compile(r"^/runs/by-role$"), lambda m, p: view_runs_by_role(p)),
|
||||
(re.compile(r"^/runs/([\w.-]+)$"), lambda m, p: view_run_detail(m.group(1))),
|
||||
]
|
||||
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
path = self.path.split("?", 1)[0]
|
||||
parsed = urlparse(self.path)
|
||||
params = parse_qs(parsed.query)
|
||||
try:
|
||||
for pat, fn in _ROUTES:
|
||||
m = pat.match(path)
|
||||
m = pat.match(parsed.path)
|
||||
if m:
|
||||
self._send(200, fn(m))
|
||||
self._send(200, fn(m, params))
|
||||
return
|
||||
self._send(404, layout("未找到", "<h1>404 · 页面不存在</h1>", ""))
|
||||
except Exception: # 客户端给固定文案,细节写服务端 stderr(不泄露 DB 内部结构;看板挂了不牵连库)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user