张三的上上级:向量检索查不到,GraphRAG 为什么能
目录
一个文档里没写的问题#
向量检索怎么查#
实测:要找的那条排第 7#
14 条笔记的相似度由中文向量模型 bge-small-zh-v1.5 实际算出,实验代码附在文末。
问题里没有李四#
调大 k、换问法、加重排序#
分两次查能接上,但已经不是一次检索#
微软研究院介绍 GraphRAG 时,把这列为传统 RAG 的第一个短板:答案要靠共同的线索把分散的信息串起来时,传统 RAG 串不起来。
GraphRAG:建索引时就把点连起来#
以微软开源的 GraphRAG 为例,它让大模型逐块抽出实体和关系,再把名字和类型都相同的实体并成一个。
两条笔记是怎么连上的#
本地搜索:从张三这个点出发#
本地搜索先找出和问题相关的实体作入口,再取出相连的实体、关系和原文片段,交给大模型。
全局搜索:回答整体的问题#
建完图,GraphRAG 会用 Leiden 算法把关系紧密的实体分成社区,再给每个社区写一份报告。全局搜索先从各份报告里分头提炼要点并打分,再汇总最重要的要点。
图不是白来的#
默认流程里,每个文本块都要交给大模型抽取一遍;微软也提供 FastGraphRAG,改用 NLP 方法抽取,省下大模型调用。
该用哪一种#
附:代码#
在图上沿着"上级"走几步,可以直接运行,把 start 改成"周八"试试:
// 抽取得到的关系:[起点, 关系, 终点, 出自第几条笔记]
const relations = [
["张三", "上级", "李四", 1],
["李四", "上级", "王五", 2],
["张三", "休假接手人", "赵六", 5],
["钱七", "导师", "张三", 7],
["赵六", "上级", "孙九", 8],
["周八", "上级", "吴十", 9],
["吴十", "上级", "王五", 10],
];
// 建图:同一个名字只有一个点
const graph = new Map();
for (const [from, rel, to, doc] of relations) {
if (!graph.has(from)) graph.set(from, []);
graph.get(from).push({ rel, to, doc });
}
// 从 start 出发,沿着"上级"走 hops 步
const start = "张三", hops = 2;
let who = start;
for (let i = 1; i <= hops; i++) {
const up = (graph.get(who) || []).find((e) => e.rel === "上级");
if (!up) { console.log(`${who} 在图里没有上级了`); break; }
console.log(`第 ${i} 步:${who} → ${up.to}(依据第 ${up.doc} 条笔记)`);
who = up.to;
}实验代码:在 Node 里用 transformers.js 算相似度
import { pipeline } from "@huggingface/transformers";
const docs = [
"张三是后端组的工程师,日常工作向李四汇报。",
"李四是后端组负责人,他的直属上级是技术总监王五。",
"张三本周完成了支付接口的重构,下周开始做对账服务。",
"张三在周三的故障复盘会上讲了数据库连接池耗尽的问题。",
"张三申请了下个月的年假,期间工作由赵六临时接手。",
"张三和前端组的周八一起排查了登录超时的问题。",
"新人钱七入职后端组,导师是张三。",
"赵六的直属上级是孙九,孙九负责测试组。",
"周八是前端组的工程师,向吴十汇报。",
"前端组负责人吴十的上级同样是王五。",
"公司规定:请假三天以上需要上级领导审批。",
"报销流程:先由直属领导签字,再交财务审核。",
"后端组每周一上午开周会,由李四主持。",
"王五、孙九和人事负责人郑一组成了公司的管理委员会。",
];
// BGE 中文模型给短问题检索段落时,问题前面加这句指令
const prefix = "为这个句子生成表示以用于检索相关文章:";
const embed = await pipeline("feature-extraction", "Xenova/bge-small-zh-v1.5", { dtype: "q8" });
const vec = async (t) => Array.from((await embed(t, { pooling: "cls", normalize: true })).data);
const docVecs = [];
for (const d of docs) docVecs.push(await vec(d));
for (const q of ["张三的上上级领导是谁?", "张三的上级的上级是谁?", "李四的上级是谁?"]) {
const qv = await vec(prefix + q);
// 向量已经归一化,点积就是余弦相似度
const ranked = docVecs
.map((v, i) => ({ i, s: v.reduce((sum, x, j) => sum + x * qv[j], 0) }))
.sort((a, b) => b.s - a.s);
console.log(q);
ranked.forEach(({ i, s }, r) => console.log(r + 1, s.toFixed(3), docs[i]));
}