LightRag系统分析

1 项目简介

项目的开源地址:GitHub - HKUDS/LightRAG: "LightRAG: Simple and Fast Retrieval-Augmented Generation"

LightRAG是香港大学开发的一种简单高效的大模型检索增强(RAG)系统。

LightRag基于LLM、VectorDB、和GraphDb构建知识库,将图结构整合到文本索引和检索过程中。

LightRag框架采用了双层检索系统,从低层次和高层次的知识发现中增强了全面信息检索。根据其官方介绍,图结构与向量表示的整合便于高效检索相关实体及其关系,显著提高了响应时间。

2 框架结构

系统的类图

3 组件分析

3.1 LLM组件

在LightRag中,LLM的用途有两用途:Embedding和Query。

Embedding的作用:根据LightRag传的内容计算vector信息,LightRag根据vector存储信息到vectordb或从vectordb中查询数据。

支持的Embedding的引擎有:openai_embedding、siliconcloud_embedding、bedrock_embedding、hf_embedding、ollama_embedding

Query的作用有三种:

  1. 根据分析文本内容之间的关系的prompt进行分析文本
  2. 提取问题关键词,lightrag根据关键从知识库中查找与之相关的内容,进行知识召回
  3. 根据LightRag使用召回的知识和要问的问题进行内容生成。

支持Query的引擎有:hf_model_complete、ollama_model_complete、bedrock_complete、gpt_4o_complete、gpt_4o_mini_complete、azure_openai_complete

3.2 kv组件

kv的有两种:

全量文件full_docs,存储应用直接传过来的文件,key为文本的hashid,value为文本内容。

chunk文件text_chunks,由于传过的文本可能超长,所以要先计算token是否超长,如果超了,就要对文本执行chunk操作,text_chunks存储的就是chunks之后的文本。

3.3 vector组件

vectordb存储的内容有三种:

chunk_vdb存储chunk文本和chunk文本的embedding信息

entity_vdb存储entity和entity文本的embedding信息

relation_vdb存储relation和relation相关文本的embedding信息

3.4 graph组件

chunk_entity_relation_graph用于图形化存储实体数据,以及实体之间的关系数据

4 处理流程分析

相关原代码的分析参考https://gitee.com/tmfll/LightRAG.git的reviewbranch中的注释

4.1 构建知识库

  1. 初始化LightRag环境,指定Embedding函数、LLM_func回调函数、LLM和Embedding使用的模型,LLM目标服务的访问地址。其中nde2vee_params中所述的dimensions的值,须和参数中的embedding_dims的值相同。
    rag = LightRAG(
        working_dir=WORKING_DIR,
        llm_model_func=ollama_model_complete,
        llm_model_name="qwen2m",
        llm_model_max_async=4,
        llm_model_max_token_size=32768,
        llm_model_kwargs={"host": "http://192.168.8.156:11434", "options": {"num_ctx": 32768}},
        node2vec_params={"dimensions": 768,
                "num_walks": 10,
                "walk_length": 40,
                "window_size": 2,
                "iterations": 3,
                "random_seed": 3,},
        embedding_func=EmbeddingFunc(
            embedding_dim=768,
            max_token_size=8192,
            func=lambda texts: ollama_embedding(
                texts, embed_model="nomic-embed-text", host="http://192.168.8.156:11434"
            ),
        ),
    )
  2. 应用首先读取文件,应用在读取文件时是按页,或是按行,或是按段,并不作特定的要求。应用读取内容后,循环调用LightRag的insert方法。
  3. lightrag的循环插入数据构建知识库逻辑如下:
    1. 存储doc_text全量信息,计算chunk和存储chunk
      1. 因为读取的内容传入进来的,可以是一个字符串,也可能是字符串例表。所以在判断的时候,如果普通的字符串要先转成列表。
      2. 遍历字符串列表,计算读取内容的hashId
      3. 从full_docs中,查看是否有相同的hashid存在,如果没有相同的存在,就执行下面的动作,否则过滤掉相同的内容。
      4. 将计算传入的文本的token数量是否超长,若超长就拆分出chunk列表,遍历chunk列表,检查text_chunks中是否有相同的chunk存在,若不存在的话,就插入到text_chunks中。
      5. 遍历新增的text_chunks中,向llm embedding服务请求计算embedding信息,拿到embedding信息后,将text_chunks和与之相关的embedding信息填入数据库中。
    2. 利用LLM提取知识
      1. 遍历新增的text_chunk信息,使用entity_extraction模板,拼装prompt信息(prompt中约定好提取内容的格式),并调用llm_model_func向LLM引擎发送指令,llm_model_func返回提取的entity和relation报文。
      2. 为防止提取的内容没有完全提取,组装历史消息,使用continue_prompt和历史消息继续向LLM 发送提取指令,直至使用entiti_if_loop_extraction向LLM查询时,返回yes或迭代次数达到了预设的值(entity_extract_max_gleaning默认为1)。
    3. 解析知识,并存储到graph数据库和vector数据库
      1. 根据约定的格式内容,从上述提取的内容中,分解出entity和relation,
      2. 进行entity合并和relation合并。将entity和relation信息,分别写到entity_graph和relation_graph中
      3. 向LLM请求计算提取到的entity和relation的embedding值,将entity和relation及相对应的embedding值,填写到entity_vdb和relation_vdb中。

