使用大型语言模型 (LLM) 提取知识图谱非常耗时且容易出错。之所以出现这些困难,是因为 LLM 被要求从内容中提取细粒度的、特定于实体的信息。受到向量搜索的好处的启发,尤其是通过相对较少的清理即可摄取内容获得良好结果的能力,让我们探索一个粗粒度的知识图谱 – 内容知识图谱 – 专注于内容之间的关系。
以实体为中心的知识图谱
从历史上看,知识图谱的节点表示特定概念(或实体),而边表示这些概念之间的特定关系。例如,使用有关我和我的雇主的信息构建的知识图谱可能如下所示:
这种精细的、以实体为中心的知识图谱允许使用 Cypher 或 Gremlin 等图形查询语言来表示各种查询。最近,知识图谱作为高级检索增强生成 (RAG) 技术的一部分,作为存储和检索信息以供 LLM 使用的替代方式而变得流行。这些想法很有说服力:知识图谱捕获向量相似性搜索会错过的信息之间的关系,而 LLM 使得只需一个提示即可从非结构化内容中提取知识图谱三元组(来源、关系、目标)。这就是为什么这个历史概念似乎与这么多人相关。
然而,从非结构化信息中提取这种细粒度的知识图谱是困难的、耗时的且容易出错的。为了获得最佳结果,您(和领域专家)需要:
使用 LLM 处理所有非结构化内容以提取信息,
通过创建“知识模式”(或本体)来指导 LLM 了解您希望提取的节点和关系的类型,
检查提取的信息图表,以确保 LLM 提取的详细信息正确,并且
在更改知识架构时重新处理所有内容。
在需要人类专家和将 LLM 应用于所有内容之间,构建和维护此图表的成本很高。底线:大多数使用 RAG 知识图谱的示例只针对几个句子或段落进行操作是有原因的。
使用以实体为中心的知识图谱比仅仅将内容分块并将其转储到向量存储中更难扩展和获得良好的结果。我们有什么方法可以将向量搜索的好处带到知识图谱中——具体来说,使构建变得像分块和嵌入内容一样简单,同时保留原始内容,直到 LLM 知道要回答的问题?
以内容为中心的知识图谱
如果我们从表示内容(例如文本块)的节点开始,而不是从细粒度的概念或实体开始,那么图形的节点正是使用向量搜索时存储的内容。节点可以表示特定的文本段落、图像或表格、文档的某个部分或其他信息。这些代表原始内容,允许 LLM 做它最擅长的事情:处理上下文并挑选出重要信息。在构建细粒度图时,这发生在问题已知之前,迫使人们猜测和/或人工指导哪些事实很重要。
事实上,这就是我们认为这些以内容为中心的知识图谱更好的部分原因:LLM 擅长处理大量上下文,当他们知道问题时这样做可以让他们大海捞针。以实体为中心的知识图谱需要将信息简化为边缘上的简单注释,这使得它们作为 LLM 的上下文不太有用。
节点之间的边表示各种结构、语义和基于元数据的属性。例如,包含超链接的块可能具有指向链接内容的“links_to”边缘。或者具有常见关键字的两个块可能有一个边缘,指示相似的内容 'has_keywords:[...]`.文本段落可以链接到它引用的同一部分中的图像或表格,或者文档中的段落可以链接到关键术语的定义。
从关于 Ben 和 DataStax 的三个文档开始,类似于上一个示例的粗粒度图可能是:
由于节点是文档块,因此如果 DataStax 上的文章有更多信息,例如它是何时成立的,图表不会改变。使用细粒度方法,我们需要决定是否应该提取这些额外的信息。
与细粒度知识图谱相比,这种方法的主要优点是:
无损 – 原始内容保留在节点中,这意味着在创建过程中不会丢弃 (不提取) 任何信息。这减少了在需求变化时重新索引信息的需要,并允许 LLM 做它最擅长的事情:根据问题从该上下文中提取答案。
无需干预 — 无需专家来调整知识提取。您可以根据关键字、超链接或数据的其他属性向现有的矢量搜索管道添加一些边缘提取,然后自动添加链接。
可扩展 — 可以使用对内容的简单操作来实现创建过程,而无需调用 LLM 来创建知识图谱。
创造
与细粒度图不同,创建这些粗粒度图的过程要简单得多。不需要领域专家。相反,内容被加载、分块并写入 store。每个数据块都可以通过各种分析来运行,以识别链接。例如,内容中的链接可能会变成 “links_to” 边缘,并且可能会从块中提取关键字以与同一主题的其他块链接。
我们使用了几种技术来添加边缘。每个块都可以使用它所代表的 URL 以及它引用的 HREF 进行注释。这允许捕获内容之间的显式链接,以及通过使用片段表示文档链接到同一页面中的定义等情况。此外,每个数据块都可以与关键字相关联,并且具有给定关键字的所有数据块都将链接在一起。
更多链接技术正在开发中,包括基于块属性的自动链接以及使用结构属性(如页面上的位置)。
检索
对这些粗粒度图的检索结合了向量搜索和知识图谱遍历的优势。可以根据与问题的相似性来确定起点,然后通过以下边缘选择其他块,并限制遍历的深度(与向量搜索节点的距离)。
包括通过嵌入距离 (相似性) 和图形距离 (相关) 相关的节点会导致更多样化的块集。图表中的许多边将导致信息加深上下文,但与问题没有直接关系。这些关系允许扩展上下文或将上下文限制为 “附近” 内容。这些额外的相关信息可以提高答案的质量并减少幻觉。
案例研究:Astra 支持文章
我们从 DataStax Astra DB 支持站点加载了 1,272 个文档,并从中链接了一些外部页面。只需不到 5 分钟的时间即可完成抓取、解析 HTML、提取超链接、将内容转换为 Markdown 并将生成的文档写入 Astra DB 存储。
除了基本的数据清理和几行代码来填充描述链接的元数据之外,我几乎不需要做任何工作。具体来说,我没有查看数据或尝试创建一个知识模式(本体)来捕获我希望提取的信息。这很重要,因为我不确定 1,272 份文件中的哪些部分对可能提出的问题有用。
我本可以通过使用 LangChain 的更多内置文档加载功能来减少代码,但它有问题,因为它想在写出页面之前将所有页面加载到内存中,因此我必须自己管理迭代。
对于以内容为中心的图形,我们将使用作为 ragstack-ai-langchain 一部分提供的 GraphStore 类。还需要安装 ragstack-ai-knowledge-store 包。
import cassiofrom langchain_openai import OpenAIEmbeddingsfrom ragstack_langchain.graph_store import CassandraGraphStore# Initialize AstraDB connection cassio.init(auto=True )# Create embeddings embeddings = OpenAIEmbeddings()# Create knowledge store graph_store = CassandraGraphStore(embeddings) ...# Add documents to knowledge store graph_store.add_documents(docs)
虽然您可以自己设置链接的元数据,但也有一些方便的实用程序可以自动执行此操作。出于我们的目的,我们希望对每个 HTML 文档执行以下操作:
使用基于源 URL 的 CSS 选择器来查找内容(例如,从块和链接中排除导航等)
从 HTML 内容中提取链接
将 HTML 内容转换为 markdown
虽然 LangChain Document Transformers 提供了部分功能,但它们不容易组合,因此我们只需编写一些代码来清理 HTML:
from markdownify import MarkdownConverterfrom ragstack_langchain.graph_store.extractors import HtmlLinkEdgeExtractor markdown_converter = MarkdownConverter(heading_style="ATX" ) html_link_extractor = HtmlLinkEdgeExtractor()def convert_html (html: Document ) -> Document: url = html.metadata["source" ]# Use the URL as the content ID. html.metadata[CONTENT_ID] = url# Apply the selectors while loading. This reduces the size of # the document as early as possible for reduced memory usage. soup = BeautifulSoup(html.page_content, "html.parser" ) content = select_content(soup, url)# Extract HTML links from the content. html_link_extractor.extract_one(html, content)# Convert the content to markdown html.page_content = markdown_converter.convert_soup(content)return html
同样,由于知识图谱实现了向量存储接口,因此很容易创建一个检索器并在 LangChain 表达式中使用它:
# Depth 0 doesn't traverse edges and is equivalent to vector similarity only. retriever = graph_store.as_retriever(search_kwargs={"depth" : 0 })def format_docs (docs ): formatted = "\n\n" .join(f"From {doc.metadata['content_id' ]} : {doc.page_content} " for doc in docs) return formatted rag_chain = ( {"context" : retriever | format_docs, "question" : RunnablePassthrough()} | prompt | llm | StrOutputParser() )
问题
我在所有示例中使用的问题是关于 Astra DB 如何实现矢量索引的相对简单的问题。
“Astra 使用哪些矢量索引算法?”
这个问题的答案需要阅读文档的多个部分,并将其与外部链接网站上的可用信息联系起来。
仅向量
答案相对较浅 – 只是谈论库过去如何实现向量搜索 (JVector)。此答案是正确的,但它不包括有关 Astra DB 使用的算法或其实际工作原理的任何详细信息。
如果我们查看为回答问题而检索的页面 - 那些与问题相似度最高的页面 - 我们会发现它没有进入任何更深入的文档:
https://docs.datastax.com/en/astra-db-serverless/get-started/concepts.html
https://docs.datastax.com/en/cql/astra/getting-started/vector-search-quickstart.html
https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html
https://docs.datastax.com/en/astra-db-serverless/get-started/astra-db-introduction.html
深度遍历
更改检索器以执行遍历很容易,并且可以给我们带来更好的结果。
# Depth 1 does vector similarity and then traverses 1 level of edges. retriever = knowledge_store.as_retriever(search_kwargs={"depth" : 1 })
答案更好;它解释了 JVector 如何实现基于图形的索引以进行可扩展向量搜索,以及如何立即使用文档。
请注意,生成结果需要更长的时间 – 17.5 秒(仅矢量搜索为 6.1 秒)。沿着我们用向量搜索检索到的前四个文档的边缘,总共检索到 31 个文档。额外的衍生物花了 LLM 更长的时间来理解,尽管他们仍然在想出答案方面做得很好。同时,感觉它并没有深刻地回答这个问题。也许是因为 LLM 需要考虑的东西太多了,它没有得到最简洁的答案。
如果有一种方法可以检索更少的文档,同时最大限度地提高多样性,那会怎样?当它们提供额外的相关信息时,特别是当它增加了检索内容的多样性时,有一种方法可以跟踪边缘?我们可以修改最大边际检索 (MMR) 来做到这一点。
MMR 遍历
MMR 遍历搜索执行向量和图形遍历的组合,以检索特定数量的文档。与传统的 MMR 不同,在选择节点后,其相邻节点也成为检索的候选节点。这允许 MMR 遍历探索图形,使用 diversity 参数来决定在多大程度上更喜欢相似节点,在多大程度上更喜欢通过向量搜索或图形遍历检索到的不同节点。
与切换到遍历一样,使用这种技术很容易改变 'retriever' :
retriever = knowledge_store.as_retriever( search_type = "mmr_traversal" , search_kwargs = { "k" : 4, "fetch_k" : 10, "depth" : 2, }, )
这个答案似乎更好。它不仅讨论了如何实现 JVector,还详细介绍了它用于高效处理搜索和更新的一些技术。
如果我们看一下检索到的内容,我们会发现它只检索了 4 个文档(在考虑了总共 15 个文档之后)。它检索了回答问题所需的相似结果(例如入门和索引概念)以及更深入的结果(JVector 的文档)的组合。
https://docs.datastax.com/en/astra-db-serverless/get-started/concepts.html
https://docs.datastax.com/en/astra-db-serverless/cli-reference/astra-cli.html
https://github.com/jbellis/jvector
https://docs.datastax.com/en/cql/astra/developing/indexing/indexing-concepts.html
结论
以内容为中心的知识图谱可作为 RAGStack 的一部分进行预览。您还可以查看案例研究中的笔记本。我们正在努力将它们贡献给 LangChain,并对边的创建和遍历方式进行各种令人兴奋的改进。请继续关注这一领域令人兴奋的后续行动。
参考文献
GraphRAG 的演变 -Neo4j GenAI Graph Gathering 2.0
微软GraphRAG框架演进之路及带来的一些思考
LazyGraphRAG:微软重磅推出高性价比下一代GraphRAG
提升大型语言模型结果:何时使用GraphRAG
微软GraphRAG最新动态:通过动态社区选择改善全球搜索
GraphRAG产业化应用落地挑战和探索:知易行难 - 企业大模型独角兽Glean实践之四