汽修RAG問答與工單閉環(huán)實踐)
1. 項目全景為什么汽修問答需要 RAG 工單閉環(huán)做汽修信息化也有幾年了我接觸過的不少維修廠和連鎖門店都有一個很尷尬的現(xiàn)狀老師傅的經(jīng)驗全在腦子里新人查故障手冊翻半天客服接電話被問得啞口無言售后工單又是另一個系統(tǒng)里的死數(shù)據(jù)修完就存檔下次遇到類似問題還得從頭摸索。這套項目就是奔著解決這個痛點去的——用 FastAPI 搭后端服務(wù)用 Milvus 存向量知識用 RAG 讓大模型基于真實維修資料問答同時把每一次問答自動關(guān)聯(lián)到工單流程里形成“提問—解決—沉淀”的閉環(huán)。這個項目適合誰參考首先是負(fù)責(zé)汽修門店數(shù)字化系統(tǒng)的后端工程師其次是研究 RAG 落地但找不到具體場景的技術(shù)同學(xué)還有想在生產(chǎn)環(huán)境用 Milvus 但對部署選型比較猶豫的人。我會把從環(huán)境搭建到接口聯(lián)調(diào)、再到工單流轉(zhuǎn)的完整路徑都寫清楚包括實際踩過的坑。很多人一提到智能問答就想到微調(diào)大模型這是最常見的誤區(qū)。汽修領(lǐng)域的故障現(xiàn)象、維修步驟、配件參數(shù)更新得很快微調(diào)一次成本高不說知識還會過期。RAG 的思路更聰明——把維修手冊、技術(shù)通報、歷史工單切塊向量化存進(jìn) Milvus提問時先檢索出最相關(guān)的知識片段再把這些片段作為上下文丟給大模型生成答案。這樣知識更新只需要重新灌庫模型本身不用動。加上工單閉環(huán)之后系統(tǒng)就不再是“一次性問答機器人”而是能持續(xù)從真實維修記錄里學(xué)到新東西的知識引擎。工單閉環(huán)是這個項目的靈魂。傳統(tǒng)問答機器人答完就結(jié)束用戶問完還得自己對著答案去干活干完了也沒有反饋。我設(shè)計的閉環(huán)是問答結(jié)果不僅要返回自然語言答復(fù)還會拆解出建議的維修項目、工時、配件然后自動生成一張草稿工單維修技師實際完成維修后工單狀態(tài)變更為“已完成”并回寫結(jié)論系統(tǒng)定期把已完成的工單再向量化回 Milvus下次遇到相似故障就能檢索到真實維修案例。這套邏輯讓知識庫自己“生長”越用越準(zhǔn)。2. 技術(shù)選型與架構(gòu)拆解2.1 FastAPI 作為服務(wù)層異步和自動文檔是最大紅利后端框架我選 FastAPI 沒有太多猶豫。首先是異步支持RAG 鏈路里有大量 IO 操作——調(diào)向量庫查詢、調(diào)大模型接口、讀寫數(shù)據(jù)庫用 async/await 能把并發(fā)吞吐?lián)纹饋肀热缙揲T店高峰期同時十幾個工位在查故障同步框架很容易把線程池打滿。其次是 FastAPI 自帶 OpenAPI 文檔前端、安卓、小程序都能直接看接口契約省掉了手動維護(hù)文檔的時間。還有 Pydantic 做參數(shù)校驗工單數(shù)據(jù)結(jié)構(gòu)復(fù)雜時嵌套模型定義得清清楚楚遠(yuǎn)比 Flask 手動校驗來得穩(wěn)妥。在這個項目里FastAPI 不只是做 HTTP 層它還承擔(dān)了流程編排的職責(zé)。一個典型的問答請求進(jìn)來先經(jīng)過POST /api/rag/query然后服務(wù)內(nèi)部依次執(zhí)行查詢 Milvus、拼裝 prompt、調(diào)用 LLM、解析結(jié)構(gòu)化結(jié)果、寫工單草稿。每一步都是獨立的 service 函數(shù)用 FastAPI 的依賴注入把它們串起來。這樣寫的好處是方便測試也方便以后替換組件——比如換 embedding 模型或者加一個多路召回都只是在 service 內(nèi)部改。2.2 Milvus 向量數(shù)據(jù)庫選型與版本坑選 Milvus 而不是其他向量庫核心原因是它支持集合collection級別的動態(tài) Schema這對汽修知識庫很重要。維修手冊和工單的字段差異很大有的需要存故障碼有的需要存車型用固定 Schema 的庫會很痛苦。Milvus 從 2.x 開始支持 JSON 字段和動態(tài)字段我可以把各種元數(shù)據(jù)一股腦塞進(jìn)去查詢時再用 filter 精確過濾比純向量檢索加后置過濾要高效得多。但這玩意兒的版本是個深坑。我最早用的是 Milvus 2.2.x后來為了用新特性升級到 2.4.x結(jié)果發(fā)現(xiàn) Attu 客戶端版本不一樣連接方式全變了。Attu 是一個 Milvus 的可視化管理工具如果你只裝了 Milvus 服務(wù)端而沒裝 Attu可視化調(diào)試會非常痛苦。這里強調(diào)一下版本對應(yīng)關(guān)系A(chǔ)ttu 2.3.0 及以前基本兼容 Milvus 2.2/2.3Milvus 2.4 的 WebUI 其實是內(nèi)置的不再強烈依賴 Attu但很多人還是習(xí)慣用 Attu 看數(shù)據(jù)。我是裝了 Milvus 2.4.1然后直接用內(nèi)置 WebUI默認(rèn)端口 9091再用 Attu 2.4.x 連接流暢度還行。如果你剛上手建議就裝 Milvus 2.4.x 版本然后 Access 方式別搞錯否則你會一頭扎進(jìn)“ETCD 報錯”里出不來。2.3 RAG 流程整體編排從 query 到 answer 的完整鏈路RAG 不是簡單的“向量檢索 大模型生成”細(xì)拆有以下幾步query 預(yù)處理對用戶輸入做去噪、同義詞擴展比如“車子啟動不了”擴展為“無法啟動”、“打不著火”這些規(guī)則可以先用正則和詞典做不要一開始就上模型。embedding 落入向量庫用同一個 embedding 模型把 query 編碼成向量這一步必須保證 encode 模型和入庫時一致否則向量空間錯位檢索結(jié)果一塌糊涂。向量召回 元數(shù)據(jù)過濾先按車型、系統(tǒng)發(fā)動機/變速箱/電氣過濾再在子集里算相似度召回 top-k。重排如果召回結(jié)果多了可以用簡單的 RRF倒數(shù)排名融合加上關(guān)鍵詞 overlap 調(diào)權(quán)比直接依賴向量距離更穩(wěn)。prompt 組裝把召回片段拼成上下文加上系統(tǒng)提示詞要求模型只依據(jù)上下文回答不要瞎編。結(jié)構(gòu)化抽取讓模型輸出建議的維修項目、工時、配件存到工單草稿里。我用 langchain 還是直接手寫坦率講這個場景我建議手寫 pipeline。LangChain 的抽象層確實方便但版本升級太頻繁出了問題很難排查。RAG 核心鏈路其實不復(fù)雜自己用 Python 寫也就 100 多行。后面我會把關(guān)鍵代碼貼出來。3. 環(huán)境準(zhǔn)備與 Milvus 本地部署非 Docker 實測3.1 Windows / Linux 下 Milvus 安裝細(xì)節(jié)Milvus 官方推薦用 Docker Compose 部署但是很多公司內(nèi)網(wǎng)或者個人開發(fā)機沒有 Docker 環(huán)境尤其 Windows 下要裝 Docker Desktop 一堆麻煩事。我實際測試過Milvus 2.4.x 也提供了不帶 Docker 的安裝方式這里給出幾個路線。路線一Windows 下用 Docker Desktop 跑。雖然用到了 Docker但只要你裝好 Docker Desktopdocker-compose.yml一拉就能跑起來。這里有個坑Windows 下 Milvus 默認(rèn)掛載卷的路徑如果包含中文或空格容器會起不來所以最好把目錄設(shè)置成純英文。路線二直接上 Linux 裸機裝。以 Ubuntu 22.04 為例不依賴 Docker 的安裝步驟大概是這樣先裝 etcd再裝 MinIO然后再裝 Milvus standalone單機版。Milvus standalone 雖然內(nèi)部依賴 etcd 和 MinIO但它有自己的啟動腳本只要配置里指向 etcd 和 MinIO 的端點就行。我用的版本組合是etcd 3.5.9、MinIO RELEASE.2023-03-20T00-00-00Z、Milvus 2.4.1。順序很重要先啟動 etcd再 MinIO最后啟動 Milvus。Windows 純本機非 Docker 安裝我沒走通官方對 Windows 原生支持不足建議如果不想用 Docker 就裝 WSL2 跑 Ubuntu然后在 WSL 里按照 Linux 方式裝。我測試下來這套組合最穩(wěn)定。3.2 Attu 客戶端連接不同 Milvus 版本的問題很多人第一次用 Attu 都懵因為 Attu 和 Milvus 的版本沒有嚴(yán)格一一對應(yīng)。Attu 的 GitHub 倉庫說支持 2.x 大版本但實際連接時如果 Milvus 版本太新接口路徑變了Attu 會顯示一堆空數(shù)據(jù)或者直接報錯。比如用 Attu 2.3.8 連 Milvus 2.4.1集群信息能顯示但查詢數(shù)據(jù)時可能拿不到向量字段的標(biāo)量值。我的經(jīng)驗是Milvus 2.4.x 盡量用 Attu v2.4.0 以上版本Milvus 2.2/2.3 則用 Attu 2.3.x。還有一個更省心的方法——Milvus 2.4 自帶的 WebUI啟動后瀏覽器訪問http://localhost:9091/webui能看集合、做搜索、看日志。我后來基本都在 WebUI 里排查數(shù)據(jù)Attu 只是偶爾用來做數(shù)據(jù)管理。3.3 etcd 與存儲配置要點Milvus 的元數(shù)據(jù)、分片信息、索引狀態(tài)都存在 etcd 里etcd 掛了 Milvus 就廢了。我有一個血淚教訓(xùn)之前搭建時 etcd 用的默認(rèn)端口 2379結(jié)果跟本地另一個服務(wù)沖突Milvus 啟動過程一直在 etcd 連接重試日志大量刷etcdserver: request timed out。排查了半天才找到元兇。建議部署時把 etcd 的listen-client-urls和listen-peer-urls分開客戶端地址用本機 IP而不要用0.0.0.0這樣安全也好排障。MinIO 的 bucket 名稱可以自己定義但 Milvus 啟動時會自動建 bucket如果你之前手動建過同名 bucket 且權(quán)限不對初始化會失敗所以最省事的做法是讓 Milvus 自動創(chuàng)建。另外Milvus 的配置文件milvus.yaml里有一個common.threadCount參數(shù)默認(rèn)是 CPU 核數(shù)。如果機器 CPU 核不多又同時跑 embedding 模型容易導(dǎo)致環(huán)境卡死。我調(diào)到 8效果穩(wěn)定。4. 后端核心實現(xiàn)FastAPI 接口與 RAG 鏈路4.1 數(shù)據(jù)切分與向量化汽修數(shù)據(jù)源五花八門PDF 維修手冊、Excel 零件表、歷史工單文本。我統(tǒng)一先轉(zhuǎn)成純文本再做結(jié)構(gòu)化切分。直接按固定字符切是最蠢的會把一句話砍成兩半。我用的策略是分層切分先按一級目錄比如“發(fā)動機系統(tǒng)”“變速箱系統(tǒng)”分成大塊再按最小語義單元——段落和列表項——切成 chunk每個 chunk 控制在 500 到 800 個 tokenchunk 之間保留 50 個 token 的 overlap。這里有一個小技巧對于維修手冊把“故障碼 可能原因 排查步驟”作為一個整體切因為這三者是一個完整檢索單元拆開了檢索噪音很大。對于歷史工單我字段里用fault_desc、solution、parts_used分別存向量化時只對fault_desc solution做 embeddingparts 作為過濾條件。embedding 模型我選擇了bge-large-zh-v1.5中文場景效果不錯維度是 1024。Milvus 建立 collection 時向量字段類型設(shè)為 FLOAT_VECTORdim 1024。如果用 OpenAI 的 embeddingAPI 有調(diào)用費用內(nèi)網(wǎng)部署不方便。bge 模型在本地用sentence-transformers加載速度也挺快。切分和向量化代碼大概長這樣from sentence_transformers import SentenceTransformer from pymilvus import Collection, FieldSchema, CollectionSchema, DataType, connections model SentenceTransformer(BAAI/bge-large-zh-v1.5) def embed_texts(texts): return model.encode(texts, normalize_embeddingsTrue) fields [ FieldSchema(nameid, dtypeDataType.INT64, is_primaryTrue, auto_idFalse), FieldSchema(nametext, dtypeDataType.VARCHAR, max_length65535), FieldSchema(namecar_model, dtypeDataType.VARCHAR, max_length128), FieldSchema(namesystem_tag, dtypeDataType.VARCHAR, max_length64), FieldSchema(namevector, dtypeDataType.FLOAT_VECTOR, dim1024), FieldSchema(namemetadata, dtypeDataType.JSON), ] schema CollectionSchema(fields, descriptioncar repair knowledge) col Collection(namecar_repair, schemaschema)4.2 檢索邏輯dense vector search 的踩坑檢索最簡單的方式就是col.search()傳 query 向量取 top_k。但如果直接全庫搜索會把完全不同的故障問混進(jìn)來。比如用戶問“怠速不穩(wěn)”你召回的是“怠速馬達(dá)更換”這算相關(guān)但如果召回的是“空調(diào)不制冷”那就是噪音。所以要先 filter 再 search用expr參數(shù)把車型、系統(tǒng)限定住。踩過最大的坑是metric_type的選擇。Milvus 支持IP內(nèi)積、L2歐氏距離、COSINE。我用 bge 模型的時候normalize_embeddingsTrue之后用 IP 和 COSINE 等價但別用 L2。具體看模型說明bge 官方建議用 COSINE。若 embedding 沒歸一化IP 的分?jǐn)?shù)范圍不穩(wěn)定檢索前幾名容易錯。還有一個容易被忽略的是search_params里的ef參數(shù)針對 HNSW 索引。索引類型我用HNSW時ef 越大召回越準(zhǔn)但越慢。在線問答場景我設(shè)ef128這能兼顧精度與速度。另一個參數(shù)nprobe是 IVF 索引的如果老版本用了 IVF 別搞混。檢索代碼加上過濾條件后query_embedding embed_texts([query])[0].tolist() search_params {metric_type: COSINE, params: {ef: 128}} expr system_tag in [發(fā)動機, 電氣] result col.search(data[query_embedding], anns_fieldvector, paramsearch_params, limit5, exprexpr, output_fields[text, car_model])注意expr里字符串要用單引號包著字段名不能用反引號。我剛開始用習(xí)慣了 SQL 的語法寫著寫著就報語法錯誤。Milvus 的 expr 語法基本兼容過濾表達(dá)式的子集但 JSON 字段訪問要用metadata[part_no]這種用法具體官方文檔要看一眼。4.3 生成與工單創(chuàng)建聯(lián)動召回得到 top-5 片段后就要組裝 prompt。我用了兩個模型一個是本地部署的 Qwen2.5-14B通過 vLLM 部署另一個是 OpenAI 兼容接口??紤]到大模型輸出的穩(wěn)定性我把 prompt 寫得非常死板你是汽修專家。只依據(jù)以下資料回答問題不要編造。 資料 1. [片段1] 2. [片段2] ... 問題{user_query} 請輸出 JSON字段包括 - answer: 自然語言回答 - likely_causes: 可能原因數(shù)組 - suggestions: 建議操作列表 - parts: 建議配件列表每個包含名稱和數(shù)量 - estimated_hours: 預(yù)計工時數(shù)字 如果資料信息不足answer 里明確說“資料中未找到請人工核實”。為什么要讓模型輸出 JSON因為我需要直接把生成結(jié)果映射到工單草稿。FastAPI 的響應(yīng)模型天然支持 Pydantic大模型輸出 JSON 后我用json.loads解析再存為工單對象的字段。如果允許模型自由發(fā)揮那工單數(shù)據(jù)就是一堆垃圾。在 FastAPI 里創(chuàng)建工單的接口和問答接口分開更清晰app.post(/api/rag/query) async def rag_query(req: QueryRequest): contexts search_milvus(req) messages build_prompt(contexts, req.query) llm_resp await call_llm(messages) parsed parse_json(llm_resp) draft_id await create_work_order_from_parsed(req, parsed) return {answer: parsed[answer], draft_work_order_id: draft_id}這一步關(guān)聯(lián)了問答和工單問答結(jié)果不只是給用戶看的同時已經(jīng)是半結(jié)構(gòu)化工單。前端可以顯示“已生成草稿工單工單編號 WO-20250521-001”用戶確認(rèn)后即可進(jìn)入維修流程。5. 工單閉環(huán)從智能問答到執(zhí)行反饋5.1 工單數(shù)據(jù)結(jié)構(gòu)設(shè)計工單表我用 PostgreSQL 存儲但核心業(yè)務(wù)邏輯在 FastAPI 中處理下面是關(guān)鍵的字段設(shè)計字段名類型說明work_order_idvarchar工單號格式 WO-YYYYMMDD-NNNquery_texttext原始問題answer_texttext大模型生成的答案car_modelvarchar車型vinvarchar車架號fault_codevarchar故障碼可為空likely_causesjsonb可能原因數(shù)組suggestionsjsonb建議操作列表partsjsonb配件列表名稱、數(shù)量estimated_hoursnumeric預(yù)計工時statusvarchar狀態(tài)DRAFT/IN_PROGRESS/DONE/CLOSEDactual_solutiontext修理工實際處理方法閉環(huán)關(guān)鍵actual_partsjsonb實際使用配件created_attimestamp創(chuàng)建時間closed_attimestamp關(guān)閉時間設(shè)計時我特別加了actual_solution和actual_parts。這是跟普通問答系統(tǒng)最大的不同問答結(jié)束后系統(tǒng)并沒有完事它等著維修技師干完活把結(jié)果填回來。有了這兩列才能持續(xù)更新知識庫。5.2 問答結(jié)果如何映射為工單在create_work_order_from_parsed里面我把解析后的 JSON 映射到上面的表。這里有一個細(xì)節(jié)如果用戶問的是“這個故障代碼 P0300 是什么意思”生成結(jié)果里可能沒有具體配件estimated_hours 可能是 null。沒關(guān)系工單草稿允許空值維修技師在確認(rèn)草稿時可以補充。但是如果用戶問的是“換機油”系統(tǒng)應(yīng)該能自動預(yù)測工時和機油濾芯配件這就要靠 prompt 引導(dǎo)。我在 prompt 里增加了規(guī)則“如果問題明顯是維修請求必須輸出 parts、estimated_hours如果只是知識詢問parts 可以為空數(shù)組”。這樣能減少垃圾工單的產(chǎn)生。另外工單創(chuàng)建前我會調(diào)用一個簡單的去重函數(shù)根據(jù)car_model query_text 時間范圍最近1小時判斷有沒有重復(fù)工單防止用戶一直點同一個按鈕刷出來一堆草稿。5.3 閉環(huán)反饋與知識庫更新閉環(huán)的落地點在于每天凌晨跑一個批處理腳本把最近 24 小時狀態(tài)為CLOSED且actual_solution非空的工單再次切分向量化后寫入 Milvus。寫入的時候用text 故障描述 query_text 實際解決 actual_solution再附上實際的配件、車型等信息。這樣知識庫里會不斷加入真實維修案例而不是只有官方手冊內(nèi)容。同時我會更新舊數(shù)據(jù)的knowledge_source字段讓它區(qū)分是官方手冊還是歷史工單。檢索時可以把來源權(quán)重調(diào)高一點比如官方手冊的metadata[source]為manual工單的為work_order在重排階段對work_order的命中做一個小激勵因為真實維修案例往往更有參考價值。這一套閉環(huán)跑起來后系統(tǒng)越用越懂你們門店的常見問題。舉個例子某品牌車在店里經(jīng)常出現(xiàn)某個通病官方手冊沒寫但第一個維修師傅手工填了實際解決過程之后第二個師傅再問就能檢索到。這就是閉環(huán)的意義。6. 常見問題與排查實錄6.1 Milvus 連接失敗與 etcd 問題最典型的現(xiàn)象是 FastAPI 啟動時connections.connect(aliasdefault, hostlocalhost, port19530)報連接超時。先別急著查 Milvus 本身先看 etcd 是否正常。排查步驟檢查 etcd 進(jìn)程ps aux | grep etcd。如果沒有啟動/path/to/etcd --data-dir/data/etcd --listen-client-urlshttp://0.0.0.0:2379 --advertise-client-urlshttp://0.0.0.0:2379。檢查 Milvus 日志/var/log/milvus/server.log。如果看到grpc: addrConn.createTransport failed大概率是 Milvus 配置里的 etcd 地址寫錯了。驗證 etcd 連通性curl http://localhost:2379/health返回{health:true}才正常。我遇到最扯的一次是 etcd 啟動成功了但防火墻把 2379 端口擋住了Milvus 進(jìn)程在另外一臺機器上連不上。所以如果你把 etcd 和 Milvus 分開部署請確保端口是通的。6.2 Attu 版本不兼容如果打開 Attu 后發(fā)現(xiàn)集合列表為空但數(shù)據(jù)明明在里面或者執(zhí)行查詢時報milvus collection not found十有八九是 Attu 的 proto 版本比 Milvus 老。你可以在 Attu 頁面右上角的設(shè)置里看連接的 Milvus 版本或者看 Attu 的 Release Notes。解決辦法很簡單升級 Attu 到與 Milvus 匹配的版本。我整理了一張對照表按這個來基本不會錯Milvus 版本Attu 推薦版本備注2.2.x2.2.x老項目常用2.3.x2.3.x較穩(wěn)定2.4.x2.4.x 或內(nèi)置 WebUI內(nèi)置 WebUI 更舒服另外注意Milvus 2.4 開始Attu 連接時默認(rèn)端口 19530我曾經(jīng)在 web UI 之間切換過結(jié)果同一個瀏覽器 session 混了導(dǎo)致數(shù)據(jù)看不到清理緩存或換個無痕窗口就好了。6.3 RAG 效果不理想如何調(diào)優(yōu)很多剛?cè)腴T的朋友調(diào) RAG搜索沒結(jié)果就覺得是 embedding 模型問題其實大概率是切分方式和過濾條件的問題。比如汽修故障碼 P0300缺火如果按系統(tǒng)切分成“發(fā)動機系統(tǒng)”一個大塊再 embed 的時候整塊太長信息被稀釋了。我的調(diào)優(yōu)順序建議先看召回片段是否相關(guān)。直接在 Milvus WebUI 里手動查 query 的 top10。如果前 5 個都不相關(guān)那就是切分粒度或 embedding 問題。如果相關(guān)的排在第 5 位之后那就是重排問題。檢查 filter 條件是否太嚴(yán)。比如用戶沒填車型你默認(rèn)過濾car_model 那必然什么都查不到。應(yīng)該是“有車型就過濾沒有則跳過”。調(diào)整 overlap。chunk 之間的 overlap 對跨句語義有影響我測下來 50-100 token 的 overlap 對維修手冊比較合適。調(diào)整ef參數(shù)。在線服務(wù)里 ef 可以設(shè)為 64 提升速度但離線評估時用 256。如果你用 IVF 索引nprobe 通常設(shè)為 8-16數(shù)值太低召回很差。評估方面我建了一個 200 條真實問題的評測集每條文成兩部分是否命中相關(guān)維修手冊、大模型答案是否讓修理工滿意。計算基本指標(biāo)如 hit5、MRR再加上人工打分。僅僅看向量相似度是不靠譜的因為詢問題目表述和文檔差很遠(yuǎn)相似度數(shù)值不能完全代表語義相關(guān)性。6.4 FastAPI 并發(fā)與超時優(yōu)化RAG 鏈路里最慢的是調(diào)用大模型我用 Qwen2.5-14B 單卡部署單次生成平均 2-3 秒。如果用戶在 FastAPI 接口等太久前端會報超時。我這里做了兩個優(yōu)化第一用asynciohttpx.AsyncClient異步調(diào)用 LLM不要用同步 requests。同步會阻塞事件循環(huán)并發(fā)一高全部卡死。改成異步之后能支撐 20 個并發(fā)請求同時問答。第二給 LLM 請求設(shè)置 timeout。我用 openai 的 async client 時timeout60秒但實際大部分請求 10 秒內(nèi)能完成。超時后做降級不回工單只返回一個固定提示“系統(tǒng)繁忙請稍后再試”。這個兜底非常重要不然大模型偶爾卡死了接口就一直掛著數(shù)據(jù)庫連接也會被耗盡。還有緩存。同一個問題一周內(nèi)被問超過 3 次完全可以走 Redis 緩存。我實現(xiàn)了一個簡單邏輯把 query 語義 hash 到rag:cache:{md5}TTL 7 天命中了直接返回答案和之前的工單號。這樣可以顯著降低大模型壓力也提升用戶感知速度。7. 項目后續(xù)擴展與個人經(jīng)驗總結(jié)這套系統(tǒng)已經(jīng)在我們合作的一家連鎖維修廠跑了三個月從最初的知識庫只有 200 份手冊、一天幾十次問答到現(xiàn)在沉淀了 5000 多條真實工單檢索準(zhǔn)確率大概提升了 15%。但這里有一個需要特別注意的問題工單回寫知識庫一定要做質(zhì)量過濾。有些技師隨便寫的解決方法可能并不正確直接灌進(jìn)知識庫會污染數(shù)據(jù)源。我的建議是只在知識庫里寫入狀態(tài)為“已閉環(huán)”且經(jīng)過主管審核的工單或者至少將工單數(shù)據(jù)權(quán)重調(diào)低避免帶偏后續(xù)檢索結(jié)果。另一個未來可以繼續(xù)擴展的方向是視覺能力。很多汽修故障是需要看照片的比如底盤漏油、電瓶腐蝕只靠文本 RAG 有上限。目前我考慮再用 CLIP/ViT 把維修圖片也向量化存到 Milvus 的同一個 collection 里然后做多模態(tài)搜索。FastAPI 這邊只要增加一個圖片上傳接口前端拍照就能查問題。雖然這個還沒完全落地但架構(gòu)上已經(jīng)預(yù)留了向量字段和 metadata 的冗余空間。最后再分享一個運維里的小技巧Milvus 數(shù)據(jù)備份不要只靠 etcd 快照。保險的做法是定期用milvus_backup這個官方工具把 collection 數(shù)據(jù)導(dǎo)出到本地磁盤或者 OSS因為 etcd 只是元數(shù)據(jù)真正向量數(shù)據(jù)在 MinIO兩者都要備份。曾經(jīng)有一次我不小心刪了 MinIO 里的一個 bucket結(jié)果整個集合全毀了恢復(fù)只能從備份中拉數(shù)據(jù)?,F(xiàn)在腳本每天晚上自動備份算是花錢買教訓(xùn)。這套項目如果完全重做一遍我仍然會堅持 FastAPI Milvus RAG 這個組合。FastAPI 的工程效率和類型安全對業(yè)務(wù)快速迭代太友好了Milvus 雖然部署有些小坑但生產(chǎn)可用的特性和成熟度在開源向量庫里是第一梯隊RAG 則讓 AI 能力真正貼合業(yè)務(wù)。希望這篇實操記錄能幫少走幾條彎路。