4.2 检索知识库

  1. 初始化LightRag环境,指定Embedding函数、LLM_func回调函数、LLM和Embedding使用的模型,LLM目标服务的访问地址。其中nde2vee_params中所述的dimensions的值,须和参数中的embedding_dims的值相同。
  2. 应用调用LightRag的query方法,发送查询的内容。查询的逻辑分为四种:
    1. global_query: 通过vdb和graphdb查询relation的信息,构建prompt,向LLM获得最终的结果
    2. local_query:通过vdb和graphdb查询entity的信息,构建prompt,向LLM获得最终的结果
    3. hybrid_query:通过vdb和graphdb查询relation和entity的信息,构建prompt,向LLM获得最终的结果
    4. naive:从chunk中查询信息,构建prompt,向LLM获得最终的结果
  3. global_query的promt的组装逻辑:
    1. 使用keywords_extraction模板和query内容,拼装prompt,并并调用llm_model_func向LLM引擎发送prompt,获得keywords,返回的结果一般状况下应该是一个json格式,如不是json格式,需要转换为json格式,将json格式转换为字典,从字典中获得high_level_keywords
    2. 将获得的high_level_keywords调用embedding接口,获得embedding vector,然后使用该vector,从relation_vdb中获得relation信息。
    3. 根据获得的relation信息,从chunk_entity_relation_graph中获得relation的信息
    4. 根据relation的图信息从chunk_entity_relation_graph中获得相关的entitiy节点信息。
    5. 根据relaiton信息,从text_chunk_db中获得chunk信息。
    6. 拼接3~5获得的信息中的文本,返回csv格式。
    7. 根据模板rag_response和拼接的召回知识,生成prompt信息。
    8. 调用llm_model_func向LLM引擎发送消息,获得应答。其中prompt该prompt作为system_prompt参数,query作为用户的消息参数。
  4. local_query的promt的组装逻辑:
    1. 使用keywords_extraction模板和query内容,拼装prompt,并并调用llm_model_func向LLM引擎发送prompt,获得keywords,返回的结果一般状况下应该是一个json格式,如不是json格式,需要转换为json格式,将json格式转换为字典,从字典中获得low_level_keywords
    2. 将获得的low_level_keywords调用embedding接口,获得embedding vector,然后使用该vector,从entity_vdb中获得entity信息
    3. 根据获得的entity信息,从chunk_entity_relation_graph中获得entity的节点信息,并根据该节点的边的数量,计算该节点的degree
    4. 根据entity节点,获得与该节点相关的relation边的信息,并根据degree和weight的值进行排序,并根据token的长度的限制,选择排名靠前的的relation的信息
    5. 找出entity节点的邻居节点列表,根据entity的节点的id,从text_chunk_db中根据id找出相对应的文本信息
    6. 将3~5中所获得的信息拼成csv信息
    7. 根据模板rag_response和拼接的召回知识,生成prompt信息
    8. 调用llm_model_func向LLM引擎发送消息,获得应答。其中prompt该prompt作为system_prompt参数,query作为用户的消息参数
  5. hybrid_query的promt的组装逻辑:
    1. 在生成prompt的过程中,可以看成是上述两种逻辑的综合组装prompt,该prompt作为system_prompt参数,query作为用户的消息参数,向LLM引擎发送prompt信息,获得应答。

