2026年7月9日 周四晚上19:30,报名腾讯会议了解“如何构建自进化的动态知识库(Brain)”(限30人)
免费POC, 零成本试错
FDE知识库

FDE知识库

学习大模型的前沿技术与行业落地应用


收藏

实操|如何优雅的实现RAG与GraphRAG应用中的知识文档增量更新?

发布日期:2024-11-04 19:48:42 浏览次数: 5785
作者:AI大模型应用实践

微信搜一搜,关注“AI大模型应用实践”

在RAG应用(包括GraphRAG)中,领域知识的导入与索引是后续增强生成的基础。一个常见的问题是,当领域知识发生更新与变化时,如何用最简洁、快速、低成本的方式更新对应的向量或知识图谱索引?让我们来探讨这个问题。


01

需求

一般来说企业的信息系统中都可能有较完善的知识库维护与管理应用,但是如何让变化的知识能够同步更新到RAG应用中则不一样,知识进入到RAG应用通常需要经过拆分(split)、嵌入(embedding)、向量索引(vectorindex)等步骤:

因此当更新发生时,就需要识别出输入的知识文档变化,进而将合适的策略应用到不同的知识块上,比如忽略、新增、删除或者更新。

在实际应用中有两种不同级别的增量更新策略:

一种是文档(Document)级别的简单更新策略。即在导入知识文档时识别出新增或更新的文档,然后对其进行全量解析与向量化,并做索引合并更新。


另外一种是块(Chunk)级别的更新策略。这种更加复杂但也更精细化:在一个文档发生变化的过程中,有新增的块也有发生更新的块,需要识别哪些块需要更新删除、哪些是新增的块,以及哪些块没有发生变化,应该跳过更新。

借助以上的两种策略,你可以在文档发生更新时,降低不必要的计算工作量,消除可能产生的重复块与索引,节约模型使用成本,并提高RAG应用后续检索阶段的有效性与准确性,即保持最新、有效且不重复的上下文。


02

方案

实现增量更新的解决方案通常需要借助于文档或者块的“指纹”来实现,结合必要的持久与缓存方案,在每次进行知识索引时通过“指纹”来识别出本次需要处理的文档或知识块,并执行相应的动作(如插入或者删除),跳过重复的内容,从而达到增量更新的目的。

不管是文档(Document)还是块(Chunk)级别的增量更新策略,都可以基于类似的原理来实现,我们以更细粒度的Chunk级别的增量更新为例,其原理表示如下:

  • 在每次处理开始时,计算每个块的hash指纹,这通常是是基于块的内容与元数据,并借助hash函数生成的唯一值


  • 为了实现增量加载更新,需要一个跟踪与保存每次处理的块信息的机制(源文档、块信息、hash指纹、时间戳等),比如LangChain中的RecordManager组件,LlamaIndex中的DocumentStore组件


  • 每次增量更新时,通过与上一次保存的处理信息对比hash指纹,确定数据块的处理动作

    • 如果某数据块的hash指纹在上一次处理中存在,则跳过处理

    • 如果某数据块的hash指纹在上一次处理中不存在,则做新增处理

    • 对于上一次处理中存在但是本次不存在的hash指纹,则做块删除


  • 根据确定的处理动作对数据块做相应的嵌入与索引更新即可。注意这里可能对向量数据库有一定的能力要求,以实现增量索引更新。



03

实现

在现有的两个主流底层LLM应用开发框架:LangChain与LlamaIndex中都提供了文档增量更新的实现方法。两者实现方法各有区别,但核心思想基本类似,这里做一个简单演示与研究。

【LangChain的索引API】

如果你使用了LangChain框架并需要让向量索引与输入知识文档保持同步,那么需要使用LangChain的索引API来创建知识的向量索引,而不是简单的使用from_documents方法来完成。

索引API的主要区别就在于提供了文档增量更新的能力:跳过没有变化的知识块以避免向量库中写入重复知识块、并对新增或者变化的知识块计算嵌入与写入向量库。

