系列5中講到會講解3個方面RAG的提升,它們可能與RAG的準確率有關系,但是更多的它們是有其它用途。本期來講解第二部分:查詢結構化(Query Construction)。在系列3文檔處理中,我們著重講解了文檔解析,但是我們說的文檔都是大部分是非結構化的文檔或者說它就是以一個文檔的形式存儲。而現實中我們很多有價值的數據可能以結構化(關系型數據庫、圖形數據庫等)或者半結構(關系型數據庫、文檔數據庫等)的形式存儲中,并且這些數據一般都是存儲于特定數據庫,那么如果數據存儲在結構化或者半結構化中,我們RAG又該如何與之配合。這一章就著重來講講結構化查詢(Query Construction)
目錄
- 1 查詢結構化(Query Construction)
- 2 Text-to-metadata-filter
- 3 Text-to-SQL
- 4 Text-to-Cypher
- 5 Text-to-SQL+ Semantic
1 查詢結構化(Query Construction)
現實中我們很多有價值的數據可能以結構化(關系型數據庫、圖形數據庫等)或者半結構(關系型數據庫、文檔數據庫等)的形式存儲中,并且這些數據一般都是存儲于特定數據庫,同時數據庫提供結構化的查詢功能,使用其結構化查詢(比如SQL等)比使用向量化查詢更為準確。那這時候如果我們RAG想要去查詢這些數據,就不是將數據向量化后再去查詢,而是利用大模型將用戶問題轉換為結構化查詢,再通過結構化查詢去數據庫查詢結果,最后利用結果回答用戶問題。下圖流程可以讓你明顯感受不同之處:
我們將不同結構化或者半結構化查詢歸納如下表,下面也是逐一講解各個方法
方法 | 數據源 |
---|---|
Text-to-metadata-filter | 向量數據庫 |
Text-to-SQL | 關系型數據庫 |
Text-to-Cypher | 圖形數據庫 |
Text-to-SQL+ Semantic | 關系型+向量混合數據庫 |
2 Text-to-metadata-filter
第一個要利用大模型將用戶問題轉換為結構化查詢的依舊是向量數據庫,但是這里并非做向量查詢,而是很多向量存儲其實都配備了元數據過濾功能,這些元數據其實就是結構化存儲,因此需要過濾的是存儲在向量數據庫的元數據。
這里以查詢ChromaDB的metadata為例,做一個demo代碼演示。在運行代碼之前我們需要做以下前置條件
- 這里采用智譜AI的API接口,因此可以先去申請一個API KEY(當然你使用其它模型也可以,目前智譜AI的GLM4送token,就拿它來試驗吧)
- 下載m3e-base的embedding模型
- 給一個文檔目錄,里面放入一個文檔即可,文檔內容不限
import os
from typing import List
from langchain_openai import ChatOpenAI
from langchain_core.documents import Document
from langchain_community.vectorstores import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_core.runnables import RunnablePassthrough
from langchain_community.document_loaders import DirectoryLoader# 前置工作1:文檔存儲,給文檔設置author屬性
encode_kwargs = {"normalize_embeddings": False}
model_kwargs = {"device": "cuda:0"}
embeddings = HuggingFaceEmbeddings(model_name='/root/autodl-tmp/model/AI-ModelScope/m3e-base', # 換成自己的embedding模型路徑model_kwargs=model_kwargs,encode_kwargs=encode_kwargs
)
if os.path.exists('VectorStore'):db = Chroma(persist_directory='VectorStore', embedding_function=embeddings)
loader = DirectoryLoader("/root/autodl-tmp/doc") # 換成自己的文檔路徑
documents = loader.load()
# 這里假設設置一個author作者的屬性
documents[0].metadata["author"] = "劉震云"
database = Chroma.from_documents(documents, embeddings, persist_directory="VectorStore")
database.persist()# 前置工作2:創建llm
llm = ChatOpenAI(temperature=0.95,model="glm-4",openai_api_key="你的API KEY",openai_api_base="https://open.bigmodel.cn/api/paas/v4/"
)# 前置工作3:為了將用戶問題轉換為結構化查詢,使用LangChain可以通過Pydantic類輕松指定所需的function call schema:
# 這里假設書籍有2個字段匹配,一個是內容content_search,一個是作者author_search
class BookSearch(BaseModel):content_search: str = Field(...,description=("書籍文本內容進行相似性搜索"),)author_search: str = Field(...,description=("書籍作者,僅在使用人名查詢時,才進行關鍵字匹配,其它情況不使用"),)# 第一步:讓模型將用戶問題轉換成與查詢字段匹配的格式
system = """你是將用戶問題轉換為數據庫查詢的專家。
你可以訪問關于的書籍數據庫。
給定一個問題,返回一個優化為檢索最相關結果的數據庫查詢。"""
prompt = ChatPromptTemplate.from_messages([("system", system),("human", "{question}"),]
)# 綁定前面定義的輸出數據格式,意思就是要求模型根據類結構返回數據。
structured_llm = llm.with_structured_output(BookSearch)
# 定義chain
query_analyzer = {"question": RunnablePassthrough()} | prompt | structured_llm
print(query_analyzer.invoke("作家劉震云的書"))# 第二步:轉換匹配,拼接向量查詢條件查詢Chroma
def retrieval(search: BookSearch) -> List[Document]:if search.author_search is not None:_filter = {"author": {"$eq": search.author_search}}else:_filter = Nonereturn database.similarity_search(search.content_search, filter=_filter)retrieval_chain = query_analyzer | retrieval
# 未查詢到結果
print(retrieval_chain.invoke("作家莫言的書"))
# 查詢到結果
print(retrieval_chain.invoke("作家劉震云的書"))
3 Text-to-SQL
關于關系型數據庫就不用在累贅了,但是使用大模型生成SQL可能會出現以下問題:
- 幻覺:大模型容易對虛構的表或字段產生“幻覺”,從而產生無效的查詢。方法必須將這些大模型 建立在現實中,確保它們生成與實際數據庫模式一致的有效 SQL。
- 用戶錯誤:文本轉 SQL 方法應能夠防止用戶拼寫錯誤或用戶輸入中可能導致無效查詢的其他不規范情況。
對于以上問題,有不少的解決方案供參考:
- 數據庫描述:要生成SQL查詢,必須向大模型提供數據庫的準確描述。常見的做法:為大模型提供每張表的CREATE TABLE描述,包括列名、類型等,然后再給出幾個SELECT語句的示例。
- Few-shot樣例:在prompt中添加question-query的幾個樣例可以提高query生成的準確性。在prompt中簡單的添加標準的靜態示例指導大模型如何基于question創建query。
- 錯誤處理:當遇到錯誤時,利用工具(例如SQL Agent)修復錯誤。
- 查找專有名詞中的拼寫錯誤:當查詢名稱等專有名詞時,用戶可能會不小心寫錯。我們允許大模型Agent根據向量庫搜索正確的名稱,向量庫在SQL數據庫中存儲相關專有名詞的正確拼寫。
下面以查詢sqlite數據庫為例子,,做一個demo代碼演示。在運行代碼之前我們需要做以下前置條件
- 這里采用智譜AI的API接口,因此可以先去申請一個API KEY(當然你使用其它模型也可以,目前智譜AI的GLM4送token,就拿它來試驗吧)
- 準備一個本地sqlite數據庫,并創建數據庫test.db,以及表和存入數據,腳本如下
sqllite3 test.db
create table student(id Integer,name char,score Integer);
insert into student values(1,“student1”,100);
insert into student values(2,“student2”,80);
insert into student values(3,“student3”,50);
代碼如下:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate
from langchain.chains import create_sql_query_chain
from langchain_community.utilities import SQLDatabase# 前置工作1:創建llm
llm = ChatOpenAI(temperature=0.01,model="glm-4",openai_api_key="你的API KEY",openai_api_base="https://open.bigmodel.cn/api/paas/v4/"
)# 前置工作2:改寫了一下prompt(因為默認的prompt似乎不適合智譜AI)
_sqlite_prompt = """你是一個SQLite專家。給定一個輸入問題,首先創建一個語法正確的SQLite查詢來運行,然后查看查詢的結果并返回輸入問題的答案。
除非用戶在問題中指定要獲取的特定數量的示例,否則根據SQLite使用LIMIT子句查詢最多{top_k}個結果。您可以對結果進行排序,以返回數據庫中信息量最大的數據。
永遠不要查詢表中的所有列。您必須只查詢回答問題所需的列。將每個列名用雙引號(")括起來,以表示它們為分隔符。
注意,只使用您可以在下面的表中看到的列名。注意不要查詢不存在的列。另外,要注意哪個列在哪個表中。
如果問題涉及“今天”,注意使用date('now')函數來獲取當前日期
使用以下格式:問題:問題
SQLQuery:要運行的SQL查詢
答案:最終答案只使用以下表格:
{table_info}問題: {input}
"""SQLITE_PROMPT = PromptTemplate(input_variables=["input", "table_info", "top_k"],template=_sqlite_prompt,
)db = SQLDatabase.from_uri("sqlite:///sqlite-autoconf-3460000/test.db") # 你的數據庫地址
# 使用create_sql_query_chain的方式,langchain還有agent方式,大家可以去探索使用
chain = create_sql_query_chain(llm, db, SQLITE_PROMPT)
response = chain.invoke({"question": "總共有多少學生"})
print(response)
4 Text-to-Cypher
對于圖形數據庫的作用,一般就是表示實體之間的關系,適合于知識圖譜、人際關系等應用場景。
下面以查詢neo4j圖數據庫為例子,,做一個demo代碼演示。在運行代碼之前我們需要做以下前置條件
- 這里采用智譜AI的API接口,因此可以先去申請一個API KEY(當然你使用其它模型也可以,目前智譜AI的GLM4送token,就拿它來試驗吧)
- 準備一個本地neo4j數據庫,并創建數據庫test_relationship,錄入一些作家、書籍以及相關關系
from langchain.chains import GraphCypherQAChain
from langchain_community.graphs import Neo4jGraph
from langchain_openai import ChatOpenAI# 前置工作1:創建llm
llm = ChatOpenAI(temperature=0.95,model="glm-4",openai_api_key="你的API KEY",openai_api_base="https://open.bigmodel.cn/api/paas/v4/"
)# 前置工作2:準備好圖形數據庫
graph = Neo4jGraph(url="bolt://localhost:7687", username="test_relationship", password="***"
)
graph.refresh_schema()# 使用langchain的GraphCypherQAChain進行封裝,這里面的提示效果對于智譜AI來說一般般,沒調試出來最終結果
chain = GraphCypherQAChain.from_llm(llm, graph=graph, verbose=True)print(chain.run("誰是一地雞毛的作者?"))
5 Text-to-SQL+ Semantic
現在混合類型(結構化和非結構化)數據存儲越來越普遍。向關系數據庫添加向量支持是支持混合檢索方法的關鍵推動因素。比如PostgreSQL 的開源 pgvector 擴展將 SQL 的表現力與語義搜索提供的對語義的細致入微的理解相結合。那么在如何將用戶問題轉換為這種混合類型數據庫就提出了更高的挑戰。
在對這種類型數據庫,更加復雜的query生成,需要創建few-shot prompt或者增加query-checking等環節來提升準確度,這里就不舉例子,有興趣深入的同學可以搜索研究。