
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/graphify本篇技术指南以 graphify 项目中为 Trae以及 Claude Code、Cursor、Codex、Gemini CLI 等生成的 Agent 技能参考文档 graphify/skills/trae/references/query.md及其 skillgen 产物 tools/skillgen/expected/graphify__skills__trae__references__query.md为骨架完整讲解在图谱构建完成之后如何回答自然语言问题先做受限于图谱词表的查询扩展再执行 BFS/DFS 遍历、最短路径/graphify path与单节点解释/graphify explain最后通过save-result与reflect把答案沉淀成可复用的工作记忆。读完你将掌握一套从查询到回写、可审计且不会虚构边的完整图查询工作流并了解其底层实现位于 graphify/serve.py、graphify/cli.py 与 graphify/reflect.py 中的哪些位置。适用场景与文档定位该参考文档约定在以下时机被加载用户针对一个已存在的图谱提出自然语言问题例如X 是怎么工作的谁调用了 Y用户运行了/graphify path或/graphify explain。此时核心技能文件会把查询流程的完整实现委托给本文档query stub 指向此处。技能主入口文件 graphify/skill-trae.md 的## Usage块中给出了这些命令的用户视角/graphify query question # BFS traversal - broad context /graphify query question --dfs # DFS - trace a specific path /graphify query question --budget 1500 # cap answer at N tokens /graphify path AuthModule Database # shortest path between two concepts /graphify explain SwinTransformer # plain-language explanation of a node整个流程遵循两条统一约定优先使用graphifyCLI安装后执行graphify query/path/explainCLI 不可用时降级为内联 NetworkX 遍历读取graphify-out/graph.json用networkx.readwrite.json_graph.node_link_graph重建图并就地遍历。这保证了无论运行环境是否装有完整 CLIAgent 都能完成同样质量的查询。两种遍历模式BFS 与 DFS文档开篇就要求根据问题的形态选择遍历算法二者在graphify query中通过--dfs开关区分ModeFlagBest forBFS (default)(none)What is X connected to? - broad context, nearest neighbors firstDFS--dfsHow does X reach Y? - trace a specific chain or dependency path通俗地讲BFS默认回答扩散型问题——想知道某节点周边有哪些邻居、拿到广而浅的上下文时使用DFS回答追溯型问题——想知道一条具体调用链或依赖链上每一步时使用一路钻到底再回溯。前置检查确认图谱已构建任何遍历开始之前必须先确认graphify-out/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构建图谱——不要凭空回答。这里读取graphify-out/.graphify_python技能在首次运行步骤中写入的解释器路径来保证后续所有 Python 片段使用同一解释器环境。Step 0 —— 受限查询扩展遍历前必做为什么要做扩展字面匹配的固有缺陷文档明确声明graphify queryCLI 内部对节点的匹配方式是大小写折叠后的子串 IDF逆向文档频率加权——没有词干还原、没有同义词、没有跨语言匹配下方内联回退逻辑的行为与之完全一致。这一限制在源码中可以得到印证查询打分与种子选择实现在 graphify/serve.py其中 serve.py#L272-L291 的_query_terms()仅做分词与停用词过滤而 serve.py#L294-L297 定义的匹配层级是纯字符层面的加分项_EXACT_MATCH_BONUS 1000.0 _PREFIX_MATCH_BONUS 100.0 _SUBSTRING_MATCH_BONUS 1.0 _SOURCE_MATCH_BONUS 0.5可见它只比较字符形态精确/前缀/子串并不理解语义。因此当用户问题的用词与图谱节点标签不在同一语言或同一领域词汇表内时字面匹配会返回 0 命中用户说俄语 обработчик而图谱标签是英文 handler用户说 authentication而图谱里的概念名是 Guardian。结果就是答案坍缩成噪声。文档给出的解法不是引入猜词而是先针对真实图谱词表做一次可审计的查询扩展——绝不发明词元token。第 1 步从节点标签抽取词表$(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) 要点解析用re.findall(r[^\W\d_], ...)按 Unicode 规则切出单词再用驼峰/大写拆分正则把camelCase、PascalCase、HTTPClient等标识符拆成基础词元例如JSONParser→json、parser仅保留长度在 3~30 之间的小写词元长度过滤与下文遍历脚本中len(t) 3的阈值保持一致结果写入graphify-out/.vocab.txt并打印词表规模。第 2 步从词表中挑选最多 12 个语义匹配词元读取graphify-out/.vocab.txt后针对用户问题从中选出语义上匹配查询意图、且不超过 12 个的词元。硬性约束如下这是防幻觉的关键只能挑选词表中真实存在的词元禁止自造若某个查询概念在词表中找不到合理词元跳过它——绝不用训练记忆中的近义词顶替若没有任何词元匹配查询则输出空列表并明确告诉用户该语料对这个问题没有相关词汇不要伪造一次搜索跨语言翻译俄语 аутентификация → 仅在词表存在时才寻找auth、credential、token、security形态还原handlers仅当词表中存在handler时映射过去todos仅当存在todo时映射。第 3 步把扩展结果显式打印给用户在正式跑查询之前先输出审计信息使扩展过程可追溯Query expanded to (from graph vocab, N tokens): [token1, token2, ...]若列表为空就直说并停止——不要进入遍历阶段。Step 1 —— 遍历CLI 优先、内联回退用空格连接已选词元构成扩展后的查询串作为下文QUESTION的内容——注意不是用户的原始问题原始问题仅保留到最后的save-result用于归档。方式 A使用 CLIgraphify query QUESTION # or: graphify query QUESTION --dfs --budget 3000参数说明无 flag → BFS--dfs→ DFS 深度优先--budget N→ 限制回答的 token 预算默认 2000见下文内联脚本中char_budget token_budget * 4的实现。方式 BCLI 不可用时内联遍历文档要求按以下顺序处理找到标签与扩展词元最匹配的 1~3 个起始节点 → 从各起始节点执行相应遍历 → 阅读子图节点标签、边关系、置信度标签、源码位置→只用图中存在的内容作答引用具体事实时注明source_location→ 图谱信息不足就明说绝不虚构边。完整内联脚本$(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) 把脚本中的三个占位符替换掉后即可运行占位符替换为QUESTION扩展后的查询串MODEbfs或dfsBUDGETtoken 预算默认2000与--budget N一致实现细节值得注意起始节点选择按词元在标签中命中的次数打分取前 3命中为 0 则无起点直接退出DFS迭代式栈实现深度限制 6 层避免遍历整图BFS逐层扩散 3 层frontier 推进输出预算控制~4 字符/token的估算超预算截断并提示可用--budget N增加多图兼容通过next(iter(_raw.values()), {})统一处理nx.Graph与nx.MultiGraph两种形态保证重边场景下也能取到边的属性输出行同时携带source_file/source_location节点与relation/confidence边这些字段与图谱中 EXTRACTED/INFERRED/AMBIGUOUS 的置信标注体系对应是作答时可引用的证据。从源码角度看这套词元种子打分逻辑与serve.py中真正被 CLI 使用的_score_query/_score_nodes见 serve.py#L461-L472 附近的封装属于同一设计以词元命中为核心、子串匹配加权tests/bench_query_scoring.py 专门对查询评分路径做基准验证CLI 的查询端到端行为由 tests/test_query_cli.py 覆盖。作答要求拿到上述子图输出后作答遵守铁律只用图中内容引用事实要引用source_location信息不足要承认绝不凭记忆补齐图中不存在的边。回写与工作记忆让一次查询变成下一次的起点save-result把问答写回图谱答案写好之后要保存回图让未来的查询受益。做法是把扩展词元一并放进--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完整答案文本需包含扩展词元追踪信息NODE1 NODE2作答时引用的节点标签列表。这闭合了反馈环下一次--update会把该问答抽取成图谱中的一个节点使查询历史本身成为图的一部分。为问答标记 outcome供 reflect 聚合为让未来的会话能从这次经验中学习给save-result追加--outcome需要纠正时再加--correctionuseful— 引用的节点很好地回答了问题这些节点将有机会成为preferred sources首选来源dead_end— 这个问题/路径没有通向任何有价值的结果下次不要重复推导corrected— 之前保存的答案是错的--correction记录正确内容。会话开始reflect --if-stale 刷新经验在开始任何图查询工作前先刷新并阅读经验教训graphify reflect --if-stale然后阅读graphify-out/reflections/LESSONS.md。它会列出首选来源从此处开始、已知死胡同跳过它们与历史纠正。即使没有安装 git hook自己运行reflect也能保持经验文件为最新若 post-commit hook 已安装--if-stale保证本次会话开始时的运行几乎零成本当LESSONS.md比所有输入都新时该参数使命令成为空操作。reflect的底层实现在 graphify/reflect.py模块 docstringreflect.py#L1-L27说明了这套确定性工作记忆的设计——它读取save-result写入的 QA 记忆文档聚合useful / dead_end / corrected信号输出单一经验文件。值得强调的机制节点按带符号、时间衰减的分数排序而非简单计数useful为正、dead_end/corrected为负带半衰期——新近的死胡同权重高于几个月前的 useful节点只有被足够多条独立结果佐证后才晋升为 preferred一次保存不能凭空制造一条可信经验有图时经验还按社区标签分组无图则退化为单一扁平章节确定性、无 LLM给定相同输入与now输出字节级稳定产物放在graphify-out/reflections/LESSONS.md而不是 wiki 内因为export wiki每次运行会删除所有wiki/*.md写进 wiki 会在下次导出时被清掉。/graphify path两概念间的最短路径用途在图谱中寻找两个命名概念之间的最短路径。CLI 方式graphify path NODE_A NODE_B内联回退$(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的含义以及为什么这些连接有意义。处理要点find_node同样以词元命中数为依据找最佳匹配节点任何一端找不到匹配就明确报错健壮性处理NetworkXNoPath两端可达性断裂、NodeNotFound节点根本不存在分别给出清晰提示输出每一跳都带relation与confidence解释时必须讲清楚边的语义。写解释后用以下命令保存供未来的路径查询复用$(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_B/graphify explain单节点的通俗解释用途对图谱中的单个节点给出通俗解释——它与什么相连、为什么这些连接有意义。CLI 方式graphify explain NODE_NAME内联回退$(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 句的解释该节点是什么、它与哪些东西相连、这些连接为什么重要并用source_location/source_file作为引用依据。相比查询流程explain 输出的节点元数据更完整source、type、degree、邻居及其source_file便于快速建立对单点角色的整体认识。保存结果$(cat graphify-out/.graphify_python) -m graphify save-result --question Explain NODE_NAME --answer ANSWER --type explain --nodes NODE_NAMECLI 侧的 explain 行为由 tests/test_explain_cli.py 覆盖验证。全套流程速查与关键纪律把三套流程浓缩为一张可操作的清单流程CLI内联回退要点回写命令自然语言查询graphify query Q [--dfs] [--budget N]词元打分选 ≤3 起点DFS 深限 6 层 / BFS 扩散 3 层按 token 预算截断输出save-result --type query --nodes ...最短路径graphify path A Bnx.shortest_path 逐跳 relation/confidence 输出save-result --type path_query --nodes A B单节点解释graphify explain N找最佳匹配节点并列出全部邻居与连接属性save-result --type explain --nodes N贯穿始终的纪律可以归纳为四条先扩展后遍历任何自然语言问题都必须先对照graphify-out/.vocab.txt做受限扩展并打印审计信息词表为空则停止。先用图说话答案只能来自子图输出中的节点、边、relation、confidence与source_location禁止虚构不存在的边。答案必须回写save-result携带--outcome useful|dead_end|corrected必要时--correction让图谱随每次问答自我改进。会话开始先读经验graphify reflect --if-stale后读graphify-out/reflections/LESSONS.md从 preferred sources 起步、绕开已知 dead ends、参考历史 corrections。在仓库中继续深入参考文档本体graphify/skills/trae/references/query.md以及 skillgen 生成的期望产物 tools/skillgen/expected/graphify__skills__trae__references__query.md技能主文件graphify/skill-trae.md含全部/graphify子命令 Usage核心技能骨架见 graphify/skill.md查询评分与词元实现graphify/serve.py重点看_query_termsL272、停用词表_QUERY_STOPWORDSL240含英/德/法/西/葡/意多语言提问词过滤与中文切词处理、匹配加分常量L294-L297以及_score_query/_score_nodes包装L461-L472CLI 子命令注册点graphify/cli.py——query在 L1068、save-result在 L1312、reflect在 L1343、path在 L1400、explain在 L1567工作记忆 / reflect 实现graphify/reflect.py时间衰减打分、preferred/dead ends/corrections 聚合、--if-stale与确定性输出机制测试与基准tests/test_query_cli.py、tests/test_explain_cli.py、tests/bench_query_scoring.py。【免费下载链接】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),仅供参考