RAG:检索增强生成是什么?

RAG的主要流程

Retrieval-Augmented Generation: 检索增强生成

能够根据问题的特点还有上下文, 生成更加个性化和精确的回答

  • 为LLM提供来自外部知识源的额外信息的概念。这允许它们生成更准确和有上下文的答案,同时减少幻觉
  • (1)检索:外部相似搜索 (2)增强:提示词更新 (3)生成:更详细的提示词输入LLM
  • 主要流程: 输入问题-> Retrieval检索向量数据库 -> 得到Context上下文 -> 生成详细的prompt提示 -> 再根据LLM生成对应的回答 -> 最后返回给用户

LangChain中RAG中的Retrieve实现

  • Source: 很多数据源的支持
    • github
    • YouTube
    • discord
    • Twitter
    • PPT等等
  • Loader: 对Source数据源Load到LangChain这个系统里面
  • Transform: 对数据进行向量转化, 对不同数据格式进行转化
  • Embedding: 向量化, 嵌入到向量空间
  • Store: 将向量数据存储到向量数据库里面
  • Retrieve: 通过Retrieve方式进行检索

Loader: 让大模型具备实时学习能力

Loader加载MarkDown文件

# 我是一个markdown加载示例
- 第一项目
- 第二个项目
- 第三个项目

## 第一个项目
AI研习社最厉害专业的AI研究基地

## 第二个项目
AIGC打造未来AI应用天地

## 第三个项目
AI研习社是一个非常牛逼的AI媒体
#使用loader来加载markdown文本
from langchain.document_loaders import TextLoader

loader = TextLoader("loader.md")
loader.load()

输出:

[Document(page_content='# 我是一个markdown加载示例\n- 第一项目\n- 第二个项目\n- 第三个项目\n\n## 第一个项目\nAI研习社最厉害专业的AI研究基地\n\n## 第二个项目\nAIGC打造未来AI应用天地\n\n## 第三个项目\nAI研习社是一个非常牛逼的AI媒体', metadata={'source': 'loader.md'})]

Loader加载CVS文件

loader.csv

#使用loader来加载cvs文件
from langchain.document_loaders.csv_loader import CSVLoader

#loader = CSVLoader(file_path="loader.csv")

loader = CSVLoader(file_path="loader.csv",source_column="Location")
data = loader.load()
print(data)

输出:

[Document(page_content='\ufeffProject: AI GC培训\nDES: 培训课程\nPrice: 500\nPeople: 100\nLocation: 北京', metadata={'source': '北京', 'row': 0}), Document(page_content='\ufeffProject: AI工程师认证\nDES: 微软AI认证\nPrice: 6000\nPeople: 200\nLocation: 西安', metadata={'source': '西安', 'row': 1}), Document(page_content='\ufeffProject: AI应用大会\nDES: AI应用创新大会\nPrice: 200门票\nPeople: 300\nLocation: 深圳', metadata={'source': '深圳', 'row': 2}), Document(page_content='\ufeffProject: AI 应用咨询服务\nDES: AI与场景结合\nPrice: 1000/小时\nPeople: 50\nLocation: 香港', metadata={'source': '香港', 'row': 3}), Document(page_content='\ufeffProject: AI项目可研\nDES: 可行性报告\nPrice: 20000\nPeople: 60\nLocation: 上海', metadata={'source': '上海', 'row': 4})]

Loader加载Excel文件

安装插件:

! pip install "unstructured[xlsx]"

示例:将example目录下的所有*.xlsx文件都加载进来

#某个目录下,有excel文件,我们需要把目录下所有的xlxs文件加载进来
#! pip install "unstructured[xlsx]"

from langchain.document_loaders import DirectoryLoader

#目录下的.html和.rst文件不会被这种loader加载
#loader = DirectoryLoader("目录地址",glob="指定加载说明格式的文件")
loader = DirectoryLoader(path="./example/",glob="*.xlsx")
docs = loader.load()
len(docs)

Loader加载HTML

#使用loader来加载html文件
#from langchain.document_loaders import UnstructuredHTMLLoader

