mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 02:01:57 +00:00
271bc6e782
真实模型下问「上周的销售额是多少」,助手写 SUM(amount) AS 销售额, COUNT(*) AS 订单数, MIN / MAX(paid_date) AS 区间首日 /
末日,报告四列都引用了,口径「周销售」只取 SUM(amount)。按「全部引用列」投影后还是 4 列,对不上口径的 1 列,整条比、
四个数都标相关口径,说明整段兜底。
- ledger/match._cells / _cell(algo 4):整条(含按全部引用列投影)没证明、又引用了这条查询两列以上的,被引用的每一列单独
投影到那一列,和每份口径的每个指标读的那一列(card_reads;口径版本没有口径卡的按取数的每一列)逐个比,仍只走
match_template / explain_sql_diff,上次的投影规则照用:投影不了(分组列、DISTINCT……)、过了时限、不可比的格照用整条
查询的那一个候选。结果记在 queries[].cells {列名: {status, candidates}},另记 one_row(sql_semantics.one_row:不分组的
聚合,一定只出一行)。整条这一层的结论、候选一字不改;老工件没有 cells,照旧按整条读。
- ledger/labels:单元格按它那一格的结论标(_view);指标用了同一条查询的好几格时,只有那条查询一定只出一行才把逐格的
结论合起来用(多行的结果里每一列各自对得上,不等于每一行的几列一起对得上)。整题照拍板 L1 不变:每个数都证明来自
同一份口径才等价;一格证明了、别的格只是相关的整题不等价,原因写「有的数和相关口径不一致,不来自口径」(以前一律说
「这次的推理」)。出具入口照 ledger/issue 现有判法只在整题等价时给,没有放宽。
- 逐格证明了的口径各处都算上(labels.candidates_of):工件 meta 的 calibers(召回按旧版本反查)、ledger/uses 的使用记录、
召回时找原答案对应的那一格、E4;E1 另数各种结论各几格(cells),E2 的兜底比例按标签上给人看的那一份说明数。
- GET /runs/{id}/ledger 的 queries 带上逐格的 cells(遮罩同候选);证据片段的 ledger 键、轮次摘要的计数本来就按每个数的
标签,数据变了、形状不变。前端只补类型;check-ledger 加一段:同一条查询一格来自口径(正文挂小标记)、一格相关(差在哪
先说多查了几列),整题不等价不给出具。
- 评测逐题比 SQL(eval/score.sql_match)取和期望配上的那一格(score.matched_cells:评分时配上的格),没给时取逐格证明了
的那一格;结果带 column。runner、rescore 传它。
测试:tests/test_ledger_cells.py(同一条查询一列等价、一列不等价各自标对;两份口径只证明了一份标那一份;口径多个指标按列
配对;指标跨两格只在一行的查询上用逐格结论;分组列、DISTINCT 逐格也不投影;照真实例子造的虚构库真跑一遍:销售额标等价
已证明,订单数、首末日标相关,整题不等价、不给出具、不自动出具,证据片段的 ledger 键一致;评测取配上的那一格)。去掉逐格
再跑,10 条失败;去掉「一行才合起来用」、去掉评测取格,各 1 条失败。
366 lines
11 KiB
TypeScript
366 lines
11 KiB
TypeScript
/**
|
||
* 口径账本的接口约定(docs/design/next-round/02-口径账本.md;服务端 app/ledger/view.py、issue.py、reconcile.py、certify.py)。
|
||
*
|
||
* 界面层不放逻辑:每个数的标签、整题判断、能做的动作(能不能出具、能不能自动、不能的原因、要不要先核对参数)都由服务端
|
||
* 给,界面照着画。比对在封存之后做(不在封存范围内),GET /api/runs/{id}/ledger 在比对还没出结果时答 state=pending,
|
||
* 页面按退避再取(store/ledger.ts),不靠实时流:ledger.matched 在事件总线关闭之后才追加,打开着的页面收不到。
|
||
*/
|
||
|
||
/** 绑定的一个参数:值是这次运行查到过的遮罩值时不给值(masked),也不能拿它代填出具 */
|
||
export interface LedgerBinding {
|
||
field: string
|
||
value?: unknown
|
||
masked?: boolean
|
||
}
|
||
|
||
/** 差在哪的一条(explain_sql_diff 的条目,SQL 片段已按遮罩换过)。text 是一句中文说明 */
|
||
export interface LedgerDiffItem {
|
||
aspect?: string
|
||
a?: unknown
|
||
b?: unknown
|
||
text?: string
|
||
}
|
||
|
||
export interface LedgerDiff {
|
||
items: LedgerDiffItem[]
|
||
/** 兜底说明:两条 SQL 对不上结构,没能归纳成具体差异 */
|
||
fallback: boolean
|
||
text_diff: string[]
|
||
}
|
||
|
||
/** 一份相关口径(同一个数据源、查了同样的表,但不等价) */
|
||
export interface LedgerRelated {
|
||
caliber?: string
|
||
workflow_id?: string
|
||
version?: number
|
||
level?: string
|
||
level_text?: string
|
||
workflow_name?: string | null
|
||
node_id?: string
|
||
comparable?: boolean
|
||
reason?: string
|
||
via?: string
|
||
binding?: LedgerBinding[]
|
||
diff?: LedgerDiff | null
|
||
}
|
||
|
||
/**
|
||
* 一个数的标签(四种):
|
||
* - caliber:来自口径「X」vN(how:independent 等价已证明 / composed 助手选用)
|
||
* - related:相关口径(附差在哪),数本身是这次的推理
|
||
* - reasoning:这次的推理,没有相关的已发布口径
|
||
* - unknown:没比对(reason 写为什么)
|
||
*/
|
||
export interface LedgerLabel {
|
||
kind: 'caliber' | 'related' | 'reasoning' | 'unknown'
|
||
how?: 'independent' | 'composed'
|
||
caliber?: string
|
||
workflow_id?: string
|
||
version?: number
|
||
level?: string
|
||
level_text?: string
|
||
workflow_name?: string | null
|
||
binding?: LedgerBinding[]
|
||
related?: LedgerRelated[]
|
||
reason?: string
|
||
/** 出自哪条查询(查询快照工件);指标是 queries */
|
||
query?: string
|
||
queries?: string[]
|
||
node_id?: string
|
||
column?: string | null
|
||
card?: string
|
||
metric?: string
|
||
}
|
||
|
||
/** 一条查询的比对:proven / composed / related / none / skipped */
|
||
export interface LedgerQuery {
|
||
artifact: string
|
||
node_id?: string
|
||
source?: string
|
||
tables?: string[]
|
||
status: 'proven' | 'composed' | 'related' | 'none' | 'skipped' | string
|
||
reason?: string
|
||
note?: string
|
||
candidates: (LedgerRelated & { status?: string; how?: string })[]
|
||
/**
|
||
* 逐格比过的(服务端 ledger/match._cells,algo 4):被引用的每一列各自的结论和候选。整条(按全部引用列投影)没证明、
|
||
* 报告引用了这条查询好几列时才有;每个数的标签已经按它那一格标好,界面照标签画,不拿它重算
|
||
*/
|
||
cells?: Record<string, { status: string; reason?: string; candidates: (LedgerRelated & { status?: string; how?: string })[] }>
|
||
}
|
||
|
||
/** 整题判断(主报告):所有带标签的数都来自同一口径的同一版、参数一致时 equivalent */
|
||
export interface LedgerWhole {
|
||
equivalent: boolean
|
||
reason?: string
|
||
counts?: Partial<Record<LedgerLabel['kind'], number>>
|
||
caliber?: string
|
||
workflow_id?: string
|
||
version?: number
|
||
level?: string
|
||
level_text?: string
|
||
workflow_name?: string | null
|
||
how?: 'independent' | 'composed'
|
||
binding?: LedgerBinding[]
|
||
missing?: string[]
|
||
missing_required?: string[]
|
||
}
|
||
|
||
/** 按口径正式出具这一项:能不能(available / reason)、能不能自动(auto / auto_reason)、已经发起过的那一次(issued_run) */
|
||
export interface LedgerIssueAction {
|
||
kind: 'issue'
|
||
workflow_id: string
|
||
version: number
|
||
caliber: string
|
||
workflow_name: string
|
||
how?: 'independent' | 'composed' | null
|
||
level?: string | null
|
||
level_text: string
|
||
available: boolean
|
||
auto: boolean
|
||
reason?: string
|
||
auto_reason?: string
|
||
issued_run?: string
|
||
}
|
||
|
||
/** 入口字段(比对工件里记的那一份):核对参数的表单照它画 */
|
||
export interface LedgerField {
|
||
name: string
|
||
required: boolean
|
||
has_default: boolean
|
||
}
|
||
|
||
/** 看差多少(口径对账)这一项 */
|
||
export interface LedgerReconcileAction {
|
||
kind: 'reconcile'
|
||
query: string
|
||
caliber: string
|
||
workflow_id: string
|
||
version: number
|
||
node_id?: string
|
||
workflow_name?: string | null
|
||
level_text: string
|
||
/** 参数是推断的、不全的、带遮罩值的:要人先核对(check_reason 是原因) */
|
||
needs_check: boolean
|
||
check_reason?: string
|
||
binding: LedgerBinding[]
|
||
/** 两条 SQL 对不上结构:多半要人选对照的指标 */
|
||
fallback: boolean
|
||
fields?: LedgerField[]
|
||
}
|
||
|
||
export type LedgerAction = LedgerIssueAction | LedgerReconcileAction
|
||
|
||
/** 按口径出具过的一次正式运行。tier_text 是「完整出具 / 降档出具 / 不予出具」,运行没结束时为空 */
|
||
export interface LedgerIssued {
|
||
run_id: string
|
||
status: string
|
||
end_reason?: string | null
|
||
workflow_id: string
|
||
version: number
|
||
tier?: string | null
|
||
tier_text?: string | null
|
||
auto: boolean
|
||
created_at?: string | null
|
||
}
|
||
|
||
/** 对账卡上的一组对照:这次 / 口径 / 差值 / 相对差各是哪个指标 */
|
||
export interface LedgerReconcileGroup {
|
||
label: string
|
||
ours: string
|
||
caliber: string
|
||
diff: string
|
||
rel: string
|
||
}
|
||
|
||
/** 对过的一次账 */
|
||
export interface LedgerReconciled {
|
||
run_id: string
|
||
status: string
|
||
query?: string
|
||
caliber?: string
|
||
workflow_id?: string
|
||
version?: number
|
||
/** binding:按比对时的参数;checked:人核对过的参数 */
|
||
inputs_from?: 'binding' | 'checked' | string
|
||
rows?: number[] | null
|
||
card?: string
|
||
groups?: LedgerReconcileGroup[]
|
||
created_at?: string | null
|
||
/** 「说明有用 / 看不懂」(服务端 ledger/feedback.feedback_of):看的这个人最后一次给的、各几个人 */
|
||
feedback?: LedgerFeedback
|
||
}
|
||
|
||
export type LedgerFeedbackVerdict = 'useful' | 'unclear'
|
||
|
||
/** 对账结果上的反馈:mine 是看的这个人最后一次给的(没给过为空),useful / unclear 是各几个人(每人只算最后一次) */
|
||
export interface LedgerFeedback {
|
||
mine?: LedgerFeedbackVerdict | null
|
||
useful: number
|
||
unclear: number
|
||
}
|
||
|
||
/** POST /api/ledger/answers/{run}/reconcile/{对账运行}/feedback 的答复 */
|
||
export interface LedgerFeedbackOut {
|
||
run_id: string
|
||
feedback: LedgerFeedback
|
||
}
|
||
|
||
/**
|
||
* 答案身上的召回标记(GET /runs/{id}/ledger 的 recall,服务端 ledger/recall.answer_marks;细案做法 7「依据有变」「已按 vN
|
||
* 核对,不影响」)。几种 state 都可能带:没比对过、不比对的答案照样可能被标依据有变。不放数、表名、取值
|
||
*/
|
||
export interface LedgerRecallMarks {
|
||
/** 口径升版后召回重算、数没变:在这个答案上记的「已按 vN 核对,不影响」 */
|
||
checked?: {
|
||
workflow_id?: string | null
|
||
workflow_name?: string | null
|
||
from_version?: number | null
|
||
to_version?: number | null
|
||
recall_run?: string | null
|
||
changed?: boolean
|
||
at?: string | null
|
||
}[]
|
||
/** 依据有变:kind 是 catalog(目录里影响数字的项被驳回或修改)/ structure(表结构)/ values(新码值)/ catalog_edit */
|
||
basis?: { kind?: string | null; items?: number; new_errors?: number; at?: string | null }[]
|
||
/** 看的这个人手上、说的是这个答案的 recall 待办(还没处理的) */
|
||
tasks?: { id: string; reason?: string; to_version?: number | null }[]
|
||
}
|
||
|
||
export interface LedgerCaliber {
|
||
caliber: string
|
||
workflow_id: string
|
||
version: number
|
||
level?: string
|
||
graph_hash?: string | null
|
||
workflow_name?: string | null
|
||
level_text: string
|
||
}
|
||
|
||
/**
|
||
* GET /api/runs/{id}/ledger。state:matched 比对过;pending 正在比对(或刚跑完、封存完就比);ineligible 这次运行不比对
|
||
* (reason 写为什么);unreadable 比对记录读不出
|
||
*/
|
||
export interface RunLedger {
|
||
run_id: string
|
||
state: 'matched' | 'pending' | 'ineligible' | 'unreadable'
|
||
reason?: string
|
||
/** 「封存后比对,不在封存范围内」 */
|
||
note?: string
|
||
artifact?: string
|
||
algo?: string
|
||
trigger?: string
|
||
retro?: boolean
|
||
matched_at?: string | null
|
||
post_seal?: boolean
|
||
/** 事后补比的:按现在的口径回看 / 按之后发布的口径回看 */
|
||
retro_note?: string
|
||
calibers?: LedgerCaliber[]
|
||
queries?: LedgerQuery[]
|
||
reports?: Record<string, { labels: Record<string, LedgerLabel> }>
|
||
primary?: string | null
|
||
whole?: LedgerWhole
|
||
actions?: LedgerAction[]
|
||
issued?: LedgerIssued[]
|
||
reconciled?: LedgerReconciled[]
|
||
recall?: LedgerRecallMarks
|
||
}
|
||
|
||
/** 证据片段接口(GET /runs/{id}/evidence/segments/{seg})的 ledger 键:这个片段的标签和比对的来历 */
|
||
export interface SegmentLedger {
|
||
label: LedgerLabel
|
||
artifact?: string
|
||
algo?: string
|
||
trigger?: string
|
||
retro?: boolean
|
||
matched_at?: string | null
|
||
post_seal?: boolean
|
||
note?: string
|
||
retro_note?: string
|
||
}
|
||
|
||
/** POST /api/ledger/answers/{run}/issue 的答复:created 为假是同一次已经发起过、交回那一次 */
|
||
export interface LedgerIssueOut {
|
||
run_id: string
|
||
created: boolean
|
||
issued: LedgerIssued
|
||
}
|
||
|
||
/** POST /api/ledger/answers/{run}/reconcile 的请求体 */
|
||
export interface LedgerReconcileIn {
|
||
query: string
|
||
workflow_id: string
|
||
version: number
|
||
inputs?: Record<string, unknown>
|
||
confirm?: boolean
|
||
pairs?: { ours: string; metric: string }[]
|
||
}
|
||
|
||
export interface LedgerReconcileOut {
|
||
run_id: string
|
||
reconciled: LedgerReconciled
|
||
}
|
||
|
||
/** 认不出列怎么配时 409(ledger_reconcile_needs_pairs)另带的选项 */
|
||
export interface LedgerPairOptions {
|
||
columns: string[]
|
||
metrics: { id: string; name: string; unit?: string }[]
|
||
}
|
||
|
||
// ---------------------------------------------------------------- 认证队列(可沉淀的问题)
|
||
|
||
export interface CertifyTurn {
|
||
run_id: string | null
|
||
conversation_id?: string | null
|
||
turn_id?: string | null
|
||
question: string
|
||
}
|
||
|
||
export interface CertifyFrozen {
|
||
workflow_id: string
|
||
name: string
|
||
status?: string
|
||
published_version?: number | null
|
||
}
|
||
|
||
/** 三种来源:确定性重复(SQL 形状相同)、问题聚类、被反复用的答案 */
|
||
export interface CertifyItem {
|
||
id: string
|
||
kind: 'shape' | 'cluster' | 'reused'
|
||
question: string
|
||
turns: CertifyTurn[]
|
||
frozen_into?: CertifyFrozen[]
|
||
// shape
|
||
source?: string
|
||
shape?: string
|
||
runs?: number
|
||
conversations?: number
|
||
params?: { index: number; distinct: number }[]
|
||
constants?: number
|
||
related?: { caliber?: string; workflow_id?: string; version?: number; level?: string }[]
|
||
representative?: { run_id: string; query?: string }
|
||
peers?: string[]
|
||
// cluster
|
||
count?: number
|
||
// reused
|
||
uses?: { exports: number; app: boolean; extracted: boolean; canvas: boolean }
|
||
}
|
||
|
||
export interface CertifyQueue {
|
||
items: CertifyItem[]
|
||
considered: { runs: number; matched: number; questions: number }
|
||
threshold: { runs: number; conversations: number; exports: number }
|
||
embedder?: unknown
|
||
}
|
||
|
||
/** 固化成口径草稿(POST /copilot/from-run,freeze: true)的答复 */
|
||
export interface FreezeOut {
|
||
workflow_id: string
|
||
name: string
|
||
nodes: number
|
||
edges: number
|
||
params: string[]
|
||
metrics: string[]
|
||
sources: CertifyTurn[]
|
||
}
|