AI实战:编写RAG发力自助模牌室运营

今年有"爱理财的小羊"天天亏,但有鸡爪卖几个亿,豪车好物随便带货,让我看明白了"被做空"也是能赚的。年初看到自动棋牌室,本来是想投资的, 错过了, 写篇AI运营棋牌室的小文吧, 打发下2023的最后一周...

前言

你好,我是旅梦。最近边学习AI, 边写了一些AI系列的文章。现在,我们基于之前对LangChain的理解,一起来动手实战!

LlamaIndex 一 简单文档查询 - 掘金 (juejin.cn)里,我们编写了一个基于本地文档的问答系统。从那一刻开始,我们会开发RAG应用了。

在我最近的一系列文章中,多次聊到RAG。它是现在AI产品的主要形式之一,全称Retrieval-Augmented Generation,即检索增强生成。它会在LLM知识库之外,检索外部知识,使LLm在生成内容时可以动态地从知识库检索相关内容。RAG能增强LLM的生成能力,让内容在外部知识的加持下更丰满且准确,特别是"专家库"、"新闻"等。

工作原理

  • 检索

对于用户的输入,模型首先会从外部知识库中查找出相关的文档和段落。我们会基于ChromaDB或Faiss这样的向量数据库来构建这个检索系统。

  • 编码

找到相关的文档或段落后,LLM将它们与输入一起编码

  • 生成

大模型通过相关的文档和段落,以及用户提问,生成答案。

RAG 的优点是,它让我们可以从LLM外部知识库中检索信息,使AI结合本身业务产品更好落地。增强了模型能力,又贴合了自身业务。RAG类的业务,在企业中的需求量特别大,也是LangChain开发的主要产品方向之一。

开发步骤

  • 文档加载

AI助力之前,首先要是处理各种文档,先加载。LangChain提供了各种类型的文档加载器,可以将企业的各种HTML、PDF、代码、文本归档加载,以前我们是做大数据 ,现在更牛,我们将这些数据交给大模型,LLM更懂这些数据,更好的完成我们指派的任务。

图片中包括了我们常用的Text、CSV、HTML、JSON、Markdown、PFD等加载器, 加载后的文件成为LangChain的Document对象。

  • 文本转换

要将文本内容转换成LLM能理解的格式,即向量化。   首先,我们将文档分割成小块,因为LLM 的传输上下文有大小限制,方便embedding计算。

  1. 将文本拆分成小的,更好语义的文本块,方便之后的检索匹配。   比如用户的文章在这个文件的那个部分,甚至哪个自然段中。一般是句子。
  2. 将这些小块组成一个大块,直到到达我们切块时定义的大小,即chunk_size。
  3. 各个块之间保持一部分的重叠, 方便带来更好的上下文,即块之间的关系。chunk_overlap

基于以上对文本块分割的理解 ,LangChain需要考虑以下业务:

  1. 怎么样对文本分块?   最普通的是字符分割器Character TextSplitter。很好理解,按字符来分割,换行符\n\n特别适合做句子的分割,分块计算以字符数为单位。
python 复制代码
from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(separator="\n\n", chunk_size=1000,chunk_overlap=200,length_function=len,is_separator_regex=False )

建议大家在这里慢下来,这是理解RAG检索优于传统检索的关键所在。separator1的值为\n\n,定义了最小块以句子来分割,方便检索时,LLM向量计算以句子为单位,更准确。chunk_size值为1000, 这是一个Node的大小,即向量存储一个单位的大小。chunk_overlap值为200,块与块之间保有足够的上下文关联,因为用户想要的东西可能在上一个块的尾部没有结束,下一个块的开始,所以这种情况下,chunk_overlap可以保证检索的连续不被chunk_size武力打断,length_function值为len,以字符来分割嘛,那算长度当然是len这个python函数。is_separator_regex没有启用,我们还是用的朴素的\n\n来分割。

有CharacterTextSplitter打底,其它的就好理解了。继续上菜,RecursiveCharacterTextSplitter。它和CharacterTextSplitter差不多,区别之处在于可以接受一个分隔符列表,可以按照这个列表逐一尝试将每个chunk 分割成足够小的块。感觉有点庖丁解牛的感觉。默认的分割列表是["\n\n", "\n", " ", ""]。