为了实现对文档块的跟踪,索引API的使用需要借助一个记录管理器的组件(Record Manager),以跟踪每个知识块的源文档ID、hash指纹以及时间戳等。这里直接给出参考代码:

from langchain.indexes import SQLRecordManager, index
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
from langchain_text_splitters import CharacterTextSplitter
from langchain_community.document_loaders import DirectoryLoader

#嵌入模型、向量库
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vector_store = Chroma(
    collection_name="example_collection",
    embedding_function=embeddings,
    persist_directory="./db_chroma"
)

#记录管理器,用来跟踪向量库中已经存储的Document(hash、时间戳、source_id)
namespace = f"chroma/mydocs"
record_manager = SQLRecordManager(
    namespace, db_url="sqlite:///record_manager_cache.sql"
)
record_manager.create_schema()

#文档加载与分割
loader = DirectoryLoader("../data",glob='*.txt')
docs = loader.load()
docs = CharacterTextSplitter(separator='\n',chunk_size=30,chunk_overlap=2).split_documents(docs)

#向量话并索引
result = index(
    docs,
    record_manager,
    vector_store,
    cleanup='incremental',
    source_id_key="source",
)

#打印处理情况
print(result)

这里的核心区别就在于index()方法的使用。该方法除了需要输入处理的块(docs)、向量库(vector_store)、记录管理器(record_manager)、表示源文档ID的key名(source_id_key)外,还有一个cleanup参数,该参数决定了LangChain对向量库中现有知识块的清理方式,支持三种方式。三种方式都会根据hash值跳过重复块,并插入新知识块,但对已有块的清理方式则有区别

  • none:不会对已有块做任何清理动作

  • incremental:如果源文档知识块发生了变更(出现新的块hash指纹),则会清除知识块的旧版本

  • full:如果源文档知识块发生了变更(出现新的块hash指纹),或者做了部分块的删除(注意此时未出现新的hash指纹),都会清除知识块的旧版本


也就是说incremental与full的区别在于:如果源文档中只有部分知识块被删除(即不包含在当前正在被索引的知识块中),incremental模式不会从向量库中清除这些部分知识块,但full模式会清除。

我们用上面的代码样例来对这两种模式做详细测试,假设为如下的知识文档内容创建向量索引:

首次处理后的结果信息如下(无论incremental或full模式),由于采用了按行分割,所以添加了3个知识块:

现在我们把知识内容修改成如下,即删除了最后一行,并修改了第二行:

重新运行上面的代码(无论incremental或者full),处理信息如下:

这里跳过了第一行对应的chunk(num_skipped=1),新增了第二行对应的chunk(num_added=1),并且删除了原来的第二行与第三行对应的chunk(num_deleted=2)。可以看到,由于这里出现了知识块的修改(第二行),所以incremental与full模式效果一致。

现在让我们再直接删除第二行,输入文件变成:

此时,两种模式下的处理就会有区别:

incremental清理模式:由于删除了第二个chunk,但是并未出现新的chunk指纹,所以不会做清理动作,只会跳过第一个重复块(num_skipped=1):

full清理模式:不仅会跳过第一个重复块(num_skipped=1),还会删除掉第二个chunk(num_deleted=1):


【LlamaIndex框架的数据摄入管道】

如果你采用LlamaIndex框架,则需要借助LlamaIndex中的数据摄入管道来实现知识增量更新,并指定文档存储(docstore)以及文档存储策略(docstore_strategy),核心代码如下:

......
pipeline = IngestionPipeline(
    transformations=[
        TokenTextSplitter(chunk_size=20, chunk_overlap=0,separator="\n"),
        embedded_model
    ],
    vector_store=vector_store,
    docstore=RedisDocumentStore.from_host_and_port("localhost"6379, namespace="document_store"),
    docstore_strategy='upserts'
)

