# 基于技能的自进化构图

为图驱动分析准备和优化图模式：了解构图智能体如何保留关系语义、固定评测查询、探索候选结构，并保存验收结果与领域经验。

## 技能如何参与构图

自进化构图是 iGraph 图驱动分析中的图模式准备能力。当关系源数据需要转为可查询的图，或既有图模式需要针对固定查询优化时，构图智能体负责准备候选图模式并接受验收。Agent Runtime 的流程技能组织数据集状态检查、工具步骤与编辑审批；Graph Engine 的构图子智能体负责关系图模式、候选评估与领域经验。独立 SQL 参考执行器（SQL oracle）独立计算源数据答案，使图模式优化接受同一组业务查询的检验；已经可用的图仍可直接用于主智能体分析。

| 层次 | 执行方式 | 积累与复用 |
| --- | --- | --- |
| 流程技能 | LocalSkills 加载内置 SKILL.md 与当前用户已批准的技能。graph_construction 规定状态检查、图加载、优化预览与提交流程。 | 候选技能经过审核后进入用户技能目录，后续任务加载该操作流程。 |
| 构图策略 | TabularConstructionSubAgent 在固定评测查询集下调用受约束变换，独立比较源表答案与图查询答案。 | DomainExperienceStore 记录正确性、耗时收益与结果类型；相似任务检索证据并调整候选顺序。 |

两层具有独立的存储边界：候选 SKILL.md 在批准前不会被 LocalSkills 加载；构图策略经验保存在默认工作区归属目录下的 SQLite 中。构图经验更新不会自动改写内置 SKILL.md，也不会自动晋升为已批准的用户技能。

流程技能与构图策略分别解决“任务怎样执行”和“当前数据采用什么图模式”。例如，graph_construction 先核对当前附件所属的数据集，再检查构图状态；已就绪时加载已有图，需要修改时先预览。进入构图子智能体后，选择路径物化还是属性实体化，则由当前输入结构、查询评测查询集和策略证据决定。外层步骤正确不等于内层候选有效，内层优化成功也不代表用户已经批准后续编辑。

当前 Agent Runtime 使用 Agno LocalSkills 加载内置技能与默认工作区的活动技能。候选目录不参加加载；批准操作将候选移入活动技能目录。构图子智能体只使用本次保留的数据源、构图专用工具和当前工作区的领域经验，不接收主会话消息、通用记忆或文件系统工具。