5 存在的问题

       代码逻辑总体清晰,易于理解,安装的依赖包相比较其它的Rag少很多,易于使用,处理英文时效果较佳。

     个人认为还存在以下的可改进点:

  1. prompt模板为英文描述,在处理中文时,返回的关键词或entity或relation会存在中英文混搭的情况,此时知识的召回的效果较差。若在构建知识库时,根据LLM query提取的entity或relation的文本去计算Embedding的语言类型和进行检索时根据query获得的keyword的语言类型不一致时,会导致检索失败,无法进行知识的召回。针对中文的情况,需要额外进行调优。

  2. 在chunk语句时,简单的根据Token的最大长度进行chunk,导致语句不完整,特别在中文环境下,容易导致出现无法识别的字符。

  3. 返回的实体中,存在并不是文本中的内容,但实际上是prompt中举例的内容,也可以LLM自己猜的内容。代码在处理的逻辑中,并未将这些与事实上无关的文本信息过滤。

  4. 没有并行处理的逻辑,当文本较大时,处理耗时较久。

  5. 在处理图数据库时的逻辑似乎并不够高效,存在反复处理的逻辑。节点和Relation之间的关系的设置和检索可以更高效。

  6. 本库在实际工程化的时候可以考虑以下改进点:例如进行文本的切分首先构建全量的普通的文档的chunk库,此进可以进行在进行知识检索的时候,先使用naive模式进行知识的召回;再根据chunk库构建vector db和graph db,在知识图谱构建完成后,可以根据相关的知识库进行更准确的知识召回 。

