拆分實(shí)戰(zhàn):L0硬規(guī)則前置與L1模型兜底的兩級(jí)流水線)
1. 為什么要在本地做任務(wù)拆分1.1 從一次線上事故說(shuō)起去年冬天我在做一個(gè)內(nèi)部工單自動(dòng)分派的小系統(tǒng)。需求本身不復(fù)雜用戶提交一段自然語(yǔ)言描述系統(tǒng)判斷它屬于哪個(gè)部門(mén)、緊急程度如何、要不要轉(zhuǎn)人工。最開(kāi)始我圖省事直接調(diào)了一個(gè)大模型接口把整段文本丟進(jìn)去讓它一次性輸出結(jié)構(gòu)化結(jié)果。上線第三天就出事了。有一張工單寫(xiě)著“機(jī)房空調(diào)外機(jī)異響已持續(xù)兩小時(shí)”模型把它分到了“行政后勤”緊急程度標(biāo)了“低”。實(shí)際上機(jī)房溫度已經(jīng)開(kāi)始爬升這種單子必須走基礎(chǔ)設(shè)施告警通道。事后復(fù)盤(pán)問(wèn)題不在模型能力而在于我把所有判斷都押在了一次推理上——沒(méi)有硬性規(guī)則兜底沒(méi)有中間校驗(yàn)?zāi)P洼敵鍪裁淳褪鞘裁?。這件事之后我重新設(shè)計(jì)了整條鏈路核心思路就一句話能用確定性規(guī)則解決的絕不交給模型模型只處理規(guī)則覆蓋不到的模糊地帶。這就是后來(lái)我一直在用的 L0 硬規(guī)則前置加 L1 模型兜底的兩級(jí)流水線。1.2 兩級(jí)流水線到底解決什么問(wèn)題先說(shuō)清楚這兩個(gè)層級(jí)分別是什么。L0 層是硬規(guī)則層由人工編寫(xiě)的確定性邏輯構(gòu)成。關(guān)鍵詞匹配、正則表達(dá)式、字典查詢、數(shù)值閾值判斷這些都屬于 L0。它的特點(diǎn)是快、準(zhǔn)、可解釋、零成本但覆蓋面有限遇到?jīng)]見(jiàn)過(guò)的表達(dá)方式就抓瞎。L1 層是模型兜底層用大模型或者小型的本地分類模型來(lái)處理 L0 沒(méi)命中的那部分輸入。它的特點(diǎn)是覆蓋面廣、能理解語(yǔ)義但慢、有成本、輸出不穩(wěn)定。兩級(jí)流水線的價(jià)值在于把大部分請(qǐng)求攔截在 L0只讓真正需要語(yǔ)義理解的請(qǐng)求進(jìn)入 L1。我實(shí)測(cè)下來(lái)在一個(gè)日均三千條工單的場(chǎng)景里L(fēng)0 能攔住大約七成剩下三成走 L1。這意味著模型調(diào)用成本直接砍掉七成響應(yīng)速度也上了一個(gè)臺(tái)階——L0 的判斷通常在毫秒級(jí)完成用戶幾乎無(wú)感。更重要的是可靠性。L0 的規(guī)則是我自己寫(xiě)的每一條都能追溯到具體的業(yè)務(wù)邏輯。即使 L1 出了問(wèn)題L0 仍然能保證核心場(chǎng)景不出錯(cuò)。這種“降級(jí)可用”的特性在本地部署環(huán)境里尤其重要。1.3 適合哪些場(chǎng)景不適合哪些場(chǎng)景這套架構(gòu)不是什么萬(wàn)能藥它有明確的適用邊界。適合的場(chǎng)景有幾個(gè)共同特征任務(wù)邊界清晰、存在大量可枚舉的模式、對(duì)響應(yīng)速度和成本敏感、需要可解釋性。比如工單分類、意圖識(shí)別、表單字段抽取、日志告警分級(jí)、內(nèi)容審核初篩這些都很適合。不適合的場(chǎng)景也很明顯。如果你的任務(wù)本身就是開(kāi)放式的比如讓模型寫(xiě)一篇文章、做一段創(chuàng)意文案那 L0 層幾乎幫不上忙硬套兩級(jí)流水線只會(huì)增加復(fù)雜度。另外如果任務(wù)量很小一天就幾十條請(qǐng)求那直接調(diào)模型更省事沒(méi)必要為了省那點(diǎn)成本去維護(hù)一套規(guī)則。還有一個(gè)容易被忽略的點(diǎn)L0 規(guī)則是需要持續(xù)維護(hù)的。業(yè)務(wù)在變用戶的表達(dá)方式在變規(guī)則也得跟著更新。如果你沒(méi)有精力去定期 review 規(guī)則命中率和誤判率那這套架構(gòu)的優(yōu)勢(shì)會(huì)隨著時(shí)間推移逐漸衰減。2. L0 硬規(guī)則層的設(shè)計(jì)與實(shí)現(xiàn)細(xì)節(jié)2.1 規(guī)則的組織方式優(yōu)先級(jí)隊(duì)列而非平鋪列表很多人寫(xiě)規(guī)則就是一堆 if-else 堆在一起能跑就行。但規(guī)則一多維護(hù)起來(lái)就是災(zāi)難。我的做法是把規(guī)則組織成一個(gè)帶優(yōu)先級(jí)的隊(duì)列每條規(guī)則有明確的優(yōu)先級(jí)、匹配條件和命中后的動(dòng)作。為什么用優(yōu)先級(jí)隊(duì)列而不是簡(jiǎn)單的順序執(zhí)行因?yàn)橐?guī)則之間可能存在沖突。比如一條規(guī)則說(shuō)“包含‘退款’關(guān)鍵詞就分到售后組”另一條規(guī)則說(shuō)“包含‘投訴’關(guān)鍵詞就分到客訴組”。如果一條工單同時(shí)包含這兩個(gè)詞按什么順序判斷就決定了最終結(jié)果。優(yōu)先級(jí)機(jī)制讓這種沖突變得可控——我明確知道哪條規(guī)則先跑哪條后跑。具體實(shí)現(xiàn)上我用一個(gè)列表來(lái)存儲(chǔ)規(guī)則對(duì)象每個(gè)對(duì)象包含這幾個(gè)字段class L0Rule: def __init__(self, name, priority, matcher, action, description): self.name name # 規(guī)則名稱用于日志追蹤 self.priority priority # 優(yōu)先級(jí)數(shù)字越小越先執(zhí)行 self.matcher matcher # 匹配函數(shù)返回布爾值 self.action action # 命中后的動(dòng)作返回分類結(jié)果 self.description description # 規(guī)則說(shuō)明方便后續(xù)維護(hù)規(guī)則列表按 priority 排序執(zhí)行時(shí)從前往后遍歷第一條命中的規(guī)則直接返回結(jié)果不再繼續(xù)往下走。這種“短路”機(jī)制保證了執(zhí)行效率也避免了多條規(guī)則同時(shí)命中時(shí)的歧義。2.2 匹配器的幾種常見(jiàn)寫(xiě)法匹配器是 L0 層的核心它決定了規(guī)則能不能準(zhǔn)確識(shí)別目標(biāo)。我常用的匹配器有這么幾類。關(guān)鍵詞匹配是最簡(jiǎn)單也最常用的一種。但直接寫(xiě)if 退款 in text太粗糙了容易誤傷。比如“退款政策是什么”和“我要申請(qǐng)退款”都包含“退款”但意圖完全不同。我的做法是結(jié)合上下文窗口來(lái)判斷——檢查關(guān)鍵詞前后一定范圍內(nèi)是否出現(xiàn)了特定的修飾詞。def keyword_matcher(keywords, context_windowNone, required_contextNone): def match(text): for kw in keywords: idx text.find(kw) if idx -1: continue if context_window and required_context: start max(0, idx - context_window) end min(len(text), idx len(kw) context_window) context text[start:end] if any(rc in context for rc in required_context): return True else: return True return False return match正則匹配適合處理有固定格式的內(nèi)容比如訂單號(hào)、手機(jī)號(hào)、身份證號(hào)。寫(xiě)正則的時(shí)候要注意不要追求一條正則搞定所有情況那樣只會(huì)寫(xiě)出誰(shuí)也看不懂的怪物。拆成多條簡(jiǎn)單的正則每條負(fù)責(zé)一種格式維護(hù)起來(lái)輕松得多。字典查詢適合處理枚舉值。比如部門(mén)名稱、產(chǎn)品類別這種有限集合直接建一個(gè)映射字典查詢速度是 O(1)。我一般會(huì)把字典單獨(dú)放在一個(gè) JSON 文件里方便非技術(shù)人員也能更新。數(shù)值閾值判斷在處理告警分級(jí)時(shí)特別有用。比如溫度超過(guò)多少度算緊急、響應(yīng)時(shí)間超過(guò)多少秒算超時(shí)這些都是明確的數(shù)值邊界用規(guī)則處理比模型靠譜得多。2.3 規(guī)則命中后的動(dòng)作設(shè)計(jì)命中規(guī)則之后做什么這個(gè)環(huán)節(jié)看似簡(jiǎn)單其實(shí)也有講究。我的原則是L0 層的輸出必須和 L1 層的輸出保持同樣的數(shù)據(jù)結(jié)構(gòu)。這樣上層業(yè)務(wù)代碼不需要關(guān)心結(jié)果是哪一層產(chǎn)生的統(tǒng)一處理就行。輸出結(jié)構(gòu)一般包含這幾個(gè)字段字段名類型說(shuō)明categorystring分類結(jié)果confidencefloat置信度L0 固定為 1.0sourcestring來(lái)源標(biāo)記L0 或 L1rule_namestring命中的規(guī)則名稱L1 時(shí)為 nullraw_inputstring原始輸入便于追溯source字段很重要。它讓下游系統(tǒng)知道這個(gè)結(jié)果是規(guī)則給的還是模型給的。規(guī)則給的結(jié)果可以直接信任模型給的結(jié)果可能需要人工復(fù)核或者二次校驗(yàn)。rule_name則在排查問(wèn)題時(shí)特別有用——我能快速定位是哪條規(guī)則命中了如果發(fā)現(xiàn)誤判直接改那條規(guī)則就行。2.4 規(guī)則維護(hù)的幾個(gè)實(shí)操心得規(guī)則寫(xiě)起來(lái)容易維護(hù)起來(lái)難。我踩過(guò)幾個(gè)坑這里分享一下。第一條每條規(guī)則必須有單元測(cè)試。規(guī)則多了之后改一條可能影響另一條。沒(méi)有測(cè)試覆蓋的話你根本不知道改動(dòng)會(huì)帶來(lái)什么后果。我的做法是給每條規(guī)則寫(xiě)至少三個(gè)測(cè)試用例一個(gè)應(yīng)該命中的、一個(gè)不應(yīng)該命中的、一個(gè)邊界情況的。第二條記錄規(guī)則命中日志。每次規(guī)則命中都記一條日志包含規(guī)則名稱、輸入文本、命中時(shí)間。這些日志是后續(xù)優(yōu)化的金礦——你能看到哪些規(guī)則命中率高、哪些規(guī)則從來(lái)沒(méi)被觸發(fā)過(guò)、哪些規(guī)則經(jīng)常誤判。第三條定期清理僵尸規(guī)則。業(yè)務(wù)變化快半年前寫(xiě)的規(guī)則可能現(xiàn)在已經(jīng)沒(méi)用了。我每個(gè)月會(huì)跑一次統(tǒng)計(jì)把連續(xù)三十天零命中的規(guī)則標(biāo)記出來(lái)確認(rèn)后刪除。規(guī)則庫(kù)越精簡(jiǎn)維護(hù)成本越低。第四條規(guī)則命名要有意義。不要用rule_001、rule_002這種命名用refund_request_with_order_id這種描述性的名字。半年后你回頭看能一眼看懂這條規(guī)則是干什么的。3. L1 模型兜底層的接入與調(diào)優(yōu)3.1 模型選型本地小模型還是遠(yuǎn)程大模型L1 層用什么模型這個(gè)決策取決于你的部署環(huán)境和精度要求。如果對(duì)數(shù)據(jù)隱私要求高、網(wǎng)絡(luò)環(huán)境不穩(wěn)定、或者需要極低延遲那就選本地部署的小模型。我常用的是參數(shù)量在 1B 到 7B 之間的指令微調(diào)模型量化后顯存占用可以壓到 4GB 以內(nèi)普通帶獨(dú)顯的機(jī)器就能跑。精度上肯定不如大模型但對(duì)于分類、抽取這類任務(wù)配合好的提示詞效果完全夠用。如果對(duì)精度要求高、任務(wù)復(fù)雜度大、且能接受一定的網(wǎng)絡(luò)延遲那就用遠(yuǎn)程大模型接口。但要注意遠(yuǎn)程調(diào)用意味著你的數(shù)據(jù)要出本地如果涉及敏感信息得先做脫敏處理。我的建議是先用本地小模型跑一版看效果能不能接受。能接受就用本地的省心省錢(qián)。實(shí)在不行再考慮遠(yuǎn)程方案。很多時(shí)候我們對(duì)模型精度的要求是被自己嚇出來(lái)的實(shí)際業(yè)務(wù)場(chǎng)景里85% 的準(zhǔn)確率可能就已經(jīng)夠用了。3.2 提示詞設(shè)計(jì)讓模型輸出結(jié)構(gòu)化結(jié)果L1 層的模型調(diào)用和普通聊天不一樣我需要的是結(jié)構(gòu)化的、可解析的輸出而不是一段自由文本。所以提示詞的設(shè)計(jì)要圍繞這個(gè)目標(biāo)來(lái)。我的提示詞模板一般包含這幾個(gè)部分你是一個(gè)任務(wù)分類助手。請(qǐng)根據(jù)用戶輸入的內(nèi)容判斷它屬于以下哪個(gè)類別 類別列表 - 售后咨詢 - 技術(shù)支持 - 投訴建議 - 其他 要求 1. 只輸出類別名稱不要輸出任何其他內(nèi)容 2. 如果無(wú)法判斷輸出“其他” 3. 不要解釋你的判斷理由 用戶輸入 {user_input}這個(gè)模板的關(guān)鍵在于約束輸出格式。我明確告訴模型只輸出類別名稱這樣我拿到結(jié)果后可以直接用字符串匹配來(lái)解析不需要復(fù)雜的后處理。但實(shí)際跑下來(lái)模型有時(shí)候還是會(huì)“話多”輸出類似“這個(gè)應(yīng)該屬于售后咨詢”這樣的內(nèi)容。所以我在解析層加了一層容錯(cuò)先嘗試精確匹配匹配不上就用關(guān)鍵詞模糊匹配再匹配不上就歸入“其他”并記錄日志。3.3 置信度處理模型說(shuō)“不確定”怎么辦模型輸出的置信度是個(gè)好東西但很多接口不直接提供。我的做法是通過(guò)多次采樣來(lái)估算置信度。具體來(lái)說(shuō)同一個(gè)輸入讓模型跑三次溫度參數(shù)設(shè)成 0.7 左右看三次結(jié)果是否一致。三次都一樣置信度標(biāo)為高兩次一樣置信度標(biāo)為中三次都不一樣置信度標(biāo)為低。置信度低的請(qǐng)求我不會(huì)直接采用模型結(jié)果而是轉(zhuǎn)人工復(fù)核。這樣雖然增加了一點(diǎn)人工成本但避免了模型胡猜導(dǎo)致的錯(cuò)誤。在實(shí)際系統(tǒng)里低置信度的請(qǐng)求占比通常不到 5%人工完全扛得住。還有一種情況是模型明確表示無(wú)法判斷。這時(shí)候我會(huì)檢查是不是 L0 層的規(guī)則覆蓋不夠考慮補(bǔ)充新規(guī)則。L1 層的“無(wú)法判斷”日志是 L0 規(guī)則優(yōu)化的最佳輸入。3.4 超時(shí)與降級(jí)策略L1 層是外部依賴不管是本地模型還是遠(yuǎn)程接口都有可能超時(shí)或失敗。必須有降級(jí)策略。我的做法是設(shè)置一個(gè)硬超時(shí)時(shí)間比如 3 秒。超過(guò) 3 秒沒(méi)返回直接放棄 L1走默認(rèn)兜底邏輯。默認(rèn)兜底邏輯很簡(jiǎn)單歸入“其他”類別標(biāo)記為“待人工處理”。同時(shí)我會(huì)記錄超時(shí)日志如果某個(gè)時(shí)間段超時(shí)率突然升高說(shuō)明模型服務(wù)可能出了問(wèn)題需要排查。這種監(jiān)控在本地部署環(huán)境里尤其重要因?yàn)楸镜胤?wù)的穩(wěn)定性往往不如云服務(wù)。還有一個(gè)細(xì)節(jié)L1 的調(diào)用要異步化。不要讓主流程阻塞等待模型返回。我的做法是把 L1 調(diào)用放到一個(gè)隊(duì)列里主流程先返回一個(gè)“處理中”的狀態(tài)等模型結(jié)果出來(lái)后再更新。這樣用戶體驗(yàn)更好系統(tǒng)吞吐量也更高。4. 兩級(jí)流水線的串聯(lián)與調(diào)度4.1 主流程的偽代碼實(shí)現(xiàn)把 L0 和 L1 串起來(lái)的主流程其實(shí)不復(fù)雜但有幾個(gè)關(guān)鍵點(diǎn)要注意。def process_task(user_input): # 第一步L0 規(guī)則匹配 l0_result l0_match(user_input) if l0_result is not None: log_hit(L0, l0_result.rule_name, user_input) return l0_result # 第二步L0 未命中走 L1 log_miss(L0, user_input) try: l1_result l1_predict(user_input, timeout3.0) if l1_result.confidence low: return fallback_result(user_input, reasonlow_confidence) return l1_result except TimeoutError: log_error(L1_timeout, user_input) return fallback_result(user_input, reasontimeout) except Exception as e: log_error(L1_error, str(e), user_input) return fallback_result(user_input, reasonerror)這段代碼里有幾個(gè)設(shè)計(jì)決策值得說(shuō)明。L0 命中后直接返回不再走 L1。這是性能優(yōu)化的關(guān)鍵。有人可能會(huì)想L0 和 L1 都跑一遍然后取置信度高的那個(gè)這樣不是更準(zhǔn)嗎理論上是的但代價(jià)是每次請(qǐng)求都要調(diào)模型成本翻倍速度也慢。而且 L0 規(guī)則是我精心設(shè)計(jì)的命中就意味著高置信度沒(méi)必要再讓模型確認(rèn)一遍。L1 的異常處理要細(xì)分。超時(shí)、報(bào)錯(cuò)、低置信度這三種情況的處理邏輯可能不同。超時(shí)可能是服務(wù)過(guò)載需要擴(kuò)容報(bào)錯(cuò)可能是代碼 bug需要修復(fù)低置信度可能是輸入太模糊需要優(yōu)化提示詞或補(bǔ)充規(guī)則。分開(kāi)記錄日志排查問(wèn)題時(shí)才能快速定位。兜底結(jié)果要包含足夠的信息。fallback_result不僅返回一個(gè)默認(rèn)分類還要帶上原因標(biāo)記。這樣人工復(fù)核的時(shí)候能知道這條為什么沒(méi)處理好。4.2 日志與監(jiān)控讓流水線可觀測(cè)兩級(jí)流水線跑起來(lái)之后你必須能回答這幾個(gè)問(wèn)題L0 命中率是多少L1 的平均延遲是多少兜底率是多少哪些規(guī)則從來(lái)沒(méi)命中過(guò)回答這些問(wèn)題靠的就是日志。我的日志體系分三層。請(qǐng)求級(jí)日志記錄每一次請(qǐng)求的完整處理路徑輸入文本、L0 是否命中、命中了哪條規(guī)則、L1 的返回結(jié)果、最終輸出、總耗時(shí)。這條日志是排查具體問(wèn)題的依據(jù)。統(tǒng)計(jì)級(jí)日志按小時(shí)或按天聚合L0 命中率、L1 調(diào)用量、L1 平均延遲、兜底率、各分類的分布。這些指標(biāo)反映了系統(tǒng)的整體健康度。規(guī)則級(jí)日志專門(mén)記錄 L0 規(guī)則的命中情況每條規(guī)則的命中次數(shù)、命中率、誤判反饋。這是優(yōu)化規(guī)則庫(kù)的依據(jù)。我一般用 SQLite 存這些日志查詢方便不需要額外部署數(shù)據(jù)庫(kù)服務(wù)。數(shù)據(jù)量大了之后可以換成 PostgreSQL 或者 ClickHouse。4.3 性能優(yōu)化讓 L0 更快讓 L1 更穩(wěn)L0 層的性能優(yōu)化空間其實(shí)不大因?yàn)樗緛?lái)就是毫秒級(jí)的。但有幾個(gè)小技巧可以讓它更快。把最常命中的規(guī)則放在最前面。優(yōu)先級(jí)隊(duì)列的順序直接影響平均匹配次數(shù)。我每個(gè)月會(huì)根據(jù)命中統(tǒng)計(jì)調(diào)整一次規(guī)則順序把高頻規(guī)則前置。匹配器里避免重復(fù)計(jì)算。比如多個(gè)規(guī)則都需要對(duì)文本做分詞那就把分詞結(jié)果緩存起來(lái)不要每個(gè)規(guī)則都重新分一次。用編譯后的正則。Python 的re模塊支持預(yù)編譯正則表達(dá)式編譯一次多次使用比每次重新編譯快很多。L1 層的優(yōu)化重點(diǎn)在穩(wěn)定性。設(shè)置合理的超時(shí)時(shí)間太短容易誤殺太長(zhǎng)影響體驗(yàn)。我的經(jīng)驗(yàn)值是本地模型 2 秒遠(yuǎn)程接口 5 秒。做好重試機(jī)制但重試次數(shù)不要超過(guò)兩次否則延遲會(huì)累積。監(jiān)控模型服務(wù)的資源占用本地部署時(shí)顯存和內(nèi)存是瓶頸要提前發(fā)現(xiàn)。4.4 一個(gè)完整的配置示例下面是我在一個(gè)實(shí)際項(xiàng)目里用的配置脫敏后分享出來(lái)。l0: rules_file: rules/department_rules.json enable_cache: true cache_size: 1000 l1: provider: local model_path: /models/classifier-7b-q4 max_tokens: 64 temperature: 0.1 timeout: 2.0 retry: 1 confidence_sampling: 3 fallback: default_category: 其他 notify_channel: internal_alert logging: level: INFO request_log: logs/requests.db stats_interval: 3600這個(gè)配置里confidence_sampling: 3表示采樣三次估算置信度。retry: 1表示失敗后重試一次。stats_interval: 3600表示每小時(shí)輸出一次統(tǒng)計(jì)日志。5. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄5.1 L0 規(guī)則誤判怎么排查規(guī)則誤判是最常見(jiàn)的問(wèn)題。表現(xiàn)是明明應(yīng)該分到 A 類的輸入被規(guī)則分到了 B 類。排查思路分三步。第一步找到命中的規(guī)則。通過(guò)請(qǐng)求日志里的rule_name字段定位是哪條規(guī)則干的。第二步分析誤判原因。把輸入文本和規(guī)則條件對(duì)照著看通常是關(guān)鍵詞匹配太寬泛或者上下文判斷邏輯有漏洞。第三步修復(fù)并加測(cè)試。修改規(guī)則后把這條誤判的輸入加進(jìn)測(cè)試用例防止以后回歸。我遇到過(guò)一個(gè)典型案例規(guī)則里寫(xiě)了“包含‘退’字就分到售后組”結(jié)果“退休金咨詢”被誤分到了售后。修復(fù)方法是把關(guān)鍵詞從“退”改成“退款”和“退貨”同時(shí)加上上下文判斷要求“退”字后面跟著“款”或“貨”。5.2 L1 模型輸出不穩(wěn)定的處理模型輸出不穩(wěn)定通常表現(xiàn)為同樣的輸入不同時(shí)間跑出來(lái)的結(jié)果不一樣。這可能是溫度參數(shù)設(shè)太高也可能是模型本身的問(wèn)題。先檢查溫度參數(shù)。分類任務(wù)建議設(shè)成 0.1 或更低讓輸出盡量確定。如果溫度已經(jīng)很低了還不穩(wěn)定那就是模型能力問(wèn)題考慮換模型或者補(bǔ)充 L0 規(guī)則來(lái)覆蓋這些不穩(wěn)定場(chǎng)景。還有一種情況是輸入本身有歧義。比如“這個(gè)怎么弄”這種沒(méi)有上下文的輸入模型確實(shí)沒(méi)法判斷。這種應(yīng)該歸入“其他”并轉(zhuǎn)人工而不是強(qiáng)行讓模型猜。5.3 兩級(jí)流水線的常見(jiàn)問(wèn)題速查表問(wèn)題現(xiàn)象可能原因排查方法解決方案L0 命中率突然下降業(yè)務(wù)輸入模式變化對(duì)比近期和歷史的輸入樣本補(bǔ)充新規(guī)則或調(diào)整現(xiàn)有規(guī)則L1 延遲升高模型服務(wù)過(guò)載檢查 CPU/GPU 占用率擴(kuò)容或降低并發(fā)兜底率升高L0 和 L1 都失效查看兜底原因分布針對(duì)性優(yōu)化 L0 或 L1同一輸入結(jié)果不一致溫度參數(shù)過(guò)高檢查模型配置降低溫度參數(shù)規(guī)則沖突多條規(guī)則同時(shí)命中查看規(guī)則優(yōu)先級(jí)調(diào)整優(yōu)先級(jí)或合并規(guī)則日志寫(xiě)入失敗磁盤(pán)滿或權(quán)限問(wèn)題檢查磁盤(pán)空間和文件權(quán)限清理日志或修復(fù)權(quán)限5.4 幾個(gè)我踩過(guò)的坑坑一規(guī)則里用了太寬泛的關(guān)鍵詞。比如“問(wèn)題”這個(gè)詞幾乎每條工單里都有用它做匹配條件等于沒(méi)匹配。關(guān)鍵詞一定要具體寧可多寫(xiě)幾條規(guī)則也不要用模糊詞??佣颂幚砜蛰斎?。用戶提交空字符串或者只有空格的內(nèi)容L0 和 L1 都可能出問(wèn)題。我的做法是在入口處加一個(gè)校驗(yàn)空輸入直接返回“無(wú)效輸入”不進(jìn)入流水線。坑三模型返回結(jié)果帶了多余字符。比如我要求輸出“售后咨詢”模型返回“售后咨詢?!倍嗔藗€(gè)句號(hào)。解析的時(shí)候要做 trim 和標(biāo)點(diǎn)清理不然匹配不上??铀臎](méi)有做并發(fā)控制。L1 層如果同時(shí)收到大量請(qǐng)求本地模型可能扛不住。我加了一個(gè)信號(hào)量來(lái)控制并發(fā)數(shù)超過(guò)閾值的請(qǐng)求排隊(duì)等待而不是直接壓垮模型服務(wù)??游逡?guī)則文件改了沒(méi)重啟服務(wù)。早期我把規(guī)則寫(xiě)死在代碼里改規(guī)則要重新部署。后來(lái)改成從 JSON 文件加載并且支持熱更新——檢測(cè)到文件修改時(shí)間變化就重新加載不用重啟。6. 從單機(jī)到生產(chǎn)擴(kuò)展與演進(jìn)方向6.1 規(guī)則庫(kù)的版本管理規(guī)則庫(kù)是這套系統(tǒng)的核心資產(chǎn)必須做版本管理。我的做法是把規(guī)則文件納入 Git 倉(cāng)庫(kù)每次修改都提交一次寫(xiě)清楚修改原因和影響范圍。這樣出問(wèn)題的時(shí)候可以快速回滾到上一個(gè)版本。同時(shí)我會(huì)給規(guī)則文件加一個(gè)版本號(hào)字段每次加載時(shí)記錄當(dāng)前版本。請(qǐng)求日志里也帶上版本號(hào)這樣排查問(wèn)題時(shí)能知道當(dāng)時(shí)用的是哪版規(guī)則。6.2 從單級(jí) L1 到多級(jí) L1當(dāng)業(yè)務(wù)復(fù)雜度上升后單級(jí) L1 可能不夠用。比如有些任務(wù)需要先分類再抽取字段這時(shí)候可以設(shè)計(jì)成 L1a 負(fù)責(zé)分類、L1b 負(fù)責(zé)抽取形成多級(jí)模型流水線。但要注意每增加一級(jí)延遲和成本都會(huì)上升。多級(jí) L1 只適合那些確實(shí)需要多步推理的復(fù)雜任務(wù)。簡(jiǎn)單的分類任務(wù)單級(jí) L1 就夠了。6.3 規(guī)則自動(dòng)挖掘的嘗試手動(dòng)寫(xiě)規(guī)則效率有限我嘗試過(guò)用歷史數(shù)據(jù)自動(dòng)挖掘規(guī)則。思路很簡(jiǎn)單把 L0 未命中的輸入收集起來(lái)用聚類算法找出高頻模式然后人工確認(rèn)后轉(zhuǎn)成規(guī)則。這個(gè)方法確實(shí)能發(fā)現(xiàn)一些我沒(méi)想到的模式但自動(dòng)挖掘出來(lái)的規(guī)則往往太具體泛化能力差。我的做法是把自動(dòng)挖掘作為輔助手段最終規(guī)則還是人工編寫(xiě)和確認(rèn)。6.4 本地部署的資源規(guī)劃本地跑 L1 模型資源規(guī)劃很重要。以 7B 模型量化到 4bit 為例顯存占用大約 4GB加上推理時(shí)的 KV Cache 和中間激活建議預(yù)留 6GB 顯存。CPU 推理的話內(nèi)存占用大約 8GB速度會(huì)慢很多只適合低并發(fā)場(chǎng)景。如果并發(fā)量高可以考慮用推理框架做批處理把多個(gè)請(qǐng)求合并成一批一起推理吞吐量能提升好幾倍。但批處理會(huì)增加單次延遲需要根據(jù)業(yè)務(wù)場(chǎng)景權(quán)衡。7. 一些個(gè)人體會(huì)這套兩級(jí)流水線我用了快一年最大的感受是不要迷信模型也不要排斥模型。規(guī)則和模型各有各的適用場(chǎng)景關(guān)鍵是把它們放在正確的位置上。L0 層讓我對(duì)系統(tǒng)有了掌控感。每一條規(guī)則都是我寫(xiě)的我知道它在做什么、為什么這么做。這種確定性在出問(wèn)題的時(shí)候特別寶貴——我能快速定位、快速修復(fù)而不是對(duì)著一堆模型輸出干瞪眼。L1 層則給了我應(yīng)對(duì)未知的底氣??倳?huì)有規(guī)則覆蓋不到的情況總會(huì)有新的表達(dá)方式出現(xiàn)這時(shí)候模型就是最后一道防線。它不完美但比沒(méi)有強(qiáng)。如果你也在做類似的任務(wù)拆分系統(tǒng)我的建議是先把 L0 做扎實(shí)再考慮 L1。很多人一上來(lái)就想著用模型解決一切結(jié)果系統(tǒng)又慢又貴還不穩(wěn)定。先把能確定的確定下來(lái)剩下的再交給模型這才是務(wù)實(shí)的做法。最后分享一個(gè)小技巧定期 review L1 的低置信度日志。這些日志里藏著 L0 規(guī)則優(yōu)化的線索。每次 review你都能發(fā)現(xiàn)幾條可以補(bǔ)充的規(guī)則。堅(jiān)持幾個(gè)月L0 的命中率會(huì)穩(wěn)步上升L1 的壓力會(huì)越來(lái)越小。這是一個(gè)正向循環(huán)越跑越順。