級(jí)應(yīng)用的工程化實(shí)踐)
1. 項(xiàng)目概述從“玩具”到“工具”的AI工程化之路最近和不少做AI應(yīng)用的朋友聊天發(fā)現(xiàn)一個(gè)挺有意思的現(xiàn)象大家用大模型API搞個(gè)Demo做個(gè)聊天機(jī)器人或者文本總結(jié)工具速度都很快效果乍一看也挺唬人。但一旦想把東西拿給真實(shí)用戶用或者想把它嵌入到自己的核心業(yè)務(wù)流里問(wèn)題就全冒出來(lái)了。比如你讓模型幫你分析一份最新的行業(yè)報(bào)告它可能給你編造幾個(gè)根本不存在的數(shù)據(jù)你想讓它根據(jù)公司內(nèi)部知識(shí)庫(kù)回答客戶問(wèn)題它要么說(shuō)“我不知道”要么就開(kāi)始一本正經(jīng)地胡說(shuō)八道。這感覺(jué)就像你買了一臺(tái)號(hào)稱“全能”的工程機(jī)械結(jié)果發(fā)現(xiàn)它既不會(huì)挖坑也不會(huì)吊裝離了說(shuō)明書就寸步難行只能當(dāng)個(gè)擺設(shè)。這背后的核心矛盾其實(shí)就是當(dāng)前大模型能力與真實(shí)世界需求之間的“最后一公里”問(wèn)題。大模型尤其是那些千億、萬(wàn)億參數(shù)的基礎(chǔ)模型本質(zhì)是一個(gè)基于海量互聯(lián)網(wǎng)文本訓(xùn)練出來(lái)的“概率預(yù)測(cè)大師”。它擅長(zhǎng)續(xù)寫、概括、翻譯這些模式相對(duì)固定的任務(wù)但對(duì)于需要精確、實(shí)時(shí)、私有化知識(shí)的場(chǎng)景就顯得力不從心了。它不知道你公司上周剛更新的產(chǎn)品定價(jià)表也讀不懂你數(shù)據(jù)庫(kù)里那些結(jié)構(gòu)復(fù)雜的客戶記錄更無(wú)法主動(dòng)去調(diào)用一個(gè)外部API來(lái)查詢今天的天氣或股票價(jià)格。于是兩個(gè)概念在AI工程化的實(shí)踐中被推到了前臺(tái)RAG檢索增強(qiáng)生成和智能體Agent。這倆不是什么遙不可及的學(xué)術(shù)概念而是我們這些一線開(kāi)發(fā)者用來(lái)給大模型“打補(bǔ)丁”、“裝手腳”的實(shí)用工具箱。簡(jiǎn)單來(lái)說(shuō)RAG解決的是“知識(shí)”問(wèn)題讓模型能說(shuō)“正確”的話智能體解決的是“行動(dòng)”問(wèn)題讓模型能做“有用”的事。這篇文章我就結(jié)合自己趟過(guò)的坑和做過(guò)的項(xiàng)目來(lái)拆解一下為什么在今天的AI應(yīng)用開(kāi)發(fā)中你幾乎繞不開(kāi)這兩項(xiàng)技術(shù)以及它們是如何協(xié)同工作把一個(gè)“聊天玩具”變成真正可用的“生產(chǎn)力工具”的。2. 核心困境拆解大模型的“無(wú)知”與“無(wú)能”在深入RAG和智能體之前我們必須先搞清楚我們到底在解決什么問(wèn)題。把大模型直接當(dāng)“萬(wàn)能大腦”來(lái)用通常會(huì)撞上以下幾堵南墻。2.1 知識(shí)幻覺(jué)與事實(shí)性謬誤這是最頭疼的問(wèn)題沒(méi)有之一。你問(wèn)模型“我們公司旗艦產(chǎn)品Arix Pro的最大續(xù)航是多少” 盡管你的產(chǎn)品文檔里白紙黑字寫著“18小時(shí)”但模型可能會(huì)基于它在訓(xùn)練數(shù)據(jù)里看到的其他筆記本信息自信地回答“根據(jù)公開(kāi)信息大約是12小時(shí)”。這種現(xiàn)象被稱為“幻覺(jué)”Hallucination。模型并不是在“撒謊”它只是在基于概率生成最“流暢”和“合理”的文本而這個(gè)文本可能與事實(shí)相去甚遠(yuǎn)。在金融、法律、醫(yī)療等對(duì)準(zhǔn)確性要求極高的領(lǐng)域這種幻覺(jué)是致命的。我曾參與一個(gè)金融合規(guī)審核的項(xiàng)目初期直接調(diào)用模型分析交易條款它偶爾會(huì)“創(chuàng)造”出一些不存在的監(jiān)管條例編號(hào)差點(diǎn)引發(fā)誤判。這讓我們意識(shí)到不能讓模型在“未知”或“私有”領(lǐng)域自由發(fā)揮。它需要一個(gè)可靠的知識(shí)來(lái)源作為依據(jù)。2.2 知識(shí)陳舊與缺乏實(shí)時(shí)性大模型的訓(xùn)練數(shù)據(jù)是有截止日期的。比如GPT-4的知識(shí)截止到2023年4月。這意味著它不知道這之后發(fā)生的任何事件、發(fā)布的新產(chǎn)品、變更的法律法規(guī)。你無(wú)法用它來(lái)查詢今天的股價(jià)、分析剛出爐的財(cái)報(bào)、或者解答關(guān)于某個(gè)昨天才爆出新聞的科技事件。對(duì)于需要實(shí)時(shí)響應(yīng)的應(yīng)用如客服、新聞?wù)?、市?chǎng)分析這是一個(gè)硬傷。模型需要一種機(jī)制能動(dòng)態(tài)地獲取并理解最新的信息。2.3 無(wú)法訪問(wèn)私有與結(jié)構(gòu)化數(shù)據(jù)企業(yè)的核心價(jià)值往往沉淀在內(nèi)部CRM里的客戶記錄、Confluence里的項(xiàng)目文檔、數(shù)據(jù)庫(kù)中的訂單信息、GitLab里的代碼庫(kù)。這些數(shù)據(jù)要么是私有的從未出現(xiàn)在公開(kāi)互聯(lián)網(wǎng)上要么是高度結(jié)構(gòu)化的如JSON、SQL表與模型訓(xùn)練時(shí)見(jiàn)到的自然文本相差甚遠(yuǎn)。模型對(duì)此一無(wú)所知就像一個(gè)被關(guān)在圖書館門外的人空有理解能力卻無(wú)書可讀。2.4 缺乏執(zhí)行與交互能力模型本質(zhì)上是一個(gè)“思考者”和“表達(dá)者”而不是一個(gè)“行動(dòng)者”。它可以寫出一段完美的代碼但不能自己點(diǎn)擊“運(yùn)行”按鈕它可以描述如何發(fā)送一封郵件但不能真的調(diào)用SMTP接口它可以分析是否需要查詢數(shù)據(jù)庫(kù)但不能自己建立連接并執(zhí)行SQL。在很多自動(dòng)化流程中我們需要AI不僅能“想”和“說(shuō)”還要能“做”。這就需要為模型賦予調(diào)用工具Tools、執(zhí)行任務(wù)Tasks的能力。注意很多人容易混淆“自動(dòng)化腳本”和“智能體”。一個(gè)簡(jiǎn)單的、按固定流程運(yùn)行的腳本不是智能體。智能體的核心在于“決策”——它能夠根據(jù)模型的推理動(dòng)態(tài)地決定下一步調(diào)用哪個(gè)工具、傳入什么參數(shù)、如何處理返回結(jié)果。這個(gè)決策環(huán)路是智能體價(jià)值的關(guān)鍵。3. RAG詳解為模型構(gòu)建“外部記憶體”面對(duì)“無(wú)知”的困境最直觀的想法就是給模型“喂資料”。RAG就是一套系統(tǒng)化的“喂資料”方法論。它的核心思想不是去改變模型本身那需要昂貴的微調(diào)而是在模型之外構(gòu)建一個(gè)動(dòng)態(tài)的、可檢索的“知識(shí)庫(kù)”在生成答案前先把相關(guān)的參考資料找出來(lái)和問(wèn)題一起交給模型。這樣模型就能基于你提供的“上下文”來(lái)生成答案大幅提高準(zhǔn)確性和可控性。3.1 RAG的核心工作流程一個(gè)典型的RAG系統(tǒng)可以分為“索引”和“檢索-生成”兩個(gè)主要階段。階段一索引Indexing這個(gè)階段是離線的目的是把你的原始知識(shí)文檔、網(wǎng)頁(yè)、數(shù)據(jù)庫(kù)表等處理成便于快速檢索的格式。加載與切分首先你需要把各種格式的文檔PDF、Word、Markdown、HTML等加載進(jìn)來(lái)并切割成大小合適的“塊”Chunks。這里有個(gè)關(guān)鍵技巧切分不是簡(jiǎn)單按字?jǐn)?shù)。好的切分要兼顧語(yǔ)義完整性。比如按段落切分比按固定500字切分更好對(duì)于Markdown可以按標(biāo)題層級(jí)切分。使用像LangChain的RecursiveCharacterTextSplitter或Semantic Splitter能獲得更好的效果。向量化這是RAG的“魔法”步驟。使用一個(gè)嵌入模型Embedding Model將每一個(gè)文本塊轉(zhuǎn)換成一個(gè)高維度的向量比如1536維。這個(gè)向量就像是這段文本的“數(shù)學(xué)指紋”語(yǔ)義相近的文本其向量在空間中的距離也會(huì)很近。常用的嵌入模型有OpenAI的text-embedding-3系列、開(kāi)源的BGE、Voyage等。存儲(chǔ)將這些向量以及對(duì)應(yīng)的原始文本塊存儲(chǔ)到一個(gè)專門的“向量數(shù)據(jù)庫(kù)”中。常見(jiàn)的向量數(shù)據(jù)庫(kù)有Pinecone、Weaviate、Qdrant、Milvus以及PGVectorPostgreSQL的擴(kuò)展。選擇哪個(gè)取決于你的規(guī)模、性能要求和運(yùn)維能力。階段二檢索與生成Retrieval Generation這個(gè)階段是在線響應(yīng)用戶查詢時(shí)發(fā)生的。問(wèn)題向量化當(dāng)用戶提出一個(gè)問(wèn)題Query系統(tǒng)使用同樣的嵌入模型將這個(gè)問(wèn)題也轉(zhuǎn)化為一個(gè)向量。相似性檢索在向量數(shù)據(jù)庫(kù)中尋找與“問(wèn)題向量”最相似的若干個(gè)“文本塊向量”。這個(gè)過(guò)程通常使用余弦相似度或點(diǎn)積來(lái)計(jì)算。系統(tǒng)會(huì)返回相似度最高的前k個(gè)文本塊例如top-5。上下文構(gòu)建與提示將這k個(gè)檢索到的文本塊作為“參考上下文”與用戶的原始問(wèn)題一起構(gòu)造成一個(gè)詳細(xì)的提示Prompt發(fā)送給大語(yǔ)言模型。一個(gè)經(jīng)典的提示模板可能是“請(qǐng)基于以下上下文信息回答問(wèn)題。如果上下文信息不足以回答問(wèn)題請(qǐng)直接說(shuō)‘根據(jù)提供的信息我無(wú)法回答這個(gè)問(wèn)題’。上下文{檢索到的文本}。問(wèn)題{用戶問(wèn)題}”。生成最終答案大語(yǔ)言模型基于這個(gè)富含上下文的提示生成最終答案。由于答案的“素材”來(lái)源于你提供的可靠文檔其產(chǎn)生幻覺(jué)的概率大大降低。3.2 RAG實(shí)戰(zhàn)中的關(guān)鍵技巧與避坑指南搭建一個(gè)能跑的RAG原型很簡(jiǎn)單但要讓它在生產(chǎn)環(huán)境穩(wěn)定、準(zhǔn)確、高效需要注意大量細(xì)節(jié)。技巧一分塊策略是成敗的第一步切忌無(wú)腦固定長(zhǎng)度分塊512個(gè)字符的塊可能會(huì)把一個(gè)完整的概念攔腰截?cái)?。?duì)于技術(shù)文檔可以按章節(jié)/子章節(jié)切分對(duì)于對(duì)話記錄按對(duì)話輪次切分。重疊Overlap是必要的在切分時(shí)讓相鄰的文本塊有少量重疊比如100個(gè)字符。這能確保一些關(guān)鍵信息如出現(xiàn)在段落末尾的定義不會(huì)因?yàn)榍『帽磺性谶吔缍鴣G失提高了檢索的召回率。實(shí)操心得我通常會(huì)先用不同的分塊策略按段落、按標(biāo)題、固定長(zhǎng)度重疊對(duì)一小部分?jǐn)?shù)據(jù)做索引然后用一組標(biāo)準(zhǔn)問(wèn)題去測(cè)試檢索質(zhì)量選擇效果最好的策略。沒(méi)有銀彈必須結(jié)合數(shù)據(jù)特性實(shí)驗(yàn)。技巧二檢索不是“一錘子買賣”簡(jiǎn)單相似性檢索的局限如果用戶問(wèn)“蘋果公司最新財(cái)報(bào)怎么樣”而你的知識(shí)庫(kù)里只有一篇題為“Apple Q4 2023 Financial Results”的PDF由于“蘋果”和“Apple”的語(yǔ)義雖然一樣但向量表示可能因語(yǔ)言不同而有差異可能導(dǎo)致檢索失敗。引入查詢重寫與擴(kuò)展在檢索前可以先讓大模型對(duì)原始查詢進(jìn)行重寫或擴(kuò)展。例如將“蘋果財(cái)報(bào)”重寫為“Apple financial report earnings Q4 2023”。這能顯著提升檢索的魯棒性。LangChain中的MultiQueryRetriever就是這個(gè)思路的自動(dòng)化實(shí)現(xiàn)?;旌蠙z索Hybrid Search除了向量檢索還可以結(jié)合傳統(tǒng)的關(guān)鍵詞檢索如BM25。關(guān)鍵詞檢索在精確匹配術(shù)語(yǔ)、縮寫、產(chǎn)品型號(hào)等方面有優(yōu)勢(shì)。將兩者的結(jié)果進(jìn)行加權(quán)融合如 Reciprocal Rank Fusion能兼顧語(yǔ)義相似性和字面匹配度效果通常比單一方法好。技巧三提示工程是質(zhì)量的“放大器”檢索到了對(duì)的文檔但如果提示沒(méi)寫好模型依然可能忽略上下文或生成糟糕的答案。明確指令在提示中強(qiáng)烈要求模型“必須且僅能”依據(jù)提供的上下文回答??梢栽O(shè)置“引用”格式要求模型在答案中標(biāo)注出處來(lái)自哪個(gè)文檔塊這既方便用戶溯源也便于我們后期評(píng)估RAG鏈路的質(zhì)量。處理“未找到”場(chǎng)景一定要在提示中告訴模型如果上下文不相關(guān)或不足應(yīng)該如何回應(yīng)。一個(gè)友好的“我暫時(shí)沒(méi)有找到相關(guān)信息您可以嘗試……”比一個(gè)胡編亂造的答案體驗(yàn)好得多。上下文排序與過(guò)濾檢索到的top-k個(gè)塊其相關(guān)性可能差異很大。可以在送入最終提示前用一個(gè)更輕量的模型或規(guī)則對(duì)它們進(jìn)行重排序或過(guò)濾只保留最相關(guān)的幾個(gè)避免無(wú)關(guān)信息干擾模型也節(jié)省了令牌Token消耗。注意RAG不是微調(diào)的替代品而是互補(bǔ)。對(duì)于需要模型深度理解特定領(lǐng)域術(shù)語(yǔ)、風(fēng)格或推理模式的任務(wù)如生成特定格式的法律文書微調(diào)Fine-tuning可能更有效。RAG更擅長(zhǎng)處理需要大量、動(dòng)態(tài)、事實(shí)性知識(shí)支撐的問(wèn)答場(chǎng)景。在實(shí)際項(xiàng)目中我經(jīng)??吹健癛AG 輕量微調(diào)”的組合拳用RAG解決知識(shí)問(wèn)題用微調(diào)讓模型更“懂行”。4. 智能體詳解為模型安裝“手腳”與“調(diào)度中心”解決了“知識(shí)”問(wèn)題我們來(lái)看“行動(dòng)”問(wèn)題。智能體Agent的本質(zhì)是賦予大語(yǔ)言模型使用工具Tools、進(jìn)行規(guī)劃Planning、并持續(xù)執(zhí)行Execution的能力。你可以把它想象成一個(gè)項(xiàng)目的“高級(jí)主管”它理解老板用戶的最終目標(biāo)“做一份競(jìng)品分析報(bào)告”知道自己手下可用工具有哪些人搜索工具、文檔分析工具、圖表生成工具然后自己制定步驟先搜索最新信息再總結(jié)各自優(yōu)缺點(diǎn)最后生成報(bào)告草稿并一步步指揮協(xié)調(diào)直到任務(wù)完成。4.1 智能體的核心架構(gòu)推理-行動(dòng)循環(huán)智能體的工作遵循一個(gè)經(jīng)典的“感知-思考-行動(dòng)”循環(huán)在LLM語(yǔ)境下常被稱為ReActReasoning Acting框架。觀察智能體接收到用戶的目標(biāo)或當(dāng)前任務(wù)狀態(tài)。思考大語(yǔ)言模型作為“大腦”分析當(dāng)前狀況。它需要決定任務(wù)完成了嗎如果沒(méi)完成下一步該做什么應(yīng)該調(diào)用哪個(gè)工具調(diào)用時(shí)需要什么參數(shù)行動(dòng)根據(jù)思考的結(jié)果智能體調(diào)用相應(yīng)的工具如執(zhí)行一段代碼、調(diào)用一個(gè)API、查詢數(shù)據(jù)庫(kù)并獲取工具執(zhí)行的結(jié)果。再觀察將工具執(zhí)行的結(jié)果成功或失敗以及返回的數(shù)據(jù)作為新的觀察反饋給“大腦”。循環(huán)模型基于新的觀察再次進(jìn)行思考決定下一步行動(dòng)如此循環(huán)直至任務(wù)完成或無(wú)法繼續(xù)。4.2 智能體中的關(guān)鍵角色工具Tools工具是智能體能力的延伸。一個(gè)工具本質(zhì)上是一個(gè)函數(shù)它有明確的名稱、描述、參數(shù)格式。智能體通過(guò)提示詞學(xué)習(xí)這些工具的功能。常見(jiàn)的工具有網(wǎng)絡(luò)搜索如SerpAPI或DuckDuckGo Search讓智能體能獲取實(shí)時(shí)信息。代碼執(zhí)行如Python REPL讓智能體能進(jìn)行數(shù)學(xué)計(jì)算、數(shù)據(jù)處理。文件操作讀寫本地文件。API調(diào)用連接企業(yè)內(nèi)部或第三方服務(wù)如發(fā)送郵件、查詢數(shù)據(jù)庫(kù)、操作CRM。專屬工具你為特定業(yè)務(wù)編寫的任何函數(shù)比如“查詢本月銷售數(shù)據(jù)”、“為客戶生成保單號(hào)”。定義工具的技巧名稱和描述要清晰模型的“思考”依賴于你對(duì)工具的描述。search_web就不如search_web_for_latest_news明確。描述要寫清楚“這個(gè)工具是干什么的”以及“在什么情況下使用它”例如“使用此工具在維基百科上搜索關(guān)于歷史人物或事件的摘要信息。”處理好復(fù)雜參數(shù)如果工具參數(shù)是一個(gè)復(fù)雜對(duì)象最好在描述中給出清晰的JSON結(jié)構(gòu)示例幫助模型正確格式化請(qǐng)求。4.3 主流智能體框架與平臺(tái)選擇現(xiàn)在有很多優(yōu)秀的框架和平臺(tái)能幫助我們構(gòu)建智能體降低開(kāi)發(fā)門檻。LangChain / LangGraph這是目前生態(tài)最豐富、最靈活的框架之一。LangChain提供了構(gòu)建智能體所需的所有基礎(chǔ)組件工具、記憶、鏈。而LangGraph更進(jìn)一步允許你以“圖”的形式可視化地定義智能體的工作流特別適合構(gòu)建有復(fù)雜狀態(tài)轉(zhuǎn)移和分支邏輯的多步驟智能體。它給了開(kāi)發(fā)者極大的控制權(quán)但學(xué)習(xí)曲線相對(duì)陡峭。AutoGen由微軟推出專注于多智能體協(xié)作。你可以創(chuàng)建多個(gè)角色化的智能體如程序員、測(cè)試員、產(chǎn)品經(jīng)理讓它們通過(guò)對(duì)話來(lái)協(xié)作解決復(fù)雜任務(wù)。這在需要多角度評(píng)審或分工的場(chǎng)景下非常強(qiáng)大比如協(xié)同代碼開(kāi)發(fā)、方案辯論等。Dify / Coze這類屬于低代碼/無(wú)代碼AI應(yīng)用平臺(tái)。它們提供了可視化的界面讓你可以通過(guò)拖拽組件的方式快速組裝包含RAG、智能體、工作流在內(nèi)的復(fù)雜應(yīng)用。Dify的“工作流”畫布和Coze的“Bot”創(chuàng)建界面都非常直觀。它們的優(yōu)勢(shì)在于極快的原型開(kāi)發(fā)和部署速度特別適合產(chǎn)品經(jīng)理、業(yè)務(wù)人員或需要快速驗(yàn)證想法的開(kāi)發(fā)者。但深度定制能力可能不如純代碼框架。如何選擇如果你是研究者或需要極度定制復(fù)雜邏輯的工程師LangGraph是你的不二之選。如果你想探索多智能體社會(huì)的交互與協(xié)作AutoGen提供了絕佳的試驗(yàn)場(chǎng)。如果你的目標(biāo)是快速將AI能力轉(zhuǎn)化為可用的業(yè)務(wù)應(yīng)用且團(tuán)隊(duì)中不一定有資深A(yù)I工程師Dify或Coze這類平臺(tái)能讓你在幾小時(shí)內(nèi)就看到成果。我個(gè)人在為企業(yè)做內(nèi)部效率工具PoC時(shí)經(jīng)常先用Dify快速搭出可演示的版本驗(yàn)證價(jià)值后再考慮用代碼重構(gòu)。4.4 智能體開(kāi)發(fā)中的常見(jiàn)陷阱與調(diào)試心得智能體開(kāi)發(fā)聽(tīng)起來(lái)很酷但調(diào)試起來(lái)可能讓人抓狂因?yàn)樗婕胺谴_定性的LLM推理。陷阱一智能體陷入“死循環(huán)”或“無(wú)效行動(dòng)”這是最常見(jiàn)的問(wèn)題。智能體可能反復(fù)調(diào)用同一個(gè)工具而不推進(jìn)或者在幾個(gè)無(wú)關(guān)工具間來(lái)回切換。對(duì)策設(shè)置明確的停止條件Stop Condition和最大迭代次數(shù)。在提示詞中清晰地告訴模型“如果你認(rèn)為已經(jīng)獲得了足夠的信息來(lái)回答問(wèn)題或者連續(xù)三次嘗試都無(wú)法取得進(jìn)展請(qǐng)直接輸出最終答案?!?在代碼層面務(wù)必設(shè)置循環(huán)上限比如10次防止無(wú)限循環(huán)消耗資源。調(diào)試技巧開(kāi)啟智能體的詳細(xì)日志觀察它的“思考”過(guò)程??纯此诿恳徊降降资侨绾谓馕鲇^察、決定行動(dòng)的。很多時(shí)候問(wèn)題出在工具的描述不夠清晰或者上一步工具返回的結(jié)果格式讓模型產(chǎn)生了誤解。陷阱二工具調(diào)用參數(shù)錯(cuò)誤模型可能會(huì)誤解你的要求給工具傳入錯(cuò)誤類型或格式的參數(shù)。對(duì)策強(qiáng)化工具描述中的示例。對(duì)于復(fù)雜參數(shù)使用JSON Schema進(jìn)行嚴(yán)格定義。一些高級(jí)框架支持“工具調(diào)用”Function Calling模式模型會(huì)輸出結(jié)構(gòu)化的調(diào)用請(qǐng)求這比從自然語(yǔ)言文本中解析參數(shù)要可靠得多。此外可以在工具函數(shù)內(nèi)部增加健壯的類型檢查和錯(cuò)誤處理當(dāng)參數(shù)錯(cuò)誤時(shí)返回清晰的錯(cuò)誤信息幫助模型進(jìn)行修正。陷阱三處理開(kāi)放域任務(wù)的不可控性你告訴智能體“幫我策劃一個(gè)周末旅行”這個(gè)目標(biāo)非常開(kāi)放。智能體可能會(huì)調(diào)用搜索工具查找“旅行”然后開(kāi)始預(yù)訂機(jī)票和酒店如果它有這些工具權(quán)限這顯然存在風(fēng)險(xiǎn)和成本。對(duì)策為智能體設(shè)定明確的邊界和權(quán)限。在提示詞開(kāi)頭就定義它的角色和限制例如“你是一個(gè)旅行信息咨詢助手只能提供信息查詢和方案建議不能執(zhí)行任何實(shí)際的預(yù)訂、支付或更改數(shù)據(jù)的操作?!?同時(shí)在工具層面進(jìn)行權(quán)限控制對(duì)于高風(fēng)險(xiǎn)工具如寫數(shù)據(jù)庫(kù)、發(fā)郵件需要額外的確認(rèn)機(jī)制或根本不對(duì)智能體開(kāi)放。實(shí)操心得開(kāi)發(fā)智能體時(shí)我習(xí)慣采用“由簡(jiǎn)入繁”的策略。先實(shí)現(xiàn)一個(gè)只有一個(gè)核心工具的簡(jiǎn)單智能體確保它能穩(wěn)定運(yùn)行。然后逐步添加更多工具和更復(fù)雜的推理邏輯。每加一個(gè)新功能都用一組測(cè)試用例去驗(yàn)證觀察智能體的決策是否符合預(yù)期。把智能體想象成一個(gè)需要“訓(xùn)練”和“引導(dǎo)”的新員工清晰的指令提示詞和規(guī)范的流程工具設(shè)計(jì)至關(guān)重要。5. RAG與智能體的融合構(gòu)建下一代AI應(yīng)用單獨(dú)使用RAG或智能體已經(jīng)能解決很多問(wèn)題但當(dāng)我們把兩者結(jié)合起來(lái)時(shí)就能構(gòu)建出真正強(qiáng)大、自主的AI應(yīng)用。我稱之為“有記憶、會(huì)思考、能行動(dòng)”的智能系統(tǒng)。5.1 融合的典型模式模式一智能體驅(qū)動(dòng)RAG在這種模式下智能體作為總控RAG作為它手下的一個(gè)“專家工具”。場(chǎng)景用戶問(wèn)“對(duì)比一下我們產(chǎn)品A和競(jìng)爭(zhēng)對(duì)手產(chǎn)品B在能耗方面的表現(xiàn)并給出建議?!绷鞒讨悄荏w“思考”要回答這個(gè)問(wèn)題我需要兩份資料我們產(chǎn)品A的規(guī)格書以及競(jìng)爭(zhēng)對(duì)手產(chǎn)品B的公開(kāi)評(píng)測(cè)或官網(wǎng)數(shù)據(jù)。智能體“行動(dòng)”調(diào)用“內(nèi)部知識(shí)庫(kù)查詢工具”即RAG系統(tǒng)傳入查詢“產(chǎn)品A 規(guī)格書 能耗”。獲取結(jié)果。智能體“行動(dòng)”調(diào)用“網(wǎng)絡(luò)搜索工具”傳入查詢“產(chǎn)品B 評(píng)測(cè) 能耗 2024”。獲取結(jié)果。智能體“思考”現(xiàn)在我有了兩份資料我需要分析對(duì)比并生成建議。智能體“行動(dòng)”調(diào)用“文本分析與報(bào)告生成工具”可以是另一個(gè)LLM調(diào)用傳入兩份資料和對(duì)比指令生成最終答案。這里RAG負(fù)責(zé)解決獲取內(nèi)部精確知識(shí)的任務(wù)而智能體負(fù)責(zé)規(guī)劃整個(gè)任務(wù)流并協(xié)調(diào)RAG和搜索引擎等多個(gè)工具。模式二RAG增強(qiáng)智能體在這種模式下智能體在每一步?jīng)Q策時(shí)都能獲得來(lái)自RAG系統(tǒng)的背景知識(shí)支持使其決策更精準(zhǔn)。場(chǎng)景一個(gè)用于處理IT運(yùn)維工單的智能體。流程用戶提交工單“服務(wù)器X的磁盤空間報(bào)警了?!敝悄荏w在決定采取什么行動(dòng)前先通過(guò)RAG查詢“服務(wù)器X的歷史運(yùn)維記錄”、“磁盤清理標(biāo)準(zhǔn)操作流程SOP”、“與該服務(wù)器相關(guān)的應(yīng)用清單”。RAG返回相關(guān)文檔歷史記錄顯示該服務(wù)器上周剛清理過(guò)日志SOP指出先檢查/var/log目錄應(yīng)用清單顯示上面跑著核心數(shù)據(jù)庫(kù)。智能體“思考”根據(jù)SOP我應(yīng)該先建議檢查日志目錄。但歷史記錄顯示剛清理過(guò)可能問(wèn)題復(fù)發(fā)??紤]到是核心數(shù)據(jù)庫(kù)服務(wù)器操作需謹(jǐn)慎。智能體“行動(dòng)”生成回復(fù)“根據(jù)SOP建議先檢查/var/log目錄占用情況可使用du -sh /var/log/*命令。請(qǐng)注意該服務(wù)器上周已進(jìn)行日志清理且運(yùn)行核心數(shù)據(jù)庫(kù)建議在業(yè)務(wù)低峰期操作并先確認(rèn)數(shù)據(jù)庫(kù)日志配置。是否需要我為您生成更詳細(xì)的檢查腳本”在這個(gè)例子里RAG在智能體決策的“思考”環(huán)節(jié)提供了關(guān)鍵的背景信息使得智能體的建議不再是泛泛而談而是具有針對(duì)性和歷史上下文。5.2 融合架構(gòu)的設(shè)計(jì)考量當(dāng)你決定融合兩者時(shí)架構(gòu)設(shè)計(jì)需要仔細(xì)權(quán)衡。記憶管理智能體有自己的工作記憶Working Memory來(lái)存儲(chǔ)多輪對(duì)話和中間結(jié)果而RAG擁有長(zhǎng)期的知識(shí)記憶。需要設(shè)計(jì)好兩者之間的信息交換通道。例如智能體可以將本輪對(duì)話的摘要或關(guān)鍵結(jié)論作為新的文檔存入RAG知識(shí)庫(kù)實(shí)現(xiàn)知識(shí)的積累。流程編排復(fù)雜的任務(wù)可能涉及多次RAG檢索和多個(gè)工具調(diào)用。使用像LangGraph這樣的框架可以清晰地編排這些步驟定義條件分支例如如果RAG檢索結(jié)果為空則轉(zhuǎn)向網(wǎng)絡(luò)搜索。評(píng)估與監(jiān)控融合系統(tǒng)更復(fù)雜評(píng)估點(diǎn)也更多。需要監(jiān)控RAG的檢索相關(guān)性、智能體的工具調(diào)用成功率、任務(wù)完成度、最終答案的用戶滿意度等。建立一套評(píng)估體系對(duì)迭代優(yōu)化至關(guān)重要。6. 從概念到落地構(gòu)建你的第一個(gè)AI智能助理理論說(shuō)了這么多我們來(lái)看一個(gè)具體的、可落地的例子構(gòu)建一個(gè)“技術(shù)文檔智能問(wèn)答助理”。它既能回答關(guān)于你公司技術(shù)產(chǎn)品的具體問(wèn)題用RAG又能根據(jù)問(wèn)題幫你生成代碼片段或執(zhí)行簡(jiǎn)單的系統(tǒng)診斷命令用智能體。6.1 技術(shù)棧選擇與環(huán)境準(zhǔn)備為了平衡靈活性和開(kāi)發(fā)效率我們選擇以下技術(shù)棧后端框架LangChainLangGraph。它提供了我們所需的所有基礎(chǔ)模塊且LangGraph能很好地描述智能體的工作流。向量數(shù)據(jù)庫(kù)Chroma。它輕量、易用適合原型和中小規(guī)模項(xiàng)目無(wú)需單獨(dú)部署服務(wù)。嵌入模型OpenAI text-embedding-3-small。在效果和成本間取得良好平衡API調(diào)用方便。大語(yǔ)言模型OpenAI GPT-4o。作為智能體的“大腦”其推理和指令跟隨能力較強(qiáng)。前端簡(jiǎn)單的Gradio界面用于快速演示。首先安裝必要的Python包pip install langchain langchain-openai langchain-chroma langgraph gradio6.2 核心模塊實(shí)現(xiàn)第一步構(gòu)建RAG索引模塊我們創(chuàng)建一個(gè)類來(lái)處理文檔的加載、切分和索引。from langchain_community.document_loaders import DirectoryLoader, TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_openai import OpenAIEmbeddings from langchain_chroma import Chroma import os class KnowledgeBaseIndexer: def __init__(self, persist_directory./chroma_db): self.embeddings OpenAIEmbeddings(modeltext-embedding-3-small) self.persist_dir persist_directory self.text_splitter RecursiveCharacterTextSplitter( chunk_size1000, chunk_overlap200, separators[\n\n, \n, 。, , , , , , ] ) def index_documents(self, docs_directory): 加載指定目錄下的所有文本文檔并創(chuàng)建索引 loader DirectoryLoader(docs_directory, glob**/*.txt, loader_clsTextLoader) documents loader.load() print(f已加載 {len(documents)} 個(gè)文檔) # 切分文檔 splits self.text_splitter.split_documents(documents) print(f切分為 {len(splits)} 個(gè)文本塊) # 創(chuàng)建向量存儲(chǔ) vectordb Chroma.from_documents( documentssplits, embeddingself.embeddings, persist_directoryself.persist_dir ) vectordb.persist() print(f索引已創(chuàng)建并保存至 {self.persist_dir}) return vectordb def get_retriever(self, k4): 獲取檢索器 vectordb Chroma(persist_directoryself.persist_dir, embedding_functionself.embeddings) return vectordb.as_retriever(search_kwargs{k: k})第二步定義智能體可用的工具我們定義三個(gè)工具RAG檢索工具、代碼執(zhí)行工具、系統(tǒng)命令工具需謹(jǐn)慎使用。from langchain.tools import tool from langchain.utilities import BashProcess import subprocess # 工具1: RAG檢索工具 tool def search_knowledge_base(query: str) - str: 當(dāng)用戶詢問(wèn)關(guān)于產(chǎn)品、API、配置等內(nèi)部技術(shù)文檔問(wèn)題時(shí)使用此工具從知識(shí)庫(kù)中查找相關(guān)信息。 # 這里需要接入第一步創(chuàng)建的檢索器 # 假設(shè)我們有一個(gè)全局的 retriever 對(duì)象 docs retriever.invoke(query) context \n\n.join([doc.page_content for doc in docs]) return f從知識(shí)庫(kù)中檢索到以下相關(guān)信息\n{context} if context else 知識(shí)庫(kù)中未找到相關(guān)信息。 # 工具2: 安全的代碼執(zhí)行工具僅限Python tool def execute_python_code(code_snippet: str) - str: 當(dāng)用戶請(qǐng)求生成或驗(yàn)證Python代碼片段時(shí)使用此工具在安全沙箱中執(zhí)行代碼并返回結(jié)果。輸入必須為純Python代碼字符串。 try: # 使用exec在局部作用域中執(zhí)行避免污染全局環(huán)境 local_vars {} exec(code_snippet, {}, local_vars) # 嘗試獲取一個(gè)顯式的結(jié)果如果沒(méi)有則說(shuō)明代碼可能已打印輸出 # 更健壯的做法是重定向stdout這里為簡(jiǎn)化示例 output str(local_vars.get(result, 代碼執(zhí)行完成無(wú)返回值請(qǐng)檢查是否有打印輸出。)) return f代碼執(zhí)行成功。輸出{output} except Exception as e: return f代碼執(zhí)行出錯(cuò){str(e)} # 工具3: 受限的系統(tǒng)命令工具示例生產(chǎn)環(huán)境需極度小心 bash BashProcess() tool def run_safe_system_command(command: str) - str: 當(dāng)用戶需要檢查系統(tǒng)狀態(tài)如磁盤空間、進(jìn)程且命令安全時(shí)使用此工具。僅允許執(zhí)行白名單內(nèi)的命令。 SAFE_COMMANDS [df -h, uptime, whoami, date] if command.strip() not in SAFE_COMMANDS: return 錯(cuò)誤該命令不在允許的安全命令列表中。 try: result bash.run(command) return result except Exception as e: return f命令執(zhí)行失敗{str(e)}第三步使用LangGraph構(gòu)建智能體工作流我們?cè)O(shè)計(jì)一個(gè)簡(jiǎn)單的圖智能體先嘗試用RAG回答問(wèn)題如果RAG結(jié)果不理想或用戶要求執(zhí)行操作則調(diào)用相應(yīng)工具。from langgraph.graph import StateGraph, END from langchain_openai import ChatOpenAI from typing import TypedDict, Annotated import operator # 定義狀態(tài)結(jié)構(gòu) class AgentState(TypedDict): question: str rag_answer: str tool_calls: list final_answer: str # 初始化模型和工具 llm ChatOpenAI(modelgpt-4o, temperature0) tools [search_knowledge_base, execute_python_code, run_safe_system_command] llm_with_tools llm.bind_tools(tools) # 1. 路由節(jié)點(diǎn)決定使用RAG還是調(diào)用工具 def router(state: AgentState): question state[question] # 簡(jiǎn)單的啟發(fā)式路由如果問(wèn)題包含“如何實(shí)現(xiàn)”、“代碼”、“執(zhí)行”、“命令”等傾向于調(diào)用工具 tool_keywords [代碼, 執(zhí)行, 命令, 運(yùn)行, 寫一個(gè), 如何實(shí)現(xiàn), debug] if any(kw in question for kw in tool_keywords): return call_tools else: return use_rag # 2. RAG處理節(jié)點(diǎn) def rag_node(state: AgentState): docs retriever.invoke(state[question]) context \n\n.join([doc.page_content for doc in docs]) prompt f基于以下上下文信息回答問(wèn)題。如果信息不足請(qǐng)如實(shí)告知。 上下文 {context} 問(wèn)題 {state[question]} 答案 response llm.invoke(prompt) return {rag_answer: response.content} # 3. 工具調(diào)用節(jié)點(diǎn) def tool_node(state: AgentState): messages [(user, state[question])] # 讓模型決定調(diào)用哪個(gè)工具 ai_msg llm_with_tools.invoke(messages) tool_calls ai_msg.tool_calls if not tool_calls: return {final_answer: 我無(wú)法處理這個(gè)請(qǐng)求因?yàn)樗枰覉?zhí)行不支持的特定操作。} results [] for tool_call in tool_calls: tool_name tool_call[name] tool_to_call next(t for t in tools if t.name tool_name) result tool_to_call.invoke(tool_call[args]) results.append(f{tool_name} 結(jié)果{result}) # 讓模型根據(jù)工具結(jié)果總結(jié)最終答案 summary_prompt f用戶問(wèn)題{state[question]}\n工具執(zhí)行結(jié)果{ .join(results)}\n請(qǐng)根據(jù)以上結(jié)果給出清晰完整的最終回答。 final_msg llm.invoke(summary_prompt) return {tool_calls: tool_calls, final_answer: final_msg.content} # 4. 構(gòu)建圖 workflow StateGraph(AgentState) workflow.add_node(rag_node, rag_node) workflow.add_node(tool_node, tool_node) workflow.set_conditional_entry_point( router, { use_rag: rag_node, call_tools: tool_node, } ) workflow.add_edge(rag_node, END) workflow.add_edge(tool_node, END) agent workflow.compile()第四步集成與測(cè)試最后我們將索引器、智能體和一個(gè)簡(jiǎn)單的Web界面集成起來(lái)。import gradio as gr # 初始化 indexer KnowledgeBaseIndexer() # 假設(shè)文檔已放在 ./docs 目錄下首次運(yùn)行需要?jiǎng)?chuàng)建索引 # vectordb indexer.index_documents(./docs) retriever indexer.get_retriever(k4) def chat_with_agent(message, history): 處理用戶消息 result agent.invoke({question: message}) final_answer result.get(final_answer) or result.get(rag_answer) return final_answer # 創(chuàng)建Gradio界面 demo gr.ChatInterface( fnchat_with_agent, title技術(shù)文檔智能助理, description可以回答技術(shù)文檔問(wèn)題也能執(zhí)行簡(jiǎn)單的代碼和系統(tǒng)命令安全限制內(nèi)。 ) if __name__ __main__: demo.launch()6.3 部署與優(yōu)化注意事項(xiàng)安全性是重中之重上述示例中的run_safe_system_command工具極度簡(jiǎn)化絕對(duì)不要在生產(chǎn)環(huán)境中直接開(kāi)放Bash命令執(zhí)行。必須建立嚴(yán)格的命令白名單、參數(shù)校驗(yàn)、權(quán)限控制和沙箱環(huán)境。RAG檢索質(zhì)量定期評(píng)估和優(yōu)化你的分塊策略、嵌入模型和檢索器??梢砸胫嘏判蚰P蛠?lái)提升top-k結(jié)果的精度。智能體穩(wěn)定性為智能體的推理循環(huán)設(shè)置超時(shí)和最大步數(shù)限制避免成本失控或死循環(huán)。記錄完整的交互日志便于分析和調(diào)試。成本控制RAG的嵌入調(diào)用和智能體的多次LLM調(diào)用都會(huì)產(chǎn)生費(fèi)用??梢酝ㄟ^(guò)緩存常見(jiàn)的檢索結(jié)果、對(duì)智能體的思考步驟進(jìn)行適當(dāng)限制比如在簡(jiǎn)單問(wèn)題上不使用ReAct來(lái)優(yōu)化成本。這個(gè)項(xiàng)目示例展示了如何將RAG作為知識(shí)來(lái)源將智能體作為決策和執(zhí)行中樞構(gòu)建出一個(gè)能理解私有文檔、又能進(jìn)行簡(jiǎn)單操作的實(shí)用AI助手。你可以在此基礎(chǔ)上擴(kuò)展更多的工具如連接Jira、查詢數(shù)據(jù)庫(kù)、優(yōu)化路由邏輯、并為其添加記憶功能使其能處理更復(fù)雜的多輪對(duì)話任務(wù)。