验证过程

    相关的环境已打容器镜像:swr.cn-north-4.myhuaweicloud.com/tiger202203/lightrag-cuda-conda-11:121-ubuntu2204

    验证过程如下:

  1. 下载基础镜像swr.cn-north-4.myhuaweicloud.com/tiger202203/cuda-conda-11:121-ubuntu2204-c036

  2. 创建容器

  3. conda create -n lightrag python=3.11

  4. 执行pip install -r requirement.txt

  5. python setup.py install

  6. 按照官方文档安装ollama,下载qwen模型和nomic-embed-text

  7. 在example目录下,参考lightrag_ollama_demo.py,设置LightRag参数和要分析的文档,创建知识库。本代码分析的文档是《三国演义》

    import os                                                                                                                                                                                                        
    import logging                                                                                                                                                                                                   
    from lightrag import LightRAG, QueryParam                                                                                                                                                                        
    from lightrag.llm import ollama_model_complete, ollama_embedding                                                                                                                                                 
    from lightrag.utils import EmbeddingFunc                                                                                                                                                                         
                                                                                                                                                                                                                     
    WORKING_DIR = "./dickens"                                                                                                                                                                                        
                                                                                                                                                                                                                     
    logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.INFO)                                                                                                                                      
                                                                                                                                                                                                                     
    if not os.path.exists(WORKING_DIR):                                                                                                                                                                              
        os.mkdir(WORKING_DIR)                                                                                                                                                                                        
                                                                                                                                                                                          
                                                                                                                                                                                                                     
    rag = LightRAG(                                                                                                                                                                                                  
        working_dir=WORKING_DIR,                                                                                                                                                                                     
        llm_model_func=ollama_model_complete,                                                                                                                                                                        
        llm_model_name="qwen2m",                                                                                                                                                                                     
        llm_model_max_async=4,                                                                                                                                                                                       
        llm_model_max_token_size=32768,                                                                                                                                                                              
        llm_model_kwargs={"host": "http://127.0.0.1:11434", "options": {"num_ctx": 32768}},                                                                                                                          
        node2vec_params={"dimensions": 768,                                                                                                                                                                          
                "num_walks": 10,                                                                                                                                                                                     
                "walk_length": 40,                                                                                                                                                                                   
                "window_size": 2,                                                                                                                                                                                    
                "iterations": 3,                                                                                                                                                                                     
                "random_seed": 3,},                                                                                                                                                                                  
        embedding_func=EmbeddingFunc(                                                                                                                                                                                
            embedding_dim=768,                                                                                                                                                                                       
            max_token_size=8192,                                                                                                                                                                                     
            func=lambda texts: ollama_embedding(                                                                                                                                                                     
                texts, embed_model="nomic-embed-text", host="http://127.0.0.1:11434"                                                                                                                                 
            ),                                                                                                                                                                                                       
        ),                                                                                                                                                                                                           
    )                                                                                                                                                                                                                
                                                                                                                                                                                                                     
    with open("./data/sgyy.txt", "r", encoding="utf-8") as f:                                                                                                                                                        
        rag.insert(f.read())
  8. 在example目录下,参考lightrag_ollama_demo.py,设置LightRag参数和和查询的语句,进行知识检索。

    import os                                                                                                                                                                                                        
    import logging                                                                                                                                                                                                   
    from lightrag import LightRAG, QueryParam                                                                                                                                                                        
    from lightrag.llm import ollama_model_complete, ollama_embedding                                                                                                                                                 
    from lightrag.utils import EmbeddingFunc                                                                                                                                                                         
                                                                                                                                                                                                                     
    WORKING_DIR = "./dickens"                                                                                                                                                                                        
                                                                                                                                                                                                                     
    logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.INFO)                                                                                                                                      
                                                                                                                                                                                                                     
    if not os.path.exists(WORKING_DIR):                                                                                                                                                                              
        os.mkdir(WORKING_DIR)                                                                                                                                                                                        
                                                                                                                                                                                                                     
                                                                                                                                                                                                                     
    rag = LightRAG(                                                                                                                                                                                                  
        working_dir=WORKING_DIR,                                                                                                                                                                                     
        llm_model_func=ollama_model_complete,                                                                                                                                                                        
        llm_model_name="qwen2m",                                                                                                                                                                                     
        llm_model_max_async=4,                                                                                                                                                                                       
        llm_model_max_token_size=32768,                                                                                                                                                                              
        llm_model_kwargs={"host": "http://192.168.8.156:11434", "options": {"num_ctx": 32768}},                                                                                                                      
        node2vec_params={"dimensions": 768,                                                                                                                                                                          
                "num_walks": 10,                                                                                                                                                                                     
                "walk_length": 40,                                                                                                                                                                                   
                "window_size": 2,                                                                                                                                                                                    
                "iterations": 3,                                                                                                                                                                                     
                "random_seed": 3,},                                                                                                                                                                                  
        embedding_func=EmbeddingFunc(                                                                                                                                                                                
            embedding_dim=768,                                                                                                                                                                                       
            max_token_size=8192,                                                                                                                                                                                     
            func=lambda texts: ollama_embedding(                                                                                                                                                                     
                texts, embed_model="nomic-embed-text", host="http://192.168.8.156:11434"                                                                                                                             
            ),                                                                                                                                                                                                       
        ),                                                                                                                                                                                                           
    )                                                                                                                                                                                                                
                                                                                                                                                                                                                     
    # Perform local search                                                                                                                                                                                           
    print("-----------------------------------------------------------------------------")                                                                                                                           
    print('global 刘备是谁:',rag.query("刘备是谁?", param=QueryParam(mode="global")))                                                                                                                       
    print("*****************************************************************************")                                                                                                                           
    print('local 刘备是谁:',  rag.query("刘备是谁?", param=QueryParam(mode="local")))                                                                                                                        
    print("############################################################################")                                                                                                                            
    print('hybrid 刘备是谁:', rag.query("刘备是谁?", param=QueryParam(mode="hybrid")))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值