# 包含代码
#loader = UnstructuredHTMLLoader("loader.html")
# 只加载文本信息
from langchain.document_loaders import BSHTMLLoader
loader = BSHTMLLoader("loader.html")
data = loader.load()
data

Loader加载json数据

安装插件:

! pip install jq
#使用loader来加载json文件
#需要先安装 ! pip install jq

from langchain.document_loaders import JSONLoader
loader = JSONLoader(
    file_path = "simple_prompt.json",jq_schema=".template",text_content=True
)
data = loader.load()
print(data)

Loader加载pdf文件

安装:

! pip install pypdf
#loader加载pdf文件

from langchain.document_loaders import PyPDFLoader
loader = PyPDFLoader("loader.pdf")
pages = loader.load_and_split()
pages[0]

LangChain文档转换实战

  • 文档切割器和按字符分割
  • 代码文档分割器
  • 按token分割文档
  • 文档总结、精炼、翻译

原理:

  1. 将文档分成小的、有意义的块(句子).
  2. 将小的块组合成为一个更大的块,直到达到一定的大小.
  3. 一旦达到一定的大小,接着开始创建与下一个块重叠的部分.

第一个文档切割

from langchain.text_splitter import RecursiveCharacterTextSplitter

#加载要切割的文档
with open("test.txt") as f:
    zuizhonghuanxiang = f.read()

#初始化切割器
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=50,#切分的文本块大小,一般通过长度函数计算
    chunk_overlap=20,#切分的文本块重叠大小,一般通过长度函数计算
    length_function=len,#长度函数,也可以传递tokenize函数
    add_start_index=True,#是否添加起始索引
)

text = text_splitter.create_documents([zuizhonghuanxiang])
print(text[0])
print(text[1])

字符串切割

from langchain.text_splitter import CharacterTextSplitter

#加载要切分的文档
with open("test.txt") as f:
    zuizhonghuanxiang = f.read()

#初始化切分器
text_splitter = CharacterTextSplitter(
    separator="。",#切割的标志字符,默认是\n\n
    chunk_size=50,#切分的文本块大小,一般通过长度函数计算
    chunk_overlap=20,#切分的文本块重叠大小,一般通过长度函数计算
    length_function=len,#长度函数,也可以传递tokenize函数
    add_start_index=True,#是否添加起始索引
    is_separator_regex=False,#是否是正则表达式
)
text = text_splitter.create_documents([zuizhonghuanxiang])
print(text[0])

代码文档切割

from langchain.text_splitter import (
    RecursiveCharacterTextSplitter,
    Language,
)

#支持解析的编程语言
#[e.value for e in Language]

#要切割的代码文档
PYTHON_CODE = """
def hello_world():
    print("Hello, World!")
#调用函数
hello_world()
"""
py_spliter = RecursiveCharacterTextSplitter.from_language(
    language=Language.PYTHON,
    chunk_size=50,
    chunk_overlap=10,
)
python_docs = py_spliter.create_documents([PYTHON_CODE])
python_docs

按token切割文档

from langchain.text_splitter import CharacterTextSplitter

#要切割的文档
with open("test.txt") as f:
    zuizhonghuanxiang = f.read()

#初始化切分器
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
    chunk_size=4000,#切分的文本块大小,一般通过长度函数计算
    chunk_overlap=30,#切分的文本块重叠大小,一般通过长度函数计算
)

text = text_splitter.create_documents([zuizhonghuanxiang])
print(text[0])

文档的总结、精炼和翻译

安装插件:

! pip install doctran==0.0.14
#加载文档
with open("letter.txt") as f:
    content = f.read()
from dotenv import load_dotenv
import os
load_dotenv("openai.env")
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
OPENAI_API_BASE = os.environ.get("OPENAI_PROXY")
OPENAI_MODEL = "gpt-4"
OPENAI_TOKEN_LIMIT = 8000

from doctran import Doctran
doctrans = Doctran(
    openai_api_key=OPENAI_API_KEY,
    openai_model=OPENAI_MODEL,
    openai_token_limit=OPENAI_TOKEN_LIMIT
)
documents = doctrans.parse(content=content)
#总结文档
summary = documents.summarize(token_limit=100).execute()
print(summary.transformed_content)

