閑來無事,想研究一下RAG的實現流程,看網上用langchain的比較多,我自己在下面也跑了跑,代碼很簡單,以次博客記錄一下,方便回顧
langchain
LangChain 是一個基于大型語言模型(LLM)開發應用程序的框架。LangChain 簡化了LLM應用程序生命周期的每個階段。
比如,在下面的實現中,LangChain可以將LLM、提示詞模板、檢索器組合在一起快速的完成檢索增強整個流程,而不需要你去關心底層具體是怎么實現的。
代碼demo
實現思路:
- 加載文檔,并對文檔進行切分
- 將切分后的文檔轉化為向量,存儲到向量庫中
- 根據用戶query去向量庫中檢索,找到最相關的回復,并拼接到prompt中
- 根據最新的prompt調用大模型產生增強回復
加載文檔 -> 切分文檔 -> 創建向量數據庫 -> 執行相似度搜索 -> 構建并增強 prompt -> 使用模型生成回答
import os
from openai import OpenAI
import requests
from langchain.text_splitter import CharacterTextSplitter
from weaviate.embedded import EmbeddedOptions
from langchain.prompts import ChatPromptTemplate
from langchain.schema.runnable import RunnablePassthrough
from langchain.schema.output_parser import StrOutputParserfrom langchain_community.document_loaders import TextLoader
from langchain_community.chat_models import ChatOpenAI
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain.schema import Document
from langchain_core.messages import HumanMessage, SystemMessage"""
實現一
"""
def method_one(vectorstore,llm,query):# 根據用戶query進行檢索,并將檢索結果拼接到prompt中def augment_prompt(query: str):# 獲取top2的文本片段results = vectorstore.similarity_search(query, k=1)source_knowledge = "\n\n".join([x.page_content for x in results])# 構建promptaugmented_prompt = f"""你叫david,你需要解答xxx問題###參考樣例###{source_knowledge}"""return augmented_promptprompt=augment_prompt(query)print(prompt)# 封裝輸入messages = [SystemMessage(content=prompt),HumanMessage(content=query),]# 生成檢索增強回復res = llm.invoke(messages)return res.content"""
實現二
"""
def method_two(vectorstore,llm,query):# 將 vectorstore 轉換為一個檢索器retriever = vectorstore.as_retriever()# 定義提示模板template = """你叫david,你需要解答xxx問題###參考樣例###{context}###用戶問題###{question}"""prompt = ChatPromptTemplate.from_template(template)print(prompt)# LangChain 提供了一個高度模塊化和可組合的框架就是鏈,使得你可以根據任務的特性自定義每個組件,并將它們按需組合成執行流程# 定義一個執行流程鏈,包含如下組件# {"context": retriever, "question": RunnablePassthrough()}:用來將上下文(通過檢索器獲得)和用戶問題傳遞給后續組件# prompt里面的占位符與上述定義的context和question是要保持一致的# StrOutputParser():該組件用于解析模型的輸出,將其轉換為字符串格式rag_chain = ({"context": retriever, "question": RunnablePassthrough()}| prompt| llm| StrOutputParser())response = rag_chain.invoke(query)return responseif __name__=="__main__":# 加載單個文檔,這里只需要匹配單個文檔里面的片段path="../rag/faq.txt"loader = TextLoader(path)documents = loader.load()# 如果需要加載多個文檔,將上述path改為跟路徑即可,然后通過下述兩行代碼對多個文檔進行切分# text_splitter = CharacterTextSplitter()# doc = text_splitter.split_documents(documents)# 切分文檔,給定的文檔內容主要是通過換行符分隔的text = documents[0].page_contentchunks = [Document(page_content=chunk) for chunk in text.split("\n\n\n") if chunk.strip()]# 將文檔片段轉化為向量,并存儲到 # Chroma 是一個 開源的向量數據庫,用于存儲和檢索向量嵌入model_name = "../model/bge-base-zh-v1.5"embedding = HuggingFaceEmbeddings(model_name=model_name)vectorstore_hf = Chroma.from_documents(documents=chunks, embedding=embedding , collection_name="huggingface_embed")vectorstore = Chroma.from_documents(chunks, embedding)# 初始化對話模型llm = ChatOpenAI(openai_api_key="",openai_api_base="",model='qwen-max')# 用戶queryquery = "今天天氣如何?"# 檢索增強之后的回答enhanced_result=method_one(vectorstore,llm,query)# enhanced_result=method_two(vectorstore,llm,query)print(enhanced_result)
思考
- 在嘗試中發現,文檔的嵌入模型選擇對匹配結果也影響很大
- 文檔越規范越好切(不同的切分規則對檢索和增強都有影響)