各位老鐵,歡迎來到我們專欄的第一個實戰項目。
在過去的三個章節里,我們已經完成了所有的理論儲備和環境搭建。我們理解了LLM的本質,掌握了Prompt Engineering的要領,洞悉了Embedding和向量數據庫的魔力,并且熟悉了LangChain這個強大的“瑞士軍刀”。
現在,是時候將這些散落的“龍珠”匯集起來,召喚出我們的第一條“神龍”了。
這個項目,是當前AI應用領域最經典、最實用、也是商業價值最高的場景之一:本地知識庫問答。我們將從零開始,構建一個完整的系統,它可以:
- 讀取并“學習”你提供的一份或多份本地文檔(例如,一份PDF格式的產品說明書或公司規章制度)。
- 當用戶用自然語言提問時,系統能夠僅僅依據這些文檔的內容,給出精準的回答。
- 如果文檔中沒有相關信息,它會明確地告知用戶“根據已有信息無法回答”,而不是憑空捏造(產生幻覺)。
這個項目,是RAG(檢索增強生成)模式的終極體現。完成它,你將不再是一個AI領域的旁觀者,而是一個能夠交付真正價值的AI應用工程師。
本章內容詳盡,代碼完整,請務必保持專注,跟上我的節奏。讓我們開始構建。
4.1 項目藍圖與技術棧規劃
在動工之前,我們首先要像一個架構師一樣,畫出我們系統的藍圖。一個清晰的架構圖,是我們后續所有工作的指導方針。
我們的系統,本質上包含兩個核心的、相互獨立的流程:
- 知識注入流程 (Ingestion Pipeline):這個流程是一次性的(或者定期執行的)。它的任務是將原始文檔處理成向量化的知識,并存入向量數據庫。
- 問答檢索流程 (Query Pipeline):這個流程是實時響應用戶請求的。它接收用戶問題,從向量數據庫中檢索相關知識,并交由LLM生成最終答案。
下面這張圖,就是我們本次項目的核心架構圖:
基于這個藍圖,我們確定本次項目的技術棧:
- 核心框架:
Python 3.10+
- AI編排:
LangChain
- 我們整個工作流的粘合劑。 - 大語言模型 (LLM):
通義千問 (qwen-turbo)
- 由阿里云達摩院提供,負責最終的理解和生成。 - Embedding模型:
通義千問文本向量模型 (text-embedding-v1)
- 負責將文本翻譯成向量。 - 向量數據庫:
ChromaDB
- 一個輕量級、開源、可本地運行的向量數據庫。 - 文檔加載:
PyPDFLoader
- LangChain集成的一個用于加載PDF文檔的工具。 - 交互界面:
Streamlit
- 一個能用純Python快速構建Web UI的庫。
準備工作:
-
獲取API Key:
- 訪問阿里云百煉平臺。
- 開通服務并進入控制臺,在“API-KEY管理”中創建你的專屬API Key。
- 嚴格遵循我們的安全準則:在你的項目根目錄創建
.env
文件,并寫入:
同時,將DASHSCOPE_API_KEY="sk-YourAlibabaCloudApiKey"
.env
加入.gitignore
文件。
-
安裝所有依賴:
- 確保你已激活之前創建的
venv
虛擬環境。 - 執行以下命令,一次性安裝所有我們需要的庫:
pip install langchain langchain-community dashscope chromadb pypdf streamlit langchain-chroma
langchain-chroma
: ChromaDB與LangChain集成的最新獨立包,我們遵循最佳實踐,使用這個版本。
- 確保你已激活之前創建的
-
準備一份測試文檔:
- 在你的項目根目錄下,創建一個名為
docs
的文件夾。 - 找一份你感興趣的PDF文檔放進去,比如一份產品說明書、一篇論文、或者一份公開的報告。
- 在你的項目根目錄下,創建一個名為
一切就緒,讓我們開始編寫知識注入流程的代碼。
4.2 流程一:構建知識注入管道 (ingest.py)
這個腳本的任務是讀取docs
文件夾下的所有PDF,將它們處理后存入本地的ChromaDB數據庫。這個腳本只需要運行一次。
在你的項目根目錄,創建一個名為ingest.py
的文件,并寫入以下代碼:
import os
from dotenv import load_dotenvfrom langchain_community.document_loaders import PyPDFLoader, DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings.dashscope import DashScopeEmbeddings
from langchain_chroma import Chroma# 加載.env文件中的環境變量
load_dotenv()# 定義文檔目錄和Chroma持久化目錄
DOCS_PATH = "docs"
CHROMA_PATH = "chroma_db"def main():"""主執行函數:1. 加載文檔2. 分割文檔3. 向量化并存入ChromaDB"""print("--- 開始知識注入流程 ---")# 1. 加載文檔print(f"[步驟1] 從 '{DOCS_PATH}' 目錄加載PDF文檔...")if not os.path.exists(DOCS_PATH):print(f"錯誤:文檔目錄 '{DOCS_PATH}' 不存在。請創建該目錄并放入PDF文件。")returnloader = DirectoryLoader(DOCS_PATH, glob="*.pdf", loader_cls=PyPDFLoader)documents = loader.load()if not documents:print("錯誤:在'docs'目錄中未找到任何PDF文件。")returnprint(f"成功加載 {len(documents)} 個文檔。")# 2. 分割文檔print("\n[步驟2] 將加載的文檔分割成小塊(chunks)...")text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100)chunks = text_splitter.split_documents(documents)print(f"文檔被成功分割成 {len(chunks)} 個小塊。")# 3. 向量化并存入ChromaDBprint("\n[步驟3] 初始化Embedding模型并創建Chroma數據庫...")embeddings = DashScopeEmbeddings(model="text-embedding-v1")db = Chroma.from_documents(chunks, embeddings, persist_directory=CHROMA_PATH)print(f"\n--- 知識注入成功!---")print(f"向量化數據已成功存入 '{CHROMA_PATH}' 目錄。")print("現在您可以運行查詢腳本或Web UI來與您的知識庫進行交互了。")if __name__ == "__main__":main()
代碼解析:
DirectoryLoader
: LangChain提供的便捷工具,自動遍歷docs
目錄,使用PyPDFLoader
加載所有PDF文件。RecursiveCharacterTextSplitter
: 智能文本分割器。chunk_size=500
定義了每個文本塊的目標大小,chunk_overlap=100
確保塊之間的上下文連續性。DashScopeEmbeddings
: LangChain對通義千問文本向量模型的封裝。我們只需實例化,LangChain便會在需要時自動調用它。langchain_chroma.Chroma
: 我們使用最新的獨立包來與ChromaDB交互。Chroma.from_documents
方法是一個強大的“三合一”操作,它會自動完成文本塊的向量化、存儲,并通過persist_directory
參數將數據庫完整地寫入本地磁盤。
如何運行?
在終端(已激活虛擬環境)中,直接運行此腳本:
python ingest.py
腳本執行完畢后,你的項目目錄下會多出一個名為chroma_db
的文件夾,這就是我們AI的“長期記憶體”!
4.3 流程二:構建問答檢索管道 (app.py)
知識庫已經建好,現在我們要構建一個能使用這個知識庫的問答系統。我們直接使用Streamlit來構建一個簡單的Web界面。
在你的項目根目錄,創建一個名為app.py
的文件,并寫入以下代碼:
import os
import streamlit as st
from dotenv import load_dotenvfrom langchain_community.embeddings.dashscope import DashScopeEmbeddings
from langchain_chroma import Chroma
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain.prompts import PromptTemplate
from langchain.chains import RetrievalQA# 加載.env文件中的環境變量
load_dotenv()# --- 全局配置 ---
CHROMA_PATH = "chroma_db"
PROMPT_TEMPLATE = """
請注意:你是一個專業的AI助手,你的任務是根據下面提供的“背景信息”來回答問題。
請嚴格依據“背景信息”的內容進行回答,不要自行發揮或使用你的內部知識。
如果“背景信息”中沒有足夠的內容來回答問題,請直接說“根據已有信息,我無法回答該問題。”。
不允許在答案中添加任何非“背景信息”中的內容。背景信息:
{context}問題:
{question}請給出你的回答:
"""def main():"""主函數,構建Streamlit Web應用"""st.set_page_config(page_title="阿威的AI知識庫", page_icon="🤖")st.title("🤖 阿威的AI知識庫")st.markdown("你好!我是你的AI助手。請在下方輸入框中提出你關于已上傳文檔的問題。")@st.cache_resourcedef initialize_components():if not os.path.exists(CHROMA_PATH):st.error(f"錯誤:向量數據庫目錄 '{CHROMA_PATH}' 不存在。請先運行 'ingest.py' 來創建數據庫。")return None, Noneembeddings = DashScopeEmbeddings(model="text-embedding-v1")db = Chroma(persist_directory=CHROMA_PATH, embedding_function=embeddings)llm = ChatTongyi(model_name="qwen-turbo")return db, llmdb, llm = initialize_components()if db is None or llm is None:returnretriever = db.as_retriever()prompt = PromptTemplate(template=PROMPT_TEMPLATE, input_variables=["context", "question"])qa_chain = RetrievalQA.from_chain_type(llm=llm,chain_type="stuff",retriever=retriever,return_source_documents=True,chain_type_kwargs={"prompt": prompt})query = st.text_input("請輸入你的問題:", placeholder="例如:阿里巴巴開發手冊里藏著什么寶貝?")if st.button("提問"):if not query:st.warning("請輸入問題后再提問。")else:with st.spinner("AI正在思考中,請稍候..."):try:result = qa_chain.invoke({"query": query})st.subheader("💡 AI的回答:")st.write(result["result"])with st.expander("查看答案來源"):for doc in result["source_documents"]:st.info(f"來源文件: {os.path.basename(doc.metadata.get('source', '未知'))}")st.write(doc.page_content)st.divider()except Exception as e:st.error(f"在回答問題時發生錯誤: {e}")if __name__ == "__main__":main()
代碼解析:
st.cache_resource
: Streamlit的緩存裝飾器,用于緩存模型加載、數據庫連接等耗時操作,避免重復執行,提升應用響應速度。Chroma(...)
: 這里我們直接實例化Chroma
類,通過persist_directory
從本地加載數據庫。embedding_function
是必須提供的,因為Chroma需要用同一個Embedding模型來向量化用戶的查詢。ChatTongyi
: LangChain對通義千問聊天模型的封裝,我們選擇qwen-turbo
模型以平衡性能和成本。PROMPT_TEMPLATE
: 這是我們RAG系統的靈魂!我們通過嚴格的指令為LLM設定了行為邊界,包括角色扮演、核心指令、兜底策略和幻覺抑制,這直接決定了系統的可靠性。db.as_retriever()
: 將ChromaDB實例轉換為一個標準的LangChainRetriever
對象,它封裝了所有檢索邏輯。RetrievalQA.from_chain_type
: LangChain提供的構建RAG應用的便捷Chain。chain_type="stuff"
表示將檢索到的所有文檔片段全部“塞”進Prompt的{context}
部分,這是最直接的策略。
如何運行?
注意! Streamlit應用有其專屬的啟動方式。你不能使用python app.py
來運行它。
請在終端(已激活虛擬環境)中,使用以下正確的命令來啟動Web應用:
streamlit run app.py
執行該命令后,你的瀏覽器會自動打開一個新標簽頁,顯示出我們應用的交互界面。現在,你可以像使用聊天機器人一樣,輸入你關于文檔內容的問題,然后點擊“提問”按鈕,見證AI的強大能力。
示例:
4.4 總結、挑戰與展望
恭喜你!你已經成功地從零開始,構建并運行了一個功能完備、邏輯嚴謹的AI知識庫問答系統。
讓我們回顧一下我們所取得的成就:
- 我們設計并實現了分離的知識注入和問答檢索兩大流程,這是工程上的最佳實踐。
- 我們掌握了使用LangChain整合文檔加載、文本分割、Embedding、向量存儲的全過程。
- 我們學會了如何構建一個高質量的RAG Prompt,來約束LLM的行為,抑制幻覺。
- 我們成功地將**檢索器(Retriever)和大語言模型(LLM)**通過
RetrievalQA
鏈無縫地結合在一起。 - 我們還用
Streamlit
為我們的應用穿上了一件漂亮的“外衣”,讓它變得可用、可交互。
然而,作為一個嚴謹的工程師,我們也要清醒地認識到,我們這個V1版本的系統還存在一些挑戰和可優化的空間:
- 檢索質量: RAG系統的天花板取決于檢索器能否找對相關的文檔。如何優化檢索效果是一個重要的課題。
- 分塊策略 (Chunking):
chunk_size
和chunk_overlap
的設置對結果影響很大,需要根據文檔類型進行調整。 - 成本與延遲: 在生產環境中,需要對API調用和數據庫查詢的延遲與成本進行監控和優化。
- 評估體系: 如何科學地評估問答系統的好壞?這需要一套評估標準和數據集,是AI應用工程化中的重要一環。
這些挑戰,也正是我們未來繼續深入學習和探索的方向。
在本章之后,你已經掌握了構建“知識型”AI應用的核心技能。在下一章,我們將探索一個更令人興奮的領域——AI智能體(Agent)。我們將賦予AI“手”和“腳”,讓它不再僅僅是“回答”問題,而是能夠“執行”任務,成為一個真正能為你干活的“數字員工”。
實戰之旅,精彩繼續。我們下期見。