python 复制代码
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter( chunk_size=1000,chunk_overlap=200,length_function=len,is_separator_regex=False )

上面的demo,我们就没有传seperator,而使用了默认的分割列表,它的分割粒度比CharacterTextSplitter可以细很多。

MarkdownHeaderTextSplitter, 特别适合Markdown格式的文本处理,可以理解为RecursiveCharacterTextSplitter中的分隔列表中包含Markdown的语法符号,如#、##等。

ini 复制代码
from langchain.text_splitter import MarkdownHeaderTextSplitter
text_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
md_headers_splits=markdown_splitter.split_text(markdown_document)

最后,我们来介绍一下TokenTextSplitter,它会按LLM的需要来做分割。

ini 复制代码
from langchain.text_splitter import TokenTextSplitter
text_splitter = TokenTextSplitter(chunk_size=100)

选择哪版分割器,要看我们处理的文档类型,比如Markdown或html,或者csv文件,都有鲜明的分割特征,当然采用更具体的分割器,我们可以把各种分割器理解为基类和派生类的关系,有时间,我们一起看看LangChain的源码。

  1. chunk_size 的设计     分块后,文本是要上传到LLM去进行embedding计算的,所以我们在做chunk_size时,要考虑LLM的上下文大小限制。 以GPT-3.5-turbo 模型为例,上下文窗口(输入token和输出token)的上限是4096个token,超过就会报错。如果在检索的时候,匹配到了n个相关信息块,那么每个块的大小应该小于2048/n, 大家应该就有chunk_size设计的感觉了。     除了数学计算外,token_size的设计还跟业务有关,我们知道,size 大了,粒度不够,可能会影响检索的准确性,太小了,块太多,性能和费用开销就大,如何把握这个火候呢,看业务需求。     如果是拼写检查、代码建议、文本分析、语法检查类的nlp, 分块最好小一些,因为它比较细致。     如果是文章翻译、文本摘要、问答任务等跟整体相关度更大的业务,可以更大些。

  2. doctran 等的介入    分割的同时, 我们就可以做各种手脚了,哈哈。比如通过EmbeddingsRedundantFilter可以帮助我们识别相似的文档,以节省时间空间。doctran可以帮助我们在分割的同时,完成翻译,为出海项目服务。我们还可以为Document对象提供更多的文件或块元数据,方便描述或操作。假如我们在做问答类项目的时候,我们可以使用doctran将文档转化为Q/A格式

所以,当文档以不同的格式被load进来后(Document),文档转换对文本进行分割,同时还可以附加一些操作,准备好这些数据块(Node), 以后面的索引(index)和检索服务。

  • 文本嵌入

    文本块有了, 结合LLM来做嵌入(Embeddings),因为大模型里的数据计算不是基于文本的, 而是基于向量的,在这里我们可以简单理解为将文本数据转换为数值表示,更方便处理和比较。

如果大家还不太熟悉,建议先看看Embedding相关的文档,我们需要有向量空间思维才好往下继续。下图大概介绍了比如大模型NPL任务中的分类问题,其实任务概念或事物都会是多维空间中的一个点,我们可以用向量表示,越接近,它们的向量就越接近...

LangChain为我们提供了抽象接口,方便对接各大模型的Embedding接口。

ini 复制代码
from langchain.embeddings import OpenAIEmbeddings
embeddings_model = OpenAIEmbeddings()

它支持两种方法,第一种是embed_documents方法,为文档做嵌入,另一种是embed_query 方法,只接收一个文件,快速为用户构建基于此文件的搜索查询。

实例

embed_documents 主要负责对文档进行embedding并存储。

ini 复制代码
embeddings = embeddings_model.embed_documents( [ "您好,有什么需要帮忙的吗?", "我上午订的吐气场眉包间,但是下午有事,打不了", "您的手机号是?", "13312341234", ])

数组里面是文档哈。

ini 复制代码
embedded_query = embeddings_model.embed_query("用户要退的包间是哪个?")

嵌入的存储

