RAG進(jìn)階技術(shù)!這十種方法你一定要知道 原創(chuàng)
在當(dāng)今這個(gè)信息爆炸的時(shí)代,AI 系統(tǒng)已經(jīng)深入到我們生活的方方面面,從醫(yī)療健康助手到教育輔導(dǎo)工具,再到企業(yè)知識(shí)管理機(jī)器人,AI 正在幫助我們更高效地獲取和處理知識(shí)。但隨著應(yīng)用場(chǎng)景的復(fù)雜化,傳統(tǒng)的 AI 系統(tǒng)面臨著諸多挑戰(zhàn):如何生成真正相關(guān)的回答?如何理解復(fù)雜的多輪對(duì)話?如何避免自信地輸出錯(cuò)誤信息?這些問題在基于 RAG(Retrieval-Augmented Generation,檢索增強(qiáng)生成)的系統(tǒng)中尤為突出。
RAG 結(jié)合了文檔檢索的強(qiáng)大能力與語言生成的流暢性,能夠讓系統(tǒng)基于上下文給出有根據(jù)的回答。然而,基礎(chǔ)的 RAG 系統(tǒng)在處理復(fù)雜查詢、多輪對(duì)話以及特定領(lǐng)域的專業(yè)知識(shí)時(shí),常常會(huì)“掉鏈子”,出現(xiàn)“幻覺”(生成錯(cuò)誤信息)或丟失上下文的情況。那么,我們?cè)撊绾紊?jí) RAG 系統(tǒng),讓它變得更智能、更可靠呢?今天,就讓我們一起探索如何通過高級(jí) RAG 技術(shù),提升問答系統(tǒng)的性能!
基礎(chǔ) RAG 的局限性
先來看看基礎(chǔ) RAG 系統(tǒng)的架構(gòu):它的工作流程大致是這樣的,首先將文檔加載進(jìn)來,通過各種分塊技術(shù)將其拆分成小塊,然后使用嵌入模型將這些小塊轉(zhuǎn)化為向量,存儲(chǔ)到向量數(shù)據(jù)庫中。當(dāng)用戶提出問題時(shí),系統(tǒng)會(huì)在向量數(shù)據(jù)庫中檢索與問題相關(guān)的文檔片段,再將這些片段與問題一起傳遞給語言模型,最終生成回答。
聽起來是不是很簡(jiǎn)單?但正是這種簡(jiǎn)單性,導(dǎo)致了基礎(chǔ) RAG 系統(tǒng)的諸多問題:
- 幻覺問題:模型可能會(huì)生成一些與原始文檔毫不相關(guān),甚至是錯(cuò)誤的內(nèi)容。在醫(yī)學(xué)或法律等對(duì)準(zhǔn)確性要求極高的領(lǐng)域,這可是致命的缺陷。
- 缺乏領(lǐng)域?qū)R恍?/strong>:基礎(chǔ) RAG 系統(tǒng)在處理特定領(lǐng)域的復(fù)雜話題時(shí),往往會(huì)因?yàn)闄z索到不相關(guān)或不準(zhǔn)確的信息而“翻車”。
- 多輪對(duì)話困境:在多輪對(duì)話中,基礎(chǔ) RAG 系統(tǒng)很容易丟失上下文,導(dǎo)致回答支離破碎,無法滿足用戶的需求。
那么,我們?cè)撊绾瓮黄七@些局限呢?這就需要引入高級(jí) RAG 技術(shù),對(duì) RAG 系統(tǒng)的各個(gè)環(huán)節(jié)——索引、檢索和生成——進(jìn)行優(yōu)化升級(jí)!
索引與分塊:構(gòu)建堅(jiān)實(shí)基礎(chǔ)
一個(gè)好的索引是 RAG 系統(tǒng)的核心。我們首先要考慮如何高效地導(dǎo)入、拆分和存儲(chǔ)數(shù)據(jù)。接下來,就讓我們看看幾種先進(jìn)的索引和分塊方法。
1、HNSW:高效檢索的利器
HNSW(Hierarchical Navigable Small Worlds,層次可導(dǎo)航小世界)算法是一種在大數(shù)據(jù)集中快速查找相似項(xiàng)的強(qiáng)大工具。它通過構(gòu)建一個(gè)基于圖的結(jié)構(gòu),能夠高效地找到近似最近鄰(ANN)。具體來說,它有以下幾個(gè)關(guān)鍵特點(diǎn):
- 鄰近圖:HNSW 構(gòu)建了一個(gè)圖,圖中的每個(gè)點(diǎn)都與附近的點(diǎn)相連,這使得搜索過程更加高效。
- 層次結(jié)構(gòu):算法將點(diǎn)分層組織,頂層連接較遠(yuǎn)的點(diǎn),底層連接較近的點(diǎn),從而加快了搜索速度。
- 貪婪路由:在搜索時(shí),HNSW 從高層的某個(gè)點(diǎn)開始,逐步向下層移動(dòng),直到找到局部最小值,大大減少了查找相似項(xiàng)所需的時(shí)間。
實(shí)際應(yīng)用中,我們可以通過設(shè)置參數(shù)(如每個(gè)節(jié)點(diǎn)的鄰居數(shù)量、構(gòu)建圖時(shí)考慮的鄰居數(shù)量等)來優(yōu)化 HNSW 的性能。通過 HNSW,我們能夠在海量數(shù)據(jù)中快速準(zhǔn)確地找到與用戶問題最相關(guān)的文檔片段,為后續(xù)的回答生成提供堅(jiān)實(shí)基礎(chǔ)。
動(dòng)手實(shí)踐 HNSW
接下來,讓我們通過代碼來實(shí)現(xiàn) HNSW 算法。這里我們使用的是 FAISS 庫,它是一個(gè)高效的相似性搜索庫,非常適合與 HNSW 結(jié)合使用。
import faiss
import numpy as np
# 設(shè)置 HNSW 參數(shù)
d = 128 # 向量的維度
M = 32 # 每個(gè)節(jié)點(diǎn)的鄰居數(shù)量
# 初始化 HNSW 索引
index = faiss.IndexHNSWFlat(d, M)
# 設(shè)置 efConstruction 參數(shù),控制構(gòu)建索引時(shí)考慮的鄰居數(shù)量
efConstruction = 200
index.hnsw.efConstruction = efConstruction
# 生成隨機(jī)數(shù)據(jù)并添加到索引中
n = 10000 # 要索引的向量數(shù)量
xb = np.random.random((n, d)).astype('float32')
index.add(xb) # 構(gòu)建索引
# 設(shè)置 efSearch 參數(shù),影響搜索過程
efSearch = 100
index.hnsw.efSearch = efSearch
# 執(zhí)行搜索
nq = 5 # 查詢向量的數(shù)量
xq = np.random.random((nq, d)).astype('float32')
k = 5 # 檢索最近鄰的數(shù)量
distances, indices = index.search(xq, k)
# 輸出結(jié)果
print("查詢向量:\n", xq)
print("\n最近鄰索引:\n", indices)
print("\n最近鄰距離:\n", distances)
通過上述代碼,我們可以看到 HNSW 在處理大規(guī)模數(shù)據(jù)集時(shí)的高效性和準(zhǔn)確性。它能夠快速找到與查詢向量最相似的文檔片段,為后續(xù)的語言模型生成環(huán)節(jié)提供高質(zhì)量的輸入。
2、語義分塊:讓信息更有意義
傳統(tǒng)的分塊方法通常是基于固定大小來拆分文本,但這種方法可能會(huì)將一個(gè)完整的概念或信息拆得支離破碎。而語義分塊則不同,它根據(jù)文本的含義來劃分分塊,每個(gè)分塊都代表一個(gè)連貫的信息單元。具體操作是計(jì)算句子嵌入之間的余弦距離,如果兩個(gè)句子在語義上相似(低于某個(gè)閾值),就把它們歸為同一個(gè)分塊。這種方法的優(yōu)點(diǎn)是能夠生成更有意義、更連貫的分塊,從而提高檢索的準(zhǔn)確性。不過,它需要使用基于 BERT 等的編碼器,計(jì)算成本相對(duì)較高。
動(dòng)手實(shí)踐語義分塊
接下來,我們通過代碼來實(shí)現(xiàn)語義分塊。這里我們使用的是 LangChain 庫中的 ??SemanticChunker?
? 類,它利用 OpenAI 的嵌入模型來實(shí)現(xiàn)語義分塊。
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai.embeddings import OpenAIEmbeddings
# 初始化語義分塊器
text_splitter = SemanticChunker(OpenAIEmbeddings())
# 將文檔分割為語義相關(guān)的分塊
docs = text_splitter.create_documents([document])
print(docs[0].page_content)
通過上述代碼,我們可以看到語義分塊能夠根據(jù)文本的語義內(nèi)容生成更有意義的分塊,這對(duì)于后續(xù)的檢索和生成環(huán)節(jié)非常有幫助。
3、基于語言模型的分塊:精準(zhǔn)捕捉文本結(jié)構(gòu)
這種方法利用強(qiáng)大的語言模型(如擁有 70 億參數(shù)的模型)來處理文本,將其拆分成一個(gè)個(gè)完整的語句,然后再將這些語句組合成分塊,既保證了每個(gè)分塊的完整性,又兼顧了上下文信息。雖然這種方法計(jì)算量較大,但它能夠根據(jù)文本的具體內(nèi)容靈活調(diào)整分塊方式,生成高質(zhì)量的分塊,特別適合對(duì)文本結(jié)構(gòu)要求較高的應(yīng)用場(chǎng)景。
動(dòng)手實(shí)踐基于語言模型的分塊
接下來,我們通過代碼來實(shí)現(xiàn)基于語言模型的分塊。這里我們使用的是 OpenAI 的 GPT-4o 模型,通過異步調(diào)用生成每個(gè)分塊的上下文信息。
import asyncio
from langchain_openai import ChatOpenAI
async def generate_contexts(document, chunks):
async def process_chunk(chunk):
response = await client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "Generate a brief context explaining how this chunk relates to the full document."},
{"role": "user", "content": f"<document> \n{document} \n</document> \nHere is the chunk we want to situate within the whole document \n<chunk> \n{chunk} \n</chunk> \nPlease give a short succinct context to situate this chunk within the overall document for the purposes of improving search retrieval of the chunk. Answer only with the succinct context and nothing else."}
],
temperature=0.3,
max_tokens=100
)
context = response.choices[0].message.content
return f"{context} {chunk}"
# 并行處理所有分塊
contextual_chunks = await asyncio.gather(
*[process_chunk(chunk) for chunk in chunks]
)
return contextual_chunks
通過上述代碼,我們可以看到基于語言模型的分塊能夠生成高質(zhì)量的分塊,并為每個(gè)分塊生成上下文信息,這對(duì)于后續(xù)的檢索和生成環(huán)節(jié)非常有幫助。
4、利用元數(shù)據(jù):為檢索增添更多上下文
元數(shù)據(jù)可以為文檔提供額外的上下文信息,比如日期、患者年齡、既往病史等。在檢索時(shí),通過過濾這些元數(shù)據(jù),我們可以排除無關(guān)的信息,讓檢索結(jié)果更加精準(zhǔn)。例如,在醫(yī)療領(lǐng)域,如果查詢與兒童相關(guān)的內(nèi)容,就可以直接過濾掉 18 歲以上患者的記錄。在索引時(shí),將元數(shù)據(jù)與文本一起存儲(chǔ),能夠大大提高檢索的效率和相關(guān)性。
動(dòng)手實(shí)踐元數(shù)據(jù)的使用
接下來,我們通過代碼來實(shí)現(xiàn)元數(shù)據(jù)的使用。這里我們使用的是 LangChain 庫中的 ??Document?
? 類,它允許我們?cè)谖臋n中存儲(chǔ)元數(shù)據(jù)。
from langchain_core.documents import Document
# 創(chuàng)建帶有元數(shù)據(jù)的文檔
doc = Document(
page_cnotallow="This is a sample document.",
metadata={"id": "doc1", "source": "https://example.com"}
)
# 打印文檔內(nèi)容和元數(shù)據(jù)
print(doc.page_content)
print(doc.metadata)
通過上述代碼,我們可以看到元數(shù)據(jù)能夠?yàn)槲臋n提供更多的上下文信息,這對(duì)于后續(xù)的檢索和生成環(huán)節(jié)非常有幫助。
檢索:精準(zhǔn)定位關(guān)鍵信息
檢索是 RAG 系統(tǒng)中的關(guān)鍵環(huán)節(jié),它決定了我們能否從海量數(shù)據(jù)中找到與用戶問題真正相關(guān)的文檔。接下來,讓我們看看幾種提升檢索性能的技術(shù)。
5、混合搜索:語義與關(guān)鍵詞的完美結(jié)合
混合搜索將向量搜索(語義搜索)和關(guān)鍵詞搜索結(jié)合起來,充分發(fā)揮兩者的優(yōu)點(diǎn)。在一些領(lǐng)域,如 AI 技術(shù),很多術(shù)語都是特定的關(guān)鍵詞,比如算法名稱、技術(shù)術(shù)語等。單獨(dú)使用向量搜索可能會(huì)遺漏這些重要信息,而關(guān)鍵詞搜索則可以確保這些關(guān)鍵術(shù)語被納入考慮范圍。通過同時(shí)運(yùn)行這兩種搜索方式,并根據(jù)權(quán)重系統(tǒng)合并和排序結(jié)果,我們可以得到一個(gè)更全面、更精準(zhǔn)的檢索結(jié)果列表。
動(dòng)手實(shí)踐混合搜索
接下來,我們通過代碼來實(shí)現(xiàn)混合搜索。這里我們使用的是 LangChain 庫中的 ??WeaviateHybridSearchRetriever?
? 類,它結(jié)合了 Weaviate 向量數(shù)據(jù)庫的向量搜索和關(guān)鍵詞搜索能力。
from langchain_community.retrievers import WeaviateHybridSearchRetriever
# 初始化混合搜索檢索器
retriever = WeaviateHybridSearchRetriever(
client=client,
index_name="LangChain",
text_key="text",
attributes=[],
create_schema_if_missing=True,
)
# 執(zhí)行混合搜索
results = retriever.invoke("the ethical implications of AI")
print(results)
通過上述代碼,我們可以看到混合搜索能夠結(jié)合向量搜索和關(guān)鍵詞搜索的優(yōu)點(diǎn),生成更全面、更精準(zhǔn)的檢索結(jié)果。
6、查詢重寫:讓問題更“友好”
人類提出的問題往往并不是最適合數(shù)據(jù)庫或語言模型理解的形式。通過使用語言模型重寫查詢,可以顯著提升檢索的效果。比如,將“AI 代理是什么,為什么它們是 2025 年的下一個(gè)大事件”重寫為“AI 代理 大事件 2025”,這樣更符合數(shù)據(jù)庫的檢索邏輯。此外,還可以通過重寫提示詞來優(yōu)化與語言模型的交互,提高結(jié)果的質(zhì)量和準(zhǔn)確性。
動(dòng)手實(shí)踐查詢重寫
接下來,我們通過代碼來實(shí)現(xiàn)查詢重寫。這里我們使用的是 LangChain 庫中的 ??ChatOpenAI?
? 類,它允許我們利用 OpenAI 的語言模型來重寫查詢。
from langchain_openai import ChatOpenAI
# 初始化語言模型
chatgpt = ChatOpenAI(model_name="gpt-4o", temperature=0)
# 重寫查詢
query = "what are AI agents and why they are the next big thing in 2025"
rewritten_query = chatgpt.invoke(query)
print(rewritten_query)
通過上述代碼,我們可以看到查詢重寫能夠?qū)⑷祟惖膯栴}轉(zhuǎn)化為更適合數(shù)據(jù)庫和語言模型理解的形式,從而提高檢索的效果。
7、多查詢檢索:從不同角度挖掘信息
由于查詢的措辭稍有不同,檢索結(jié)果可能會(huì)大相徑庭。多查詢檢索器利用大型語言模型(LLM)根據(jù)用戶輸入生成多個(gè)不同角度的查詢,然后對(duì)每個(gè)查詢分別檢索相關(guān)文檔,最后將所有查詢的結(jié)果匯總,從而提供更廣泛的相關(guān)文檔集合。這種方法無需進(jìn)行大量的手動(dòng)調(diào)整,就能提高找到有用信息的概率。
動(dòng)手實(shí)踐多查詢檢索
接下來,我們通過代碼來實(shí)現(xiàn)多查詢檢索。這里我們使用的是 LangChain 庫中的 ??MultiQueryRetriever?
? 類,它允許我們利用 OpenAI 的語言模型生成多個(gè)查詢,并從 Chroma 向量數(shù)據(jù)庫中檢索相關(guān)文檔。
from langchain.retrievers.multi_query import MultiQueryRetriever
# 初始化多查詢檢索器
mq_retriever = MultiQueryRetriever.from_llm(
retriever=similarity_retriever3, llm=chatgpt,
include_original=True
)
# 執(zhí)行多查詢檢索
query = "what is the capital of India?"
docs = mq_retriever.invoke(query)
print(docs)
通過上述代碼,我們可以看到多查詢檢索能夠從多個(gè)角度生成查詢,并從向量數(shù)據(jù)庫中檢索相關(guān)文檔,從而提高找到有用信息的概率。
生成:打造高質(zhì)量回答
最后,我們來到了 RAG 系統(tǒng)的生成環(huán)節(jié)。這一環(huán)節(jié)的目標(biāo)是為語言模型提供盡可能與問題相關(guān)的上下文,避免無關(guān)信息引發(fā)“幻覺”。以下是一些提升生成質(zhì)量的技巧。
8、自動(dòng)裁剪:去除無關(guān)信息
自動(dòng)裁剪技術(shù)可以過濾掉從數(shù)據(jù)庫中檢索到的與問題無關(guān)的信息,防止語言模型被誤導(dǎo)。具體操作是,在檢索時(shí),根據(jù)相似度分?jǐn)?shù)找到一個(gè)顯著下降的臨界點(diǎn),排除分?jǐn)?shù)低于該臨界點(diǎn)的對(duì)象,從而確保傳遞給語言模型的信息都是最相關(guān)的。
動(dòng)手實(shí)踐自動(dòng)裁剪
接下來,我們通過代碼來實(shí)現(xiàn)自動(dòng)裁剪。這里我們使用的是 LangChain 庫中的 ??PineconeVectorStore?
? 類,它允許我們利用 Pinecone 向量數(shù)據(jù)庫進(jìn)行相似性搜索,并根據(jù)相似度分?jǐn)?shù)過濾信息。
from langchain_pinecone import PineconeVectorStore
from langchain_openai import OpenAIEmbeddings
# 初始化向量存儲(chǔ)
vectorstore = PineconeVectorStore.from_documents(
docs, index_name="sample", embedding=OpenAIEmbeddings()
)
# 執(zhí)行相似性搜索并獲取相似度分?jǐn)?shù)
docs, scores = vectorstore.similarity_search_with_score("dinosaur")
for doc, score in zip(docs, scores):
doc.metadata["score"] = score
print(docs)
通過上述代碼,我們可以看到自動(dòng)裁剪能夠根據(jù)相似度分?jǐn)?shù)過濾掉無關(guān)信息,從而提高傳遞給語言模型的信息質(zhì)量。
9、重新排序:讓重要信息優(yōu)先
重新排序技術(shù)使用更高級(jí)的模型(通常是交叉編碼器)重新評(píng)估和排序最初檢索到的對(duì)象。它會(huì)考慮查詢和每個(gè)對(duì)象之間的配對(duì)相似度,重新確定相關(guān)性,并將最相關(guān)的文檔放在最前面。這樣,語言模型接收到的數(shù)據(jù)質(zhì)量更高,生成的回答也更精準(zhǔn)。
動(dòng)手實(shí)踐重新排序
接下來,我們通過代碼來實(shí)現(xiàn)重新排序。這里我們使用的是 LangChain 庫中的 ??FlashrankRerank?
? 類,它允許我們利用高級(jí)模型重新評(píng)估和排序檢索到的文檔。
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import FlashrankRerank
# 初始化重新排序器
compressor = FlashrankRerank()
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor, base_retriever=retriever
)
# 執(zhí)行重新排序
query = "What did the president say about Ketanji Jackson Brown"
compressed_docs = compression_retriever.invoke(query)
print([doc.metadata["id"] for doc in compressed_docs])
print(compressed_docs)
通過上述代碼,我們可以看到重新排序能夠根據(jù)查詢和文檔之間的相似度重新排序,從而提高傳遞給語言模型的信息質(zhì)量。
10、微調(diào)語言模型:讓模型更懂你的領(lǐng)域
對(duì)預(yù)訓(xùn)練的語言模型進(jìn)行微調(diào),可以顯著提升檢索性能。在特定領(lǐng)域(如醫(yī)學(xué))中,可以選擇在相關(guān)數(shù)據(jù)上預(yù)訓(xùn)練的模型(如 MedCPT 系列),并收集自己的數(shù)據(jù),創(chuàng)建正負(fù)樣本對(duì)進(jìn)行微調(diào),讓模型學(xué)會(huì)領(lǐng)域內(nèi)的特定關(guān)系。經(jīng)過微調(diào)的模型在特定領(lǐng)域的檢索和生成任務(wù)中表現(xiàn)更出色。
動(dòng)手實(shí)踐微調(diào)語言模型
接下來,我們通過代碼來實(shí)現(xiàn)微調(diào)語言模型。這里我們使用的是 LangChain 庫中的 ??ChatOpenAI?
? 類,它允許我們利用 OpenAI 的語言模型進(jìn)行微調(diào)。
from langchain_openai import ChatOpenAI
# 初始化語言模型
chatgpt = ChatOpenAI(model_name="gpt-4o", temperature=0)
# 微調(diào)語言模型
# 這里需要提供自己的數(shù)據(jù)集進(jìn)行微調(diào)
# 例如,使用醫(yī)學(xué)領(lǐng)域的數(shù)據(jù)集進(jìn)行微調(diào)
# fine_tuned_model = chatgpt.fine_tune(dataset)
通過上述代碼,我們可以看到微調(diào)語言模型能夠顯著提升模型在特定領(lǐng)域的性能,從而提高生成回答的質(zhì)量。
高級(jí) RAG 技術(shù):讓 AI 回答更靠譜
通過上述一系列高級(jí) RAG 技術(shù),我們可以對(duì) RAG 系統(tǒng)的各個(gè)環(huán)節(jié)——索引、檢索和生成——進(jìn)行優(yōu)化升級(jí),從而提升系統(tǒng)的整體性能。無論是醫(yī)療健康助手、教育輔導(dǎo)工具,還是企業(yè)知識(shí)管理機(jī)器人,這些技術(shù)都能讓 AI 系統(tǒng)在處理復(fù)雜信息需求時(shí)更加得心應(yīng)手,生成更準(zhǔn)確、更可靠、更符合上下文的回答。
總之,隨著應(yīng)用場(chǎng)景的不斷復(fù)雜化,AI 系統(tǒng)需要不斷進(jìn)化。高級(jí) RAG 技術(shù)為我們提供了一種有效的途徑,讓我們能夠打造出更智能、更強(qiáng)大的問答系統(tǒng),讓 AI 真正成為我們獲取知識(shí)、解決問題的得力助手!
本文轉(zhuǎn)載自公眾號(hào)Halo咯咯 作者:基咯咯
原文鏈接:??https://mp.weixin.qq.com/s/5i0Dso7sv_bkYFaFO0h8lQ??
