Graph Engine

Graph Engine 内部设计

Graph Engine 为数据分析提供可计算的实体关系表示与结构化证据:解析数据、查询和算法后端、检索索引、图学习,以及并发任务中的版本一致性。

#模块分层

Graph Engine 是图驱动分析的表示与计算层。智能体提出具体证据需求,Graph Engine 按工具参数访问当前图或指定数据集,执行查询、算法或学习任务,再返回可定位的对象、数值、结构和状态。构图子智能体在需要时准备或优化这些表示;已有图上的读取、统计和学习不依赖每次重新执行构图。

Graph Engine 执行路径查看大图 ↗查看大图 ↗
Graph Engine 执行路径Graph Engine 执行路径

Graph Engine 对外提供 HTTP 与 MCP 两种适配器。Backend 通过 HTTP 访问数据与任务;智能体通过 MCP 调用图工具。graph_engine 是一级模块入口,graph_data_service 承载 HTTP、图数据与计算实现,mcp_service 负责工具协议适配。它们共享工具规格和资源读写策略。

层模块责任
协议与目录graph_engine/api.py · mcp.py · tool_catalog.py统一入口、工具 JSON Schema、执行策略元信息。
数据与版本graph_data_service/graph_data.py · session.py当前图、稳定节点/关系标识、版本切换和派生缓存。
执行调度graph_engine/tool_execution.py · graph_data_service/tool_scheduler.py资源隔离、读写冲突、排队与调度和生命周期。
构图子智能体graph_engine/construction/源数据解析、查询工作负载、候选转换、成本验收与领域经验。
图计算与 MLgraph_data_service/compute.py · isolated_compute.py · graph_ml/算法后端、隔离工作进程、训练/推理与模型文件。

HTTP 工具转发使用 POST /sessions/{session_id}/tools/{tool_name},请求体为 {"arguments": {...}}。GraphDataClient 注入用户、会话、Run、generation 和操作身份;这是服务间入口,浏览器使用 Backend 公共 API。构图工具另有数据集作用域适配。

#图结果如何成为分析证据

图结果需要同时保留数值或对象本身,以及解释它们所需的上下文。节点名称、关系方向、边权含义、图版本和返回范围共同决定一条结果能支持什么说明。HTTP 或 MCP 请求成功不等于业务成功;工具结果中的 success、错误、截断标记和作业终态都参与后续判断。

证据层次相关工具解释前需要核对
图与对象身份get_graph_info / search_node / get_node_info已加载状态、graph_version、真实名称与类型,以及候选是否歧义。
连接与结构shortest_path / graph_connectivity / node_centrality / community_detection方向、权重解释、图是否完整、所选算法及多重边投影规则。
模型结果train / infermodel_id、任务、特征来源、输入图或数据集、评估划分及完成状态。
呈现与报告visualize_subgraph / create_analysis_report展示节点是否截断,报告 source_results 是否来自已完成操作;视图和报告不改变原始证据含义。

版本与资源调度帮助同一批相关操作围绕正确的输入执行,但不能消除数据缺失或分析方法选择不当带来的限制。例如,有界查询范围内未找到路径不能证明全图不可达,社区分组不能直接证明业务类别,链接预测分数不能充当因果关系证据。后续各节分别说明这些数据与算法契约。

create_analysis_report 返回用于界面展示的 analysis_report.v2 报告结构,不会自动生成 Markdown 或 PDF 文件。需要下载文件时,由 Agent Runtime 工作区工具在 Ephemeral 中生成内容,再用 workspace_publish(path=..., name=...) 发布到 Derived;显式指定带扩展名的 name。这些工作区工具不属于 Graph Engine 的 72 项默认工具目录。

#图表示与关系标识

GraphData 是只读图容器。节点在计算侧使用连续整数索引,_id2node / _node2id 保存业务节点名称与整数索引的双向映射。边以 edge_index 的两行表示源和目标;节点、边的特征、类型与标签保存在独立数组中。写入流程生成新图对象或版本,而不是就地修改分析中的图。

字段形状 / 类型含义
edge_index[2, E] · int64关系的源与目标节点索引。
edge_weight[E] · float32 / float64可选边权;构图属性图保留 float64 数值。
node_feat / edge_feat[N, …] / [E, …]模型输入特征;与语义属性分离。
node_type / edge_type[N] / [E]异构节点与关系类型。
node_label / edge_label[N] / [E] or multi-column分类等监督任务的标签。
graph_metadata.construction_graphconstruction_graph.v1保留节点/边 ID、属性及构图语义。