- [构图实验：Amazon、AVS、MAG、StackExchange 与 PK/FK 基线对比](/zh/experiments/#graph-construction)

## 构图与进化闭环

![自进化构图：方案比较与经验复用](/diagrams/evolution-zh.svg)

圆环表示本轮构图的候选迭代：依据领域、查询热点与已有经验选择策略，在当前图副本上构造候选，经过独立验收后接受或拒绝，再根据反馈选择下一候选。接受会更新当前保留图，拒绝则保留原图继续探索。外侧虚线表示跨任务复用：结束探索并通过最终复验后，才保存有效策略证据，供后续构图调整候选优先级。

本节的固定评测查询集、独立 SQL 参考执行器、候选优化和领域经验适用于 SQL、CSV，以及按表格或逐行记录导入的 TXT。只有 txt_mode=entities 使用另一条路径：分块调用 LLM、核对关系两端是否出现在同一原文分块中、去重建图，再校验 construction_graph.v1 数据格式。该模式完成不代表关系含义已通过 SQL 对照验证。

- 关系型输入依据主键与外键建立属性图 G₀，保留记录标识、类型、NULL 和关系方向；语义 TXT 则进入独立的分块抽取路径。
- 读取领域描述与数据结构，生成业务查询或接收指定 queries；冻结评测查询集并记录 SHA-256。
- 分别运行源数据参考执行器和图执行器，测量当前图的正确率、索引准备成本与查询耗时。
- 按领域先验、图结构和历史证据选择策略；使用当前真实表名、字段和节点 ID 实例化候选。
- 在副本上变换，执行完整源数据完整性检查和全部冻结查询；采纳符合阈值的改进，否则保留当前图。
- 记录候选结果；最终独立复验后保存图和 evolution-report.json，并将结构化证据用于后续构图。

默认 llm 模式由模型生成评测查询并规划候选，模型请求使用独立的构图配置。rules 模式使用确定性查询和代码生成的候选，适合离线验证；当前会尝试 materialize_join、generate_entity_table 和 transform_batch，不会遍历所有变换。LLM 出错不会自动降级为 rules 后宣称成功。

| 阶段 | 可调用工具 | 推进条件 |
| --- | --- | --- |
| 评测查询集生成 | inspect_schema, set_workload | 有效查询数量等于 query_count，保留种子计划已覆盖的类别；成功冻结一次后结束该阶段。 |
| 候选演化 | inspect_schema、evaluate_queries、8 个变换入口、finish | 评测查询集已冻结；变换反馈决定当前图是否更新。评测查询集修改工具不再提供。 |
| 最终验证 | 由工作进程执行 | 再次检查源数据完整性、评测查询集摘要与全部查询结果，模型不能绕过。 |

评测查询生成默认最多请求模型 3 次。模型必须调用 set_workload，返回说明文字不会冻结查询。字段缺失、计划重复或覆盖类别减少时，工作进程返回结构化错误，模型可在剩余预算内修正。候选优化默认最多 12 次模型响应，每次最多包含 10 个工具调用；调用按顺序执行，与查询生成阶段共享默认 24 次工具调用的总预算。

finish 是结束请求，不是发布指令。LLM 模式要求至少一个真正到达查询评估的候选，且当前图已通过全部冻结查询，才接受 finish；响应中的 final_validation_pending=true 表示最终验证仍未执行。达到演化轮数上限也会进入最终验证：如果至少评估过一个候选且保留图通过，任务仍可成功；如果始终只有无效调用，则以 no_evolution_candidate_evaluated 失败。

```json
{
  "tool": "finish",
  "arguments": {
    "reason": "All frozen queries pass; no further candidate is justified."
  }
}
```

## 源数据预览与字段说明

上传 SQL、CSV 或 TXT 后，构图面板即可读取原始数据预览，无需先运行构图。构图完成后仍可展开查看。SQL 和 CSV 可切换表，查看字段、声明类型、主外键、字段含义及示例行；TXT 展示原文开头的内容。读取预览不会启动构图，也不会修改已保存的图或会话活动图。

| 含义来源 | 如何理解 |
| --- | --- |
| 注释 · comment | 来自可识别的 SQL 字段注释，优先于其他说明。并非所有数据库方言的注释语法都能读取。 |
| 结构声明 · schema | 解释声明的主键、外键及引用目标，属于结构事实，不等于完整业务定义。 |
| 名称推测 · inferred | 仅为常见字段名提供需核实的提示，例如 name、status、quantity、DisplayName。不会据此编造币种、单位、统计口径或状态值映射。 |
| 未提供 · unavailable | 没有可用注释、键声明或受支持的名称提示时，明确显示未提供；需查阅数据字典或业务说明。 |

| 边界 | 当前限制 |
| --- | --- |
| 样本 | 每表最多 5 行，每个单元格最多展示 300 个字符；TXT 摘录最多 4,000 个字符。 |
| 源文件与响应 | SQL / CSV 预览限制源文件为 32 MiB；TXT 仅读取有界前缀，不因整个文件超过 32 MiB 拒绝摘录。响应上限为 4 MiB UTF-8，最多 128 张表、每表 256 列；CSV 表组还检查解压后的总大小。 |
| 行数 | 可确定时显示行数；大型 TXT 摘录或只读 SQLite 样本可能没有总行数。样本不是随机抽样，也不是完整数据统计。 |
| 独立预算 | SQL 预览另有解析时间、token 数和单语句大小限制；SQLite 读取另限时间与单值大小。full_scale 构图配置不会放宽预览限制。 |

SQL 文本预览把 CREATE TABLE 和 INSERT ... VALUES 作为数据解析，不执行上传的 SQL；无法展示的语句或值会产生警告或占位内容。SQLite 镜像只读访问，虚拟表跳过，生成列等不受支持的结构会被拒绝。CSV 可随面板所选分隔符重新预览；TXT 始终显示原文摘录。大整数为避免浏览器精度丢失可能以字符串返回，二进制值不会原样显示。

> **预览不等于构图验收**
> 预览读取的是保留的上传内容及 CSV 表组结构，不会套用尚未提交的 options.tables 等构图改动。看到样本不保证整个文件可以构图；预览失败也不等于构图一定失败。构图仍会独立检查完整源数据、配置、资源预算及最终结果。

```http
GET /api/personal-datasets/{dataset_id}/construction/source-preview
GET /api/projects/{project_id}/datasets/{dataset_id}/construction/source-preview

# Optional CSV delimiter, URL-encoded when necessary
GET /api/personal-datasets/{dataset_id}/construction/source-preview?delimiter=%09
```

## 输入与关系语义

| 输入 | 支持与约束 |
| --- | --- |
| CSV | UTF-8 / BOM、表头、严格列数；单元格保留为字符串。 |
| SQL | SQLite 兼容普通 DDL、字面值 INSERT、支持的 dump 事务标记，或 .sql 扩展名的 SQLite 镜像。 |
| TXT | UTF-8 / BOM；auto 自动识别严格制表符表格否则逐行记录，lines 强制逐行记录，entities 通过 LLM 识别实体和关系。 |

- 每行成为节点；可靠 PK 决定稳定标识，完整复合键参与编码。
- FK 从子表行连接到实际引用的父表行；相同列名不会自动产生关系。
- 没有可靠 PK 时使用行位置，并记录警告；悬空引用不会创建虚假节点。
- SQL 不连接生产数据库，也不运行任意数据库脚本；不支持的视图、触发器等需预先转成普通表。

普通文本可以是连续段落，也可以一行描述一个或多个事实。entities 不要求表头，也不要求每行恰好对应一条边；必须设置 mode=llm，不能同时设置 delimiter、primary_key、foreign_keys 或 tables。系统按原文顺序将全文切成带有限重叠的分块，发送给构图模型。每条保留关系的两个端点都必须出现在同一原文分块中，否则丢弃该候选。如果某个分块在重试后仍未返回合法结果或明确的“无关系”结果，或者调用失败，整个任务失败，不保存不完整的图。这些检查不能证明模型对关系的解释正确，使用前仍需核对原文。

```text
张三就职于星河公司。
李四向张三汇报。
星河公司位于合肥。
```

SQL 入口按 SQLite 文件头识别数据库镜像，其余内容按 UTF-8 SQL 文本解析。默认 bounded 配置将镜像或 SQL 文本导入内存 SQLite；显式 full_scale 配置下的 SQLite 镜像改为只读文件访问。系统从 sqlite_master、table_xinfo 和 foreign_key_list 读取表、列、主键顺序及外键分组。文本只接受普通表和索引定义、字面值 INSERT 及受支持的导出事务标记；SELECT、WITH、UPDATE、RETURNING 等入口会被拒绝。镜像也检查视图、触发器、虚拟表和生成列，不能绕过构图输入约束。

关系比较不仅取决于可见文本。SQL 入口保存列声明类型及 BINARY、NOCASE、RTRIM 比较规则，构建外键对应关系时把父列放在等式左侧，由 SQLite 使用父列的类型亲和性与排序规则。比如父键声明 TEXT COLLATE NOCASE 时，大小写不同的引用可能对应同一父记录；不能在图侧改成任意字符串比较。SQL 标识符可按 SQLite 的 ASCII 大小写规则解析，数据单元格本身不会统一转为小写。

CSV 严格校验表头和列数，保留字符串值：0012 不会自动变成数字，空串也不会统一变成 NULL。CSV 外键中的空串表示缺少引用；SQL 空串则是实际文本值，可能匹配父表中的空串。直接上传单个 CSV 时，API 可通过 options.table_name、primary_key、foreign_keys 和内联 tables 补充结构；内联表只接收名称、列、行和键定义等数据，不读取外部文件路径。column_types 只是说明性标签，不会把 CSV 字符串转换为数值。工作台配置后上传的 CSV 表组使用随文件保存的结构定义，详见多表输入。

单元格必须是有限的 JSON 标量：字符串、数值、布尔值或 NULL。BLOB、嵌套数组、对象与非有限浮点数需要在导入前转换。CSV 的 amount="120.0" 仍是字符串，不能直接作为 SUM/AVG 的数值列；需要数值聚合时，应先准备具有明确数值类型的 SQL 数据，或使用数值标量的内联表。

以下说明性 CSV 配置假设上传表头为 order_id,customer_id,amount，并以内联表提供 customers，用于说明键声明与类型边界。

```json
{
  "table_name": "orders",
  "primary_key": [
    "order_id"
  ],
  "foreign_keys": [
    {
      "columns": [
        "customer_id"
      ],
      "ref_table": "customers",
      "ref_columns": [
        "id"
      ]
    }
  ],
  "tables": [
    {
      "name": "customers",
      "columns": [
        "id",
        "name"
      ],
      "rows": [
        {
          "id": "C001",
          "name": "Alice"
        }
      ],
      "primary_key": [
        "id"
      ]
    }
  ]
}
```

行标识由表名、主键模式和完整键值列表生成 SHA-256 派生标识。复合主键不能只取第一列；调换源行顺序不会改变有主键记录的身份。无可靠主键时使用表名与行位置，因此重排行会改变身份，报告会给出警告。CSV 未显式提供主键时，只检查 id、表名_id、单数表名_id 这类明确名称并验证非空唯一，不会仅凭某一列偶然唯一就把它认作主键。

```text
Keyed row:
row_id = "row_" + SHA256(JSON([table, "pk", [key_1, key_2, ...]]))[:40]

Keyless row:
row_id = "row_" + SHA256(JSON([table, "position", row_index]))[:40]

JSON is canonicalized; complete composite keys participate in identity.
```

构建初图时，所有源行先生成节点，再按声明外键的实际匹配添加边。外键目标的非空键必须唯一；重复目标会导致导入失败。含 NULL 的复合外键不产生关系，非空但找不到目标的悬空外键保留原行并记录警告。列类型与比较规则放在该表首条行记录的 relational_schema 中；后续图模式变换必须继续保留这份语义，而不是只保留业务值。

TXT 默认 auto 只自动识别列数一致、至少有一行数据的制表符表格；否则每一行成为 text 字段中的一条记录，包括空行。lines 强制逐行导入。显式 delimiter 可将 TXT 按指定分隔符作为严格表格读取，但不能和 lines 或 entities 同时使用。逐行导入不会凭空识别实体关系；需要语义关系时，在工作台明确选择“实体与关系”模式。

## 多表 CSV 与键定义

工作台上传一个或多个 CSV 时，会打开表结构配置窗口。每个文件对应一张表；检查表名、表头、主键和外键后再上传。窗口按字段名提出键建议，建议不等于已验证的数据关系：仍需确认业务含义，构图时还会检查键的唯一性与实际引用。当前窗口按逗号读取 CSV 表头；不同分隔符的数据应先转换，或使用支持 delimiter 的 API 输入。

上传接口接收 files 与 csv_schema。csv_schema 使用 version="csv_bundle.v1"，tables 中逐一声明 file_name、name、columns、primary_key 和 foreign_keys。服务器将文件和定义保存为一个数据集，保留原始 CSV 内容。它不是任意 ZIP 上传入口，也不能在一次多文件请求中混合 CSV、SQL 和 TXT。表组至少一张、最多 128 张；文件名和表名必须满足唯一性要求。

构图使用已保存的表组定义，拒绝再用 options.tables、primary_key 或 foreign_keys 覆盖它。SQL 多表数据则由同一个 SQL 文件中的表与外键定义提供。直接单 CSV 的内联 tables 是另一种 API 输入方式，不能与已保存的表组混为一谈。

- [查看上传格式与数据集复用](/zh/datasets/#formats)

## 固定评测查询集与 DSL

set_workload 仅在查询生成阶段接收一次 queries，数量必须等于 query_count。期望答案由源数据独立参考执行器计算，模型不提交答案。冻结后不得修改问题或计划来提高得分；同一计划分别编译为 SQL 并交由 GraphExecutor 在当前图上执行。

| DSL 字段 | 语义 |
| --- | --- |
| root / joins | 根别名固定 t0；沿已声明 FK 正向或反向连接，支持 inner / left，最多 3 跳。 |
| filters | 限定列名，例如 t0.amount。比较支持 eq、ne、lt、le、gt、ge、in、contains、is_null、not_null。 |
| projection / group_by | 非聚合投影必须符合分组约束；结果保留重复记录。 |
| aggregations | count、sum、min、max、avg；sum / avg 只接受数值列。空组 count 为 0，其余聚合为 null。 |
| order_by / limit | limit 要求确定性排序，order_by 覆盖输出别名；校验排序和同分组语义。 |

GraphExecutor 只读取当前图，从记录节点、桥接边、属性实体和迁移字段恢复关系行。它不能读取源表或参考执行器答案。source_integrity_errors 还遍历所有源记录和声明 FK，校验记录标识、字段、关系与列语义，覆盖评测查询集没有访问的数据。

查询覆盖包含关系遍历、可选关系、多跳、值筛选、NULL、聚合、分组与排序。15/15 表示该固定评测查询集的全部查询通过；它不是任意自然语言问题的正确率承诺。

生成器先按表建立查询池，而不是让一张宽表占满整个测试集。每张表可贡献记录计数、明细、属性查找、排序、分组、空值检查等计划；status、category 等业务类别和 amount、price 等指标优先于纯标识符。主键或外键可以用于精确查找，但不会被当作金额去求和，也不会优先作为业务类别分组。筛选常量来自该列的实际样本，避免凭空构造永远无结果的测试条件。

关系查询沿源数据的外键匹配索引展开，最多三步。生成阶段避免立即折返同一外键和反复使用同一有向关系；有实际可达行时才加入多跳计数或路径明细。LEFT JOIN 另保留缺少关联的记录。选择最终集合时先均衡根表覆盖，再轮换查询类别；最多生成 2,000 个计划。如果无法凑齐 query_count 个不同的可执行计划，会明确失败，不用改写问题文字或重复计划补足数量。

评测查询集有三个来源。options.queries 直接提供固定计划；rules 模式使用生成器的结果；LLM 模式把合法种子计划、领域描述、输入结构、样本和必需覆盖类别交给模型，再由 set_workload 校验。覆盖约束是相对于当前种子计划，不要求无外键数据凭空出现关系查询。显式提供的 queries 仍进行结构与语义验证，但不执行种子覆盖类别的补齐要求。

| 无效计划 | 拒绝原因 |
| --- | --- |
| 只更换 id 或问题文字，执行内容相同 | 语义指纹相同，不能贡献新的查询覆盖。 |
| is_null 同时携带 value | 空值判断不接收比较值；用 {column, op}。 |
| limit=10，但只排序部分输出列 | 必须排序全部输出别名，否则截取结果不确定。 |
| 用任意等值列代替未声明 FK | joins 必须对应输入结构中的完整外键定义。 |
| SQL TEXT 列与浮点数直接比较 | 不支持隐式 float→TEXT 比较；提供明确文本常量。 |

冻结前会补齐空数组和 limit:null，对计划做语义去重并复制为规范 JSON；冻结后保存 workload_sha256，每次候选开始及最终返回前再次检查。下面的 COUNT 查询是说明性的可执行计划，可放入 queries 数组并设置 query_count=1；空值检查只统计缺失 amount，不把 NULL 当作零。

```json
{
  "id": "missing_amount",
  "question": "How many orders have no amount?",
  "root": {
    "table": "orders",
    "alias": "t0"
  },
  "joins": [],
  "filters": [
    {
      "column": "t0.amount",
      "op": "is_null"
    }
  ],
  "projection": [],
  "group_by": [],
  "aggregations": [
    {
      "function": "count",
      "column": "*",
      "as": "missing_count"
    }
  ],
  "order_by": [],
  "limit": null
}
```

独立参考执行器对内存 SQL 数据复制原始连接，保留数据定义和比较规则；对于 full_scale 的只读 SQLite 文件，则核对文件身份后打开独立只读连接。CSV 与内联数据使用无额外 SQLite 类型转换的临时表，保留原有标量类型。同一 DSL 通过限定标识符与参数绑定编译为 SQL，筛选值不会拼成可执行语句。图侧独立执行同一逻辑，两侧只在比较结果时汇合。

filters、projection、group_by 和聚合输入的 column 都引用源列，写成 t0.status、t1.amount 等 alias.column 形式。聚合产生的 cnt 等输出别名只能用于 order_by，不能放进 filters 充当源字段；当前 DSL 没有 HAVING。COUNT(*) 可用 *，但其他聚合需要有效列。若问题依赖聚合后筛选，不能靠把 cnt 改成某个源字段来冒充相同语义。

JOIN 的方向相对于外键声明。若 orders.customer_id → customers.id，forward 从订单别名进入 customers，reverse 从客户别名进入 orders；反向遍历时仍保留 foreign_key.table=orders、ref_table=customers，不能交换子表与父表。from 是已经存在的表别名，table 是本步新加入的表，alias 为它分配新的别名。两种方向都只能使用已声明的完整外键。

## 构图技能与变换工具

私有构图子智能体跨阶段共有 12 个不同工具名：4 个读取或控制工具、7 个单步变换入口，以及 transform_batch 组合入口。optimize_graph 内含 6 类操作，单步入口合计覆盖 12 类变换策略；组合入口复用这些操作，不是第 13 类独立策略。这套工具与主智能体的公共 MCP 目录分开。

| 控制工具 | 参数 | 作用 |
| --- | --- | --- |
| inspect_schema | {} | 读取源数据结构、当前图数据结构、样本与真实节点/边 ID。 |
| set_workload | queries[] | 生成阶段冻结一次评测查询集，不接受期望答案。 |
| evaluate_queries | {} | 返回当前图的逐题验证结果、通过数、错误和计时。 |
| finish | reason | 请求最终验收；LLM 模式要求至少一个有效候选到达参考执行器，且当前查询全部通过。 |

| 变换入口 | 必需参数 | 图模式变化与约束 |
| --- | --- | --- |
| connect_columns | table, columns[], ref_table, ref_columns[] | 按列值连接；已声明 FK 遵循源关系语义，其余关系标记 column_join。可选 edge_type。 |
| materialize_join | path[] | 物化 2–3 步连续 FK 路径；保留中间 node_ids 与路径重数。每步含 table、columns、ref_table、ref_columns、direction。可选 edge_type。 |
| generate_entity_table | table, column, node_type | 去重属性值成为共享实体，保留源值；可选 edge_type，节点类型不能覆盖源表。 |
| unfold_multi_category | table, column, node_type | 拆分多值类别，关系保留 position，源单元格不变；可选 edge_type、delimiter。 |
| add_primary_key | table, columns[] | 候选键须非空且唯一；记录 identity_key，保留原始数据定义（DDL） 与稳定行 ID。 |
| remove_primary_key | table | 删除提出的 identity_key 约束，保留源主键和记录标识。 |
| optimize_graph | operations[] | 执行下面 6 类结构变换；所有候选仍通过同一完整性与查询验收。 |
| transform_batch | steps[] | 组合 1–6 个单步变换，整体作为一个候选验收；禁止嵌套，结构或属性变换必须排在 materialize_join 之前。 |

| op | 必需字段（另含 op） | 验收语义 |
| --- | --- | --- |
| collapse_node | node_id, edge_type | 仅折叠恰好两条当前 FK 且无入向 FK 的桥接行；行数据及重数保存在边上。 |
| node_property_to_edge | node_id, property, edge_ids | 把业务字段移到当前关联边；可选 target_property、remove_from_node。 |
| extract_property_node | node_ids, property, node_type, edge_type | 共享实体保存 value，边标记 extracted_attribute；可选 remove_from_node。 |
| delete | target, ids | 可选 property、cascade；不能损失任何源记录或声明关系。 |
| reverse_edges | edge_ids | 反转指定实际边；源 FK 的方向变化受关系完整性约束。 |
| rename_type | target, old_type, new_type | 重命名业务节点/边类型，保留身份和语义。 |

property 指 properties.data 内的业务字段，例如 status。data、table、row_index 等内部字段不能当作普通属性移动。非路径变换会使旧路径快捷边失效并先移除它们；重复图版本与相同参数的候选会被拒绝。

图模式可以改变，逻辑源行必须始终能从当前图恢复。resolve_rows 首先核对节点和边的 ID、真实端点以及 table、row_index、data 字段，然后读取三类当前存储：行节点自身的数据、承载桥接记录的行边、以及通过属性关联边或迁移字段记录指向的当前值。它返回独立副本供查询执行，不读历史图，也不从源表补回缺失字段。

| 当前图模式 | 恢复依据 | 失败示例 |
| --- | --- | --- |
| 行节点 | properties.table / row_index / data | 相同行标识重复，或 data 不是字段对象。 |
| 桥接行边 | 自身 data 与 row_endpoints 的完整外键角色 | 声明的 row_endpoints 不是该边的真实端点。 |
| 提取后的属性 | 源行 → 属性实体；extracted_attribute 指出原字段 | 关联到错误表的行、实体缺少 value，或值与当前行冲突。 |
| 迁移到边的字段 | relocated_attributes 标记 row_id、column、value | row_id 并非当前边的关联行，或多个位置存放冲突值。 |

例如把订单 status 提取为共享状态实体时，查询读的是该实体当前的 value。如果实体仍写 paid，而原节点上留下 pending，resolve_rows 会拒绝冲突，不能任选一份让测试通过。如果状态字段从节点删除、属性关系也被删除，完整性校验会报告源字段丢失。即使当前评测查询集没有筛选 status，也不能接受这种候选。

collapse_node 对源行有更严格条件：所属表不能被任何声明外键引用，当前行必须恰好连着两条属于该表的外键边，不能同时连着其他边，也不能折叠到自身。转换后的边仍保存原行数据与两个外键角色；重复购买分别成为不同的行边。这个约束针对桥接记录，不支持把任意高连接节点压成一条边。

materialize_join 也不只保存起点和终点。每条快捷边保留完整路径与中间节点序列；GraphExecutor 检查每一步是否对应当前外键邻接关系，再用路径计数核对该起点的快捷路径集合是否完整。缺少其中一条路径时，不启用这组不完整快捷边，而回到原始关系执行，避免把多跳连接的重复性悄悄压缩。

候选缓存绑定当前图版本，接受新图后才重新生成；失败候选不会改变当前图。重复检查使用“当前图内容＋工具名＋参数”的摘要，因此同一方案在同一图上不会反复消耗查询评估预算。非路径变换先移除旧快捷边，因为节点、字段或方向变化可能让旧路径失效；如需继续物化，应依据新图重新生成。

transform_batch 的每一步使用对应工具的原始参数结构：{tool, arguments}。只有完整组合后的图参与查询和成本验收，不要求各个中间状态先获得性能收益；任意一步失败则丢弃整个候选。连续路径物化可以保留不同快捷路径，但后续普通变换会使快捷路径失效，因此此类顺序直接被拒绝。

属性实体还可以成为实际查询入口。GraphExecutor 先根据当前图确认该表某列的全部非 NULL 行都被覆盖，且实体值与当前行值完全一致，再建立属性索引，用于 t0 根表筛选。覆盖不全、值已过期或拆词后的类别结构不能缩小扫描范围；执行器不会读取源表补全索引。报告用 physical_plan、source_rows_examined、attribute_index_hits 等字段记录是否使用该入口。

## 正确性优先的成本验收

设 A 为通过查询数，L 为每题多次执行耗时中位数之和，P 为索引准备耗时，R 为预期查询执行次数。成本把索引准备分摊到预期使用量上，避免只测单次查询而忽略预处理。

```text
Cost(G) = L(G) + P(G) / expected_query_runs
gain(round) = (Cost(current, round) - Cost(candidate, round)) / Cost(current, round)

performance_pass = all_rounds_correct
  AND median(gain) > min_speedup
  AND wins_over_threshold >= ceil(2 * comparison_rounds / 3)
  AND median_gain_when_current_first > min_speedup
  AND median_gain_when_candidate_first > min_speedup

accept = source_integrity_ok AND graph_growth_within_limit AND (
  A(candidate) > A(current)
  OR (A(candidate) = A(current) = query_count AND performance_pass)
)
```

默认 expected_query_runs=100、min_speedup=0.05、max_graph_growth=3.0。双方全部查询正确时，不直接拿候选与旧基线计时比较，而是执行 comparison_rounds 组成对测量，交替先测当前图或候选图。每组都必须保持查询正确，收益中位数及两种执行顺序各自的收益中位数都要严格超过阈值，且至少三分之二组超过阈值。最终还会独立复验保留下来的图。

运行报告记录源数据完整性错误、逐题差异、资源超限和 no_measured_improvement 等结果。最终图全部通过后，具备策略签名的有效候选才可写入 accepted、no_gain 或 unsafe 经验；no_change、graph_growth_budget_exceeded、comparison_query_failure 和无签名候选不写入策略观察。拒绝候选时继续保留当前图。

接受步骤在运行查询前设置多道检查。参数与图结构不合法时直接拒绝；候选与当前图相同时返回 no_change；节点与边总数超过初图规模乘以 max_graph_growth 时返回 graph_growth_budget_exceeded；完整源记录与外键核对失败时返回 source_fidelity_failed。只有通过这些检查，才运行完整冻结查询。source_fidelity_failed 是协议中的固定错误码，中文含义是源数据完整性检查失败。

正确率使用通过查询的数量，不用自然语言回答质量评分。即使一个候选更慢，只要比当前图通过更多查询，也能成为下一轮的当前图；这种中间接受不代表已经达到发布条件。只有双方全部查询正确时，才按 min_speedup 比较成本。最后一次验证会重新执行实际保留下来的图，避免把曾经通过的候选或模型的结束请求误当作最终结果。

每条查询先预热一次，再取 benchmark_repeats 个计时样本；每个样本自适应重复执行，目标至少运行 min_query_seconds，并受次数与总时间预算约束。报告将样本时间除以实际执行次数，再取中位数，各题相加得到 L。每轮索引准备单独计入 P，参考执行器耗时不计入图成本。结果预览最多 20 行，比较仍覆盖完整结果：无排序时保留重复行，有排序时检查次序和同分组。这里测量的是进程内 GraphExecutor，不是生产 NebulaGraph 的端到端延迟。

模型得到的逐题反馈包含是否正确、结果条数、错误与耗时，不包含 expected_preview 或 actual_preview 中的具体答案。完整答案预览保留在私有运行报告中。这样模型可以定位错误类型和慢查询，同时仍由执行器提供正确答案与最终判断。

history[].comparison 保留每轮顺序、双方成本、收益、超过阈值的次数及接受判据。逐题 timing_iterations、measured_query_runs、warmup_runs 解释实际执行次数，不能再把 benchmark_repeats 当作总查询调用数。materialized_path_hits 统计计时执行命中的物化路径，不是新增边数。

## 领域经验如何改变策略

领域画像支持 finance、academic、commerce、healthcare、social、general。自动识别基于领域描述、表名和列名；描述命中权重为数据结构的 2 倍，最高得分必须达到 3；当第二类也达到 3 且最高分不足它的 1.75 倍时归入 general。显式指定类别的 confidence 为 1。

DomainExperienceStore 继续使用 users/<user>/construction-experience/strategies.sqlite3，其中 <user> 为默认工作区归属。经验保存策略类型、结构计数、查询组合、正确性和成本收益；原始数据行、SQL、提示词、表列名及完整工具参数不进入可迁移经验。经验通过数据结构与逻辑行生成逻辑源摘要来去重；报告中的 source_sha256 单独记录原始文件字节摘要。

| 步骤 | 计算或规则 |
| --- | --- |
| 匹配上下文 | 比较对数化结构计数、查询族 Jaccard 与归一化查询组合；过滤不兼容上下文，最低相似度 0.35，默认最多返回 8 条。 |
| 结果奖励 | accepted：0.25 + 0.75 × gain；no_gain：−0.25 + 0.75 × min(gain, 0)；unsafe：−1。gain 截断到 [−1, 1]。 |
| 策略评分 | 先按相似度加权奖励，以 +2 平滑分母，再乘平均相似度，最后截断到 [−1, 1]。 |
| 证据置信度 | supported 需要 ≥3 个独立源、接受率 ≥2/3、无 unsafe 且平均收益为正；存在安全失败、no_gain 不少于 accepted 或评分非正时为 cautioned；其余为 candidate。 |
| 候选优先级 | 领域先验与当前结构得分，加上相似度加权经验。supported 的经验权重为 1，其余为 0.35；unsafe 证据另有 0.75 惩罚。 |

```text
policy_score = clip(
  (sum(reward_i * similarity_i) / (sum(similarity_i) + 2))
  * mean(similarity_i), -1, 1
)

priority = domain_and_structure_prior
         + evidence_weight * similarity * policy_score
         - safety_penalty
```

同一源、领域、策略与结构模式只保留最新证据，重复运行不会增加独立源数量。负反馈降低优先级而非永久禁用策略；相似结构也必须在新数据上重新验证。经验保留最多 2,048 条源模式证据与 256 个模式；写入回执保留最近 4,096 条及仍被证据引用的旧回执。

经验检索发生在评测查询集冻结并测量基线之后，因此上下文包含实际查询组合，而不只是“电商”这个领域标签。当前构图期间的候选反馈立即返回模型；持久策略库则只在最终图通过之后更新，供后续任务使用。重新执行相同输入会替换该源与模式的最新观察，而不会把重试次数当成新的来源数量。

策略摘要只识别可解释的结构特征。optimize_graph 若在同一批里混合多种 op，就不把整批效果归功于某一种策略；无法提取签名的候选仍可评估，只是不写可迁移证据。候选无变化、超过图增长上限也不记录策略观察。accepted 和 no_gain 必须附带全部查询通过的计数；不满足该条件的观察会归为 unsafe，收益归零。

经验服务是可选指导。experience_enabled=false 时不检索也不写入；没有配置存储实例时报告 no_store；读取失败记为 unavailable，写入失败记为 write_failed。只要当前图通过完整验证，经验库暂时不可用不会把一个有效构图结果变成失败。报告中的这些状态应与“经验为空”的冷启动区分。

候选最终排序还结合本轮实测热点：search_priority = measured_query_cost_share + 0.1 × priority。前一项是候选覆盖查询占当前总查询耗时的比例，后一项才是领域与经验优先级。transform_batch 当前没有独立策略签名，组合候选仍可验收并记入 history，但不会把整批收益归给单一策略并写入经验库。

## 发起构图与检查结果

应用中先上传个人数据集，再从数据集面板启动构图。主智能体的 graph_construction 技能检查状态，并在 ready 后调用 load_constructed_graph。首次 build 使用 Backend 数据集接口；公共 MCP 目录没有同名的 build 工具。

```bash
curl -X POST http://localhost:21108/api/personal-datasets \

# Replace DATASET_ID with dataset.dataset_id from the upload response.
curl -X POST http://localhost:21108/api/personal-datasets/DATASET_ID/construction/build \
  -H "Content-Type: application/json" \
  -d '{"options":{"mode":"rules","query_count":15}}'
```

排查自己的数据时，先核对节点与外键边数量，再查看 workload_coverage 是否对应预期业务，检查 history 中的候选原因，最后比较 experience 的检索和写入状态。若所有查询通过但 accepted_candidates=0，应读取 no_measured_improvement 与成本，而不是直接判断构图失败。本页的小型配置与查询片段用于说明机制。

## 构图状态与失败后处理

| 状态或响应 | 含义与下一步 |
| --- | --- |
| source_ready / not_built | 源文件已保存，尚无可加载的构图结果；先看样本与配置，再发起构图。 |
| building | 读取状态中的阶段、消息和进度；关闭页面或 HTTP 请求结束不会自动取消服务器任务。 |
| ready | 保存图已可读取。再次 build 会复用现有结果，不会用新 options 重新运行构图。需要比较另一组构图配置时，使用新的数据集。 |
| failed | 根据 failure_code、消息与报告定位问题，修正数据或配置后重试；失败不是可用图。 |
| construction_running_unconfirmed | HTTP 等待超时，服务器可能仍在运行。先查询状态，不要立即另开重复任务。 |
| construction_build_conflict / 429 | 同一数据集正在以不同配置构图，或并发容量已满。等待现有任务完成后再提交。 |

进度展示实际阶段和已有指标，不代表中间图已可发布。构图期间和恢复检查期间，图编辑会被拒绝；成功后再读取真实 revision 进行编辑。构图结果保存到数据集与加载到会话是两步，详见数据集与工作台操作说明。

- [从数据集进入分析](/zh/datasets/#workflow)
- [工作台操作](/zh/workspace/)

## 预览、提交与版本冲突

构图文档采用 construction_graph.v1。编辑前读取最新 graph 和 revision；preview 验证候选操作，commit 持久化同一组操作。请求携带 base_revision，防止用旧图覆盖新版本。

| 方法 | 个人数据集路径 | 用途 |
| --- | --- | --- |
| GET | /api/personal-datasets/{id}/construction | 能力与状态 |
| GET | /api/personal-datasets/{id}/construction/source-preview | 原始样本与字段说明，构图前即可读取 |
| GET | /api/personal-datasets/{id}/construction/schema | 已保存图的类型与字段统计，以及源表结构；不包含原始样本值 |
| POST | /api/personal-datasets/{id}/construction/build | 发起首次构图；已 ready 时复用现有图 |
| GET | /api/personal-datasets/{id}/construction/graph | 读取图与版本 |
| POST | /api/personal-datasets/{id}/construction/preview | { base_revision, operations } 验证编辑 |
| POST | /api/personal-datasets/{id}/construction/commit | 同一请求结构，保存新版本 |

自动构图的候选验收与保存后编辑是两个阶段。构图子智能体在隔离副本上探索，并以源数据完整性和冻结查询选择结果；保存图后的 preview/commit 是用户明确请求的版本化编辑流程。每次编辑先读取真实 ID 与 base_revision，预览成功后提交完全相同的操作。出现 revision_conflict 时必须重新读取和预览，不能只把请求中的版本号加一后重放。

提交成功后，数据集的图版本增加，但当前分析会话仍需要 load_constructed_graph 才会切换到该保存结果。由 get_graph_info 核对加载后的规模和状态，再执行分析工具。构图报告是当前构建过程的说明，不能作为后续手动编辑仍通过旧评测查询集的证明。

这三种“预览”用途不同：source-preview 查看上传内容；schema 展示已保存图的类型结构和字段统计；POST preview 验证版本化编辑的影响。个人与项目数据集均提供对应 construction 系列接口，项目路径前缀为 /api/projects/{project_id}/datasets/{id}，并按归属校验访问。

## 资源预算与运行报告

| 配置 | 默认值 | 说明 |
| --- | --- | --- |
| max_source_bytes | 32 MiB | bounded 配置下可提高至 64 MiB |
| max_source_rows | 200,000 | 最大行数；超限失败，不静默截断 |
| source_timeout_seconds | 10 | SQL 解析秒数；上限 60 |
| text_max_characters | 1,000,000 | entities 全文字符上限；可调范围 1–4,000,000 |
| text_chunk_tokens / text_overlap_tokens | 1,000 / 150 | 语义分块近似 token 数和重叠；重叠必须小于分块 |
| text_max_chunks / text_max_calls | 64 / 96 | 完整分块数和含重试的模型调用总上限 |
| GRAPH_CONSTRUCTION_QUERY_COUNT | 15 | 冻结查询数量 |
| GRAPH_CONSTRUCTION_MAX_ITERATIONS | 12 | 候选改进轮数 |
| GRAPH_CONSTRUCTION_MAX_SECONDS | 900 | 构图总时间预算（秒） |
| GRAPH_CONSTRUCTION_CONCURRENCY | 1 | 每个 Graph Engine 进程；每用户最多同时一个构图 |

关系构图默认还包括 max_tool_calls=24、benchmark_repeats=5、comparison_rounds=3、min_query_seconds=0.003、llm_timeout_seconds=120；bounded 配置下单次图查询最多 10 秒与 200,000 条中间记录。语义 TXT 每个分块最多尝试 3 次，并与整个任务共享 max_seconds；字符数、分块数、调用数或图规模超限会使整个构图失败。两条路径都不会把截断数据生成的图当作完整成功结果。

上述源解析默认值属于 resource_profile="bounded"。API/CLI 可显式选择 full_scale，将源文件、源行、SQL 导入时间上限分别提高到 8 GiB、10,000,000 行和 7,200 秒，并提高图规模及内部查询预算。它不是自动流式处理承诺：CSV 和文本仍会装入内存，只有 SQLite 镜像改为只读文件访问。HTTP 构图仍将 max_seconds 限在 1–3,600 秒，整体截止时间优先于较长的内部预算；CLI 的 full_scale 范围另行处理。部署资源和入口限制也可能先达到上限。

| 报告字段 | 内容 |
| --- | --- |
| config / llm_used / model | 执行参数、是否调用模型及模型身份。 |
| source_schema / domain_profile | 源结构、领域类别与置信度。 |
| queries / workload_sha256 / workload_coverage | 冻结的业务问题、DSL、评测查询集校验值及覆盖信息。 |
| baseline / final | 逐题正确性、错误、答案预览与时间测量。 |
| history | 候选工具参数、策略、接受状态、失败原因及当前/候选成本。 |
| experience | 匹配上下文、检索证据、候选优先级、写入结果与更新后评分。 |
| initial_size / final_size / performance / phase_seconds | 图规模、成本收益与各阶段耗时。 |

evolution-report.json 使用 tabular_evolution.v1 描述一次构图。上表中的固定评测查询、基线、候选历史与策略经验适用于关系构图路径；语义 TXT 使用 construction_kind=text_entities 和 semantic_extraction 记录分块抽取情况，不执行同一套 SQL 对照评测。图版本与报告由 Graph Engine 保存，领域经验按执行路径记录；用户另行选择何时把保存结果加载到分析会话。

| 构图选项 | 默认值 / 范围 | 用途 |
| --- | --- | --- |
| query_count | 15 / 1–100 | 冻结计划数量，显式 queries 长度必须匹配。 |
| max_iterations | 12 / 1–30 | LLM 演化响应轮数；rules 模式限制已尝试候选数。 |
| max_tool_calls | 24 / 1–100 | 评测查询集生成与演化共享的模型工具调用预算。 |
| workload_attempts | 3 / 1–5 | 模型冻结合法评测查询集的响应尝试次数。 |
| benchmark_repeats | 5 / 1–15 | 每条图查询的计时样本数；每个样本可能重复执行多次。 |
| sample_rows | 3 / 1–10 | 每表向构图模型提供的样本数；不改变前端源数据预览固定的 5 行上限。 |
| expected_query_runs | 100 / 1–100,000 | 摊销索引准备成本时的预期运行次数。 |
| llm_timeout_seconds | 120 / 1–600 | 单次模型请求上限，同时受总剩余时间限制。 |
| max_seconds | 900 / 1–3,600 | HTTP 构图入口的整个任务时间上限；环境可调整默认值。 |
| min_speedup | 0.05 / 0–0.9 | 全部正确时所需成本改善比例。 |
| max_graph_growth | 3.0 / 1–10 | 相对初始节点与边总数的最大倍数。 |
| experience_top_k | 8 / 1–32 | 最多检索的领域策略记录数。 |
| experience_enabled | true / boolean | 跨构图经验检索与写入开关。 |
| comparison_rounds | 3 / 2–9 | 当前图与候选图交替顺序的成对比较轮数。 |
| min_query_seconds | 0.003 / 0–0.1 | 每个计时样本的目标执行秒数；仍受实际次数和截止时间约束。 |

显式 options 优先于对应 GRAPH_CONSTRUCTION_<字段大写> 环境变量，最后使用默认值。query_count 等计数必须为真正的整数，布尔值不能冒充 1；超时和比例要求有限数值并落在允许区间。mode 只能为 llm 或 rules；domain_category 支持 auto 与六个领域类别。源文件大小、行数和 SQL 导入时间属于单独的源解析选项，不要把任意配置名称都推导成可用环境变量。

总时间预算通过上下文局部的 deadline 向下传递。嵌套解析、变换和查询使用更早的截止时间；长序列扫描在进入、每 1,024 项和结束时检查。单次模型请求的超时为 llm_timeout_seconds 与剩余总时间中的较小值；SQL 导入和单题执行也受自己的上限及总剩余时间双重约束。服务模式还有进程监督，用于处理无法在 Python 检查点及时返回的本地代码或网络调用。

| 产物 | 内容与使用方式 |
| --- | --- |
| graph.json | 通过最终验证的 directed、nodes、edges；图 JSON 本身不包含报告内容，CLI 不会额外加入 version 字段。 |
| evolution-report.json | tabular_evolution.v1；含阶段、冻结计划、基线、候选历史、最终验证、配置和经验状态。按阶段原子更新，可用于定位失败。 |
| strategies.sqlite3 | 默认工作区归属下的结构化策略经验。部署路径由通过范围检查的数据源目录确定，不由模型传入任意路径。 |
| 工作进程回执 | 包含 build_id、graph.json 文件名及文件 SHA-256，或 failure_code；监督进程据此核对本次产物，不把旧文件当作成功。 |

运行报告的 source_sha256 是输入文件字节摘要，workload_sha256 是规范查询集合摘要，final_graph_sha256 是规范图内容摘要；它们各自验证不同对象。跨构图来源去重另用逻辑源摘要，工作进程回执的 sha256 则针对实际 graph.json 文件字节。不要把这些字段互相替换，尤其不要用“文件摘要变化”推断出现了一个新的逻辑数据来源。

| 失败码或原因 | 诊断方向 |
| --- | --- |
| workload_generation_failed | 模型在尝试上限内未提交合法查询集合；检查 workload_validation_errors。 |
| tool_call_budget_exhausted | 评测查询集与演化工具调用总数超限；检查 tool_calls 与调用轨迹。 |
| no_evolution_candidate_evaluated | LLM 模式没有候选通过前置检查并到达查询评估。 |
| query_accuracy_target_not_met | 最终图未通过所有冻结计划；检查 final.results，不能发布为成功。 |
| construction_time_budget_exhausted | 总截止时间耗尽，也可能在嵌套步骤中检测到；缩小数据/查询规模或在支持范围内调整预算。 |

工作台展示构图状态、进度与保存结果，目前没有完整 evolution-report.json 的专用下载按钮。CLI 报告位于 --output 指定目录；服务部署的报告位于 Graph Engine 数据根目录下 users/<user_id>/datasets/<dataset_id>/construction/evolution-report.json。排查时由有权访问该部署的人员读取对应文件，不应把页面进度摘要当作完整报告。