嵌入的计算需要一些时间和token开销,我们将大模型返回的结果存储到内存或向量数据库中,以免重复计算。下次再来时, 直接读取。

  • 缓存CacheBackedEmbeddings

会对文档进行hash处理,得到唯一hash值,做为key, CacheBackedEmbeddings是key:value 存储,值就是向量值。

ini 复制代码
# 导入内存存储,临时存储一下
from langchain.storage import InMemoryStore
# 创建一个实例
store = InMemoryStore()
# 导入 OpenAIEmbeddings CacheBackedEmbeddings
from langchain.embeddings import OpenAIEmbeddings, CacheBackedEmbeddings
# 创建一个OpenAIEmbeddings的实例,做文档嵌入
underlying_embeddings = OpenAIEmbeddings()
# 使用CacheBackedEmbeddings 创建一个embedder 实例,提供缓存功能,和存储空间
embedder = CacheBackedEmbeddings.from_bytes_store( underlying_embeddings, # 负责嵌入 
store, # 嵌入的存储
namespace=underlying_embeddings.model # 区分别的存储的命名空间名字,这里以模型名来)
# embed_documents 接受多个文档进行embedding计算
embeddings = embedder.embed_documents(["打麻将不", "麻和友为你服务"])
  • 向量数据库

LangChain支持多种向量数据库,我比较常用的是Chroma。

检索器

LagnChain提供了Retriever,为用户的查询提供检索的入口,我们来介绍下向量检索器。

ini 复制代码
# 添加OpenAI环境变量
import os
os.environ["OPENAI_API_KEY"] = 'Your OpenAI Key'

# 文本加载器
from langchain.document_loaders import TextLoader
loader = TextLoader('./南昌麻将算子大全.txt', encoding='utf8')

# 创建索引
from langchain.indexes import VectorstoreIndexCreator
index = VectorstoreIndexCreator().from_loaders([loader])

# 定义查询字符串, 使用创建的索引执行查询
query = "庄家58四十霸王精吊德国大七队多少钱?"
result = index.query(query)
print(result) # 打印查询结果

结果是 每年157,总计471个    VectorstoreIndexCreator封装了vectorstore、embedding及text_splitter所以用起来非常方便。

如果要指定,也可以

ini 复制代码
from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
index_creator = VectorstoreIndexCreator(
    vectorstore_cls=Chroma,
    embedding=OpenAIEmbeddings(),
    text_splitter=CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
)

在上面的代码里,我们用到了索引的概念,它帮助我们更好, 更高效的管理和定位文档信息。

总结

  • RAG开发的流程更加清晰
  • 对分块的参数和意义更加了解
  • 结合自助模牌室运营,让我们可以更好的理解怎么将RAG 和行业结合。

参考资料

  • 黄佳老师的LangChain实战课
相关推荐
是十一月末25 分钟前
Opencv实现图片的边界填充和阈值处理
人工智能·python·opencv·计算机视觉
机智的叉烧1 小时前
前沿重器[57] | sigir24:大模型推荐系统的文本ID对齐学习
人工智能·学习·机器学习
凳子花❀1 小时前
强化学习与深度学习以及相关芯片之间的区别
人工智能·深度学习·神经网络·ai·强化学习
泰迪智能科技013 小时前
高校深度学习视觉应用平台产品介绍
人工智能·深度学习
盛派网络小助手3 小时前
微信 SDK 更新 Sample,NCF 文档和模板更新,更多更新日志,欢迎解锁
开发语言·人工智能·后端·架构·c#
Eric.Lee20214 小时前
Paddle OCR 中英文检测识别 - python 实现
人工智能·opencv·计算机视觉·ocr检测
云起无垠4 小时前
第79期 | GPTSecurity周报
gpt·aigc
cd_farsight4 小时前
nlp初学者怎么入门?需要学习哪些?
人工智能·自然语言处理
AI明说4 小时前
评估大语言模型在药物基因组学问答任务中的表现:PGxQA
人工智能·语言模型·自然语言处理·数智药师·数智药学
Focus_Liu4 小时前
NLP-UIE(Universal Information Extraction)
人工智能·自然语言处理