输出结果:

#翻译一下文档
translation = documents.translate(language="chinese").execute()
print(translation.transformed_content)

输出结果:

#精炼文档,删除除了某个主题或关键词之外的内容,仅保留与主题相关的内容
refined = documents.refine(topics=["marketing","Development"]).execute()
print(refined.transformed_content)

输出结果:

Lost in the middle 长上下文精度处理问题

如何处理长文本切分信息丢失?

LangChain解决方案: 先将文本碎片化, 然后根据问题将最相关的切分块放在头尾的位置, 这样回答的问题精度会比较高

安装长文本转换器:

! pip install sentence-transformers

长文本分块

from langchain.chains import LLMChain,StuffDocumentsChain
from langchain.document_transformers import (
    LongContextReorder
)
from langchain.embeddings import HuggingFaceBgeEmbeddings
from langchain.vectorstores import  Chroma

#使用huggingface托管的开源LLM来做嵌入,MiniLM-L6-v2是一个较小的LLM 
embedings = HuggingFaceBgeEmbeddings(model_name="all-MiniLM-L6-v2")

text = [
    "篮球是一项伟大的运动。",
    "带我飞往月球是我最喜欢的歌曲之一。",
    "凯尔特人队是我最喜欢的球队。",
    "这是一篇关于波士顿凯尔特人的文件。",
    "我非常喜欢去看电影。",
    "波士顿凯尔特人队以20分的优势赢得了比赛。",
    "这只是一段随机的文字。",
    "《艾尔登之环》是过去15年最好的游戏之一。",
    "L.科内特是凯尔特人队最好的球员之一。",
    "拉里.伯德是一位标志性的NBA球员。"
]

retrieval = Chroma.from_texts(text,embedings).as_retriever(
    search_kwargs={"k": 10}
)
query = "关于我的喜好都知道什么?"

#根据相关性返回文本块
docs = retrieval.get_relevant_documents(query)
docs

输出结果:

根据相关性进行重排

#对检索结果进行重新排序,根据论文的方案
#问题相关性越低的内容块放在中间
#问题相关性越高的内容块放在头尾

reordering = LongContextReorder()
reo_docs = reordering.transform_documents(docs)

#头尾共有4个高相关性内容块
reo_docs

检测精度效果

from dotenv import load_dotenv
load_dotenv("openai.env")
import os

api_key = os.environ.get("OPENAI_API_KEY")
api_base = os.environ.get("OPENAI_API_BASE")

#检测下这种方案的精度效果
from langchain.prompts import PromptTemplate
from langchain.llms import OpenAI

#设置llm
llm = OpenAI(
    api_key=api_key,
    api_base=api_base,
    model="gpt-3.5-turbo-instruct",
    temperature=0
)

document_prompt = PromptTemplate(
    input_variables=["page_content"],template="{page_content}"
)

stuff_prompt_override ="""Given this text extracts:
----------------------------------------
{context}
----------------------------------------
Please answer the following questions:
{query}
"""

prompt = PromptTemplate(
    template=stuff_prompt_override,
    input_variables=["context","query"]
)

llm_chain = LLMChain(
    llm=llm,
    prompt=prompt
)

WorkChain = StuffDocumentsChain(
    llm_chain=llm_chain,
    document_prompt=document_prompt,
    document_variable_name="context"
)

#调用
WorkChain.run(
    input_documents=reo_docs,
    query="我最喜欢做什么事情?"
)

文本向量化实现方式

文本向量化: 一种更高效的检索方式

例如: 向量坐标离的越近越容易一起被搜到, 例如搜索宠物, 那么猫和狗都会被搜索到

引入需要的包

! pip install --upgrade langchain
! pip install --upgrade openai==0.27.8
! pip install -U langchain-openai

查看是否安装完成:

! pip show openai
! pip show langchain
! pip show langchain-openai

测试文本向量化:

from langchain_openai import OpenAIEmbeddings

e_model = OpenAIEmbeddings()
ebeddings = e_model.embed_documents(
     [
        "你好",
        "你好啊",
        "你叫什么名字?",
        "我叫王大锤",
        "很高兴认识你大锤",
    ]
)
ebeddings