docs = SimpleDirectoryReader(input_files=["../data/datafile1.txt"],filename_as_id=True).load_data()
nodes = pipeline.run(documents=docs,show_progress=False)
......

更多的信息可以参考LlamaIndex的官方文档。


04

GraphRAG的增量更新

Graph RAG是最近的一个热点,借助于知识图谱与图数据库对知识中的实体与关系进行组织与表示,同时结合向量检索、社区识别算法等实现复杂知识关系的检索与答案生成。实现Graph RAG的一种方式是借助成熟框架如Microsoft GraphRAG,但目前尚未能够实现增量更新。由于涉及到图与社区等高级数据结构,GraphRAG的知识增量更新要比普通RAG更复杂。

这里推荐一个开源的nano-GraphRAG框架,这是一个保留了Microsoft GraphRAG核心功能,但又更轻量级、更简洁的版本,且提供了一定的知识增量更新的能力。其核心思想也是借助对原始文档与知识块的hash值做分析,识别出需要添加的新知识块,并在上一次生成的Graph图基础上进行图的增量更新,插入新的实体与关系。

nano-GraphRAG也提供了社区识别与生成的功能,所以会在每次图的增量更新基础上,重新进行社区信息的生成,但社区信息的增量更新目前尚未实现,即每次都会对所有社区信息做识别与生成。

有兴趣的朋友可以在Github搜索该项目以了解细节,我们将在后续对该项目进行深入研究与测试。


05

问题

以上探讨了RAG应用中常见的一个知识文档增量更新的问题,这对于企业级的RAG应用、存在大量经常变化的知识文档的场景下的快速同步与降低成本有重要的意义。当然仍然有一些问题可以做进一步优化与思考,比如:

  • 基于Chunk指纹来识别知识变化,在简单的基于固定chunk_size分割的RAG应用中,一点小的中间内容变化可能导致大量分割后的chunk的hash指纹发生变化,从而影响增量更新效果。


  • 可能存在少量知识文本发生变化,但实际语义并未发生变化的场景,这也会带来一些无效的更新。但如果借助LLM来识别语义是否变化,又会带来新的性能与成本消耗。


  • 针对复杂知识结构或者知识索引的增量更新。比如多模态的复杂知识文档的增量更新,如何更有效且高效的识别知识变化;以及除了向量索引之外的其他形式索引的增量更新,如上文提到的Graph Index等。


  • 在实际企业应用中,知识文档的动态更新可能需要结合数据特点与业务要求制定更灵活的策略:对于实时性要求较高的数据可以使用更频繁的动态更新策略;而对于实时性要求较低或变化频次很低的数据可以采用更简单的批量更新策略。


相信未来这些问题都会有更完善的解决方案。

53AI,企业落地大模型首选服务商

产品:场景落地咨询+大模型应用平台+行业解决方案

承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

添加专属顾问

回到顶部

加载中...

扫码咨询

扫码登录
登录即表示您同意《53AI网站服务协议》
服务协议

欢迎您使用【53AI 官方网站】(以下简称“本网站”或“我们”)。本《会员服务协议》(以下简称“本协议”)是您(以下简称“会员”或“用户”)与【深圳市博思协创网络科技有限公司】之间关于注册、登录及使用本网站会员服务所订立的法律协议。

在您注册或登录前,请务必审慎阅读、充分理解各条款内容,特别是免除或限制责任的条款、知识产权条款、争议解决条款等。此类条款将以加粗形式提示您注意。 当您通过微信公众号授权、手机验证码验证或其他方式成功登录本网站时,即视为您已完全理解并同意接受本协议的全部内容。

一、 定义

本网站:指由【深圳市博思协创网络科技有限公司】运营的,域名为【53ai.com】的网站及相关移动端页面。

会员服务:指本网站向注册会员提供的知识库文章查阅、内容检索及其他相关增值服务。

知识库内容:指本网站发布的包括但不限于文字、图表、数据、研究报告、行业分析等数字化内容资源。

