設(shè)計(jì)與落地:從六層拆解到性能調(diào)優(yōu))
開頭想先聊一個(gè)問(wèn)題LLM智能聊天助手這個(gè)項(xiàng)目到底難在哪兒很多人第一反應(yīng)是“把大模型API接進(jìn)來(lái)不就行了”但真把一個(gè)聊天助手做到能穩(wěn)定使用、能在業(yè)務(wù)場(chǎng)景里干活你會(huì)發(fā)現(xiàn)模型API只是最底層的一環(huán)。這個(gè)系列我會(huì)以一個(gè)實(shí)際搭建過(guò)的項(xiàng)目為藍(lán)本拆解從架構(gòu)設(shè)計(jì)、技術(shù)選型到鏈路實(shí)現(xiàn)、問(wèn)題排查的全過(guò)程。第一篇先講整體架構(gòu)、底座搭建和一條最小可用的對(duì)話鏈路適合正在做或準(zhǔn)備做LLM聊天助手的開發(fā)者參考。1. 整體架構(gòu)LLM智能聊天助手到底由哪些部分組成1.1 先說(shuō)清楚聊天助手和“接個(gè)API”差在哪里我拿到項(xiàng)目需求時(shí)第一版確實(shí)就是“接個(gè)API”——用戶發(fā)消息我們轉(zhuǎn)發(fā)給模型拿結(jié)果返回。演示的時(shí)候效果不錯(cuò)但一旦進(jìn)入真實(shí)使用場(chǎng)景三個(gè)問(wèn)題馬上暴露出來(lái)。第一個(gè)是上下文管理。真實(shí)用戶不會(huì)只問(wèn)一句話他會(huì)連續(xù)追問(wèn)會(huì)聊到一半糾正自己剛才的說(shuō)法。每次請(qǐng)求把所有歷史都丟給模型token消耗按輪次滾雪球不說(shuō)模型還會(huì)被太久遠(yuǎn)的上下文干擾。第二個(gè)是幻覺(jué)。當(dāng)用戶問(wèn)的是你們內(nèi)部產(chǎn)品的功能細(xì)節(jié)模型沒(méi)學(xué)過(guò)這些數(shù)據(jù)就會(huì)一本正經(jīng)地編一個(gè)答案出來(lái)。第三個(gè)是狀態(tài)和成本。生產(chǎn)環(huán)境里用戶列表、會(huì)話ID、錯(cuò)誤重試、超時(shí)處理、模型限流這些都是API直連不會(huì)替你解決的事。所以哪怕是一個(gè)MVP我也建議你把它當(dāng)“系統(tǒng)”來(lái)做而不是當(dāng)“接口調(diào)用”來(lái)做。聊天助手是把模型能力包裝成可持續(xù)運(yùn)行的產(chǎn)品核心工作是管理上下文、掛接知識(shí)、控制成本、保證可觀測(cè)。1.2 我采用的六層功能結(jié)構(gòu)把需求捋完一遍之后我把整個(gè)智能聊天助手拆成了六個(gè)相對(duì)獨(dú)立的層會(huì)話接入層處理用戶連接、消息收發(fā)、流式輸出對(duì)前端只暴露統(tǒng)一接口。狀態(tài)管理層維護(hù)會(huì)話ID、歷史消息、用戶畫像、會(huì)話過(guò)期策略。Prompt編排層負(fù)責(zé)系統(tǒng)提示詞、用戶意圖改寫、輸出格式約束決定“模型以什么身份、按什么規(guī)則回答問(wèn)題”。檢索增強(qiáng)層對(duì)接內(nèi)部知識(shí)庫(kù)做向量檢索或關(guān)鍵詞檢索把相關(guān)資料拼進(jìn)上下文這是克制幻覺(jué)的關(guān)鍵。模型路由層根據(jù)問(wèn)題難度、成本預(yù)算、當(dāng)前負(fù)載決定用大模型還是小模型用哪個(gè)服務(wù)商。評(píng)估觀測(cè)層記錄每次請(qǐng)求的token數(shù)、延遲、模型輸出質(zhì)量方便事后復(fù)盤和迭代。這層結(jié)構(gòu)不是一次到位的。最開始我只有接入、狀態(tài)、模型三塊后來(lái)因?yàn)椤盎卮饜?ài)編造”的問(wèn)題才加了檢索增強(qiáng)層因?yàn)橘~號(hào)成本失控才加了模型路由層。所以我的經(jīng)驗(yàn)是架構(gòu)可以分層但你不用一開始就把六層全部做滿先保住能跑通的主鏈路遇到問(wèn)題再往對(duì)應(yīng)層里補(bǔ)能力。1.3 一個(gè)關(guān)鍵認(rèn)知先分層但別過(guò)度設(shè)計(jì)很多團(tuán)隊(duì)一上來(lái)就畫了很漂亮的架構(gòu)圖然后花兩周時(shí)間把每一層都搭好最后發(fā)現(xiàn)連一個(gè)完整可試用的對(duì)話流程都沒(méi)有。我自己的教訓(xùn)是反向的第一版寫得非常隨意所有邏輯都揉在一個(gè)文件里結(jié)果每加一個(gè)功能就要牽動(dòng)全局。比較務(wù)實(shí)的做法是先按上面六層劃分模塊邊界但每一層只實(shí)現(xiàn)最必需的能力。比如檢索增強(qiáng)層第一版可以只是“從文件里匹配關(guān)鍵詞”不用一上來(lái)就是向量庫(kù)模型路由層第一版可以只支持一個(gè)模型。這樣既保留了后續(xù)演進(jìn)空間又不會(huì)陷入過(guò)度設(shè)計(jì)。需要強(qiáng)調(diào)的是評(píng)估觀測(cè)層不要省哪怕第一版只是把每次請(qǐng)求的輸入輸出打到日志里后面排查問(wèn)題時(shí)你會(huì)感謝當(dāng)時(shí)的自己。2. 技術(shù)選型與底座搭建每個(gè)決定背后的原因2.1 模型選型閉源API、開源本地模型還是云端私有化模型是整個(gè)智能聊天助手的大腦選型直接決定效果、成本和數(shù)據(jù)合規(guī)邊界。當(dāng)時(shí)我面前有三類方案各有各的適用場(chǎng)景方案單次請(qǐng)求成本首字延遲數(shù)據(jù)私密性效果穩(wěn)定性適合階段成熟商業(yè)API中等按token計(jì)費(fèi)一般幾百毫秒數(shù)據(jù)出域需服務(wù)條款允許整體穩(wěn)定模型升級(jí)策略由服務(wù)商決定MVP驗(yàn)證、快速上線本地開源小模型7B/14B主要是GPU電費(fèi)單請(qǐng)求成本低取決于顯卡和部署方式數(shù)據(jù)不出內(nèi)網(wǎng)依賴微調(diào)和提示詞工程波動(dòng)稍大有GPU資源、對(duì)私密性要求高云端私有化部署按實(shí)例時(shí)長(zhǎng)計(jì)費(fèi)并發(fā)高時(shí)成本高較穩(wěn)定數(shù)據(jù)限定在獨(dú)立環(huán)境可控性強(qiáng)中大型業(yè)務(wù)、合規(guī)要求嚴(yán)格的場(chǎng)景我的選擇是先商業(yè)API做MVP業(yè)務(wù)跑通之后再把一部分高頻場(chǎng)景切到本地小模型。切換的動(dòng)作之所以不難是因?yàn)橐婚_始就在模型路由層做了抽象業(yè)務(wù)代碼不直接依賴某個(gè)具體模型的SDK。如果你現(xiàn)在也在選型我建議你千萬(wàn)別把模型“硬編碼”到業(yè)務(wù)代碼里至少留一個(gè)統(tǒng)一的調(diào)用入口。這樣未來(lái)?yè)Q模型、做降級(jí)都只改一個(gè)文件。2.2 編排框架用但不要被框架綁架做LLM聊天助手很難繞開一個(gè)話題要不要用LangChain這類編排框架。我的答案是用它的組件化思路但不要整個(gè)項(xiàng)目依賴它。先說(shuō)為什么用??蚣芴幚怼疤崾驹~模板渲染”“接口統(tǒng)一封裝”“回調(diào)與日志”這些瑣事很順手能幫你快速拼出原型。但框架也有自己的問(wèn)題版本迭代太快很多組件今天這樣寫明天就廢棄抽象層級(jí)多出問(wèn)題時(shí)你很難看清真正發(fā)給模型的消息體長(zhǎng)什么樣還有一點(diǎn)框架封裝好的“記憶模塊”往往不夠貼合你的業(yè)務(wù)需求。我自己實(shí)踐下來(lái)的做法是提示詞模板自己維護(hù)記憶邏輯自己寫外部只借用框架里的工具類組件。調(diào)試這個(gè)聊天助手時(shí)最常干的一件事就是把每次請(qǐng)求拼好的messages打印出來(lái)看這種透明的控制力是框架給不了你的。如果你只是做實(shí)驗(yàn)可以隨便用如果是做生產(chǎn)項(xiàng)目核心鏈路建議自己掌握。2.3 向量庫(kù)與會(huì)話存儲(chǔ)數(shù)據(jù)和狀態(tài)往哪放當(dāng)項(xiàng)目引入知識(shí)庫(kù)問(wèn)答之后就涉及兩塊數(shù)據(jù)存儲(chǔ)一塊是知識(shí)庫(kù)的向量數(shù)據(jù)一塊是會(huì)話語(yǔ)義狀態(tài)。向量庫(kù)我當(dāng)時(shí)對(duì)比了三類內(nèi)存版適合幾百條文檔的Demo演示優(yōu)點(diǎn)是零部署輕量級(jí)文件版適合小規(guī)模知識(shí)庫(kù)單機(jī)就能跑生產(chǎn)級(jí)分布式版適合文檔量大、查詢并發(fā)高的場(chǎng)景但要運(yùn)維成本。我的建議是知識(shí)庫(kù)文檔少于1萬(wàn)段時(shí)用一個(gè)輕量文件版或單機(jī)版足夠別一上來(lái)就上重存儲(chǔ)。會(huì)話存儲(chǔ)的方案更直白熱會(huì)話放Redis全量會(huì)話落數(shù)據(jù)庫(kù)。每次用戶發(fā)消息先從Redis里取最近N輪歷史拼好消息列表發(fā)給模型會(huì)話結(jié)束后把完整記錄異步寫入數(shù)據(jù)庫(kù)。這樣做的原因是對(duì)話過(guò)程中的讀取頻次遠(yuǎn)高于寫入頻次Redis扛熱數(shù)據(jù)數(shù)據(jù)庫(kù)保證不丟。另外會(huì)話一定要帶過(guò)期策略否則閑置會(huì)話會(huì)一直占用內(nèi)存。3. 核心鏈路實(shí)現(xiàn)一次對(duì)話在智能聊天助手中的完整旅程3.1 先搭一個(gè)最小可用鏈路流式輸出必須做聊天助手最基礎(chǔ)的鏈路就是“用戶發(fā)消息—組裝上下文—調(diào)用模型—返回回答”。這個(gè)鏈路里我踩過(guò)最大的坑是響應(yīng)用戶體驗(yàn)。一開始我用的是非流式接口用戶發(fā)完一句話要等好幾秒才看到整段答案體驗(yàn)接近“發(fā)了個(gè)請(qǐng)求在轉(zhuǎn)圈”。后來(lái)改成流式輸出模型生成一個(gè)字傳一個(gè)字用戶感知到的等待時(shí)間大幅縮短。用大家常說(shuō)的指標(biāo)就是首字時(shí)間TTFT比整體耗時(shí)更能決定用戶對(duì)“快慢”的感受。做一個(gè)簡(jiǎn)單的流式接口并不復(fù)雜。如果用FastAPI大概長(zhǎng)這樣from fastapi import FastAPI from fastapi.responses import StreamingResponse app FastAPI() def generate_answer(user_message: str, history: list[dict]): # 偽代碼流式調(diào)用模型接口 response provider.chat( messagesbuild_messages(history, user_message), streamTrue ) for chunk in response: delta chunk[choices][0][delta].get(content, ) if delta: yield fdata: {delta}\n\n app.post(/chat) async def chat(user_message: str, session_id: str): history load_recent_history(session_id) return StreamingResponse( generate_answer(user_message, history), media_typetext/event-stream )前端的處理邏輯也簡(jiǎn)單用EventSource或fetch stream讀取data:開頭的行然后逐段追加到界面上。記得在接口返回結(jié)束時(shí)發(fā)一個(gè)結(jié)束標(biāo)記前端據(jù)此關(guān)閉流。這個(gè)最小鏈路跑通之后后面加記憶、加檢索都是在build_messages()這個(gè)函數(shù)里做文章。3.2 把“記憶”裝進(jìn)來(lái)多輪上下文到底該怎么管聊到多輪對(duì)話新手最容易犯的錯(cuò)誤是“把歷史全部拼上”。我算過(guò)一筆賬假設(shè)每輪用戶輸入和模型輸出平均400個(gè)token到第10輪時(shí)單次請(qǐng)求攜帶的歷史約4000個(gè)token到第50輪時(shí)已經(jīng)將近2萬(wàn)個(gè)token。這不僅僅是費(fèi)用問(wèn)題——上下文過(guò)長(zhǎng)模型注意力會(huì)被稀釋回答質(zhì)量反而下降。所以我的做法是設(shè)置一個(gè)上下文預(yù)算按token估算值從最新消息往回取。核心思路是用一個(gè)簡(jiǎn)化的token計(jì)數(shù)方式把字符串長(zhǎng)度除以合適的系數(shù)估算token數(shù)超出預(yù)算就裁剪。def build_messages(history: list[dict], new_message: str, max_context_tokens3200): messages [{role: system, content: SYSTEM_PROMPT}] budget max_context_tokens # 新消息肯定要放進(jìn)去先估算它的token new_token_count estimate_tokens(new_message) budget - new_token_count # 從最近一條歷史往前取直到預(yù)算耗盡 for item in reversed(history): item_token_count estimate_tokens(item[content]) if budget - item_token_count 0: break messages.insert(1, item) budget - item_token_count messages.append({role: user, content: new_message}) return messages這個(gè)方案里有個(gè)細(xì)節(jié)容易被忽略系統(tǒng)提示詞也會(huì)占token所以預(yù)算計(jì)算時(shí)要把系統(tǒng)提示詞先扣掉。另外一些關(guān)鍵信息不能簡(jiǎn)單隨滑動(dòng)窗口丟掉比如用戶說(shuō)過(guò)“我叫某某偏好簡(jiǎn)潔回答”這種信息我會(huì)單獨(dú)抽出來(lái)放到系統(tǒng)提示詞里保證它一直在上下文中。更復(fù)雜的做法是對(duì)早期歷史做摘要壓縮這個(gè)后面單獨(dú)講。3.3 把“知識(shí)”掛進(jìn)來(lái)RAG讓答案不再是編的聊天助手做知識(shí)問(wèn)答的時(shí)候如果只靠模型自身參數(shù)它就會(huì)“一本正經(jīng)地胡說(shuō)八道”。我引入RAG檢索增強(qiáng)生成之后幻覺(jué)問(wèn)題明顯緩解。思路不復(fù)雜把內(nèi)部文檔切成小塊、做向量化、存入向量庫(kù)用戶提問(wèn)時(shí)先檢索相關(guān)片段把片段作為資料拼到提示詞里讓模型基于資料回答。核心流程我拆成了四步每一步都有值得注意的細(xì)節(jié)。第一步文檔切塊。我一開始按固定字?jǐn)?shù)硬切結(jié)果很多完整句子被切斷檢索出來(lái)的片段語(yǔ)義不完整。后來(lái)改成按段落切段落太長(zhǎng)的再按句子邊界二次切分并且讓相鄰塊之間保留一定重疊。第二步向量化。選一個(gè)好的Embedding模型很關(guān)鍵嵌入式模型的效果差距會(huì)直接影響檢索準(zhǔn)確性。第三步檢索。我用的是“向量召回閾值過(guò)濾”取top_k個(gè)相關(guān)片段凡相似度分?jǐn)?shù)低于閾值的直接丟棄。第四步拼裝。把檢索到的資料用固定格式拼進(jìn)提示詞。def build_rag_messages(question: str, history: list[dict]): query_vec embed(question) results vector_store.search(query_vec, top_k5) valid [r for r in results if r.score 0.7] context \n\n.join( f[資料{i1}] {r.text} for i, r in enumerate(valid) ) system_prompt f你是內(nèi)部智能客服助手。 請(qǐng)優(yōu)先根據(jù)以下資料回答問(wèn)題 如果資料不足以回答請(qǐng)直接說(shuō)明“我無(wú)法從現(xiàn)有資料中找到答案”。 不要編造任何資料之外的內(nèi)容。 【資料庫(kù)】 {context} return [{role: system, content: system_prompt}] historytop_k取多少需要按業(yè)務(wù)調(diào)。取太少信息覆蓋不足取太多無(wú)關(guān)內(nèi)容會(huì)淹沒(méi)關(guān)鍵信息也讓token消耗變大。我的經(jīng)驗(yàn)是5到8之間比較穩(wěn)再配合相似度閾值做兜底。另外如果知識(shí)庫(kù)文檔量比較大建議在向量檢索之前先做一層關(guān)鍵詞預(yù)篩把明顯不相關(guān)的內(nèi)容擋在外面檢索性能會(huì)好很多。3.4 輸出質(zhì)量別忽略提示詞工程里的“邊界約束”很多人在聊天助手里寫提示詞只寫“你是一個(gè)助手”這等于什么都沒(méi)約束。真正影響輸出質(zhì)量的是那些邊界性約束。我做問(wèn)答功能時(shí)在系統(tǒng)提示詞里堅(jiān)持寫幾條第一資料不足時(shí)明說(shuō)不知道第二回答盡量按資料內(nèi)容表述不要自行發(fā)揮第三涉及數(shù)據(jù)或數(shù)字時(shí)引用資料編號(hào)。這看起來(lái)很簡(jiǎn)單但實(shí)際體驗(yàn)差異巨大。我在項(xiàng)目里做過(guò)對(duì)照沒(méi)有加“資料不足直接說(shuō)不知道”的時(shí)候模型遇到庫(kù)里沒(méi)有的內(nèi)容有大約一半概率會(huì)硬編一個(gè)答案加上之后大多數(shù)情況它會(huì)誠(chéng)實(shí)表示不知道。提示詞不是萬(wàn)能藥但它是成本最低的幻覺(jué)抑制手段。類似的約束還包括“不要輸出Markdown以外的內(nèi)容”“回答要簡(jiǎn)潔控制在多少字以內(nèi)”這些都按業(yè)務(wù)場(chǎng)景需要去加。4. 常見(jiàn)問(wèn)題與排查實(shí)錄把線上聊天助手調(diào)穩(wěn)的幾件事4.1 回答總是在“編”知識(shí)與對(duì)話鏈路脫節(jié)我遇到過(guò)最典型的故障是知識(shí)庫(kù)已經(jīng)接上了但用戶問(wèn)產(chǎn)品問(wèn)題模型還是在編答案。排查日志我發(fā)現(xiàn)檢索出來(lái)的片段相似度分?jǐn)?shù)全部低于閾值等于沒(méi)有向模型提供任何有效資料。問(wèn)題出在文檔切分階段原文檔是一整塊PDF導(dǎo)出的純文本段落之間沒(méi)有任何分隔被切塊后大量片段語(yǔ)義都變成了“無(wú)意義碎塊”。這種情況的處理思路有兩個(gè)方向。一個(gè)方向是優(yōu)化切分回到文檔預(yù)處理先清理格式、識(shí)別標(biāo)題和段落邊界再做切分另一個(gè)方向是降低檢索召回門檻同時(shí)增加一個(gè)重排步驟把語(yǔ)義更相關(guān)的片段排到前面。我現(xiàn)在基本是把兩個(gè)方向都做了但最關(guān)鍵的是先讓“有效資料被檢索到”再談后續(xù)優(yōu)化。4.2 多輪對(duì)話后回復(fù)越來(lái)越散歷史上下文的“角色錯(cuò)亂”運(yùn)行一段時(shí)間后有使用者反饋“聊到第幾輪助手就開始答非所問(wèn)甚至用奇怪的口吻說(shuō)話”。我把發(fā)給模型的完整messages數(shù)組打出來(lái)發(fā)現(xiàn)了一個(gè)很隱蔽的問(wèn)題某條歷史消息的角色被存成了system導(dǎo)致模型以為那是系統(tǒng)指令后面的對(duì)話邏輯一下子全亂了。這就是為什么我強(qiáng)調(diào)“調(diào)試時(shí)一定要打印完整消息體”。只要打印出來(lái)角色錯(cuò)亂、內(nèi)容截?cái)?、歷史順序顛倒這些問(wèn)題一眼就能看出來(lái)。修復(fù)方案是歷史存取時(shí)嚴(yán)格區(qū)分user和assistant角色前端展示和模型輸入分開處理另外在構(gòu)建messages時(shí)做一個(gè)角色校驗(yàn)發(fā)現(xiàn)非法角色直接丟棄或修正。4.3 響應(yīng)慢得像在“打字”性能瓶頸定位線上出現(xiàn)“回復(fù)很慢”的反饋時(shí)我沒(méi)有急著換更強(qiáng)的模型而是先看鏈路各環(huán)節(jié)的耗時(shí)數(shù)據(jù)。我記錄了四個(gè)時(shí)間點(diǎn)收到請(qǐng)求時(shí)間、檢索完成時(shí)間、模型首字返回時(shí)間、完整返回時(shí)間。對(duì)比之后發(fā)現(xiàn)慢的瓶頸不在模型推理而在每次請(qǐng)求都去全量向量庫(kù)檢索加上上下文太長(zhǎng)模型需要處理大量歷史token。對(duì)癥處理后速度明顯提升給向量檢索加了一層關(guān)鍵詞預(yù)篩把候選集從全量縮到一小部分上下文預(yù)算從4096降到2048優(yōu)先保證最近的輪次疊加一層對(duì)話級(jí)緩存同樣的高頻問(wèn)題直接命中緩存返回。如果你的聊天助手也慢先用性能分析工具定位再動(dòng)手改不要憑感覺(jué)。4.4 問(wèn)題排查速查表現(xiàn)象優(yōu)先排查點(diǎn)我常用的處理方法答案憑空編造知識(shí)庫(kù)檢索是否命中、相似度閾值是否過(guò)高打印檢索分?jǐn)?shù)調(diào)小閾值或優(yōu)化切分多輪后答非所問(wèn)歷史消息角色是否錯(cuò)亂、上下文是否超限打印完整messages修復(fù)角色存儲(chǔ)整體響應(yīng)慢模型推理、檢索耗時(shí)、上下文長(zhǎng)度分層打點(diǎn)統(tǒng)計(jì)耗時(shí)預(yù)篩壓縮上下文費(fèi)用快速上漲每次請(qǐng)求攜帶的歷史是否過(guò)長(zhǎng)、是否反復(fù)檢索限制上下文預(yù)算高頻問(wèn)題緩存輸出格式不穩(wěn)定系統(tǒng)提示詞約束不清晰在提示詞里明確“輸出內(nèi)容用指定格式”這五類問(wèn)題幾乎覆蓋了聊天助手上線初期的絕大多數(shù)故障。我的體會(huì)是不要試圖靠“換一個(gè)大模型”來(lái)解決所有問(wèn)題。多數(shù)問(wèn)題的根因在鏈路設(shè)計(jì)上下文管理、檢索質(zhì)量、可觀測(cè)性。把這些基礎(chǔ)打牢換了模型之后表現(xiàn)依然穩(wěn)定。4.5 一個(gè)性價(jià)比最高的調(diào)試技巧最后分享一個(gè)我在這類項(xiàng)目里用得最多的技巧給所有發(fā)給模型的請(qǐng)求體加日志。不用打全部只打系統(tǒng)提示詞前200字、用戶消息、歷史條數(shù)、總token估算值、檢索命中的資料總數(shù)。這些信息足夠幫你定位大部分線上問(wèn)題。有一次用戶反饋“回答內(nèi)容沒(méi)問(wèn)題但語(yǔ)氣不對(duì)”我翻日志發(fā)現(xiàn)某條歷史消息的content里混入了前端傳過(guò)來(lái)的拼接文本導(dǎo)致模型誤以為那是用戶表達(dá)。這種問(wèn)題如果不看請(qǐng)求體靠猜可以猜很久。日志就是你調(diào)試LLM應(yīng)用的“儀表盤”早做早受益。做LLM智能聊天助手這一年多我最大的感受是這個(gè)方向的門檻不在“會(huì)不會(huì)調(diào)API”而在“愿不愿意把鏈路當(dāng)系統(tǒng)來(lái)打磨”。把上下文當(dāng)資源去管理把知識(shí)檢索當(dāng)基礎(chǔ)設(shè)施去建設(shè)把日志當(dāng)?shù)谝粌?yōu)先級(jí)的調(diào)試工具很多看似玄學(xué)的模型問(wèn)題都能變成可解的工程問(wèn)題。等這批基礎(chǔ)打牢后面再聊重排序、評(píng)測(cè)集、多模型路由這些進(jìn)階話題就有扎實(shí)的地基了。