mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 07:02:00 +00:00
538799e70d
为什么:同一类问题被反复问、Agent 反复写出同一个形状的 SQL,就该沉淀成一份口径;以前 from-run 保留 Agent,沉淀出的模板没有固定 SQL,以后永远比对不上。第 2 波的统计在 E2 反馈、E3 认证队列上照实写了「目前没有数据」,这一波补上能补的。
做了什么(新增 ledger/certify.py):
- GET /api/ledger/certify-queue(搭建者):确定性重复(问数据运行里被报告引用、没对上口径的查询,按数据源 + normalize_sql 文本分组,≥3 次运行且来自 ≥2 段对话;跨运行变化的字面量是参数候选,展示的形状不带取值)、问题聚类(app/chat/questions 全站唯一的那份)、被反复用的答案(导出 ≥2 次用第 2 波的 report.exported 审计、做成过应用看蓝图来源、在画布里打开或提取过看工作流 origin)。每项给代表问题、轮次、相关口径、是否已固化。
- POST /api/ledger/certify-queue/dismiss:「不是口径」写审计 ledger.certify.dismiss,之后不再进队。
- 固化成口径草稿(from-run 加 freeze、peers 后转调 certify.freeze):报告引用的查询变成固定 SQL 的取数节点,跨轮变化的字面量换成 '{{ input.pN }}'(缺省值取代表运行的),单行结果的数值列成口径卡指标,报告核对规则按受管要求写、契约 report_from + metrics_from + required(多行加 cells);过 validate_graph 和受管门禁。新工作流 origin.sources 记来源问题。
- 发布之后补比:api/workflows.commit_publish 提交后调 certify.after_publish,来源运行按新口径补比一次(新的 trigger=certified,界面写「按之后发布的口径回看」,时间线和账本接口同一句)。
- 统计:E2 加对过账的答案数、参数经人核对的、指标由人选的次数(反馈照实写第 4 波);E3 认证队列的转化(固化、发布、不是口径)。
测试:tests/test_ledger_certify.py(参数推断与模板、三种信号进队、门槛、不是口径、固化草稿过受管门禁、发布后来源运行补比成等价已证明并出队、再问标等价已证明、统计)。
147 lines
7.6 KiB
Python
147 lines
7.6 KiB
Python
"""口径账本的记法(docs/design/next-round/02-口径账本.md 做法 2,拍板 L8):比对结果存成内容寻址工件,不建新表。
|
||
|
||
一次比对 = 一份工件 kind=caliber_match(格式 agentlab.caliber_match/1)+ 一条封存之后追加的事件 ledger.matched
|
||
{artifact, manifest_hash, algo, counts, trigger}(engine/run_append 的 sealed 方式,同按需裁判的 evidence.judged):
|
||
|
||
- 工件内容只放比对本身:运行 id、对着哪一次封存(manifest_hash / manifest_seq)、算法版本、考虑过的口径版本(工作流、版本、
|
||
级别、graph_hash)、每条查询的状态和绑定、差异条目、列映射、每个数的标签、整题判断。不放时间、耗时、工作流名(名字会改,
|
||
界面读的时候再取):同一份封存、同一个算法版本、同一批口径,再比一次得到的工件一字不差、哈希一样。
|
||
- 工件引用的 meta 记 {calibers: [等价已证明的「工作流@版本」], related: [相关的], counts}:召回按旧版本反查、统计按种类数,
|
||
都只扫 artifacts(kind 有索引),不逐份读内容。
|
||
- 事件说的是「这一次封存的这份比对」:读的时候只认 manifest_hash 等于运行现在封存的那几条,取最后一条(之后口径有变、补比
|
||
过的,以最新的为准)。工件读不出、被改过、不是这次运行这次封存的,照实说读不出,不拿它标数。
|
||
|
||
trigger 是谁触发的:sealed(运行封存后马上比的)、backfill(启动时补比)、on_demand(打开账本时按需比的)。后两种是事后按
|
||
那时的口径回看的,界面据此写「按现在的口径回看」。它只在事件里,不进工件内容(不然同一份比对因为触发方式不同哈希就不同)。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from dataclasses import dataclass
|
||
from typing import Any
|
||
|
||
from sqlalchemy import select
|
||
|
||
from app.core.events import EventType
|
||
from app.db.base import SessionLocal
|
||
from app.db.models import Run, RunEvent
|
||
|
||
#: 工件的种类和内容格式
|
||
KIND = "caliber_match"
|
||
FORMAT = "agentlab.caliber_match/1"
|
||
#: 比对算法的版本:绑定、证明、标签的规则改了就加一,界面和统计据此分得清是哪一版算法比的。
|
||
#: 2(第 3 波):认出助手选用的口径(查询 composed、标签 how=composed),指标的标签记它取自哪几条查询(queries,认证队列
|
||
#: 认「被报告引用的查询」用)
|
||
ALGO = "2"
|
||
#: 触发方式(见模块说明)。certified(第 3 波):从问数据固化出的口径发布之后,对来源运行补比的(ledger/certify)
|
||
TRIGGERS = ("sealed", "backfill", "on_demand", "certified")
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class Recorded:
|
||
"""一次运行现在这次封存的最新一份比对。content 读不出时为 None,problem 写为什么。"""
|
||
|
||
artifact: str
|
||
seq: int
|
||
ts: Any
|
||
trigger: str
|
||
algo: str | None
|
||
content: dict[str, Any] | None
|
||
problem: str | None = None
|
||
|
||
@property
|
||
def retro(self) -> bool:
|
||
"""事后按那时的口径回看的(启动时补比、打开时按需比的),不是运行一封存就比的。"""
|
||
return self.trigger != "sealed"
|
||
|
||
|
||
#: 读不出比对工件时的说法
|
||
UNREADABLE = "比对记录读不出来(文件缺失)"
|
||
TAMPERED = "比对记录与哈希不一致,疑似被修改,不能用来标口径"
|
||
FOREIGN = "比对记录不属于这次运行的这次封存,不能用来标口径"
|
||
|
||
|
||
def counts_of(queries: list[dict[str, Any]]) -> dict[str, int]:
|
||
"""每种查询状态各几条(proven / composed / related / none / skipped),0 也写。"""
|
||
out = {status: 0 for status in ("proven", "composed", "related", "none", "skipped")}
|
||
for query in queries:
|
||
status = str(query.get("status") or "")
|
||
if status in out:
|
||
out[status] += 1
|
||
return out
|
||
|
||
|
||
def meta_of(content: dict[str, Any]) -> dict[str, Any]:
|
||
"""工件引用的 meta:{calibers: [等价已证明的「工作流@版本」], related: [相关的], counts}。"""
|
||
proven: list[str] = []
|
||
related: list[str] = []
|
||
for query in content.get("queries") or []:
|
||
for cand in query.get("candidates") or []:
|
||
key = str(cand.get("caliber") or "")
|
||
if not key:
|
||
continue
|
||
bucket = proven if cand.get("status") == "proven" else related
|
||
if key not in bucket:
|
||
bucket.append(key)
|
||
related = [key for key in related if key not in proven]
|
||
return {"calibers": sorted(proven), "related": sorted(related), "counts": counts_of(content.get("queries") or [])}
|
||
|
||
|
||
async def save(run_id: str, content: dict[str, Any], *, seal: tuple[int | None, str | None],
|
||
trigger: str) -> str | None:
|
||
"""存比对工件、追加 ledger.matched。返回工件 id;运行在比对期间换了封存(没抢到)时只存了工件、没追加事件,返回 None
|
||
(不算这次运行的比对:读的时候只认事件)。"""
|
||
from app.core.artifact_store import put_json
|
||
from app.engine.run_append import append_events
|
||
|
||
if trigger not in TRIGGERS:
|
||
raise ValueError(f"不认识的比对触发方式:{trigger}")
|
||
meta = meta_of(content)
|
||
artifact = await put_json(content, kind=KIND, run_id=run_id, meta=meta)
|
||
written = await append_events(run_id, [(EventType.LEDGER_MATCHED, None, {
|
||
"artifact": artifact, "manifest_hash": content.get("manifest_hash"), "algo": content.get("algo"),
|
||
"counts": meta["counts"], "trigger": trigger,
|
||
})], require="sealed", seal=seal)
|
||
return artifact if written else None
|
||
|
||
|
||
async def latest(run_id: str) -> Recorded | None:
|
||
"""这次运行现在这次封存的最新一份比对;没比对过(或者比对的是以前的封存)为 None。"""
|
||
from app.core.artifact_store import load
|
||
|
||
async with SessionLocal() as session:
|
||
manifest = await session.scalar(select(Run.manifest_hash).where(Run.id == run_id))
|
||
if not manifest:
|
||
return None
|
||
rows = (await session.execute(
|
||
select(RunEvent.seq, RunEvent.ts, RunEvent.data).where(
|
||
RunEvent.run_id == run_id, RunEvent.type == EventType.LEDGER_MATCHED)
|
||
.order_by(RunEvent.seq.desc()))).all()
|
||
for seq, ts, data in rows:
|
||
if not isinstance(data, dict) or data.get("manifest_hash") != manifest or not data.get("artifact"):
|
||
continue
|
||
artifact = str(data["artifact"])
|
||
trigger = str(data.get("trigger") or "sealed")
|
||
try:
|
||
content = load(artifact)
|
||
except ValueError:
|
||
return Recorded(artifact, seq, ts, trigger, data.get("algo"), None, TAMPERED)
|
||
if not isinstance(content, dict):
|
||
return Recorded(artifact, seq, ts, trigger, data.get("algo"), None, UNREADABLE)
|
||
if content.get("format") != FORMAT or content.get("run_id") != run_id \
|
||
or content.get("manifest_hash") != manifest:
|
||
return Recorded(artifact, seq, ts, trigger, data.get("algo"), None, FOREIGN)
|
||
return Recorded(artifact, seq, ts, trigger, data.get("algo"), content)
|
||
return None
|
||
|
||
|
||
async def matched_run_ids(session: Any, run_ids: list[str]) -> set[str]:
|
||
"""这几次运行里有过比对事件的(不管是哪一次封存的):启动补比据此跳过比过的。"""
|
||
if not run_ids:
|
||
return set()
|
||
rows = await session.execute(select(RunEvent.run_id).where(
|
||
RunEvent.run_id.in_(run_ids), RunEvent.type == EventType.LEDGER_MATCHED).distinct())
|
||
return {r for (r,) in rows}
|
||
|
||
|
||
__all__ = ["ALGO", "FORMAT", "KIND", "TRIGGERS", "Recorded", "counts_of", "latest", "matched_run_ids", "meta_of", "save"]
|