想象一下,你的问答机器人不再是“关键词搬运工”,而是个真正会“思考”的小伙伴:它能把相关知识串成思路,再用轻松的口吻给你讲清楚。以下这套基于 Graph RAG(Graph-Enhanced Retrieval-Augmented Generation)的方案,既保留了检索效率,又增强了逻辑可解释性,适合中文内容库。
一、为什么要把“图”搬进检索里
- 摆脱关键词束缚
传统 RAG 单纯靠向量或关键词匹配,容易对多跳、跨文档问题无能为力。 - 捕捉知识关系
把“概念”和“文档片段”当成节点,用边标注它们的关联程度,检索时就能沿路径找答案。 - 可视化检索链路
结果不光给你答案,还能展示“为什么这么找”,便于审计和优化。
二、技术流程概览
1. 文本分片
defchunk_text(text, chunk_size=800, overlap=150):
chunks = []
foriinrange(0, len(text), chunk_size - overlap):
part = text[i : i + chunk_size]
ifpart:
chunks.append({
"id": len(chunks),
"text": part
})
returnchunks
将长文本分成大约 800 字的小块,重叠一部分保证上下文连贯。
2. 概念抽取
defextract_concepts(text):
prompt =f"请列出下面这段中文的 5–8 个核心关键词或实体:\n{text[:200]}…"
resp = client.chat.completions.create(
model="chatglm-6b",
messages=[{"role":"user","content":prompt}],
temperature=0
)
return[c.strip()forcinresp.choices[0].message.content.split(",")]
给每个片段贴上“标签”,让不同内容靠“共同标签”连接。
3. 构建知识图谱
importnetworkxasnx
fromscipy.spatial.distanceimportcosine
defbuild_graph(chunks, embeddings, concept_lists):
G = nx.Graph()
foridx, chunkinenumerate(chunks):
G.add_node(idx, text=chunk["text"],
emb=embeddings[idx],
concepts=concept_lists[idx])
foriinrange(len(chunks)):
forjinrange(i+1, len(chunks)):
shared = set(concept_lists[i]) & set(concept_lists[j])
ifnotshared:continue
sim =1- cosine(embeddings[i], embeddings[j])
conc_score = len(shared) / min(len(concept_lists[i]), len(concept_lists[j]))
weight =0.7* sim +0.3* conc_score
ifweight >0.6:
G.add_edge(i, j, weight=weight)
returnG
边的权重由“向量相似度”与“概念重叠度”共同决定,过滤掉微弱关联。
4. 图遍历检索
importheapq
deftraverse_graph(query_emb, G, top_k=3, max_depth=2):
sims = [(1- cosine(query_emb, G.nodes[n]["emb"]), n)forninG.nodes]
sims.sort(reverse=True)
heap = [(-s, n,0)fors, ninsims[:top_k]]
heapq.heapify(heap)
seen, result = set(), []
whileheap:
score, node, depth = heapq.heappop(heap)
ifnodeinseenordepth > max_depth:continue
seen.add(node)
result.append(( -score, node ))
fornbr, datainG[node].items():
next_score = -score * data["weight"]
heapq.heappush(heap, (next_score, nbr, depth +1))
return[nfor_, ninsorted(result, reverse=True)][: top_k *2]
先选几个相关度最高的“起点”,再沿着图里的边“多跳”扩展,平衡深度和效率。
5. 生成回答
defgenerate_reply(query, selected_chunks):
context ="\n\n---\n\n".join(c["text"]forcinselected_chunks)
prompt =f"以下是检索到的上下文:\n{context}\n\n请用轻松自然的口吻回答:{query}"
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role":"user","content":prompt}],
temperature=0.3
)
returnresp.choices[0].message.content
把摘出的关键片段拼起来,让大模型在“实材实料”的上下文中输出答案。
三、落地示例:在线教育平台
- 痛点:学生问“高数链式法则”时,对一堆公式无从下手。
- 分片后提取“链式法则”“导数复合”“变量替换”等标签;
- 检索时先找到“链式法则→公式推导→实例演示”等路径;
- 生成回答时融合多段解释,并插入生活化比喻(“就像做菜要先腌料再下锅”),加深理解。
效果:
四、几点建议
- 分片大小:中文一般 800–1,000 字,重叠 15% 左右;
- 图数据库:想要实时更新和可视化,推荐 Neo4j 或 Milvus + 图插件;
- 风格把控:提示里写“用自然口吻”,避免“AI生成感”。
用 Graph RAG,不只是让问答系统“能答”,还能让它在知识间“自如穿梭”,从此告别“答案卡壳”的尴尬。动手试试,让你的中文问答更有温度、更顺畅!