先说一个基本背景:基于知识的问答(KBQA)在不久之前还算是个新鲜事物,但现在,借助检索增强生成(RAG),它几乎成了AI爱好者的“家常便饭”。真正让人着迷的是,随着大语言模型的持续突破,自然语言处理的可能性边界每天都在被刷新。
研究智能体:应对大规模文本语料问答的挑战
一个自主AI研究智能体被设计出来,它具备深度多跳推理能力,能够回答相当复杂的问题。从实践来看,这项工作的表现已经引起了不少人的兴趣,大家也给出了很多有价值的反馈——这些建议被收集起来,对原始代码做了一系列改进。不过,这次想聊的是另一个方向:当这个思路和递归式RAG结合起来,或许能催生出更强大的研究型智能体。这个想法来源于在小模型上做的递归RAG实验,以及阅读Medium上的相关文章,尤其是知识图谱增强生成(KG-Augmented Generation)的思路。
摘要
知识图谱,或者任何图结构,本质上都是由节点和边构成的——每个节点代表一个概念,每条边表示一对概念之间的关系。这篇文章要分享的,正是如何将任意文本语料转化为“概念图”(Graph of Concepts,本文中与“知识图谱”交替使用)。
值得留意的是,这套实现中使用的所有组件都可以在本地搭建,这意味着项目能够轻松跑在个人电脑上。具体来说,采取了一种不依赖GPT的方案——更倾向于使用小型开源模型,比如Mistral 7B OpenOrca instruct和Zephyr,它们可以通过Ollama在本地运行。至于图数据的存储,虽然像Neo4j这样的数据库很好用,但为了保持简单,这里选用了Pandas内存数据框和NetworkX Python库。
本文目标
- 将任意文本语料转换为概念图
- 实现美观的可视化,就像文章横幅图片展示的那样
- 支持交互操作:拖动节点和边、缩放视图、调整图的物理属性
在正式进入方法之前,需要先理解知识图谱的基本思想以及它为什么重要——如果已经很熟悉这些概念,可以跳过下一节。
知识图谱示例
来看这段小诗:
Mary had a little lamb, You’ve heard this tale before; But did you know she passed her plate, And had a little more!
下面是从这首诗中提取出的一种知识图谱表示。

IBM有一篇非常贴切地解释知识图谱核心概念的文章,其中一段话可以作为总结:
知识图谱(又称语义网络)由现实世界中的实体(即对象、事件、情境或概念)组成,并说明它们之间的关系。这些信息通常存储在图数据库中,以图结构的形式可视化,因此被称为“图谱”。
为什么需要知识图谱?
知识图谱的用途相当广泛:可以运行图算法,计算任意节点的中心性,来理解某个概念在整体中的重要性;可以分析相连或不相连的概念集合,或者计算概念的社群,从而获得更深入的理解;还可以探索看似无关的概念之间的潜在联系。
更进一步,知识图谱还能用来实现图检索增强生成(GRAG或GAG),让“和文档对话”成为可能。这往往比传统RAG效果更好——传统RAG依赖语义相似度来检索上下文,存在一些固有缺陷:当查询没有提供足够上下文来表达真实意图时,或者当相关上下文分散在庞大的语料库中时,RAG就很难发挥作用。
举个例子:假设问“请告诉我在《百年孤独》一书中,José Arcadio Buendía 的家族谱系”。这本书记录了这个家族整整7代人,而且其中一半的人物都叫José Arcadio Buendía。面对这种问题,简单的RAG流程几乎不可能完成——它不知道该怎么问。很多时候,问对问题比得到答案更重要。
图增强生成(GAG)在一定程度上能解决这些问题。当然,还可以更进一步,把两者结合起来,构建一个图增强的检索增强生成流程,同时获得两者的优势。
总结一下:图谱不仅有趣,而且非常有用;它能带来更强大的信息分析与推理能力;并且,它们的可视化效果往往非常美观。
创建概念图
如果问GPT如何从文本中创建知识图谱,它可能会给出这样的流程:提取文本中的概念和实体(节点)→提取概念之间的关系(边)→将节点和边存储到图结构或数据库中→可视化。步骤3和4不难理解,但步骤1和2究竟如何实现?
方法流程图
下面是一套从任意文本语料中提取概念图的方法流程,与上面的经典思路类似,但有一些细微差别。

