mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 02:41:58 +00:00
fcdcc30d4e
一、可点击证据第二期
取数链路
- 智能体、工具节点、代码节点、知识检索取数时都记进证据台账;报告目录按台账编出 Q1… / K1…
- 报告可以引用快照里的一格([[v:Q1.r0.gmv]]),也可以由系统从快照生成整张表([[table:Q1 cols=… rows=0-4]]),
表里每一格都是可点的片段;取值一律以快照为准,取回快照时复验哈希
- 口径卡表达式新增 cell(x, row, column),lineage 精确到单元格
- 智能体开 cite_fields(配合 output_schema)后,循环结束做一次结构化抽取,每个字段标出取自哪次查询的哪一格,
再到快照里核对:一致记 verified;不一致以快照为准并告警;查不到的记为空值,永远不兜底成 0
- 代码节点新增 evidence_role(source / compute)和 code_sha;沙箱算出来的数不能被报告直接引用,要进口径卡
- 查询快照记下列类型,文本列按文本渲染(年份 "2026" 不再显示成「2,026」)
- 升级前发起的运行(停在审批上的)一律走旧逻辑:新的 task 只追加在循环之后,恢复时不会错号
证据接口与面板
- 片段链接上查询步骤:SQL、被引用的行加前后各 2 行、高亮到格;数据源可设 mask_columns 遮罩列,
遮罩在查询时记进快照,数据源事后改名或删除也照样遮罩(在有身份体系之前,遮罩只减少暴露,不是安全边界)
- 输入来源写明核对结果(与快照一致 / 模型报的值和快照不一致 / 没查到记为空);口径卡缺输入时也给出完整的链
- 受管出具要在契约里声明 cells 才能引用单元格;受管与否记在所钉版本上,改画布不再让受管版本的正式运行降级
- 口径卡可以用 caliber_from 钉住另一个已发布工作流里的口径卡,上游有新版本时按 upgrade_policy 处置,
和子工作流同一套规则;子工作流节点也补上了升版处置的配置项
Copilot、校验与报错
- 问数据的答案里有数字时,搭 input → 智能体 → 报告 → 出口,报告直接引用查过的单元格
- 写 SQL 前核对数据源的表结构,SQL 里出现表结构里没有的列会被打回
- 不再搭「智能体文字 → 转换节点解析 JSON」这种图;转换节点的 JSON 报错分清是上游模型写坏了、
模板里裸插了模型文字(给出 | json 的改法),还是模板本身写错了
- 工具节点遇到「查询失败」「SQL 被拒绝」判为失败;发起运行前检查绑定的工具是否存在,不存在时直接拒绝并给出入口
二、发布前自动修复
- 问题带上机器编号(code)和修复 id;新增只读接口 POST /api/workflows/{id}/publish-check 和 /autofix
- 修复分三类:答案唯一、不降低要求的一键修复;要人拿主意的给选项(不替人选);结构性问题交给 Copilot
- 一律先预览,不自动保存、不自动发布;删节点、删契约、放宽审批、关掉 strict、改级别等降低要求的改动一律拒绝,
Copilot 的方案也要过这一关;应用后重跑检查,错误必须变少且不能冒出新的
- 发布弹窗打开就先检查;问题面板新增「发布前检查」,平时就能看到、就地修掉
- 门禁修正:契约有 report_from 时不再要求 narrative(模板⑨可以发成受管);全图默认审批为「全部放行」时同样拦下
- 修掉画布保存会抹掉全图默认(defaults)的老问题:以前任何一次保存都会把它清空,审批 always 因此被悄悄放宽
验证(沙箱):tsc 干净,生产构建通过,pytest 2268 passed(两遍),check-all 17 项检查、3915 项全过
(新增 check-publish);关键行为做了变异验证、全部抓到。
884 lines
29 KiB
TypeScript
884 lines
29 KiB
TypeScript
export type NodeType =
|
||
| 'input' | 'output' | 'llm' | 'agent' | 'supervisor' | 'tool' | 'code'
|
||
| 'branch' | 'loop' | 'subgraph' | 'memory' | 'retrieve' | 'transform'
|
||
| 'human' | 'validate' | 'metrics' | 'report'
|
||
|
||
export interface GraphNode {
|
||
id: string
|
||
type: NodeType
|
||
position: { x: number; y: number }
|
||
data: { label?: string; description?: string; config: Record<string, any> }
|
||
}
|
||
|
||
export interface GraphEdge {
|
||
id?: string
|
||
source: string
|
||
target: string
|
||
sourceHandle?: string | null
|
||
targetHandle?: string | null
|
||
label?: string
|
||
}
|
||
|
||
export interface GraphSpec {
|
||
nodes: GraphNode[]
|
||
edges: GraphEdge[]
|
||
viewport?: Record<string, any>
|
||
defaults?: Record<string, any>
|
||
}
|
||
|
||
export interface Workflow {
|
||
id: string
|
||
name: string
|
||
description: string
|
||
graph: GraphSpec
|
||
tags: string[]
|
||
version: number
|
||
is_template: boolean
|
||
status?: 'draft' | 'published' | 'governed'
|
||
published_version?: number | null
|
||
/** 最近一次发布的署名。发布时没有署名为 null */
|
||
published_by?: string | null
|
||
run_count?: number
|
||
created_at?: string
|
||
updated_at?: string
|
||
}
|
||
|
||
/** 工作流的一个版本。列表接口只有前四项,取单个版本时才有 graph 等 */
|
||
export interface WorkflowVersion {
|
||
id: string
|
||
version: number
|
||
note: string
|
||
created_at?: string
|
||
workflow_id?: string
|
||
graph?: GraphSpec
|
||
graph_hash?: string | null
|
||
/** 这一版入口节点声明的字段。正式运行的表单照它填,不能照画布 */
|
||
input_fields?: { name: string; required?: boolean; [key: string]: any }[]
|
||
/** 是不是当前的已发布版本 */
|
||
published?: boolean
|
||
}
|
||
|
||
/**
|
||
* 后端写进 runs.status 的值。interrupted 既可能是停在审批上,也可能是服务重启
|
||
* 打断后挂起——显示时用 lib/status 的 resolveStatus 配合审批列表区分。
|
||
* suspended 目前后端不写,前端推导的 RunPhase 会用到,这里一并收下。
|
||
*/
|
||
export type RunStatus =
|
||
| 'queued' | 'running' | 'interrupted' | 'succeeded' | 'failed' | 'cancelled' | 'suspended'
|
||
|
||
/**
|
||
* 运行的用量与时长。
|
||
*
|
||
* 三种时长口径不同,不能混用:duration_ms / active_ms 是各段执行时长之和(审批
|
||
* 恢复、续跑的每一段都算),wall_ms 是从第一次开始到结束的墙钟,wait_ms 是等人
|
||
* 审批的总时长。老数据只有 duration_ms,而且可能只是最后一段。
|
||
*/
|
||
export interface RunUsage {
|
||
duration_ms?: number
|
||
wall_ms?: number
|
||
active_ms?: number
|
||
wait_ms?: number
|
||
input_tokens?: number
|
||
output_tokens?: number
|
||
cost_usd?: number
|
||
[key: string]: any
|
||
}
|
||
|
||
export interface Run {
|
||
id: string
|
||
workflow_id: string | null
|
||
workflow_name: string
|
||
status: RunStatus
|
||
input: Record<string, any>
|
||
output: Record<string, any>
|
||
error: string | null
|
||
usage: RunUsage
|
||
run_class?: 'formal' | 'exploratory'
|
||
version?: number | null
|
||
version_hash?: string | null
|
||
manifest_hash?: string | null
|
||
/** 封存到第几条事件为止。有它才说明这条运行的清单封存过 */
|
||
manifest_seq?: number | null
|
||
/** 失败时能定位到的节点 */
|
||
error_node_id?: string | null
|
||
started_by?: string | null
|
||
/** 发起时实际生效的记忆域和知识库(没指定时取设置里的默认值)。老后端不给 */
|
||
memory_scope?: string | null
|
||
collection?: string | null
|
||
created_at?: string
|
||
started_at?: string
|
||
finished_at?: string | null
|
||
}
|
||
|
||
export interface RunEvent {
|
||
seq: number
|
||
type: string
|
||
node_id: string | null
|
||
ts: number
|
||
data: Record<string, any>
|
||
replay?: boolean
|
||
}
|
||
|
||
export interface Approval {
|
||
id: string
|
||
run_id: string
|
||
node_id: string
|
||
mode: 'approve' | 'input' | 'edit'
|
||
title: string
|
||
/**
|
||
* 工具审批的 payload 是 {kind: 'tool_approval', tool, args, title}。探索运行里 MCP /
|
||
* 自定义工具的审批另带 trust_key(同 ToolInfo.trust_key):有它,审批卡才给「始终允许」
|
||
*/
|
||
payload: Record<string, any>
|
||
status: string
|
||
response: Record<string, any>
|
||
created_at?: string
|
||
// 以下是审批卡的上下文,老后端不给
|
||
workflow_id?: string | null
|
||
workflow_name?: string | null
|
||
node_label?: string | null
|
||
run_status?: RunStatus | null
|
||
run_class?: 'formal' | 'exploratory' | null
|
||
/** 谁处理的。null 表示处理时没有署名,显示「未署名」 */
|
||
resolved_by?: string | null
|
||
resolved_at?: string | null
|
||
}
|
||
|
||
/**
|
||
* 后端记着的最近一次测连接(模型接入、数据源、MCP 的列表接口都平铺这四项)。
|
||
* 没测过、或者连接配置改过之后全是 null。用 lib/health 的 healthFromServer 转成
|
||
* HealthRecord 给 HealthPill
|
||
*/
|
||
export interface ServerHealth {
|
||
last_checked_at?: string | null
|
||
last_check_ok?: boolean | null
|
||
last_latency_ms?: number | null
|
||
last_error?: string | null
|
||
}
|
||
|
||
export interface Provider extends ServerHealth {
|
||
id: string
|
||
name: string
|
||
kind: string
|
||
base_url: string | null
|
||
default_model: string | null
|
||
models: { id: string; label?: string; context?: number; pricing?: any }[]
|
||
enabled: boolean
|
||
extra: Record<string, any>
|
||
api_key_masked: string
|
||
has_key: boolean
|
||
}
|
||
|
||
/**
|
||
* MCP / 自定义工具在探索运行里的信任档(节点审批策略为「仅危险工具」时才看它):
|
||
* ask 每次等人批(默认);gated 每次先问门控模型,它判可疑的仍交给人;always 直接执行。
|
||
* 正式运行不看它,一律按 ask
|
||
*/
|
||
export type ToolTrust = 'ask' | 'gated' | 'always'
|
||
|
||
export interface ToolInfo {
|
||
id: string
|
||
name: string
|
||
description: string
|
||
category: string
|
||
source: 'builtin' | 'custom' | 'mcp'
|
||
dangerous: boolean
|
||
/**
|
||
* 运行时可能停下来等审批。MCP / 自定义工具:ask、gated 为 true,always 为 false;
|
||
* 更老的后端一律给 false(那时审批关卡认不出它们)
|
||
*/
|
||
runtime_approval?: boolean
|
||
/** 只有 MCP / 自定义工具有,老后端不给:没有它就不显示信任档控件 */
|
||
trust?: ToolTrust
|
||
/** 改信任档时用的键(PUT /api/tools/trust),等于 id */
|
||
trust_key?: string
|
||
schema: Record<string, any>
|
||
/**
|
||
* 只有自定义工具有:库里存着的参数定义哪里写坏了(一句中文)。绑了它的节点运行时
|
||
* 一定失败,列表上要醒目地标出来。null / 缺省表示没问题
|
||
*/
|
||
problem?: string | null
|
||
}
|
||
|
||
/**
|
||
* 数据源(GET /api/datasources 的一行)。tools 是它给模型的两个工具名
|
||
* db_query__<name> / db_schema__<name>——/api/tools 里没有它们,挑工具要从这里取
|
||
*/
|
||
export interface DataSource extends ServerHealth {
|
||
id: string
|
||
/** 标识,进了工具名,建好不能改 */
|
||
name: string
|
||
kind: string
|
||
host: string | null
|
||
port: number | null
|
||
database: string | null
|
||
username: string | null
|
||
options: Record<string, any>
|
||
readonly: boolean
|
||
description: string
|
||
enabled: boolean
|
||
password_masked?: string
|
||
has_password?: boolean
|
||
table_count?: number
|
||
schema_synced_at?: string | null
|
||
/** 上次探查为什么没拿到表;空串 = 没出错(或还没探查) */
|
||
schema_error?: string
|
||
available_schemas?: string[]
|
||
tools?: string[]
|
||
/** 缓存按哪个 schema 探的:'' 默认 schema,null 没有缓存 */
|
||
cached_schema?: string | null
|
||
}
|
||
|
||
/** 自定义工具(GET /api/custom-tools 的一行;POST / PATCH 的返回同形) */
|
||
export interface CustomTool {
|
||
id: string
|
||
name: string
|
||
description: string
|
||
kind: 'http' | 'python' | (string & {})
|
||
parameters: Record<string, any>
|
||
config: Record<string, any>
|
||
enabled: boolean
|
||
/** 同 ToolInfo.problem:参数定义写坏了的那句话,没问题是 null */
|
||
problem?: string | null
|
||
}
|
||
|
||
export interface Skill {
|
||
id: string
|
||
name: string
|
||
description: string
|
||
instructions: string
|
||
examples: { input?: string; output?: string }[]
|
||
suggested_tools: string[]
|
||
tags: string[]
|
||
enabled: boolean
|
||
}
|
||
|
||
export interface MemoryItem {
|
||
id: string
|
||
scope: string
|
||
kind: string
|
||
content: string
|
||
importance: number
|
||
use_count: number
|
||
meta: Record<string, any>
|
||
/**
|
||
* 从哪来。kind=run 时带运行、节点和工作流名;manual 是手动添加的;playground 是
|
||
* 在工具库里直接调 remember 写进来的。老后端不给
|
||
*/
|
||
source?: MemorySource | null
|
||
created_at?: string
|
||
/** 后端不再给:每次计数的回忆都会写这一行,updated_at 跟着回忆走,不是编辑时间 */
|
||
updated_at?: never
|
||
last_used_at?: string | null
|
||
}
|
||
|
||
export interface MemorySource {
|
||
kind?: 'run' | 'manual' | 'playground' | string
|
||
run_id?: string | null
|
||
node_id?: string | null
|
||
workflow_name?: string | null
|
||
node_label?: string | null
|
||
/** 来源运行是否还在。删掉之后只剩文字,不再给链接 */
|
||
run_exists?: boolean
|
||
[key: string]: any
|
||
}
|
||
|
||
export interface KbDocument {
|
||
id: string
|
||
collection: string
|
||
title: string
|
||
source: string
|
||
mime: string
|
||
chunk_count: number
|
||
/** ready | processing | failed。大文件的切块和算向量在后台跑 */
|
||
status?: string
|
||
error?: string
|
||
meta?: { progress?: { done: number; total: number } }
|
||
created_at?: string
|
||
}
|
||
|
||
export interface ValidationIssue {
|
||
level: 'error' | 'warning'
|
||
node_id?: string | null
|
||
edge_id?: string | null
|
||
message: string
|
||
/** 出问题的是节点配置里的哪一项:'prompt'、'tools'、'cases[1].condition'。老后端不给 */
|
||
field?: string | null
|
||
/** 机读编号(governed.agent_approval_never 这类),发布前检查按它认修复。老后端不给 */
|
||
code?: string | null
|
||
/** 这条问题对应的修复 id:publish-check 的 fixes 里同 id 的那一项。没有修复时为空 */
|
||
fix?: string | null
|
||
}
|
||
|
||
/** 发布等级:发布弹窗、问题面板的发布前检查共用 */
|
||
export type PublishLevel = 'published' | 'governed'
|
||
|
||
/**
|
||
* 发布前检查给的一项修复(POST /workflows/{id}/publish-check 的 fixes)。
|
||
* auto:答案唯一、不降低要求,预览后一键修;choice:要人拿主意,options 里挑(multiple 可多选);
|
||
* assist:结构性的,交给 Copilot 试着改,产出同样只是预览
|
||
*/
|
||
export interface PublishFix {
|
||
id: string
|
||
code: string
|
||
kind: 'auto' | 'choice' | 'assist'
|
||
node_id?: string | null
|
||
label: string
|
||
preview?: { field?: string | null; before?: unknown; after?: unknown } | null
|
||
options?: { value: unknown; label: string; hint?: string | null }[]
|
||
multiple?: boolean
|
||
/** 建议值:界面上标「建议」,仍然要人选 */
|
||
default?: unknown
|
||
}
|
||
|
||
export interface PublishCheck {
|
||
level: PublishLevel
|
||
ok: boolean
|
||
issues: ValidationIssue[]
|
||
fixes: PublishFix[]
|
||
}
|
||
|
||
/** 自动修复预览里给人看的一项改动:「节点 · 字段:原值 → 新值」 */
|
||
export interface AutofixChange {
|
||
fix_id: string
|
||
node_id?: string | null
|
||
node_title?: string | null
|
||
field?: string | null
|
||
before?: unknown
|
||
after?: unknown
|
||
label?: string | null
|
||
}
|
||
|
||
/** POST /workflows/{id}/autofix:只给预览,不改库、不发布 */
|
||
export interface AutofixResult {
|
||
/** 修复后的整张图;人确认后前端走保存接口存成草稿 */
|
||
graph: GraphSpec | null
|
||
changes: AutofixChange[]
|
||
applied: string[]
|
||
rejected: { fix_id: string; reason: string }[]
|
||
/** 修复后仍然存在的问题(带 code 和 fix) */
|
||
remaining: ValidationIssue[]
|
||
/** 交给 Copilot 的那一段:它认为要人拿主意的放进 questions,不硬改 */
|
||
assist: { ok: boolean; summary?: string; questions?: string[] } | null
|
||
/** 修复后是否已经没有 error */
|
||
ok: boolean
|
||
}
|
||
|
||
/** 单个节点在一次运行中的实时状态,驱动画布上的高亮。 */
|
||
export interface NodeRuntime {
|
||
/** cancelled / suspended 是终态清扫时收的:运行被取消或服务重启时还没跑完的节点 */
|
||
status: 'idle' | 'running' | 'done' | 'failed' | 'waiting' | 'skipped' | 'cancelled' | 'suspended'
|
||
durationMs?: number
|
||
preview?: any
|
||
error?: string
|
||
tokens?: string
|
||
thinking?: string
|
||
toolCalls?: { tool: string; args?: any; result?: string; ok?: boolean; agent?: string }[]
|
||
/** supervisor 的协作矩阵:谁被派了、在第几轮、几个人同时在跑。运行中长出来 */
|
||
team?: TeamRun
|
||
/** loop 走到第几轮(从 1 数)。卡片上那个"第 2/5 轮" */
|
||
iteration?: number
|
||
/** 分支/循环实际走的出口。命中的那条亮起来、其余压暗——不然一个六出口的
|
||
* 分支节点跑完,谁也说不清它到底选了哪条 */
|
||
takenHandle?: string
|
||
/** 走这条出口的理由:表达式分支命中了哪条条件,或调度者为什么派给这个人 */
|
||
reason?: string
|
||
}
|
||
|
||
/**
|
||
* 协作团队一轮里的一个人。
|
||
*
|
||
* ms 是**各自量出来**的耗时,不是拿整轮墙钟摊的——界面要显示"并行省了多少",
|
||
* 那个数按 N×墙钟 推算会把快的那个也记成最慢那条,省下的时间就被夸大了。
|
||
*/
|
||
export interface TeamMember {
|
||
agent: string
|
||
instruction: string
|
||
ms: number
|
||
status: 'running' | 'done' | 'failed' | 'waiting' | 'cancelled' | 'suspended'
|
||
result?: string
|
||
}
|
||
|
||
export interface TeamRound {
|
||
round: number
|
||
/** 这一轮同时派了几个人。1 就是串行的一步 */
|
||
parallel: number
|
||
/** 这一轮实际花的时间:并发时是最慢那个 */
|
||
wallMs: number
|
||
/** 各人耗时之和:并发时它大于 wallMs,差值就是省下的 */
|
||
sumMs: number
|
||
reason?: string
|
||
members: TeamMember[]
|
||
}
|
||
|
||
export interface TeamRun {
|
||
/** 花名册,按第一次出现的顺序——泳道的行顺序 */
|
||
members: string[]
|
||
rounds: TeamRound[]
|
||
/** 并行一共省下多少毫秒。全程串行则为 0 */
|
||
savedMs: number
|
||
/** 协作是不是已经收尾了 */
|
||
finished: boolean
|
||
}
|
||
|
||
/** 一处 {{ }} 引用 */
|
||
export interface VarRef {
|
||
node_id: string
|
||
node_label: string
|
||
field: string
|
||
expr: string
|
||
}
|
||
|
||
/** 一个可以被 {{ }} 引用的东西。由后端静态分析整张图得出 */
|
||
export interface Variable {
|
||
path: string
|
||
kind: 'input' | 'var' | 'node' | 'builtin' | 'loop'
|
||
label: string
|
||
produced_by?: string | null
|
||
produced_by_label?: string | null
|
||
order: number
|
||
refs: VarRef[]
|
||
description: string
|
||
}
|
||
|
||
export interface VarIssue {
|
||
level: 'error' | 'warning' | 'info'
|
||
message: string
|
||
node_id?: string | null
|
||
path: string
|
||
}
|
||
|
||
/**
|
||
* 一次对话。
|
||
*
|
||
* kind 分两种:chat 是问数据页(一轮 = 建图 → 跑图 → 出答案,进左侧列表),
|
||
* canvas 是画布右栏的 Copilot(依附某张图,一轮 = 一次改图,不进列表)。
|
||
*/
|
||
export interface Conversation {
|
||
id: string
|
||
title: string
|
||
kind: 'chat' | 'canvas'
|
||
workflow_id?: string | null
|
||
archived: boolean
|
||
created_at?: string
|
||
last_active_at?: string
|
||
turn_count: number
|
||
/** 列表里的副标题,让人一眼认出是哪次聊天 */
|
||
last_question: string
|
||
/**
|
||
* 最后一轮停在哪。左栏对这次没打开过的会话也能标出运行中、待审批、失败。
|
||
* 没有轮次时为 null;老后端不给
|
||
*/
|
||
last_status?: 'running' | 'waiting' | 'error' | 'cancelled' | 'suspended' | 'done' | null
|
||
/** 最后一轮的运行。建图阶段就断了的没有 */
|
||
last_run_id?: string | null
|
||
}
|
||
|
||
export interface ConversationDetail extends Conversation {
|
||
turns: ConversationTurn[]
|
||
}
|
||
|
||
export interface ConversationTurn {
|
||
id: string
|
||
seq: number
|
||
question: string
|
||
answer: string
|
||
explanation: string
|
||
graph?: GraphSpec | null
|
||
/** 可能为空:建图阶段就失败时压根没有 run,但这一轮仍然发生过 */
|
||
run_id?: string | null
|
||
status: 'running' | 'done' | 'error'
|
||
error: string
|
||
/** 复核结论。null = 这一轮没复核过,和「复核过、没发现问题」不是一回事 */
|
||
review?: ReviewResult | null
|
||
/**
|
||
* 可信度元数据(出具档位、运行类别、耗时、查库次数……),前端整块写、整块读,
|
||
* 后端只用它算 last_status。老轮次挂在 review.meta 下,后端读出来时统一放到这里
|
||
*/
|
||
meta?: Record<string, any> | null
|
||
created_at?: string
|
||
/**
|
||
* 这一轮最后一次落库的时刻(UTC,后端 TurnOut)。后端判「建图断了」按它算
|
||
* (conversations.BUILD_STALE),前端判断同一件事时用同一只钟。老后端没有
|
||
*/
|
||
updated_at?: string
|
||
}
|
||
|
||
/**
|
||
* Copilot 改图前后都在、而绑定的工具变了的节点或协作成员。
|
||
* 画布上看不出来(节点还在),跑起来才发现查不了库,所以改图回执要明说
|
||
*/
|
||
export interface ToolChange {
|
||
node_id: string
|
||
label: string
|
||
/** 协作成员的名字;节点本身的工具为 null */
|
||
member: string | null
|
||
/** 配置里的哪一项:'tools' 或 'agents[2].tools' */
|
||
field: string
|
||
before: string[]
|
||
after: string[]
|
||
added: string[]
|
||
removed: string[]
|
||
}
|
||
|
||
/**
|
||
* Copilot 自查(SSE op='check')里的一条问题。新后端给对象,老会话里存的是
|
||
* 「「node_id」message」一行字符串,两种都要认,见 CopilotCheckEntry
|
||
*/
|
||
export interface CopilotCheckIssue {
|
||
level: 'error' | 'warning'
|
||
node_id: string | null
|
||
edge_id: string | null
|
||
/** 不再以「节点 id」开头;要带节点名自己用 node_id 查 */
|
||
message: string
|
||
/** 出问题的配置项,比如 'tools'、'agents[1].tools',检查器据此定位 */
|
||
field?: string | null
|
||
/** datasource_out_of_scope、tools_dropped 这类机器码 */
|
||
code?: string
|
||
}
|
||
|
||
export type CopilotCheckEntry = string | CopilotCheckIssue
|
||
|
||
/** SSE 的自查操作。repairing 时带第几轮;warnings 是不挡运行的提醒(tools_dropped) */
|
||
export interface CopilotCheckOp {
|
||
op: 'check'
|
||
status: 'repairing' | 'passed' | 'failed' | 'error'
|
||
issues?: CopilotCheckEntry[]
|
||
warnings?: CopilotCheckIssue[]
|
||
round?: number
|
||
repaired?: number
|
||
message?: string
|
||
detail?: string
|
||
}
|
||
|
||
/**
|
||
* 一次运行跑完之后的复核结论(后端 engine/review.py)。
|
||
*
|
||
* 「跑完了」和「答得对」是两回事:检索降级、工具报错、agent 步数用满这些事
|
||
* 原先一件都不会进到答案里,用户拿到一个看起来很完整的结论,却无从知道它是在
|
||
* 什么条件下得出的。复核层就是把这段补上。
|
||
*/
|
||
export interface ReviewSignal {
|
||
kind: string
|
||
detail: string
|
||
/** broken = 答案不可信;degraded = 能用但有缺口 */
|
||
severity: 'broken' | 'degraded'
|
||
/**
|
||
* agent 为什么提前收尾(后端 engine/guards.py):steps / stall / budget_tokens / budget_usd / context。
|
||
* 老运行没有。只有 steps(或者没有这个字段的老运行)放宽步数才对症
|
||
*/
|
||
reason?: string
|
||
}
|
||
|
||
export interface ReviewResult {
|
||
/** ok=没问题 / annotated=加了说明 / rewritten=重写过 / reverted=重写里有新数字被退回 */
|
||
verdict: 'ok' | 'annotated' | 'rewritten' | 'reverted'
|
||
/** 给用户看的异常说明。排在成果上方 */
|
||
note: string
|
||
/** 重写后的答案。null 表示沿用原答案 */
|
||
answer: string | null
|
||
/** 改写之前的那份。改写是有损的,得能对照原件 */
|
||
original?: string | null
|
||
/** 换个配置重跑一遍大概率就好了 */
|
||
retry: boolean
|
||
severity: 'broken' | 'degraded' | ''
|
||
signals: ReviewSignal[]
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 可点击证据(报告撰写节点产出的文档、证据接口)
|
||
//
|
||
// 形状照后端 engine/evidence.py 和 api/evidence.py。前端对缺字段一律宽容:接口和文档
|
||
// 是分期长出来的,老运行、别的版本的后端都可能少几个键,少了就降级显示,不白屏。
|
||
// 偏移(span、start/end)按 Unicode 码点算,不是 JS 的 UTF-16 下标。
|
||
// -------------------------------------------------------------------------
|
||
|
||
/** 一处引用解析后的结果 */
|
||
export interface EvidenceCitation {
|
||
/** 不带前缀的引用,如 gmv、week */
|
||
ref?: string
|
||
/** 目录里的别名,如 m:gmv、i:week、Q1 */
|
||
alias?: string
|
||
locator?: { metric?: string; field?: string; row?: number; column?: string; table?: string }
|
||
eid?: string
|
||
/** metric / input / cell / table / column / quote / query */
|
||
kind?: string
|
||
role?: string
|
||
status?: 'resolved' | 'unresolved' | string
|
||
/** 解析不了的原因(人话) */
|
||
reason?: string
|
||
conv?: string
|
||
value?: unknown
|
||
rendered?: string
|
||
}
|
||
|
||
/** 可点的最小片段 */
|
||
export interface EvidenceSegment {
|
||
id: string
|
||
/** text / number / value / entity / quote / structural */
|
||
kind: string
|
||
text: string
|
||
span?: [number, number]
|
||
/** deterministic / none / neutral,后续期还有 probabilistic、candidate */
|
||
state?: string
|
||
/** 带前缀的原始引用,如 m:gmv|万 */
|
||
ref?: string
|
||
cite?: EvidenceCitation
|
||
strong?: boolean
|
||
/** uncited_number / unresolved_ref */
|
||
issue?: string
|
||
}
|
||
|
||
/** 句子、列表项或表格单元格 */
|
||
export interface EvidenceUnit {
|
||
id: string
|
||
/** claim / connective / heading / code。确定性规则分的,不是裁判结论 */
|
||
kind?: string
|
||
span?: [number, number]
|
||
cites?: string[]
|
||
see?: EvidenceCitation[]
|
||
segments: EvidenceSegment[]
|
||
/** 表格单元格的位置,表头 row = -1 */
|
||
loc?: { row: number; col: number }
|
||
/** 列表项的缩进层级 */
|
||
depth?: number
|
||
}
|
||
|
||
export interface EvidenceBlock {
|
||
id: string
|
||
/** heading / paragraph / list / table / quote / code / hr */
|
||
type: string
|
||
level?: number
|
||
ordered?: boolean
|
||
start?: number
|
||
lang?: string
|
||
units: EvidenceUnit[]
|
||
}
|
||
|
||
export interface EvidenceViolation {
|
||
/** uncited_number / unresolved_ref / render_mismatch / eid_mismatch / … */
|
||
code: string
|
||
message?: string
|
||
span?: [number, number]
|
||
text?: string
|
||
/** 指向的片段。结构片段(列表序号、代码围栏标签)里的数字没有可画线的文字,只能在清单里找 */
|
||
segment?: string
|
||
unit?: string
|
||
ref?: string
|
||
context?: string
|
||
}
|
||
|
||
export interface EvidenceStats {
|
||
units?: number
|
||
segments?: number
|
||
claims?: number
|
||
connective?: number
|
||
numbers?: number
|
||
numbers_cited?: number
|
||
values?: number
|
||
uncited_numbers?: number
|
||
unresolved?: number
|
||
see?: number
|
||
uncited_claims?: number
|
||
violations?: number
|
||
}
|
||
|
||
/** report_doc 工件的内容 */
|
||
export interface EvidenceDocData {
|
||
schema?: string
|
||
run_id?: string | null
|
||
node_id?: string
|
||
/** 渲染后的全文(真实数字,不含标记) */
|
||
markdown?: string
|
||
/** 建文档时的完整目录:alias → 条目 */
|
||
catalog?: Record<string, Record<string, any>>
|
||
blocks: EvidenceBlock[]
|
||
stats?: EvidenceStats
|
||
violations?: EvidenceViolation[]
|
||
}
|
||
|
||
/**
|
||
* 成果上的标注:这几个字段逐字等于某个报告撰写节点的文档,可以逐段点开看证据。
|
||
* 契约 report_from 指着的那份在最外层;另有报告也被原样放进成果的,列在 others 里
|
||
*/
|
||
export interface EvidenceFieldRef {
|
||
report_node?: string
|
||
doc_artifact?: string
|
||
fields?: string[]
|
||
others?: EvidenceFieldRef[]
|
||
}
|
||
|
||
/** 封存状态 */
|
||
export interface EvidenceSeal {
|
||
sealed?: boolean
|
||
ok?: boolean | null
|
||
/** 这件证据在不在封存范围内 */
|
||
covered?: boolean
|
||
manifest_seq?: number
|
||
legacy?: boolean
|
||
}
|
||
|
||
/** GET /runs/{id}/evidence:整次运行的证据图 */
|
||
export interface EvidenceGraph {
|
||
run_id?: string
|
||
schema?: string
|
||
/** cited / legacy_contract / legacy_text / none */
|
||
mode?: string
|
||
seal?: EvidenceSeal
|
||
reports?: { node_id?: string; doc_artifact?: string; doc_sealed?: boolean; fields?: string[]; stats?: EvidenceStats
|
||
/** 文档取回时哈希复验:false 对不上(或不是这次运行这个节点写的),null 文件不在了 */
|
||
hash_ok?: boolean | null }[]
|
||
evidence?: { alias?: string; eid?: string; kind?: string; label?: string; node_id?: string; artifact?: string
|
||
sealed?: boolean; cited_by?: string[] }[]
|
||
edges?: { from: string; to: string; rel: string }[]
|
||
}
|
||
|
||
/**
|
||
* 查询快照里的位置:单元格 {row, column};agent 的数组字段按行映射时是 {rows: [首, 尾], column | columns}
|
||
* (columns 是「字段 → 列」)。行号从 0 数
|
||
*/
|
||
export interface EvidenceLocator {
|
||
row?: number
|
||
column?: string
|
||
rows?: [number, number] | number[]
|
||
columns?: Record<string, string>
|
||
}
|
||
|
||
/** 指标的一个输入:值从哪来 */
|
||
export interface EvidenceInput {
|
||
path?: string
|
||
value?: unknown
|
||
node_id?: string
|
||
/**
|
||
* 一期:input / transform / agent / code / tool / llm …(产出节点的类型);
|
||
* 二期能追到查询快照那一格的两种:agent_field(agent 开了 cite_fields 的字段)、tool_cell(cell() 取数)
|
||
*/
|
||
via?: string
|
||
field?: string
|
||
/** 只有 code 节点有:source 取数 / compute 计算(缺省) */
|
||
role?: string
|
||
/**
|
||
* 一期 ok / missing;agent_field、tool_cell 是核对状态:verified 与快照一致、mismatch 对不上、
|
||
* unresolved 核对不了、missing 模型照实说没查到(from 为 null)
|
||
*/
|
||
status?: string
|
||
/** agent 字段的出处,agent 节点内的编号(Q1.r0.gmv)。和报告目录的全局编号不是一回事,靠 artifact 对应 */
|
||
ref?: string | null
|
||
/** 查询快照的工件 id:跳到哪一个查询步骤按它认 */
|
||
artifact?: string
|
||
locator?: EvidenceLocator
|
||
eid?: string
|
||
/** agent_field mismatch:模型报的值(value 是快照里的) */
|
||
model_value?: unknown
|
||
/** tool_cell mismatch:快照里的值(value 是算的时候用的) */
|
||
snapshot_value?: unknown
|
||
/** unresolved 的原因(人话) */
|
||
reason?: string
|
||
/** 输入对到的查询在报告目录里的编号(Q3):接口按工件补的全局编号 */
|
||
query?: string | null
|
||
/** 输入落在哪一格,全局编号(Q3.r5.amount) */
|
||
cell?: string
|
||
}
|
||
|
||
/** 口径卡钉在另一个已发布工作流某个版本里的口径卡上 */
|
||
export interface EvidenceCaliberSource {
|
||
workflow_id?: string
|
||
workflow_version?: number | string
|
||
node_id?: string
|
||
/** 接口顺手给的工作流名;没给时前端按工作流目录找 */
|
||
workflow_name?: string
|
||
}
|
||
|
||
/** 口径卡上游有新版本时声明的处置(和子工作流的升版处置同一套) */
|
||
export interface EvidenceCaliberUpgrade {
|
||
node_id?: string
|
||
workflow_id?: string
|
||
pinned?: number
|
||
latest?: number
|
||
/** recompute / dual / incomparable */
|
||
policy?: string
|
||
policy_label?: string
|
||
}
|
||
|
||
/**
|
||
* 片段证据链的一步:metric(指标)、input(指标的一个输入,也可能挂在 metric.inputs 里)、
|
||
* run_input(报告直接引用的运行输入)、query(查询快照:被引用的行加前后各 2 行)。
|
||
* input 步骤同时带着 EvidenceInput 的字段(via、status、model_value、ref、locator、artifact)
|
||
*/
|
||
export interface EvidenceStep extends Omit<EvidenceInput, 'status'> {
|
||
step: string
|
||
metric?: string
|
||
decimals?: number | null
|
||
format?: string
|
||
conv?: string | null
|
||
eid?: string
|
||
/** eid 能由工件和定位重算出来 */
|
||
eid_ok?: boolean
|
||
/** 工件取回时哈希复验通过、指标在卡里 */
|
||
hash_ok?: boolean
|
||
/** 卡里的值按同样的格式渲染出来就是报告上的字 */
|
||
render_ok?: boolean
|
||
/** 这件证据在封存范围内 */
|
||
sealed?: boolean
|
||
alias?: string
|
||
name?: string
|
||
value?: unknown
|
||
unit?: string
|
||
rendered?: string
|
||
caliber?: string
|
||
version?: string
|
||
node_id?: string
|
||
artifact?: string
|
||
expression?: string
|
||
substituted?: string
|
||
recompute_ok?: boolean | null
|
||
status?: string
|
||
inputs?: EvidenceInput[]
|
||
/** 口径卡钉在哪个工作流的哪一版(metric;方案第 5 节的写法,接口实际给在 source 上) */
|
||
caliber_from?: EvidenceCaliberSource | null
|
||
/** 上游有新版本时的处置(metric);没有升版是 null */
|
||
caliber_upgrade?: EvidenceCaliberUpgrade | null
|
||
// ---- query:查询快照的一个窗口 ----
|
||
tool?: string
|
||
/** query:数据源名;metric:口径卡钉住的来源 {workflow_id, workflow_version, node_id, workflow_name} */
|
||
source?: string | EvidenceCaliberSource | null
|
||
sql?: string
|
||
columns?: string[]
|
||
/** 只有被引用的行加前后各 2 行;遮罩列的值已经换成「已遮罩」 */
|
||
rows?: unknown[][]
|
||
/** rows[0] 在快照里是第几行(从 0 数) */
|
||
row_offset?: number
|
||
/** rows 里每一行在快照里是第几行:被引用的行隔得远时窗口不连续,按它认 */
|
||
row_index?: number[]
|
||
/** 快照一共几行 */
|
||
total_rows?: number | null
|
||
/** 查询撞了行数上限,库里还有更多 */
|
||
truncated?: boolean | null
|
||
/** 被引用的行太多,窗口只给了前面一截 */
|
||
window_truncated?: boolean
|
||
/** 被引用的行(快照里的行号)和列;cells 是精确到格的 [行, 列],有它就只高亮这几格 */
|
||
highlight?: { rows?: number[]; cols?: string[]; cells?: [number, string][] }
|
||
/** 这一步里被遮罩的列 */
|
||
masked?: string[]
|
||
/** 这一步为什么没有行(快照不在封存范围里、哈希对不上、取不回来) */
|
||
note?: string
|
||
/** 数据源改名或删掉了、按查询当时记下的遮罩处理时的那句说明。行照样有 */
|
||
mask_note?: string
|
||
}
|
||
|
||
/** GET /runs/{id}/evidence/segments/{sid}:点开一个片段 */
|
||
export interface EvidenceSegmentDetail {
|
||
report?: { node_id?: string; doc_artifact?: string }
|
||
segment?: Partial<EvidenceSegment> & { unit?: string }
|
||
unit?: { id?: string; kind?: string; text?: string; span?: [number, number]; cites?: string[] }
|
||
block?: { id?: string; type?: string }
|
||
chain?: EvidenceStep[]
|
||
/** 片段为什么是现在这个状态,一句人话 */
|
||
note?: string
|
||
/** 落在这个片段(或这一句、没有片段的)上的违规 */
|
||
violations?: EvidenceViolation[]
|
||
seal?: EvidenceSeal
|
||
/** 查询步骤里被遮罩的列(数据源 options.mask_columns)。遮罩只减少暴露,不是安全边界 */
|
||
redacted?: { columns?: string[] }
|
||
}
|