属性图可以在相同端点之间保留多条关系。查询边时,端点对与唯一 edge_id 不等价:端点只确定一组候选关系,修改或定位具体关系应使用返回的 ID。通用 append 对已构建属性图返回 UNSUPPORTED_PROPERTY_GRAPH_APPEND,避免把不同关系和属性压缩成普通边表。

构造 GraphData 时先把 edge_index 转成 int64,并要求它是两行数组;节点名称与整数索引的映射必须成对提供且彼此一致。特征默认转成 float32,训练/验证/测试掩码转成布尔数组。node_feat 的第 i 行、node_label 的第 i 项及 id2node[i] 都指向同一节点,不能分别重排。边属性也必须和 edge_index 的列顺序对齐。

python
import numpy as np
from graph_data_service.graph_data import GraphData

# Three named nodes and three directed edges.
graph = GraphData(
    num_nodes=3,
    edge_index=np.array([[0, 0, 1], [1, 2, 2]], dtype=np.int64),
    id2node={0: "customer:10", 1: "order:100", 2: "region:1"},
    node2id={"customer:10": 0, "order:100": 1, "region:1": 2},
    edge_weight=np.array([2.0, 5.0, 1.0]),
    semantic_type="directed",
)
# Columns represent customer:10 -> order:100,
# customer:10 -> region:1, and order:100 -> region:1.

上例说明数组与节点名称如何对齐,不代表电商外键的默认方向。对源表构图时,记录节点与外键方向由构图模块决定;手工导入图则应由调用方明确边的业务含义。

图的业务属性与模型特征分开存储。properties.data 中的订单金额、字符串状态等不会自动变成 node_feat。单图学习缺少 node_feat 时使用归一化的 [入度, 出度, 总度, 1] 结构特征;这不等于模型读取了业务字段。图分类另使用约定数据集中的节点类别编号。构图图对象将原始属性图保存在 graph_metadata.construction_graph 中,转换、切换版本及查询时据此保留属性和关系标识。

#图的方向与边的存储方式

判断“是否有向”需要读取业务语义,不能只检查邻接数据是否对称。例如,两个账号互相关注是两条有向关系;一条无向关系也可能以正反两条记录保存。Graph Engine 用 semantic_type 表示图语义,用 storage_type 表示边记录的实际组织方式。

字段取值解释
semantic_typedirected / undirected / unknown分别表示有向、无向和缺少足够证据;显式设置优先于数据结构推断。
storage_typedirected / bidirectional / mixed表示单向存储、成对双向存储或混合存储;不是第二套业务关系类型。
num_edge_entries / num_unique_edges原始记录数 / 逻辑边数前者是 edge_index 列数,后者按方向语义及关系标识统计。
has_self_loops / has_duplicate_entries结构检查标志识别自环及重复条目。重复判定使用端点与关系键,不逐项比较权重、特征或属性;构图属性图使用关系 ID,普通图使用关系类型作为关系键。

方向解析依次考虑显式 semantic_type、元数据、预定义数据集规则及结构证据。对称结构在没有其他信息时仍可能是 unknown。Cora、CiteSeer、PubMed 使用预定义的无向语义。to_undirected 产生新图,并可能在数组中补齐反向记录;计算逻辑边数或节点度时应先去除这类存储副本。

text
edge_index = [[0, 1], [1, 0]]

semantic_type = "directed"   -> two directed edges
semantic_type = "undirected" -> one logical undirected edge
no semantic evidence        -> symmetry alone is ambiguous

节点度与邻居数也不同。无向图中,一个自环对节点度贡献 2,但只对应一个不同的邻居节点;有向图分别统计入度、出度和总度。多个关系可以连接同一个邻居,因此不能用去重后的邻居数量替代关系条数。

#图版本与缓存

会话中的加载、无向转换、子图提取和回滚改变当前图版本。get_graph_info 可读取 graph_version;list_graph_versions 列出版本,load_graph_version 切换已保存版本。构图数据集另有 revision:只有显式 load_constructed_graph 后,构图结果才成为会话活动图。

算法需要的 NetworkX 表示和实体检索索引是派生数据。按图版本复用缓存可避免同一图被每个工具重复转换;版本变化后必须使用对应的新表示。缓存重建属于图读取,不应发布图版本。大图第一次转换仍有与节点、边规模相关的成本。

版本按会话隔离,图数据存为 NPZ,graph_versions.json 记录清单。普通 save_snapshot 路径按 GRAPH_VERSION_HISTORY_LIMIT 裁剪历史,默认 50;受控 publish_snapshot 路径目前不执行该裁剪。切回旧版本后,新版本号仍在历史最大值之后递增。隔离工作进程写入暂存目录,Graph Engine 父进程检查 generation 与 graph_version 后原子发布清单,并记录 Run、Job 与 generation 回执,避免过期执行覆盖新的状态。

