簡介
LLM大模型一般訓練的數據都是滯後的,這是就需要用到RAG知識庫,RAG知識庫可以降低大模型在輸出答案時的幻覺,也能夠讓大模型知識拓展。
知識庫架構知識
檢索流程圖
用户輸入 (User Query)
|
v
+-----------------------+
| 提示詞 (Prompt) |
+-----------------------+
|
| (1) 轉化為向量 (Embedding)
v
+-----------------------+
| 文字向量模型 (EMB) |
+-----------------------+
|
| (2) 相似度檢索 (Search)
v
+-----------------------+ +-----------------------+
| RAG 向量數據庫 (DB) | <-----> | 本地知識庫/文檔集 |
+-----------------------+ +-----------------------+
|
| (3) 召回相關片段 (Context)
v
+-----------------------+
| 大語言模型 (LLM) | <--- (將提示詞與背景片段拼接)
+-----------------------+
|
| (4) 最終生成 (Generation)
v
+-----------------------+
| 輸出結果 |
+-----------------------+
RAG原理解析
構建知識庫的流程如下:
文檔內容切片->文字向量化->向量數據庫
文檔內容切片
LLM 有上下文長度限制,且向量檢索在短文本上更精確。
文本切片方法有:
- 按字數切片
- 按句切片
- 遞歸切片
不管是哪個切片方法,目的是保留語義的完整性,因此不是某個好或某個壞,根據實際需求進行選擇。
文字向量化
將每個切片文字塊轉換為關係向量
關係向量是怎麼來的? 這些是通過對模型進行大量的上下文訓練,得到的詞語與詞語,句子與句子的語義關係上的關聯,這是LLM的基礎。
不同的文字向量模型輸出的向量矩陣也會不同,因此在構建和檢索時需要用同一個文字向量模型進行轉換。
向量數據庫
跟平時用的MySql,MS sql等本質上差不多,只不過將文本換成了向量矩陣的增刪改查
檢索的方法(這裏就列舉幾個):
- 兩個向量的餘弦角大小
- 兩個向量的歐氏舉例
- 兩個向量的分類區域
用文本也能搜索,為什麼要用向量呢? 文本的數據庫檢索一般通過正則搜索,這種無法關聯語義進行檢索,因為我們無法保證大模型在檢索時能夠按照固定的格式。舉個例子:
數據庫中存儲:小明一個月工資3000元
搜索:小明這個月多少錢
正則搜索會直接略過,而向量搜索就能夠搜到。
構建RAG知識庫
本次都利用千問的語言模型,和FAISS向量數據庫演示,來演示
案例
from operator import itemgetter
import os
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.documents import Document
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain_core.messages import HumanMessage, AIMessage
from sqlalchemy import true
#apikey
os.environ["DASHSCOPE_API_KEY"] = "你的apikey"
FAISS_PATH = "my_faiss_index"
raw_text = """
【公司內部機密:代號“深藍計劃”】
1. 項目目標:開發一款能聽懂貓語的翻譯器。
2. 核心技術:基於Transformer的“喵聲波”分析算法。
3. 團隊暗號:如果有人問“今天天氣怎麼樣?”,必須回答“我想吃魚”。
4. 截止日期:2026年12月31日。
5. 經費預算:僅剩50元人民幣,主要用於購買貓條。
"""
#初始化文本
docs = [Document(page_content=raw_text)]
text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=20)
split_docs = text_splitter.split_documents(docs)
#初始化模型
llm = ChatTongyi(model="qwen-plus")
embeddings = DashScopeEmbeddings(model="text-embedding-v1")
#創建向量數據庫
if os.path.exists(FAISS_PATH):
print("向量數據庫已存在")
ragdb = FAISS.load_local(FAISS_PATH, embeddings, allow_dangerous_deserialization=True)
else:
print("創建向量數據庫")
ragdb = FAISS.from_documents(split_docs, embeddings)
ragdb.save_local(FAISS_PATH)
#構建提示詞chain
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
final_prompt = ChatPromptTemplate.from_messages([
("system", """
你是一個專業的問答助手,你的任務是根據上下文簡潔的回答用户的問題。
<context>
{context}
</context>
"""),
MessagesPlaceholder(variable_name="history"),
("human", "{input}")
])
chain = (
#查詢rag
RunnablePassthrough.assign(
context = itemgetter("input") | ragdb.as_retriever() | format_docs
)
| RunnablePassthrough.assign(
answer = {"input":itemgetter("input"), "context":itemgetter("context"), "history":itemgetter("history")} | final_prompt | llm | StrOutputParser()
)
)
history = []
while true:
input_q = input("我:")
respond = chain.invoke({
"input": input_q,
"history": history})
print("answer:" + respond["answer"])
print("=="*30)
history.append(HumanMessage(content=input_q))
history.append(AIMessage(content=respond['answer']))
代碼解釋
代碼的流程如下:
- 初始化RAG:文本切片->文本向量模型->構建向量數據庫
- 詢問ai:提示詞->文本向量模型->向量數據庫檢索->組合prompt->餵給LLM->回答問題->記錄歷史對話
文本切片
docs = [Document(page_content=raw_text)]
text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=20)
split_docs = text_splitter.split_documents(docs)
這裏利用了langchain提供的文本分詞器RecursiveCharacterTextSplitter(遞歸分詞)
構建向量數據庫
llm = ChatTongyi(model="qwen-plus")
embeddings = DashScopeEmbeddings(model="text-embedding-v1")
#創建向量數據庫
if os.path.exists(FAISS_PATH):
print("向量數據庫已存在")
ragdb = FAISS.load_local(FAISS_PATH, embeddings, allow_dangerous_deserialization=True)
else:
print("創建向量數據庫")
ragdb = FAISS.from_documents(split_docs, embeddings)
ragdb.save_local(FAISS_PATH)
這部分要注意:新版FAISS讀取現有數據庫要設置:allow_dangerous_deserialization=True,不然會報錯
提示詞模板
final_prompt = ChatPromptTemplate.from_messages([
("system", """
你是一個專業的問答助手,你的任務是根據上下文簡潔的回答用户的問題。
<context>
{context}
</context>
"""),
MessagesPlaceholder(variable_name="history"),
("human", "{input}")
])
之前沒有講到歷史對話記錄,這次補充下:
MessagesPlaceholder這個是langchain框架的佔位符(其實是框架寫好了prompt模板,告訴ai這個是歷史對話),使用時將歷史對話記錄的數組放在這裏設置的字段中,在添加歷史對話時要使用相關的類進行聲明對話(告訴ai這句話是ai説的還是用户説的)history.append(HumanMessage(content=input_q)) history.append(AIMessage(content=respond['answer']))
Chain鏈的解釋(核心邏輯)
chain = (
#查詢rag
RunnablePassthrough.assign(
context = itemgetter("input") | ragdb.as_retriever() | format_docs
)
| RunnablePassthrough.assign(
answer = {"input":itemgetter("input"), "context":itemgetter("context"), "history":itemgetter("history")} | final_prompt | llm | StrOutputParser()
)
)
Chain鏈流程:
- 查詢RAG的chain:獲取
input字段->內容交給向量數據庫檢索->將檢索的內容(數組)轉換為字符串格式->保存到context字段並傳遞給下一個任務 - 詢問LLM的chain:獲取
input,context,history字段->填充上面定義的prompt模板->餵給LLM模型->解析成文本並保存在answer字段
itemgetter是獲取上一個任務傳遞過來的字段內容。
如果❤喜歡❤本系列教程,就點個關注吧,後續不定期更新~