戰(zhàn)“茴香豆”項(xiàng)目全流程解析)
1. 項(xiàng)目概述從“茴香豆”到RAG智能助理的實(shí)踐之路最近在整理學(xué)習(xí)筆記正好翻到之前研究RAG技術(shù)時(shí)的一個(gè)實(shí)踐項(xiàng)目標(biāo)題就叫“茴香豆”。這名字聽(tīng)起來(lái)有點(diǎn)趣味其實(shí)它指向的是一個(gè)非常具體的技術(shù)實(shí)現(xiàn)如何從零開(kāi)始搭建一個(gè)屬于自己的檢索增強(qiáng)生成智能助理。RAG也就是檢索增強(qiáng)生成現(xiàn)在可以說(shuō)是大模型應(yīng)用落地的標(biāo)配技術(shù)了。它核心要解決的就是大模型“一本正經(jīng)胡說(shuō)八道”和知識(shí)更新不及時(shí)的痛點(diǎn)。簡(jiǎn)單來(lái)說(shuō)RAG通過(guò)外掛一個(gè)專屬的知識(shí)庫(kù)讓大模型在回答問(wèn)題時(shí)先從這個(gè)知識(shí)庫(kù)里找到最相關(guān)的信息片段作為參考再組織語(yǔ)言回答這樣既能保證答案的準(zhǔn)確性又能讓模型掌握你私有的、最新的知識(shí)。這個(gè)“茴香豆”項(xiàng)目就是一個(gè)典型的RAG系統(tǒng)搭建實(shí)戰(zhàn)。它不只是一個(gè)Demo而是涵蓋了從文檔處理、向量檢索到與大模型集成的完整鏈路。對(duì)于想入門(mén)AI應(yīng)用開(kāi)發(fā)特別是希望將大模型能力與自身業(yè)務(wù)數(shù)據(jù)結(jié)合的朋友來(lái)說(shuō)走通這樣一個(gè)項(xiàng)目意義遠(yuǎn)大于單純調(diào)用API。你會(huì)深刻理解數(shù)據(jù)如何變成模型能“理解”的格式查詢?nèi)绾尉珳?zhǔn)命中知識(shí)以及整個(gè)流程中那些影響效果的關(guān)鍵“旋鈕”都在哪里。接下來(lái)我就結(jié)合自己的實(shí)操筆記把這個(gè)過(guò)程的思路、步驟和踩過(guò)的坑系統(tǒng)地梳理一遍。2. RAG系統(tǒng)核心架構(gòu)與“茴香豆”設(shè)計(jì)思路拆解2.1 為什么是RAG核心價(jià)值與問(wèn)題域界定在動(dòng)手之前我們必須先想清楚為什么要用RAG。直接使用大模型對(duì)話比如問(wèn)它“我司2024年最新的產(chǎn)品政策是什么”它大概率是無(wú)法回答的因?yàn)檫@些信息不在它的訓(xùn)練數(shù)據(jù)里。即使是一些公開(kāi)知識(shí)模型也可能因?yàn)橛?xùn)練數(shù)據(jù)截止日期或“幻覺(jué)”問(wèn)題給出錯(cuò)誤答案。RAG的價(jià)值就在于它為大模型裝上了一雙“眼睛”和一個(gè)“外部記憶體”。這雙眼睛檢索器負(fù)責(zé)在你提供的文檔庫(kù)中快速掃描找到與問(wèn)題最相關(guān)的段落這個(gè)記憶體向量數(shù)據(jù)庫(kù)則高效存儲(chǔ)和索引這些文檔內(nèi)容。“茴香豆”項(xiàng)目的設(shè)計(jì)目標(biāo)很明確構(gòu)建一個(gè)輕量級(jí)、可復(fù)現(xiàn)、效果可控的RAG智能助理原型。它不追求一步到位的企業(yè)級(jí)復(fù)雜功能而是聚焦于打通核心鏈路讓你能清晰地看到數(shù)據(jù)是如何流動(dòng)的。整個(gè)系統(tǒng)可以抽象為三個(gè)核心模塊文檔處理與索引模塊、檢索與排序模塊、提示工程與生成模塊。第一個(gè)模塊解決“知識(shí)怎么存”的問(wèn)題第二個(gè)模塊解決“知識(shí)怎么找”的問(wèn)題第三個(gè)模塊解決“找到了怎么用”的問(wèn)題。這個(gè)清晰的劃分是后續(xù)一切工作的基礎(chǔ)。2.2 “茴香豆”技術(shù)棧選型背后的考量技術(shù)選型往往決定了項(xiàng)目的上手難度和天花板。在這個(gè)項(xiàng)目中我們的選型遵循“輕量、主流、可控”的原則。1. 文檔加載與切分LangChain 自定義切分器LangChain幾乎是當(dāng)前大模型應(yīng)用開(kāi)發(fā)的事實(shí)標(biāo)準(zhǔn)框架其DocumentLoader支持PDF、Word、TXT、HTML等多種格式能省去大量解析文件的臟活累活。但LangChain自帶的RecursiveCharacterTextSplitter遞歸字符切分器有時(shí)不夠靈活。在“茴香豆”里我采用了基于語(yǔ)義的切分策略作為補(bǔ)充。例如對(duì)于技術(shù)文檔我會(huì)優(yōu)先按章節(jié)標(biāo)題Markdown的##或###進(jìn)行切分以保持上下文的完整性對(duì)于普通段落再輔以固定長(zhǎng)度重疊overlap的字符切分。這樣能更好地平衡檢索精度和上下文信息量。2. 向量化模型與向量數(shù)據(jù)庫(kù)Sentence Transformers Chroma文本轉(zhuǎn)化為向量嵌入是檢索的基石。我選擇了all-MiniLM-L6-v2這個(gè)模型它來(lái)自Sentence Transformers庫(kù)。選它的理由很實(shí)在模型大小僅80MB左右在CPU上也能跑出不錯(cuò)的速度并且在MTEB等通用語(yǔ)義相似度評(píng)測(cè)榜上表現(xiàn)均衡。對(duì)于入門(mén)和大多數(shù)中文場(chǎng)景它完全夠用。如果追求更高精度可以升級(jí)為text2vec系列或bge系列的模型。 向量數(shù)據(jù)庫(kù)方面ChromaDB以其極簡(jiǎn)的API和內(nèi)存/持久化兩種模式脫穎而出。它無(wú)需單獨(dú)部署服務(wù)幾行代碼就能集成特別適合原型開(kāi)發(fā)和中小規(guī)模知識(shí)庫(kù)萬(wàn)級(jí)文檔以內(nèi)。它的核心接口就是add_documents、query直觀易懂讓我們能把精力集中在效果優(yōu)化上而不是數(shù)據(jù)庫(kù)配置上。3. 大模型接口OpenAI API 或 本地開(kāi)源模型為了快速驗(yàn)證流程初期直接使用OpenAI的GPT-3.5/4 API是最佳選擇穩(wěn)定且效果有保障。但在“茴香豆”的后期我嘗試接入了本地部署的開(kāi)源模型如ChatGLM3、Qwen等通過(guò)FastChat或vLLM提供兼容OpenAI的API接口。這一步的意義在于實(shí)現(xiàn)數(shù)據(jù)閉環(huán)和成本可控畢竟長(zhǎng)期調(diào)用商用API是一筆不小的開(kāi)銷且敏感數(shù)據(jù)不出本地更安全。4. 前端交互Gradio 或 Streamlit一個(gè)可視化的界面能極大提升演示和調(diào)試體驗(yàn)。Gradio和Streamlit都能快速構(gòu)建Web界面。Gradio更輕量專注于機(jī)器學(xué)習(xí)Demo幾行代碼就能創(chuàng)建一個(gè)帶聊天框的界面Streamlit則更像一個(gè)數(shù)據(jù)應(yīng)用框架布局能力更強(qiáng)。在“茴香豆”項(xiàng)目中我選擇了Gradio因?yàn)樗cLangChain的集成更無(wú)縫ChatInterface組件開(kāi)箱即用。3. 從文檔到向量知識(shí)庫(kù)構(gòu)建的魔鬼細(xì)節(jié)3.1 文檔預(yù)處理清洗、格式化與結(jié)構(gòu)化很多人以為RAG就是簡(jiǎn)單地把文檔扔進(jìn)去切分但預(yù)處理的質(zhì)量直接決定了檢索的上限。垃圾進(jìn)垃圾出在這里同樣適用。首先格式統(tǒng)一與噪音去除。從不同渠道獲得的文檔掃描PDF、網(wǎng)頁(yè)爬蟲(chóng)、Word文件含有大量噪音頁(yè)眉頁(yè)腳、頁(yè)碼、無(wú)關(guān)的廣告鏈接、特殊字符等。我的做法是先用pdfplumber或pypdf2提取PDF文本用python-docx處理Word用BeautifulSoup清理HTML。一個(gè)常見(jiàn)的坑是掃描版PDF需要用OCR工具如Tesseract先轉(zhuǎn)文字但這一步會(huì)引入大量識(shí)別錯(cuò)誤需謹(jǐn)慎評(píng)估。其次文檔結(jié)構(gòu)化解析。這是提升效果的關(guān)鍵。對(duì)于技術(shù)手冊(cè)、產(chǎn)品文檔這類有明確層級(jí)結(jié)構(gòu)的文本我會(huì)先用正則表達(dá)式或基于規(guī)則的解析器識(shí)別出章節(jié)標(biāo)題如“1.1 概述”、“第二章 安裝”并以此作為元數(shù)據(jù)metadata記錄下來(lái)。這樣在后續(xù)切分時(shí)可以盡量保證一個(gè)切片包含一個(gè)完整的小節(jié)避免將一個(gè)問(wèn)題和一個(gè)答案切到兩個(gè)不同的片段中。注意元數(shù)據(jù)metadata是RAG中的“黃金信息”。除了章節(jié)標(biāo)題還可以包括文檔來(lái)源、更新時(shí)間、作者等信息。在檢索時(shí)不僅可以按向量相似度排序還可以按元數(shù)據(jù)過(guò)濾比如“只檢索2024年更新的產(chǎn)品文檔”這能大幅提升答案的時(shí)效性和準(zhǔn)確性。3.2 文本切分策略長(zhǎng)度、重疊與語(yǔ)義邊界切分是門(mén)藝術(shù)。切得太碎檢索到的片段缺乏足夠上下文模型看不懂切得太長(zhǎng)片段會(huì)包含無(wú)關(guān)信息稀釋核心內(nèi)容同時(shí)增加模型處理負(fù)擔(dān)和成本。1. 固定長(zhǎng)度重疊切分這是最基礎(chǔ)的方法。在“茴香豆”中我設(shè)置chunk_size500字符數(shù)chunk_overlap100。500字符大約是一個(gè)自然段到兩個(gè)自然段的長(zhǎng)度能容納一個(gè)相對(duì)完整的觀點(diǎn)。100字符的重疊是為了防止一個(gè)完整的句子或關(guān)鍵信息恰好被切在邊界上導(dǎo)致上下文斷裂。這個(gè)重疊區(qū)域就像一個(gè)“緩沖區(qū)”確保了信息的連續(xù)性。2. 語(yǔ)義切分僅按字符長(zhǎng)度切分會(huì)破壞語(yǔ)義完整性。我引入了semantic-text-splitter庫(kù)的啟發(fā)嘗試基于句子邊界如中文句號(hào)、問(wèn)號(hào)、感嘆號(hào)進(jìn)行切分并盡量保證每個(gè)切片的句子是語(yǔ)義上相對(duì)獨(dú)立的。更高級(jí)的做法是使用小型模型計(jì)算句子間的語(yǔ)義變化在語(yǔ)義發(fā)生較大轉(zhuǎn)折處進(jìn)行切分但這會(huì)顯著增加處理時(shí)間在原型階段性價(jià)比不高。3. 混合切分策略我的實(shí)戰(zhàn)經(jīng)驗(yàn)是先按結(jié)構(gòu)切再按長(zhǎng)度微調(diào)。例如對(duì)于一份API文檔首先識(shí)別出每個(gè)獨(dú)立的“接口說(shuō)明”板塊通常由接口名稱、URL、方法等標(biāo)題標(biāo)識(shí)將每個(gè)板塊作為一個(gè)大單元。然后在這個(gè)大單元內(nèi)部如果內(nèi)容很長(zhǎng)再使用固定長(zhǎng)度重疊的方式進(jìn)行二次切分。最后為每個(gè)切片記錄其所屬的“接口名稱”作為元數(shù)據(jù)。這樣當(dāng)用戶問(wèn)“用戶登錄接口的返回值是什么”時(shí)檢索系統(tǒng)不僅能找到語(yǔ)義相似的片段還能通過(guò)元數(shù)據(jù)快速定位到“用戶登錄接口”這個(gè)章節(jié)下的所有相關(guān)內(nèi)容精度更高。3.3 向量化嵌入與索引構(gòu)建文本切分后就來(lái)到了核心的向量化步驟。這里使用的是之前選定的all-MiniLM-L6-v2模型。from sentence_transformers import SentenceTransformer # 加載嵌入模型 embed_model SentenceTransformer(‘sentence-transformers/all-MiniLM-L6-v2‘) # 假設(shè) docs 是切分好的文本片段列表 doc_texts [doc.page_content for doc in docs] # 生成向量嵌入 doc_embeddings embed_model.encode(doc_texts, normalize_embeddingsTrue)關(guān)鍵參數(shù)normalize_embeddingsTrue非常重要。它將向量歸一化為單位長(zhǎng)度這樣后續(xù)計(jì)算余弦相似度就簡(jiǎn)化為向量點(diǎn)積計(jì)算效率最高這也是大多數(shù)向量數(shù)據(jù)庫(kù)的默認(rèn)做法。接下來(lái)是將向量存入ChromaDB。這里有一個(gè)細(xì)節(jié)連同向量一起存儲(chǔ)的還有原始的文本片段chunk和它的元數(shù)據(jù)metadata。ChromaDB會(huì)為每個(gè)文檔分配一個(gè)唯一ID。import chromadb from chromadb.config import Settings # 創(chuàng)建或連接到持久化的ChromaDB client chromadb.PersistentClient(path“./my_chroma_db“) collection client.get_or_create_collection(name“my_knowledge_base“) # 準(zhǔn)備批量添加的數(shù)據(jù) ids [f“doc_{i}“ for i in range(len(docs))] metadatas [doc.metadata for doc in docs] # 之前準(zhǔn)備好的元數(shù)據(jù) documents [doc.page_content for doc in docs] # 原始文本 # 添加文檔和其嵌入向量 collection.add( idsids, embeddingsdoc_embeddings.tolist(), # 注意轉(zhuǎn)換為list metadatasmetadatas, documentsdocuments )實(shí)操心得在構(gòu)建索引時(shí)建議對(duì)輸入文本進(jìn)行一次簡(jiǎn)單的清洗比如去除首尾空白符、合并多個(gè)換行符。有時(shí)候PDF解析會(huì)帶來(lái)奇怪的換行導(dǎo)致“用戶\n登錄”和“用戶登錄”在向量化后產(chǎn)生不必要的差異。另外對(duì)于大規(guī)模知識(shí)庫(kù)分批batch進(jìn)行encode和add操作并加入進(jìn)度提示能更好地管理內(nèi)存和掌控進(jìn)程。4. 檢索、重排與生成智能問(wèn)答鏈路的實(shí)現(xiàn)4.1 檢索器相似度計(jì)算與多路召回當(dāng)用戶提出一個(gè)問(wèn)題Query時(shí)第一步是將其轉(zhuǎn)化為向量然后在向量數(shù)據(jù)庫(kù)中進(jìn)行相似度搜索相似度計(jì)算通常使用余弦相似度。# 將用戶問(wèn)題轉(zhuǎn)化為向量 query_embedding embed_model.encode([user_question], normalize_embeddingsTrue)[0] # 在集合中進(jìn)行相似度搜索 results collection.query( query_embeddings[query_embedding.tolist()], n_results5 # 返回最相似的5個(gè)片段 )這里的n_results是一個(gè)關(guān)鍵超參數(shù)。返回太少可能遺漏關(guān)鍵信息返回太多會(huì)引入噪音并增加后續(xù)處理和模型成本。通常我會(huì)設(shè)置一個(gè)較大的初始值如10然后根據(jù)效果調(diào)整。單純的向量相似度檢索Dense Retrieval有時(shí)會(huì)漏掉一些關(guān)鍵詞匹配但語(yǔ)義表述不同的重要文檔。因此在“茴香豆”中我引入了混合檢索的思路稠密檢索如上所述基于向量相似度擅長(zhǎng)理解語(yǔ)義。稀疏檢索如BM25算法基于關(guān)鍵詞匹配擅長(zhǎng)處理專有名詞、術(shù)語(yǔ)。 可以將兩者的檢索結(jié)果取并集或按分?jǐn)?shù)融合實(shí)現(xiàn)“多路召回”提高召回率。4.2 重排序從“找到”到“找對(duì)”檢索系統(tǒng)返回了Top K個(gè)相關(guān)片段但它們的順序完全基于向量相似度分?jǐn)?shù)這個(gè)分?jǐn)?shù)不一定與“對(duì)生成最終答案最有幫助”的程度完全一致。這時(shí)就需要重排序。重排序器Reranker是一個(gè)更精細(xì)、通常也更耗資源的模型它會(huì)對(duì)檢索到的候選片段和問(wèn)題進(jìn)行一次更深入的交互式打分。一個(gè)流行的選擇是bge-reranker系列模型。from FlagEmbedding import FlagReranker reranker FlagReranker(‘BAAI/bge-reranker-large‘, use_fp16True) # 使用半精度節(jié)省內(nèi)存 pairs [[user_question, doc] for doc in retrieved_docs] scores reranker.compute_score(pairs, normalizeTrue) # 計(jì)算每個(gè)問(wèn)題文檔對(duì)的得分 # 根據(jù)重排序得分對(duì)文檔重新排序 reranked_docs [doc for _, doc in sorted(zip(scores, retrieved_docs), reverseTrue)]重排序后排名靠前的片段質(zhì)量通常會(huì)有顯著提升。在實(shí)踐中對(duì)于精度要求高的場(chǎng)景重排序幾乎是必選項(xiàng)。但它會(huì)帶來(lái)額外的延遲因此一種折中方案是先用向量檢索召回較多的候選如20個(gè)再用重排序器精選出最相關(guān)的3-5個(gè)送入大模型。4.3 提示工程與答案生成組裝上下文與提問(wèn)這是RAG鏈路的最后一環(huán)也是直接面向用戶的環(huán)節(jié)。我們需要將檢索到的最相關(guān)文檔片段作為上下文Context和用戶問(wèn)題Question一起構(gòu)造一個(gè)提示詞Prompt發(fā)送給大模型。一個(gè)經(jīng)典且有效的Prompt模板如下你是一個(gè)專業(yè)的智能助理請(qǐng)嚴(yán)格根據(jù)以下提供的上下文信息來(lái)回答問(wèn)題。如果上下文中的信息不足以回答問(wèn)題請(qǐng)直接說(shuō)“根據(jù)已知信息無(wú)法回答該問(wèn)題”不要編造信息。 上下文信息 {context} 用戶問(wèn)題{question} 請(qǐng)根據(jù)上下文信息回答在代碼中我們這樣實(shí)現(xiàn)def build_prompt(context_docs, question): # 將多個(gè)文檔片段合并為上下文 context “\n\n“.join([doc.page_content for doc in context_docs]) prompt_template “““你是一個(gè)專業(yè)的智能助理請(qǐng)嚴(yán)格根據(jù)以下提供的上下文信息來(lái)回答問(wèn)題。如果上下文中的信息不足以回答問(wèn)題請(qǐng)直接說(shuō)“根據(jù)已知信息無(wú)法回答該問(wèn)題”不要編造信息。 上下文信息 {context} 用戶問(wèn)題{question} 請(qǐng)根據(jù)上下文信息回答”“” return prompt_template.format(contextcontext, questionquestion) # 使用重排序后的前3個(gè)文檔 top_k_docs reranked_docs[:3] final_prompt build_prompt(top_k_docs, user_question) # 調(diào)用大模型 response openai_chat_completion(final_prompt) # 或調(diào)用本地模型這里有幾個(gè)至關(guān)重要的細(xì)節(jié)上下文長(zhǎng)度合并的上下文總長(zhǎng)度不能超過(guò)大模型的上下文窗口限制如GPT-3.5的4K或16K。需要在構(gòu)建Prompt時(shí)計(jì)算token數(shù)必要時(shí)截?cái)嘧畈恢匾钠巍V噶钭裱璓rompt中必須明確強(qiáng)調(diào)“嚴(yán)格根據(jù)上下文”這是抑制模型幻覺(jué)的關(guān)鍵。引用標(biāo)注在答案中可以要求模型注明答案來(lái)源于哪個(gè)文檔片段通過(guò)元數(shù)據(jù)中的ID或標(biāo)題增加可信度。例如在Prompt中加入“請(qǐng)?jiān)诖鸢改┪灿谩緛?lái)源文檔標(biāo)題】的格式注明出處”。5. 效果評(píng)估與迭代優(yōu)化讓“茴香豆”更聰明搭建完基礎(chǔ)流程只是第一步要讓RAG智能助理真正可用必須進(jìn)行效果評(píng)估和持續(xù)優(yōu)化。5.1 構(gòu)建測(cè)試集與評(píng)估指標(biāo)不能憑感覺(jué)說(shuō)“好像還行”。需要建立一個(gè)小的測(cè)試集QA對(duì)例如從知識(shí)庫(kù)中抽取20-50個(gè)問(wèn)題并準(zhǔn)備好標(biāo)準(zhǔn)答案或關(guān)鍵信息點(diǎn)。評(píng)估指標(biāo)可以包括檢索精度Top K檢索結(jié)果中是否包含了能回答問(wèn)題的正確片段可以計(jì)算Hit RateK。答案準(zhǔn)確性模型的回答與標(biāo)準(zhǔn)答案在事實(shí)層面上是否一致這需要人工或借助更強(qiáng)大的模型如GPT-4進(jìn)行評(píng)判。答案相關(guān)性答案是否緊扣問(wèn)題沒(méi)有答非所問(wèn)幻覺(jué)率答案中是否出現(xiàn)了上下文未提供的、編造的信息5.2 常見(jiàn)問(wèn)題排查與優(yōu)化技巧在實(shí)際運(yùn)行“茴香豆”的過(guò)程中我遇到了不少典型問(wèn)題以下是排查思路和優(yōu)化方法問(wèn)題1檢索不到相關(guān)文檔。檢查用戶問(wèn)題的向量表示是否合理可以嘗試將問(wèn)題用更完整、更書(shū)面化的語(yǔ)言重新表述后檢索。優(yōu)化查詢擴(kuò)展對(duì)原始問(wèn)題進(jìn)行同義詞擴(kuò)展、或者讓大模型生成幾個(gè)相關(guān)的問(wèn)題用這組問(wèn)題去檢索然后合并結(jié)果。優(yōu)化切分回顧文檔切分策略。是不是切得太碎導(dǎo)致關(guān)鍵信息被割裂嘗試增大chunk_size或采用語(yǔ)義切分。調(diào)整嵌入模型對(duì)于專業(yè)領(lǐng)域如醫(yī)學(xué)、法律通用嵌入模型可能表現(xiàn)不佳。嘗試使用在該領(lǐng)域數(shù)據(jù)上微調(diào)過(guò)的嵌入模型或者像bge-large-zh這樣在中文上表現(xiàn)更優(yōu)的模型。問(wèn)題2檢索到了相關(guān)文檔但答案還是不對(duì)或包含幻覺(jué)。檢查查看最終送入模型的上下文。是不是包含了無(wú)關(guān)或矛盾的片段Prompt指令是否足夠強(qiáng)硬優(yōu)化引入重排序這是解決此問(wèn)題最有效的手段之一確保送給模型的是最精華、最相關(guān)的片段。優(yōu)化Prompt在Prompt中增加更嚴(yán)格的約束例如“你必須且只能使用以下上下文中的信息。上下文中的信息是真實(shí)可信的請(qǐng)忽略你已有的任何可能與之沖突的知識(shí)。”上下文壓縮/摘要如果檢索到的片段很長(zhǎng)且包含冗余可以先用一個(gè)較小的模型或大模型本身對(duì)每個(gè)片段進(jìn)行摘要再將摘要作為上下文送入減少噪音。問(wèn)題3回答“根據(jù)已知信息無(wú)法回答”但明明知識(shí)庫(kù)里有。檢查這是典型的“語(yǔ)義鴻溝”問(wèn)題。用戶的問(wèn)題表述和知識(shí)庫(kù)中的文檔表述差異太大。優(yōu)化對(duì)知識(shí)庫(kù)進(jìn)行數(shù)據(jù)增強(qiáng)在構(gòu)建索引時(shí)除了原始文本還可以為每個(gè)片段人工或自動(dòng)生成幾個(gè)可能的問(wèn)題Question Generation將“問(wèn)題-片段”對(duì)一起存入向量數(shù)據(jù)庫(kù)。檢索時(shí)不僅用用戶問(wèn)題去匹配片段內(nèi)容也去匹配這些生成的問(wèn)題。使用HyDE技術(shù)讓大模型根據(jù)用戶問(wèn)題“幻想”一個(gè)假設(shè)性答案Hypothetical Document Embedding然后用這個(gè)假設(shè)答案的向量去檢索。因?yàn)榧僭O(shè)答案的表述風(fēng)格可能更接近知識(shí)庫(kù)文檔從而能更好地檢索到相關(guān)內(nèi)容。5.3 高級(jí)進(jìn)階Agentic RAG 與 查詢路由當(dāng)基礎(chǔ)RAG跑通后可以探索更高級(jí)的模式讓“茴香豆”變得更智能。Agentic RAG將RAG系統(tǒng)作為一個(gè)工具嵌入到一個(gè)智能體Agent的循環(huán)中。例如當(dāng)用戶提出一個(gè)復(fù)雜、多步驟的問(wèn)題時(shí)Agent可以自主規(guī)劃先檢索A文檔了解概念再根據(jù)結(jié)果檢索B文檔獲取具體數(shù)據(jù)最后綜合生成答案。這需要引入如LangChain的Agent框架并定義好RAG工具的調(diào)用方式。查詢路由不是所有用戶查詢都需要走RAG流程。系統(tǒng)可以設(shè)計(jì)一個(gè)路由層先判斷問(wèn)題類型如果是簡(jiǎn)單的問(wèn)候或通用知識(shí)如“你好”、“太陽(yáng)為什么東升西落”直接讓大模型基于自身知識(shí)回答。如果是需要最新信息或私有信息的問(wèn)題如“我司Q3財(cái)報(bào)要點(diǎn)”、“項(xiàng)目X的架構(gòu)圖在哪里”則觸發(fā)RAG流程。如果是需要計(jì)算或執(zhí)行某個(gè)操作如“計(jì)算一下我的報(bào)銷總額”、“創(chuàng)建一個(gè)會(huì)議邀請(qǐng)”則路由到相應(yīng)的工具或函數(shù)。 這可以通過(guò)訓(xùn)練一個(gè)簡(jiǎn)單的文本分類器或者使用大模型本身進(jìn)行意圖識(shí)別來(lái)實(shí)現(xiàn)。6. 項(xiàng)目部署與工程化思考6.1 從腳本到服務(wù)API封裝與前端集成開(kāi)發(fā)階段的代碼可能是零散的腳本。為了實(shí)用需要將其封裝成服務(wù)。一個(gè)簡(jiǎn)單的架構(gòu)是使用FastAPI構(gòu)建RESTful APIfrom fastapi import FastAPI, HTTPException from pydantic import BaseModel app FastAPI(title“茴香豆RAG智能助理API“) class QueryRequest(BaseModel): question: str top_k: int 5 class QueryResponse(BaseModel): answer: str sources: list[str] # 引用來(lái)源 app.post(“/ask“, response_modelQueryResponse) async def ask_question(request: QueryRequest): # 這里集成前面實(shí)現(xiàn)的所有步驟檢索、重排、生成 # ... answer, source_docs rag_chain.invoke(request.question) return QueryResponse(answeranswer, sources[doc.metadata.get(‘title‘, ‘N/A‘) for doc in source_docs])這樣前端如Gradio、微信小程序、企業(yè)內(nèi)部系統(tǒng)就可以通過(guò)調(diào)用這個(gè)API來(lái)獲取智能問(wèn)答服務(wù)。Gradio的集成非常簡(jiǎn)單幾乎就是一個(gè)函數(shù)調(diào)用。6.2 知識(shí)庫(kù)的更新與維護(hù)知識(shí)不是靜態(tài)的。當(dāng)有新文檔加入或舊文檔更新時(shí)需要支持知識(shí)庫(kù)的增量更新。全量重建最簡(jiǎn)單但最耗時(shí)刪除舊集合重新處理所有文檔并構(gòu)建索引。適用于知識(shí)庫(kù)較小或更新不頻繁的場(chǎng)景。增量更新更優(yōu)雅的方式。為每個(gè)文檔切片計(jì)算一個(gè)哈希值如MD5當(dāng)文檔更新時(shí)只需處理哈希值發(fā)生變化的文檔并更新向量數(shù)據(jù)庫(kù)中對(duì)應(yīng)的條目。這需要更精細(xì)的數(shù)據(jù)管理邏輯。刪除處理同樣需要支持從知識(shí)庫(kù)中刪除特定文檔。在ChromaDB中可以根據(jù)文檔的ID或元數(shù)據(jù)進(jìn)行刪除操作。6.3 性能、成本與監(jiān)控性能主要瓶頸在嵌入模型推理和向量檢索。對(duì)于大規(guī)模知識(shí)庫(kù)需要考慮將向量數(shù)據(jù)庫(kù)如Chroma部署為獨(dú)立服務(wù)并使用GPU加速嵌入模型。檢索時(shí)使用近似最近鄰搜索ANN算法如HNSW來(lái)平衡精度和速度。成本如果使用商用大模型API成本主要來(lái)自Token消耗。優(yōu)化策略包括優(yōu)化Prompt減少冗余、壓縮上下文、對(duì)簡(jiǎn)單問(wèn)題使用更便宜的模型如GPT-3.5 Turbo、設(shè)置使用頻率限制等。監(jiān)控記錄每一次問(wèn)答的日志包括用戶問(wèn)題、檢索到的文檔、生成的答案、耗時(shí)、Token使用量。這有助于分析效果瓶頸、發(fā)現(xiàn)常見(jiàn)錯(cuò)誤問(wèn)題Bad Cases并為后續(xù)的優(yōu)化提供數(shù)據(jù)支持。走完“茴香豆”這個(gè)完整的項(xiàng)目你對(duì)RAG的理解就不再停留在概念上了。你會(huì)清楚地知道一個(gè)簡(jiǎn)單的問(wèn)答背后是數(shù)據(jù)預(yù)處理、向量化、檢索、重排、提示工程等一系列環(huán)節(jié)的精密協(xié)作每一個(gè)環(huán)節(jié)都有優(yōu)化的空間。這套方法論和實(shí)操經(jīng)驗(yàn)是構(gòu)建任何更復(fù)雜AI應(yīng)用如智能客服、企業(yè)知識(shí)中樞、AI編程助手的堅(jiān)實(shí)基礎(chǔ)。最重要的是你擁有了一個(gè)完全受自己掌控的智能助理原型可以根據(jù)需要不斷喂養(yǎng)它新的知識(shí)讓它持續(xù)成長(zhǎng)真正成為你工作或?qū)W習(xí)中的得力幫手。