图对象、算法转换缓存与搜索索引均以版本为边界。GraphCompute 缓存 NetworkX 表示,EntitySearchIndex 保存创建时的 graph_version;版本不一致时重建。只读缓存的创建不会增加图版本号。请求应先读取实际 graph_version,再把该值记录在实验或分析结果中,避免把不同版本的指标直接比较。

构图修订号 revision、会话 graph_version 与运行 execution_generation 各自解决不同问题:revision 防止数据集编辑覆盖较新的修改;graph_version 选择算法输入;execution_generation 防止已停止的旧运行回写结果。只提供其中一个编号,不能替代其他两类校验。

#多重边与算法语义

算法关系投影规则原因
最短路径 / 接近中心性minimum_weight_per_pair平行边取最小代价,路径结果按节点序列区分。
最大流 / s-t 最小割sum_weights_per_pair同一方向平行边的容量累加。
PageRank / 特征向量 / Katzsum_weights_per_pair累积连接强度。
最小生成树minimum_weight_undirected按无向端点对保留最小权重。
Louvain / greedy modularitysum_weights_undirected合并无向连接强度。
未审查的多重图算法unsupported明确拒绝,避免隐式丢失关系语义。

边权必须是有限数值,布尔值和非数值不能作为合法权重。不同算法对权重的意义不同:路径使用代价,流/割使用容量,社区和部分中心性使用强度;调用前应先明确业务解释。

转换到 NetworkX 时,普通图沿用简单图表示;构图属性图若同一端点间存在不同关系标识,则使用 MultiGraph 或 MultiDiGraph。无向图同一业务关系的反向存储副本会去重。业务 edge_id 保存在属性中,NetworkX 的内部多重边键使用边索引,因此二者不能混用。

假设 A→B 有两条平行边,权重分别为 2 和 5。最短路径把它们解释为两个可选代价,投影后使用 2;最大流把它们解释为并行容量,合计为 7。这两种结果都遵循各自的算法定义。调用方必须先确定权重含义,再选择工具,不能把交易金额默认当作距离。

未声明多重图策略的方法返回 UNSUPPORTED_MULTIGRAPH_OPERATION。参数组合也参与检查:社区发现只批准了部分算法,子图提取中的 ego_graph 与 node_subset 可以保留关系,其余方法有各自限制。这个检查发生在算法执行前,避免转换时悄悄合并关系。

#工具依赖与并发控制

每个工具声明 reads、writes、barrier、background。资源还要加上用户/会话归属:graph、models、documents 等通常按会话隔离,datasets 与 construction:{dataset_id} 按用户或数据集隔离。RAW、WAR、WAW 冲突形成有向依赖,独立读操作可以并行;未知工具按 barrier 保守执行。

text
load_constructed_graph (writes graph)
  ├─ node_centrality       (reads graph)
  ├─ community_detection  (reads graph)
  └─ shortest_path        (reads graph)
       ↓ all finished
load_graph_version        (writes graph)

subgraph_extraction 默认写入新版本,只有 create_version=false 才按只读调度。前置工具失败或等待审批时,依赖项返回 TOOL_DEPENDENCY_BLOCKED。模型必须在得到真实结果后再传入依赖它的 ID 或 revision;依赖调度不会补造参数。

智能体侧的 tool_dag 调度同轮调用,Graph Engine 的 tool_scheduler 控制跨请求资源调度。后台任务提交后,资源租约持续到实际工作结束;仅返回 job_id 不能释放租约。状态查询和取消绕开图写锁等待,防止任务无法停止。

跨请求调度保留冲突请求的到达顺序。假设读取 R1 已进入执行,写入 W2 随后排队,新的读取 R3 不能绕过 W2;否则持续到来的读请求会使写请求长期无法执行。不同用户的资源互不冲突,同一用户的不同会话仍可能争用 datasets 或 construction:{dataset_id} 这类共享资源。

资源依赖和计算配额是两层控制。依赖检查决定是否会读取正在变化的数据;信号量决定最多同时运行多少普通计算或训练任务。即使两个训练任务只读同一张图,也可能因 GPU、随机数状态或用户计算配额而串行执行。

#计算进程与后台任务

NetworkX 算法的兼容路径使用有界 ProcessPool,其他同步处理器可走线程执行。受控执行在具备 execution host 与 Run 身份时使用可终止的隔离 WorkerProcess,维护停止确认与最终状态持久化。取消观察不等于计算停止;容量与租约在任务真正退出后回收。