测试向量检索:

embedded_query = e_model.embed_query("这段对话中提到了什么名字?")
embedded_query[:5]

嵌入向量缓存

设置缓存到cache目录下

from langchain.embeddings import CacheBackedEmbeddings
from langchain.storage import  LocalFileStore
from langchain.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
u_embeddings = OpenAIEmbeddings()
fs = LocalFileStore("./cache/")
cached_embeddings = CacheBackedEmbeddings.from_bytes_store(
    u_embeddings,
    fs,
    namespace=u_embeddings.model,
)
list(fs.yield_keys())

加载文档, 向量化存入cache缓存中:

#加载文档,切分文档,将切分文档向量化病存储在缓存中

raw_documents = TextLoader("letter.txt").load()
text_splitter = CharacterTextSplitter(chunk_size=600,chunk_overlap=0)
documents = text_splitter.split_documents(raw_documents)

安装faiss包, 主要用于向量算法:

! pip install faiss-cup

大概花费多少时间写入了缓存:

from langchain.vectorstores import  FAISS
%timeit -r  1 -n 1 db= FAISS.from_documents(documents,cached_embeddings)

#查看缓存中的键
list(fs.yield_keys())

ChatDoc: 一个文档检索助手

实现

  • 可以加载PDF或者xsl格式文档
  • 可以对文档进行适当切分
  • 使用openai进行向量化
  • 使用Chomadb实现本地向量存储
  • 使用智能检索实现和文档的对话

安装必要的包

#安装必须的包
! pip install wheel
! pip install docx2txt
! pip install pypdf
! pip install nltk
! pip install unstructured

测试加载文档

  • Docx:
#倒入必须的包
from langchain.document_loaders import Docx2txtLoader

#定义chatdoc
class ChatDoc():
    def getFile():
        #读取文件
        loader = Docx2txtLoader("example/fake.docx")
        text = loader.load()
        return text;

ChatDoc.getFile()

  • pdf:
#导入必须的包
from langchain.document_loaders import PyPDFLoader

#定义chatdoc
class ChatDoc():
    def getFile():
        try:
            #读取文件
            loader = PyPDFLoader("example/fake.pdf")
            text = loader.load()
            return text;
        except Exception as e:
            print(f"Error loading files:{e}")
ChatDoc.getFile()

  • excel:
#导入必须的包
from langchain.document_loaders import UnstructuredExcelLoader

#定义chatdoc
class ChatDoc():
    def getFile():
        try:
            #读取文件
            loader = UnstructuredExcelLoader("example/fake.xlsx",mode="elements")
            text = loader.load()
            return text;
        except Exception as e:
            print(f"Error loading files:{e}")
ChatDoc.getFile() 

  • 整合优化
#导入必须的包
from langchain.document_loaders import UnstructuredExcelLoader,Docx2txtLoader,PyPDFLoader
from langchain.text_splitter import  CharacterTextSplitter


#定义chatdoc
class ChatDoc():
    def __init__(self):
        self.doc = None
        self.splitText = [] #分割后的文本

    def getFile(self):
        doc = self.doc
        loaders = {
            "docx":Docx2txtLoader,
            "pdf":PyPDFLoader,
            "xlsx":UnstructuredExcelLoader,
        }
        file_extension = doc.split(".")[-1]
        loader_class = loaders.get(file_extension)
        if loader_class:
            try:
                loader = loader_class(doc)
                text = loader.load()
                return text
            except Exception as e: 
                print(f"Error loading {file_extension} files:{e}") 
        else:
             print(f"Unsupported file extension: {file_extension}")
             return  None 

    #处理文档的函数
    def splitSentences(self):
        full_text = self.getFile() #获取文档内容
        if full_text != None:
            #对文档进行分割
            text_split = CharacterTextSplitter(
                chunk_size=150,
                chunk_overlap=20,
            )
            texts = text_split.split_documents(full_text)
            self.splitText = texts

chat_doc = ChatDoc()
chat_doc.doc = "example/fake.xlsx"
chat_doc.splitSentences()
print(chat_doc.splitText)

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