链载Ai

标题: RAG高阶技巧-如何实现窗口上下文检索 [打印本页]

作者: 链载Ai    时间: 昨天 21:15
标题: RAG高阶技巧-如何实现窗口上下文检索

在本文中,我们将介绍一种提高RAG(Retrieval-Augmented Generation)模型检索效果的高阶技巧,即窗口上下文检索。我们将首先回顾一下基础RAG的检索流程和存在的问题,然后介绍窗口上下文检索的原理和实现方法,最后通过一个实例展示其效果。

基础RAG存在的问题及解决方案

基础RAG检索流程

RAG是一种结合了检索和生成的AI应用落地的方案,它可以根据给定的问题生成回答,同时利用外部知识库(例如维基百科)来增强生成的质量和多样性。RAG的核心思想是将问题和知识库中的文档进行匹配,然后将匹配到的文档作为生成模型的输入,从而生成更加相关和丰富的回答。

RAG的检索流程可以分为以下几个步骤:

基础RAG存在的问题

基础RAG的检索流程虽然简单,但是也存在一些问题,主要是在split和retrive两个步骤中。这些问题会影响RAG的检索效果,从而导致生成的回答不准确或不完整。

解决方案-窗口上下文检索

解决这个问题一般采取的方案是,在split拆分时,尽量将文本切分到最小的语义单元。retrive时,不直接使用匹配到doc,而是通过匹配到的doc拓展其上下文内容后整合到一起,在投递到LLM使用。这样,既可以提高检索的精度,又可以保留上下文的完整性,从而提高生成的质量和多样性。

具体来说,这种方案的实现步骤如下:

窗口上下文检索实践

上下文检索实现思路

我们从最终要实现的目标着手,也就是在retrive时要能通过匹配到doc拓展出与这个doc内容相关的上下文。要想实现这个目标,我们就必须建立每个doc与其上下文的关联关系。这个关系的建立其实十分简单,只需要按顺序给拆分出来的每个doc进行编号,在检索时通过当前文档的编号就能匹配到相关上下文doc的编号,进而获取上下文的内容。

基于chroma向量库的代码实践

想要实践以上思路,需要在split环节基于文档顺序,将文档编码写入元数据。在检索时,则通过元数据中的顺序分块编码来查找上下文。具体的代码如下:

importbs4,uuid
fromlangchain.text_splitterimportRecursiveCharacterTextSplitter
fromlangchain_community.document_loadersimportWebBaseLoader
fromlangchain_community.vectorstoresimportChroma
fromlangchain_openaiimportOpenAIEmbeddings
#Load,chunkandindexthecontentsoftheblog.
loader=WebBaseLoader(
web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",),
bs_kwargs=dict(
parse_only=bs4.SoupStrainer(
class_=("post-content","post-title","post-header")
)
),
)
doc=loader.load()

text_splitter=RecursiveCharacterTextSplitter(chunk_size=1000,chunk_overlap=200)
docs=text_splitter.split_documents(doc)
#这里给每个docs片段的metadata里注入file_id
file_id=uuid.uuid4().hex
chunk_id_counter=0
fordocindocs:
doc.metadata["file_id"]=file_id
doc.metadata["chunk_id"]=f'{file_id}_{chunk_id_counter}'#添加chunk_id到metadata
chunk_id_counter+=1
forkey,valueindoc.metadata.items():
ifnotisinstance(value,(str,int,float,bool)):
doc.metadata[key]=str(value)

vectorstore=Chroma.from_documents(documents=splits,embedding=OpenAIEmbeddings())

defexpand_doc(group):
new_cands=[]
group.sort(key=lambdax:int(x.metadata['chunk_id'].split('_')[-1]))
id_set=set()
file_id=group[0].metadata['file_id']

group_scores_map={}
#先找出该文件所有需要搜索的chunk_id
cand_chunks=[]
forcand_docingroup:
current_chunk_id=int(cand_doc.metadata['chunk_id'].split('_')[-1])
group_scores_map[current_chunk_id]=cand_doc.metadata['score']
foriinrange(current_chunk_id-200,current_chunk_id+200):
need_search_id=file_id+'_'+str(i)
ifneed_search_idnotincand_chunks:
cand_chunks.append(need_search_id)
where={"chunk_id":{"$in":cand_chunks}}

ids,group_relative_chunks=get(where)

group_chunk_map={int(item.metadata['chunk_id'].split('_')[-1]):item.page_contentforitemingroup_relative_chunks}
group_file_chunk_num=list(group_chunk_map.keys())
forcand_docingroup:
current_chunk_id=int(cand_doc.metadata['chunk_id'].split('_')[-1])
doc=copy.deepcopy(cand_doc)
id_set.add(current_chunk_id)
docs_len=len(doc.page_content)
forkinrange(1,200):
break_flag=False
forexpand_indexin[current_chunk_id+k,current_chunk_id-k]:
ifexpand_indexingroup_file_chunk_num:
merge_content=group_chunk_map[expand_index]
ifdocs_len+len(merge_content)>CHUNK_SIZE:
break_flag=True
break
else:
docs_len+=len(merge_content)
id_set.add(expand_index)
ifbreak_flag:
break

id_list=sorted(list(id_set))
id_lists=seperate_list(id_list)
forid_seqinid_lists:
foridinid_seq:
ifid==id_seq[0]:
doc=Document(page_content=group_chunk_map[id],
metadata={"score":0,"file_id":file_id})
else:
doc.page_content+=""+group_chunk_map[id]
doc_score=min([group_scores_map[id]foridinid_seqifidingroup_scores_map])
doc.metadata["score"]=doc_score
new_cands.append(doc)
returnnew_cands

总结

在本文中,我们介绍了提高RAG模型检索效果的高阶技巧-窗口上下文检索。我们首先回顾了基础RAG的检索流程和存在的问题,然后介绍了窗口上下文检索的原理和实现方法,最后通过一个实例展示了其效果。

我们希望这篇文章能够帮助你理解和使用窗口上下文检索这个高阶技巧,从而提高你的RAG模型的检索效果。如果你对这个技巧有任何疑问或建议,欢迎在评论区留言。谢谢你的阅读。






欢迎光临 链载Ai (https://www.lianzai.com/) Powered by Discuz! X3.5