配置默认值控制对象
AGENT_TOOL_MAX_CONCURRENCY4 (1–32)同轮就绪工具调用。
GRAPH_COMPUTE_CONCURRENCYmin(4, cpu_count)普通算法进程池。
GRAPH_COMPUTE_GLOBAL_CONCURRENCY默认跟随计算 worker 数全局普通计算并发限制。
GRAPH_COMPUTE_PER_USER_CONCURRENCYmin(4, worker 数)单用户普通计算并发限制。
GRAPH_HEAVY_PER_USER_CONCURRENCY1训练、推理等重任务。
GRAPH_HEAVY_GLOBAL_CONCURRENCYmax(1, min(2, worker 数))全局重任务并发限制。

get_job_status 返回作业状态与进度,普通作业完成结果位于 job.result;也可通过 get_job_result 读取结果。GraphMCPTools 在图工具返回后台句柄时等待最终结果,支持嵌套作业,并拒绝循环引用或缺失结果。监督学习的 infer 加载已训练模型执行前向计算,预训练的 infer 查询保存的节点向量。图分类数据绑定与当前单图绑定分开管理。

json
{
  "success": true,
  "job": {
    "job_id": "JOB_ID",
    "status": "done",
    "progress": 1,
    "result": {
      "success": true
    }
  },
  "progress_updates": []
}

此结构展示 get_job_status 的字段位置;最终业务结果位于 job.result。状态值和 result 的具体字段由实际任务决定,轮询可用 after_seq 增量读取进度。

#文档分块与上下文检索

文档检索由 LightweightRAG 管理,和图节点检索使用不同索引。upload_document 把会话文档写入文档目录,保存 doc_index.json 与 doc_embeddings.npy;retrieve_context 根据查询向量返回相关文本块。文档命中说明文本相关,不等于图中存在相应关系,分析结论仍需图工具提供证据。

默认分块长度是 512 个按空白划分的片段,相邻块重叠 128 个片段,步长为 384。这不是模型分词器的 token 数;没有空格的长中文段落不会按汉字数量切成 512 字的块。准备中文资料时应保留合理分段,并检查返回 chunks 的实际数量和内容长度。

嵌入提供方可以是本地 Sentence Transformers、远程嵌入接口或词法哈希。文档检索在词法模式下使用 384 维哈希向量;Python 哈希受进程影响,因此旧文本块会在当前进程重新编码,避免把不同进程的哈希空间混合。向量维度改变时,检索返回错误,需重建该会话文档索引。

受控文档写入通过 .rag_versions 保存候选目录,再由 rag_manifest.json 选择当前版本。这样读请求可以定位一套完整的文本索引和向量文件。返回结果中的 doc_id、chunk_id、offset 与 score 用于定位引用片段;offset 是块序号,不是原文字符偏移。

#存储与扩展边界

内存表示至少包含 [2,E] 的 int64 边索引,即约 16E 字节;普通 float32 边权另需 4E 字节,N 个节点、F 维 float32 特征另需 4NF 字节。这只是数组主体,不含字符串、Python 对象、图属性、NetworkX 对象、向量索引和工作进程副本。评估大图内存时,不能只按源文件大小估算。

结构化大图还可以走独立的分区快照路径:prepare_large_graph 流式准备已上传的边列表或三元组文件,run_large_graph_algorithm 在发布版本上提交持久化作业。该路径不会先创建完整 GraphData 或 NetworkX 图。运行前读取 get_large_graph_capabilities,核对算法的精确、近似或有界语义及资源限制;完成后获取摘要、预览和结果文件引用。分区版本与普通活动图表示有独立的读取入口,不能把大图预览当作已加载的完整会话图。

图服务当前通过 GRAPH_STORAGE_BACKEND 选择 local 或 nebula。NebulaGraph 模式需要显式启用连接;部分查询在数据库端执行,需要 NetworkX 的算法则先按能力和规模限制载入内存。默认载入上限为 50,000 个节点、200,000 条边,分别通过 NEBULA_MAX_MATERIALIZE_NODES 和 NEBULA_MAX_MATERIALIZE_EDGES 配置。materialized_scope、target_in_scope、complete_for_query 用于说明结果范围;有限跳数范围内未找到目标,不能直接解释为全图不可达。

Graph Engine 管理源文件、图版本、构图报告、领域经验和模型文件;Agent Runtime 管理消息、Run、Memory 和 Trace。图服务可以配置 NebulaGraph 存储,但不能假定所有算法都在数据库内计算:远端图查询与有界物化是不同路径。

iGraph技术文档