FEATURED · 精选文章

graphify 图查询实战指南:query / path / explain 三命令驱动的知识图谱问答、路径追溯与节点解释

发布时间 / 2026/9/8 20:19:12
来源 / 创域科博编辑部
栏目 / 资讯中心
graphify 图查询实战指南:query / path / explain 三命令驱动的知识图谱问答、路径追溯与节点解释 graphify 图查询实战指南query / path / explain 三命令驱动的知识图谱问答、路径追溯与节点解释【免费下载链接】graphifyTurn any codebase, with its docs, SQL schemas, configs, and PDFs, into a queryable knowledge graph. A /graphify skill for Claude Code, Cursor, Codex, and Gemini CLI: local deterministic AST parsing, every edge explained, no vector store.项目地址: https://gitcode.com/GitHub_Trending/graph/graphifygraphify 会把代码库连同文档、SQL 模式与配置解析成可查询的知识图谱产物为graphify-out/graph.json而本文面向的是图建好之后“怎么问”围绕/graphify query、/graphify path、/graphify explain三条查询链路讲解其双模式图遍历BFS/DFS、受约束的查询扩展constrained query expansion、答案写回save-result与工作记忆reflect机制。读完本文你将掌握在 Claude Code、Cursor、Codex 及 opencode 等 Agent 场景下直接基于已建图回答“X 连接了什么”“X 如何到达 Y”“X 是什么”三类问题并能用不超过 2000 token 的预算拿到可追溯、带源码位置引用的图证据。本文对应的规范出处是 opencode 技能skill的 query 参考文档 graphify__skills__opencode__references__query.md其模板源头是 tools/skillgen/fragments/references/query/default.md生成这类技能文件的入口在 tools/skillgen/gen.py而 opencode 技能的完整形态参见 graphify__skill-opencode.md。所有命令的底层实现都可回查 graphify/cli.py 与 graphify/serve.py。适用范围什么时候加载这份参考当用户针对一张“已存在的图”提问或显式运行/graphify path、/graphify explain时就应加载本参考。核心的 query stub在 tools/skillgen/fragments/query-stub/default.md会把完整的遍历流程指向这份文档。整条链路遵循同一原则优先使用graphify query/graphify path/graphify explainCLI安装后可用CLI 不可用时回退到内联 NetworkX 遍历加载graphify-out/graph.json用networkx.readwrite.json_graph.node_link_graph还原成图对象后自行搜索。无论走哪条路径问题的答案都只应来自图本身的内容——节点标签、边的 relation/confidence、source_location等字段禁止凭空“脑补”不存在的边或节点。两种遍历模式先想清楚你在问哪类问题模式参数适用场景BFS默认无“X 连接了什么”——广度优先先看最近邻适合获取全局上下文DFS--dfs“X 如何到达 Y”——沿单条链/依赖路径深入追溯BFS 适合“俯瞰”从一个种子节点出发逐层展开得到的是包含多级邻居的子图快照DFS 适合“追线”沿一条路径尽可能往下钻直到命中目标或超出深度上限。在 graphify/cli.py 中--dfs只是切换传给底层_query_graph_text的mode参数见 graphify/serve.py真正决定语义的是内部_dfs/_bfs两个遍历函数。前置检查确认图已存在任何查询动作前先检查图产物是否就位。技能运行环境会在graphify-out/下写入.graphify_python记录应使用的 Python 解释器路径与graph.json图本体。检查脚本如下$(cat graphify-out/.graphify_python) -c from pathlib import Path if not Path(graphify-out/graph.json).exists(): print(ERROR: No graph found. Run /graphify path first to build the graph.) raise SystemExit(1) 如果检查失败应停下并明确告知用户先运行/graphify path建图再做查询。Step 0 —— 受约束的查询扩展遍历前必做为什么必须先做这一步因为 graphify 的queryCLI 对节点做的是“case-folded 子串 IDF”匹配二进制内部没有词干还原stemming、没有同义词、没有跨语言匹配下面给出的内联回退脚本也遵循同样的匹配规则。从源码看graphify/serve.py 的_compute_idf为查询词计算全图 IDF 权重并缓存在G.graph[_idf_cache]中打分路径依赖的就是词项在标签文本上的重合度与 IDF 加权的组合。因此如果用户问题用的是与图标签不同的语言或领域词汇例如用户说俄语 “обработчик”图里却是 “handler”用户说 “authentication”图里却是类名 “Guardian”字面匹配会返回 0 命中答案就会退化成噪声。解决办法是先对照图的真实词汇表做查询扩展且绝不凭空发明 token1. 先从节点标签中抽取 token 词汇表$(cat graphify-out/.graphify_python) -c import json, re from pathlib import Path data json.loads(Path(graphify-out/graph.json).read_text(encodingutf-8)) vocab set() for n in data[nodes]: for c in re.findall(r[^\W\d_], n.get(label,) or , re.UNICODE): parts re.findall(r[A-Z](?[A-Z][a-z])|[A-Z]?[a-z]|[A-Z], c) or [c] for p in parts: t p.lower() if 3 len(t) 30: vocab.add(t) Path(graphify-out/.vocab.txt).write_text(\n.join(sorted(vocab)), encodingutf-8) print(fvocab: {len(vocab)} tokens) 脚本把每个标签先按“非字母数字”切分再把每个词按 CamelCase 边界如AuthService→AuthService拆成小写 token过滤掉 3 字符以下与 30 字符以上的噪音项短 token 如api/jwt/ios会被保留见注释中 #1392最终写入graphify-out/.vocab.txt并打印词表规模。2. 阅读graphify-out/.vocab.txt针对用户问题从中挑选至多 12 个与查询意图语义匹配的 token。硬性约束只允许挑选词表文件中真实存在的 token不得发明若某个查询概念在词表中没有合理对应 token就跳过它——不能用训练记忆里的近似同义词顶替若没有任何词表 token 能匹配该问题就输出空列表并如实告知用户“该语料对这个问题没有相关词汇”不要伪造一次搜索跨语言翻译俄语 “аутентификация” → 仅当词表里存在时才查找auth、credential、token、security形态变化“handlers” → 仅当存在时映射到handler“todos” → 仅当存在时映射到todo。3. 在真正运行查询前先把选中的 token 显式打印给用户看使整个扩展过程可审计Query expanded to (from graph vocab, N tokens): [token1, token2, ...]若列表为空就直说并停止——不要继续遍历。这套“先看词表、后给证据”的流程正是要让 Agent 的输出可被复核用户能看到它基于哪些真实词汇去查而不是靠大模型记忆中的近义词去瞎蒙。Step 1 —— 遍历CLI 优先NetworkX 内联兜底把上一步选出的 token 用空格连接成扩展后的查询串作为下面的QUESTION不要用用户的原始提问原始问题仅保留给最后一步的save-result使用。CLI 可用时优先走 CLIgraphify query QUESTION # or: graphify query QUESTION --dfs --budget 3000graphify query支持的参数在 graphify/cli.py 中手工解析包括参数含义默认值QUESTION位置参数扩展后的查询串必填--dfs切到 DFS 深度优先遍历BFS--budget N或--budgetN输出 token 预算非整数会报错2000--context C或--contextC追加上下文过滤器可多次传入用于把遍历限制在某文件/目录范围无--graph path指定 graph.json 路径默认取graphify-out/graph.json默认路径底层实现里CLI 会把图故意按无向图加载见 graphify/cli.py 的注释BFS/DFS 需要同时探索种子节点的调用方callers与被调用方callees若强制DiGraphG.neighbors()只会返回后继节点会悄悄丢掉所有“无出边”种子的调用方结果。方向信息改为在每条边上用_src/_tgt标记保留渲染时仍然正确。CLI 查询在本版本内部以depth2驱动遍历并记录 querylog见 graphify/cli.py。若 CLI 不可用就加载graphify-out/graph.json内联执行遍历按此流程找出 1–3 个标签与扩展 token 最匹配的起始节点从每个起始节点执行相应模式的遍历读取子图——节点标签、边关系、confidence 标签、源码位置只依据图内含有的内容作答引用具体事实时给出source_location若图信息不足明确说明不要臆造边。内联回退脚本完整如下把QUESTION换成扩展后的查询串、MODE换成bfs/dfs、BUDGET换成 token 预算$(cat graphify-out/.graphify_python) -c import sys, json from networkx.readwrite import json_graph import networkx as nx from pathlib import Path data json.loads(Path(graphify-out/graph.json).read_text(encodingutf-8)) G json_graph.node_link_graph(data, edgeslinks) question QUESTION mode MODE # bfs or dfs terms [t.lower() for t in question.split() if len(t) 3] # match the vocab threshold; keeps api/jwt/ios (#1392) # Find best-matching start nodes scored [] for nid, ndata in G.nodes(dataTrue): label ndata.get(label, ).lower() score sum(1 for t in terms if t in label) if score 0: scored.append((score, nid)) scored.sort(reverseTrue) start_nodes [nid for _, nid in scored[:3]] if not start_nodes: print(No matching nodes found for query terms:, terms) sys.exit(0) subgraph_nodes set() subgraph_edges [] if mode dfs: # DFS: follow one path as deep as possible before backtracking. # Depth-limited to 6 to avoid traversing the whole graph. visited set() stack [(n, 0) for n in reversed(start_nodes)] while stack: node, depth stack.pop() if node in visited or depth 6: continue visited.add(node) subgraph_nodes.add(node) for neighbor in G.neighbors(node): if neighbor not in visited: stack.append((neighbor, depth 1)) subgraph_edges.append((node, neighbor)) else: # BFS: explore all neighbors layer by layer up to depth 3. frontier set(start_nodes) subgraph_nodes set(start_nodes) for _ in range(3): next_frontier set() for n in frontier: for neighbor in G.neighbors(n): if neighbor not in subgraph_nodes: next_frontier.add(neighbor) subgraph_edges.append((n, neighbor)) subgraph_nodes.update(next_frontier) frontier next_frontier # Token-budget aware output: rank by relevance, cut at budget (~4 chars/token) token_budget BUDGET # default 2000 char_budget token_budget * 4 # Score each node by term overlap for ranked output def relevance(nid): label G.nodes[nid].get(label, ).lower() return sum(1 for t in terms if t in label) ranked_nodes sorted(subgraph_nodes, keyrelevance, reverseTrue) lines [fTraversal: {mode.upper()} | Start: {[G.nodes[n].get(\label\,n) for n in start_nodes]} | {len(subgraph_nodes)} nodes] for nid in ranked_nodes: d G.nodes[nid] lines.append(f NODE {d.get(\label\, nid)} [src{d.get(\source_file\,\\)} loc{d.get(\source_location\,\\)}]) for u, v in subgraph_edges: if u in subgraph_nodes and v in subgraph_nodes: _raw G[u][v]; d next(iter(_raw.values()), {}) if isinstance(G, nx.MultiGraph) else _raw lines.append(f EDGE {G.nodes[u].get(\label\,u)} --{d.get(\relation\,\\)} [{d.get(\confidence\,\\)}]-- {G.nodes[v].get(\label\,v)}) output \n.join(lines) if len(output) char_budget: output output[:char_budget] f\n... (truncated at ~{token_budget} token budget - use --budget N for more) print(output) 这段脚本的内核值得拆解种子选择把问题按空白切词长度 ≥ 3 才纳入对每个节点统计其标签小写化后命中的词数取命中数最高的至多 3 个节点作为起点——与词汇表脚本的阈值一致DFS 分支显式栈实现深度上限 6防止把整张图都走穿先入栈的后出保证从每个起始节点按序深入BFS 分支按层推进最多扩 3 层每层只登记“第一次见到”的邻居并记录产生它的那条边避免环与重复预算感知输出按“约 4 字符 ≈ 1 token”把字符预算设为token_budget * 4节点按相关度降序输出超预算即截断并提示可换用--budget N行格式统一节点行带src与loc边行带--relation [confidence]--方便后续作答时逐条引用。作答时只依据上面的子图输出。回答写完后把结论写回图让它改善后续查询。建议把扩展出的 token 也写进--answer正文例如Expanded from original query via vocab: [tokens]. Then traversed...这样下一次--update抽取时能把这段扩展历史当成一个图节点保留下来$(cat graphify-out/.graphify_python) -m graphify save-result --question ORIGINAL_QUESTION --answer ANSWER --type query --nodes NODE1 NODE2把ORIGINAL_QUESTION换成用户的逐字原问题ANSWER换成完整答案含 token 扩展轨迹NODE1 NODE2换成你引用过的节点标签列表。这形成闭环下一次--update会把这段 QA 作为节点抽回图里。工作记忆让后续会话从本次会话学习save-result支持追加--outcome让未来会话借鉴这次的结论——修正场景可再加--correction the right answeruseful—— 所引用节点很好地回答了问题这些节点会升级为preferred sources即优先来源dead_end—— 问题/路径没有导向任何有价值的结果下次不必再推导一遍corrected—— 已保存的答案是错的--correction记录正确结论。--outcome的三个取值在 CLI 层被严格限定见 graphify/cli.py非法值直接报错。调用链上save-result实际走的是graphify.ingest.save_query_result导入于 graphify/cli.py结果默认落入graphify-out/memory目录。每次开始图工作前先刷新并阅读经验教训运行graphify reflect --if-stale廉价、确定性、无 LLM--if-stale会在LESSONS.md已经比所有输入新时变成空操作——例如 git hook 刚刷新过它然后阅读graphify-out/reflections/LESSONS.md。它列出preferred sources从这些开始、known dead ends跳过它们以及过往corrections。自己运行reflect能保证即使没有安装 git hook经验也是最新的若已安装 post-commit hook--if-stale让会话开始时的这次运行几乎零成本。graphify reflect的完整参数与save-result一同定义在 graphify/cli.py--memory-dir默认graphify-out/memory、--out默认graphify-out/reflections/LESSONS.md、--graph/--analysis/--labels默认从 graph.json 同目录推断、--half-life-days信号权重每 N 天减半默认 30、--min-corroboration需要多少次独立 useful 才能把某节点提升为 preferred默认 2、--if-stale。真正的聚合逻辑在 graphify/reflect.py 的reflect()中运行结束后会打印形如Reflected N memories (u useful, d dead ends, c corrected)的统计。用于 /graphify path找两个概念之间的最短路径在图中求两个命名概念之间的最短路径。CLI 可用时优先graphify path NODE_A NODE_BCLI 层面graphify path接受--graph path以及--directed/--undirected两个互斥方向开关实现见 graphify/cli.py。默认按有向图处理#2487方向信息存在于每张 graph.json 中尊重它--undirected显式退化为忽略方向搜索。它还有两道防线值得了解当两个查询都解析到同一节点时会报歧义此时“最短路径”是 0 跳几乎从不是调用方想要的对应 bug #828当头部与亚军得分差距小于 10% 时会打印模糊匹配警告。CLI 不可用时内联执行$(cat graphify-out/.graphify_python) -c import json, sys import networkx as nx from networkx.readwrite import json_graph from pathlib import Path data json.loads(Path(graphify-out/graph.json).read_text(encodingutf-8)) G json_graph.node_link_graph(data, edgeslinks) a_term NODE_A b_term NODE_B def find_node(term): term term.lower() scored sorted( [(sum(1 for w in term.split() if w in G.nodes[n].get(label,).lower()), n) for n in G.nodes()], reverseTrue ) return scored[0][1] if scored and scored[0][0] 0 else None src find_node(a_term) tgt find_node(b_term) if not src or not tgt: print(fCould not find nodes matching: {a_term!r} or {b_term!r}) sys.exit(0) try: path nx.shortest_path(G, src, tgt) print(fShortest path ({len(path)-1} hops):) for i, nid in enumerate(path): label G.nodes[nid].get(label, nid) if i len(path) - 1: _raw G[nid][path[i1]]; edge next(iter(_raw.values()), {}) if isinstance(G, nx.MultiGraph) else _raw rel edge.get(relation, ) conf edge.get(confidence, ) print(f {label} --{rel}-- [{conf}]) else: print(f {label}) except nx.NetworkXNoPath: print(fNo path found between {a_term!r} and {b_term!r}) except nx.NodeNotFound as e: print(fNode not found: {e}) 把NODE_A、NODE_B换成用户的真实概念名。然后用平实语言解释这条路径每一跳hop意味着什么、为什么它有意义。写完解释后同样写回$(cat graphify-out/.graphify_python) -m graphify save-result --question Path from NODE_A to NODE_B --answer ANSWER --type path_query --nodes NODE_A NODE_BCLI 版本在打印时比内联脚本更严格地遵循“只报真实存储的关系”对应 #2074同一对节点可能并行携带多条边例如既有references又有calls它会以多图方式加载并把所有 relation 用/连接如实呈现只有存储的边完全没有 relation 时才诚实回退为 “related”。打印同时给出每跳方向的箭头--/--并输出总跳数见 graphify/cli.py。用于 /graphify explain解释单个节点及其连接给出某个节点的平实语言解释——以及一切与它相连的内容。CLI 可用时优先graphify explain NODE_NAMECLI 不可用时内联执行$(cat graphify-out/.graphify_python) -c import json, sys import networkx as nx from networkx.readwrite import json_graph from pathlib import Path data json.loads(Path(graphify-out/graph.json).read_text(encodingutf-8)) G json_graph.node_link_graph(data, edgeslinks) term NODE_NAME term_lower term.lower() # Find best matching node scored sorted( [(sum(1 for w in term_lower.split() if w in G.nodes[n].get(label,).lower()), n) for n in G.nodes()], reverseTrue ) if not scored or scored[0][0] 0: print(fNo node matching {term!r}) sys.exit(0) nid scored[0][1] data_n G.nodes[nid] print(fNODE: {data_n.get(\label\, nid)}) print(f source: {data_n.get(\source_file\,\unknown\)}) print(f type: {data_n.get(\file_type\,\unknown\)}) print(f degree: {G.degree(nid)}) print() print(CONNECTIONS:) for neighbor in G.neighbors(nid): _raw G[nid][neighbor]; edge next(iter(_raw.values()), {}) if isinstance(G, nx.MultiGraph) else _raw nlabel G.nodes[neighbor].get(label, neighbor) rel edge.get(relation, ) conf edge.get(confidence, ) src_file G.nodes[neighbor].get(source_file, ) print(f --{rel}-- {nlabel} [{conf}] ({src_file})) 把NODE_NAME换成用户问到的概念。然后写一段 3–5 句的解释这个节点是什么、它连接了什么、为什么这些连接有意义把源码位置当作引用证据使用。写完解释后写回$(cat graphify-out/.graphify_python) -m graphify save-result --question Explain NODE_NAME --answer ANSWER --type explain --nodes NODE_NAMECLI 版graphify explain见 graphify/cli.py比内联脚本更严格节点查找分“source 精确 / 精确 / 前缀 / 子串”多个层级命中多文件同名节点时会打印全部候选并要求用仓库相对路径或完整节点 ID 重试杜绝在两个等价匹配之间瞎猜参见 graphify/serve.py 的分层匹配逻辑。它还会输出节点的 ID、类型、社区名、度并叠加“工作记忆覆盖层”——若graphify reflect已为某节点写下了preferred source/dead_end/corrected之类的经验标记会显示为一行Lesson: ...代码变更后还会打上[code changed since — re-verify]提醒复核。连接列表按邻居度从高到低排序、最多展示 20 条且每条都带上该边真正的 call/import/reference发生点在调用方文件中的那个位置而非常规定义行#BUG1让解释可以直接指向“这段关系发生在哪一行代码”。设计要点回顾回顾整条链路可以看到四个贯穿始终的设计原则字面匹配 → 必须受约束扩展匹配器只做 case-folded 子串 IDF 打分IDF 实现在 graphify/serve.py没有模糊语义所以每次查询前都要对照.vocab.txt选 token宁可说“无相关词汇”也不伪造搜索。答案只来自图CLI 与内联脚本都只输出图里真实存在的节点、relation、confidence 与source_location信息不足就明说。预算与深度受控默认 2000 token 输出预算、BFS 3 层/DFS 深度 6CLI 内部以 depth 2 驱动保证长尾大图上答案不会失控膨胀。问完就写回、开工先回顾save-result --outcome记录 useful/dead_end/correctedreflect --if-stale在会话开始廉价地刷新LESSONS.md让每次查询都成为下一次查询的“经验图谱”。如果你想继续深入可以在本仓库查看这些实现命令分发与参数解析在 graphify/cli.py查询打分与遍历在 graphify/serve.py经验聚合在 graphify/reflect.py技能生成流水线在 tools/skillgen/gen.py而这份参考最初的面向所有平台的通用模板保存在 tools/skillgen/fragments/references/query/default.md。各平台Claude、Codex、opencode、Kiro、Trae 等落地后的同名文件则在 graphify/skills/opencode/references/query.md 这样的技能目录下。【免费下载链接】graphifyTurn any codebase, with its docs, SQL schemas, configs, and PDFs, into a queryable knowledge graph. A /graphify skill for Claude Code, Cursor, Codex, and Gemini CLI: local deterministic AST parsing, every edge explained, no vector store.项目地址: https://gitcode.com/GitHub_Trending/graph/graphify创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