二、 账号注册与登录

登录方式:本网站支持以下登录方式,您可根据实际情况选择:

微信公众号授权登录:您同意将您的微信OpenID信息授权给本网站,用于创建或关联会员账号。

手机验证码登录:您需提供真实有效的手机号码,并通过短信验证码完成身份验证与登录/注册。

账号安全:您的账号仅限您本人使用,禁止赠与、借用、租用、转让或售卖。因您保管不善导致的账号被盗、密码泄露等损失,由您自行承担。

实名认证:根据相关法律法规要求,我们可能要求您在特定功能下完成实名认证。如您拒绝提供,可能无法使用部分或全部服务。

未成年人保护:若您未满18周岁,请在法定监护人的陪同下阅读本协议,并在征得监护人同意后使用本服务。

三、 服务内容与规范

知识库查阅权限:会员登录后,有权按照其会员等级对应的权限范围,在线浏览、检索本网站知识库中的相关文章及内容。

服务变更:我们有权根据业务发展需要,调整、变更或终止部分服务内容,并将以网站公告、公众号消息等方式提前通知。

禁止行为:您在使用服务时不得实施以下行为:

利用技术手段批量爬取、下载、转存知识库内容;

将知识库内容用于商业目的或未经授权地向第三方传播;

干扰本网站正常运行或侵犯其他用户合法权益;

发布违法违规信息或从事违反公序良俗的活动。

四、 知识产权声明

权利归属:本网站知识库中的排版设计、软件代码等内容的知识产权均归【公司全称】或原权利人所有,受《中华人民共和国著作权法》等法律保护。

有限许可:本网站授予会员一项非独占、不可转让、不可转授权的普通许可,仅限于个人学习、研究之目的在线查阅知识库内容。

侵权追责:未经书面许可,任何单位或个人不得以任何形式复制、转载、摘编、镜像、汇编或以其他方式使用上述内容。一经发现,我们保留追究其法律责任的权利。

五、 个人信息保护

我们重视对您个人信息的保护。关于我们如何收集、使用、存储和保护您的个人信息,请单独阅读 《隐私政策》。

您通过微信公众号授权或手机号验证所提供的信息,我们将严格按照《个人信息保护法》的规定处理,仅用于身份识别、服务提供及安全验证等必要用途。

您可以随时通过网站设置或联系客服行使查阅、更正、删除个人信息及撤回授权同意的权利。

六、 免责声明

内容准确性:知识库内容仅供参考,不构成专业建议。我们不对其完整性、准确性、时效性作任何明示或暗示的保证,您应自行判断并承担使用风险。

不可抗力:因自然灾害、政策法规变化、网络故障、第三方平台接口异常(如微信接口维护、运营商短信通道故障)等不可抗力导致的服务中断或延迟,我们不承担违约责任。

第三方链接:本网站可能包含指向第三方网站的链接,该等网站的内容和服务不受我们控制,请您自行甄别风险。

七、 违约责任

如您违反本协议约定,我们有权视情节采取警告、限制功能、暂停服务、注销账号等措施,并保留要求赔偿损失的权利。

如因您的违约行为导致我们遭受行政处罚、第三方索赔或商誉损失,您应承担全部赔偿责任(包括但不限于罚款、赔偿金、律师费、公证费等)。

八、 法律适用与争议解决

本协议的订立、执行和解释均适用中华人民共和国大陆地区法律。

因本协议产生的或与本协议有关的任何争议,双方应友好协商解决;协商不成的,任何一方均可向【公司所在地】有管辖权的人民法院提起诉讼。

九、 其他

本协议构成双方就本服务达成的完整协议,取代此前任何口头或书面约定。

本协议任一条款被认定为无效或不可执行的,不影响其他条款的效力。

我们对本协议享有最终解释权,并在法律允许的范围内保留随时修改的权利。修改后的协议一经公布即生效,继续使用服务即视为同意修订内容。


已查阅