主要步骤:
- 将文本语料分割为若干块(chunks),并为每个块分配一个chunk_id。
- 对每个文本块,使用LLM提取概念及其语义关系:给这种关系分配权重W1;相同的一对概念之间可以有多种关系,每种关系都表示为一条边。
- 考虑上下文邻近性:同一文本块中间出现的概念,默认也存在一定关系。给这种“上下文关系”分配权重W2(需要注意的是,相同的概念对可能出现在多个块中)。
- 合并相似的概念对:累加它们的权重,合并它们的关系——最终,每对不同概念之间只保留一条边,边上既有权重,又有关系描述。
这套方法的Python实现代码可以在GitHub仓库中查看。接下来,简要走读一下实现的关键思路。
示例语料
为了演示,这里选用了一篇在PubMed/Cureus上发表的综述文章(遵循Creative Commons署名协议),题目是《印度应对医疗卫生人力资源挑战的机遇》。
使用Mistral模型与提示词
步骤1很简单:Langchain提供了丰富的文本分割器,可以直接将文本分割为块。真正有趣的是步骤2——提取概念及其关系,这里使用的是Mistral 7B模型。
在最终确定最佳变体之前,尝试了Mistral Instruct、Mistral OpenOrca以及Zephyr(Hugging Face上基于Mistral的衍生模型)。使用的是这些模型的4-bit量化版本——这样Mac就不会“崩溃”了——并通过Ollama在本地运行。
这些模型都是指令微调模型,包含系统提示和用户提示。只要给出明确的指令,它们通常能很好地遵循,并将结果规范地格式化为JSON。经过多次尝试,最终选定了Zephyr模型,配合如下提示词:
SYS_PROMPT = (
"You are a network graph maker who extracts terms and their relations from a given context. "
"You are provided with a context chunk (delimited by ```) Your task is to extract the ontology "
"of terms mentioned in the given context. These terms should represent the key concepts as per the context. \n"
"Thought 1: While tra versing through each sentence, Think about the key terms mentioned in it.\n"
"\tTerms may include object, entity, location, organization, person, \n"
"\tcondition, acronym, documents, service, concept, etc.\n"
"\tTerms should be as atomistic as possible\n\n"
"Thought 2: Think about how these terms can ha ve one on one relation with other terms.\n"
"\tTerms that are mentioned in the same sentence or the same paragraph are typically related to each other.\n"
"\tTerms can be related to many other terms\n\n"
"Thought 3: Find out the relation between each such related pair of terms. \n\n"
"Format your output as a list of json. Each element of the list contains a pair of terms"
"and the relation between them, like the following: \n"
"[\n"
" {\n"
' "node_1": "A concept from extracted ontology",\n'
' "node_2": "A related concept from extracted ontology",\n'
' "edge": "relationship between the two concepts, node_1 and node_2 in one or two sentences"\n'
" }, {...}\n"
"]"
)
USER_PROMPT = f"context: ```{input}``` \n\n output: "
把之前那首小诗丢进提示词,模型输出的结果如下:
[{"node_1":"Mary","node_2":"lamb","edge":"owned by"},{"node_1":"plate","node_2":"food","edge":"contained"},...]
有意思的是,模型甚至推断出了“food(食物)”这个概念,虽然在原始文本片段中并没有明确提到。
将示例文章的每个文本块都跑一遍,然后把得到的JSON转换为Pandas DataFrame,结果大致如下:

这里每一行都表示一对概念之间的一种关系。在图中,每一行对应两个节点之间的一条边;同一对概念之间可以存在多条边/多种关系。上面数据框中的count列,是任意设为4的权重。
上下文邻近性
假设:在文本语料中彼此位置接近的概念是相关的。这种关系被称为“上下文邻近性”(contextual proximity)。
为了计算上下文邻近性的边:首先,melt数据框——将node_1与node_2“熔化”为同一列,使节点落到一个统一的列中;然后,以chunk_id作为键,对数据框做自连接——这样,相同chunk_id的节点会两两成对,形成潜在的边;最后,移除自环,即删除所有node_1 == node_2的行。
最终得到的数据框,与最初的语义关系数据框结构非常相似:count表示node_1与node_2共同出现的文本块数量;chunk_id包含了所有这些共同出现的chunk列表。
到目前为止,有了两个数据框:语义关系的数据框和上下文邻近性关系的数据框(基于同块共现)。可以将两者合并,形成网络图的最终数据框。
可视化目标
至此,已经为文本构建好了概念图。但如果就到这里戛然而止,未免有些意犹未尽——目标是进行可视化,就像文章开头的主图那样。好消息是,离这个目标已经很近了。
创建概念网络
NetworkX是一个Python库,使得图的处理变得非常简单。如果还不够熟悉,可以查阅官方文档。
将DataFrame转换为NetworkX图
把得到的数据框加入NetworkX图里,只需要几行代码:
import networkx as nx
# 创建一个空图
G = nx.Graph()
# 添加节点
for node in nodes:
G.add_node(str(node))
# 添加边
for index, row in dfg.iterrows():
G.add_edge(
str(row["node_1"]),
str(row["node_2"]),
title=row["edge"], # 边的关系描述
weight=row["count"] # 边的权重(出现次数/强度)
)
NetworkX自带了大量可直接调用的网络算法。这里使用社区发现算法给节点分组并着色。社区指的是:与图中其他节点相比,内部连接更紧密的一组节点。在概念图中,社区往往对应文本中被讨论的主题簇,能帮助我们把握宏观主题。
对示例的综述文章运行Girvan–Newman算法后,检测到17个概念社区。下面是其中一个社区的示例:
['digital technology', 'EVIN', 'medical devices', 'online training management information systems', 'wearable, trackable technology']
这立刻让人对该论文中健康技术相关的宏观主题有了直观认识,并且还能据此提出更有针对性的问题。接下来计算图中每个概念的度(degree)。节点度指的是一个节点连接的边的总数。在这个场景里,度越高,说明该概念在整篇文本的主题中越居于中心。在可视化中,将用节点度作为节点大小的依据——度越高,节点越大。
图可视化
可视化是这项工作最有趣的部分,它带来一种艺术层面的满足感。这里使用的是PyVis库来创建交互式网络图。
PyVis内置了NetworkX Helper,可以把NetworkX图直接转换成PyVis对象,几乎不需要额外编码——太省心了。
回顾一下,已经准备好了以下用于可视化的要素:边的权重用于控制边的粗细;节点的社区用于控制节点的颜色;节点的度用于控制节点的大小。把这些“铃铛与口哨”都装上,图就呈现在眼前了。

在这个图里,可以自由缩放,随意拖动节点和边。页面底部还有一个滑块面板,用来调整图的物理效果。通过这种方式,不仅能更好地理解主题,还能帮助提出正确的问题,从而更深入地研究内容。
