用開(kāi)發(fā)不止是調(diào)接口:從接口到工程體系的實(shí)戰(zhàn)指南)
AI 應(yīng)用開(kāi)發(fā)不就是調(diào)個(gè)接口么這句話我聽(tīng)過(guò)太多次說(shuō)這話的人往往還沒(méi)正經(jīng)做過(guò)一個(gè) AI 應(yīng)用。前兩年大模型剛火起來(lái)的時(shí)候隨便一個(gè) HTTP 請(qǐng)求把 prompt 塞進(jìn)去拿回一段文本確實(shí)跟調(diào)普通接口沒(méi)什么區(qū)別。但真到了做產(chǎn)品、做項(xiàng)目落地的時(shí)候你會(huì)發(fā)現(xiàn)“調(diào)接口”只是最外層的殼殼底下藏著的是提示詞工程、上下文管理、Agent 編排、成本控制、容錯(cuò)降級(jí)、評(píng)測(cè)回歸這一整套東西。這篇文章我就從自己實(shí)際做過(guò)的項(xiàng)目出發(fā)聊聊 AI 應(yīng)用開(kāi)發(fā)到底在開(kāi)發(fā)什么哪些環(huán)節(jié)容易被一句話帶過(guò)以及真正決定項(xiàng)目生死的是哪幾步。這篇文章不是寫(xiě)給剛看完大模型科普的讀者的也不是寫(xiě)給純后端調(diào)參工程師的。它更適合那些已經(jīng)上手寫(xiě)過(guò)幾個(gè) demo、正準(zhǔn)備把 AI 功能塞進(jìn)真實(shí)業(yè)務(wù)系統(tǒng)、卻對(duì)工程質(zhì)量隱隱不安的開(kāi)發(fā)者。下面所有內(nèi)容都是我自己趟過(guò)坑之后的復(fù)盤(pán)不保證覆蓋所有場(chǎng)景但能保證每一條都來(lái)自真實(shí)項(xiàng)目。1. 這句話的由來(lái)與真相1.1 為什么“調(diào)個(gè)接口”的說(shuō)法會(huì)流行這個(gè)說(shuō)法能流行起來(lái)是因?yàn)?AI 應(yīng)用開(kāi)發(fā)的第一層臺(tái)階確實(shí)低得離譜。注冊(cè)一個(gè)平臺(tái)賬號(hào)復(fù)制幾行官方示例代碼填上 API Key一個(gè)能回答問(wèn)題的聊天機(jī)器人就跑起來(lái)了。整個(gè)流程跟調(diào)一個(gè)天氣接口、查一個(gè)快遞單號(hào)的復(fù)雜度差不多。于是很多人產(chǎn)生了一種錯(cuò)覺(jué)AI 應(yīng)用開(kāi)發(fā)的門(mén)檻已經(jīng)低到不需要算法基礎(chǔ)、不需要系統(tǒng)設(shè)計(jì)、甚至不需要懂模型原理。這種錯(cuò)覺(jué)在 demo 階段是成立的但 demo 和產(chǎn)品之間隔著一整條生產(chǎn)線。舉個(gè)最直觀的例子調(diào)普通接口入?yún)⒊鰠⑹谴_定的你傳用戶 ID 就返回用戶信息參數(shù)錯(cuò)了頂多報(bào)錯(cuò)重試。但調(diào)大模型接口同樣的輸入不一定得到同樣的輸出不僅輸出不確定輸出還是概率性的、有時(shí)效性的、有上下文依賴的。這意味著你不能把大模型接口當(dāng)作一個(gè)普通函數(shù)來(lái)用而是得把它當(dāng)成一個(gè)“能力不太穩(wěn)定但潛力巨大的實(shí)習(xí)生”來(lái)管理。還有個(gè)被低估的問(wèn)題普通接口的性能瓶頸在 IO 和數(shù)據(jù)庫(kù)你加個(gè)索引、做點(diǎn)緩存就能解決。大模型的瓶頸在推理延遲和 token 消耗模型生成 500 個(gè)字可能要花好幾秒調(diào)用一次可能要花幾毛錢(qián)。這些差異決定了 AI 應(yīng)用開(kāi)發(fā)的架構(gòu)設(shè)計(jì)、容錯(cuò)策略和成本模型跟傳統(tǒng)后端開(kāi)發(fā)完全不是一個(gè)套路。1.2 真實(shí)項(xiàng)目里 AI 開(kāi)發(fā)的核心矛盾我做過(guò)一個(gè)知識(shí)庫(kù)問(wèn)答系統(tǒng)最初版本確實(shí)就是個(gè)“接口調(diào)用”把用戶問(wèn)題拼接進(jìn) prompt調(diào)模型接口返回答案。跑了一個(gè)月暴露了三個(gè)問(wèn)題這些問(wèn)題現(xiàn)在回看基本上就是 AI 應(yīng)用開(kāi)發(fā)的核心矛盾。第一個(gè)矛盾是效果與成本的矛盾。模型參數(shù)量越大效果越好但單次調(diào)用成本直線上升、延遲直線上升。知識(shí)庫(kù)問(wèn)答這種場(chǎng)景用戶一次提問(wèn)要匹配檢索多篇文檔每個(gè)文檔片段都要拼接進(jìn)上下文隨便一算一次回答可能消耗幾千 token。如果每個(gè)請(qǐng)求都用最強(qiáng)模型一個(gè)月下來(lái)賬單會(huì)讓人懷疑人生。但如果用輕量模型答案質(zhì)量又壓不住。這個(gè)矛盾沒(méi)法靠調(diào)接口解決必須靠工程手段來(lái)做路由和分級(jí)。第二個(gè)矛盾是穩(wěn)定輸出與概率輸出的矛盾。業(yè)務(wù)系統(tǒng)需要的是穩(wěn)定的、可解析的輸出比如讓模型從對(duì)話中抽取一個(gè) JSON 結(jié)構(gòu)包含用戶意圖、實(shí)體、槽位。理想情況下模型每次都輸出合法 JSON實(shí)際情況是模型偶爾會(huì)多一行解釋、少一個(gè)逗號(hào)甚至直接給出 Markdown 格式而不是 JSON。你不能因?yàn)槟P洼敵霾环€(wěn)定就重構(gòu)整個(gè)業(yè)務(wù)流而必須在自己這一側(cè)加上輸出校驗(yàn)、異常重試和格式化修正的邏輯。第三個(gè)矛盾是快速迭代與效果回歸的矛盾。prompt 改一句話可能解決了 A 場(chǎng)景的 bug但與此同時(shí) B 場(chǎng)景的輸出悄悄變差了。沒(méi)有評(píng)測(cè)集的話你根本不知道這個(gè)劣化是什么時(shí)候發(fā)生的。傳統(tǒng)開(kāi)發(fā)有單元測(cè)試做回歸AI 應(yīng)用開(kāi)發(fā)里 prompt 和模型的組合就是一個(gè)黑盒唯一的辦法是建立評(píng)測(cè)基線每次變更都跑一遍。這一步很多團(tuán)隊(duì)不做結(jié)果就是 prompt 越改越亂效果全憑感覺(jué)。所以說(shuō)“調(diào)個(gè)接口”這個(gè)說(shuō)法描述的是 AI 應(yīng)用開(kāi)發(fā)的入口而不是這個(gè)領(lǐng)域的全貌。你真正要開(kāi)發(fā)的是圍繞接口構(gòu)建的一整套工程體系。2. 接口定義與模型選型沒(méi)有想象的那么簡(jiǎn)單2.1 模型選型不是越大越好很多剛?cè)腴T(mén)的開(kāi)發(fā)者選模型只看一個(gè)指標(biāo)排行榜分?jǐn)?shù)。這個(gè)思路在 demo 階段問(wèn)題不大真做產(chǎn)品就會(huì)翻車。我見(jiàn)過(guò)一個(gè)團(tuán)隊(duì)用最強(qiáng)模型做客服問(wèn)答日均調(diào)用量幾千次單次消耗 token 很多月底一算成本是預(yù)估的五倍項(xiàng)目直接被叫停。這就是典型的沒(méi)做模型選型就動(dòng)手。做模型選型要綜合考慮能力表現(xiàn)、單位成本、響應(yīng)速度、上下文窗口和生態(tài)兼容性五個(gè)維度。以文本生成場(chǎng)景為例一個(gè)復(fù)雜推理任務(wù)可能確實(shí)需要最強(qiáng)模型但一個(gè)簡(jiǎn)單的意圖分類任務(wù)用一個(gè)輕量模型加一個(gè)好的 prompt 模板就能做到不錯(cuò)的準(zhǔn)確率成本可能差一個(gè)數(shù)量級(jí)。正確的做法是建立一個(gè)分級(jí)模型體系簡(jiǎn)單任務(wù)走輕量模型復(fù)雜任務(wù)才動(dòng)用強(qiáng)模型。在接口層封裝一個(gè)路由器。這里有個(gè)小技巧我實(shí)測(cè)下來(lái)很有用。你可以拿一批真實(shí)業(yè)務(wù)數(shù)據(jù)分別用不同模型跑一遍同時(shí)記錄每次調(diào)用的 token 消耗和耗時(shí)。算一筆賬假設(shè)最強(qiáng)模型單次調(diào)用 0.02 元輕量模型單次 0.002 元日均 10 萬(wàn)次調(diào)用每天就差 1800 元一年差六十多萬(wàn)。為了省這個(gè)錢(qián)花一周時(shí)間做模型評(píng)估和路由優(yōu)化絕對(duì)值得。2.2 上下文窗口管理是接口調(diào)用的隱藏深坑上下文窗口這個(gè)概念官方文檔里寫(xiě)得很簡(jiǎn)單模型能接受的最大 token 數(shù)。但實(shí)際用起來(lái)它的坑比想象中深得多。你每輪對(duì)話都要把歷史消息傳進(jìn)接口消息越長(zhǎng)單次調(diào)用的 token 消耗越大費(fèi)用越高。更麻煩的是模型并不會(huì)因?yàn)槟銈髁撕荛L(zhǎng)的歷史就記住所有內(nèi)容實(shí)際表現(xiàn)通常是“中間那段被遺忘”很多模型對(duì)長(zhǎng)上下文的注意力分布并不均勻。我自己踩過(guò)一次印象很深的坑。做一個(gè)多輪對(duì)話助手最初設(shè)計(jì)很簡(jiǎn)單每輪把用戶全部聊天記錄拼進(jìn)上下文調(diào)接口返回回答。跑了兩個(gè)星期用戶反饋“模型好像失憶了”明明前面聊過(guò)的話題后面接不上了。查 token 消耗發(fā)現(xiàn)一個(gè) 10 輪以上的對(duì)話單次調(diào)用消耗比初始對(duì)話高出十幾倍。原因很簡(jiǎn)單上下文塞得太多模型處理不過(guò)來(lái)而且成本爆炸。后來(lái)我改成滑動(dòng)窗口策略只保留最近 5 輪對(duì)話再往前的信息做摘要把摘要壓縮成一小段放進(jìn)系統(tǒng)提示詞。這個(gè)改動(dòng)讓平均單次調(diào)用成本降了 60% 以上而且用戶體感上“失憶”問(wèn)題大大緩解因?yàn)檎旧砭桶殃P(guān)鍵信息提煉出來(lái)了。如果對(duì)話內(nèi)容涉及比較多的結(jié)構(gòu)化數(shù)據(jù)我還會(huì)用向量檢索的方式把歷史對(duì)話先存到向量數(shù)據(jù)庫(kù)里按需召回相關(guān)片段而不是一股腦全塞進(jìn)去。核心原則就一句話上下文不是垃圾桶每往里放一個(gè) token都是要花錢(qián)花算力的。設(shè)計(jì)對(duì)話系統(tǒng)時(shí)要像設(shè)計(jì)數(shù)據(jù)庫(kù)索引一樣精心設(shè)計(jì)上下文的內(nèi)容。2.3 理解接口參數(shù)才能真正控制生成結(jié)果大模型接口的請(qǐng)求體里除了 prompt 之外還帶著一堆參數(shù)。很多人只改 temperature 和 max_tokens別的都用默認(rèn)值這其實(shí)浪費(fèi)了接口的控制能力。temperature 控制隨機(jī)性取值越小輸出越保守。但很多人不知道temperature 和 top_p 是配合用的極端情況下可以一個(gè)調(diào)小一個(gè)調(diào)大讓輸出在穩(wěn)定和多樣之間找到平衡。我做分類和抽取任務(wù)會(huì)把 temperature 壓到 0.1 甚至 0做創(chuàng)意文案生成會(huì)把 temperature 調(diào)到 0.8 到 1.0。還有個(gè)參數(shù)叫 frequency_penalty 和 presence_penalty控制重復(fù)和話題新穎度。前者調(diào)高可以避免模型復(fù)讀機(jī)式地重復(fù)同樣的詞匯后者調(diào)高可以鼓勵(lì)模型談新話題減少陳詞濫調(diào)。寫(xiě)營(yíng)銷文案這類場(chǎng)景這組參數(shù)往往比調(diào) prompt 更管用。還有個(gè)容易忽略的參數(shù)是 stop 序列你可以在停止序列里放一個(gè)自定義結(jié)束標(biāo)記專門(mén)讓模型輸出以某種結(jié)構(gòu)化格式結(jié)束。我一般會(huì)在讓模型輸出 JSON 時(shí)把限制性提示寫(xiě)在 prompt 里同時(shí)用 stop 序列處理一些亂七八糟的結(jié)束符號(hào)。這些參數(shù)的組合效果不是線性的光看官方文檔不跑實(shí)驗(yàn)根本拿不準(zhǔn)。我的習(xí)慣是任何參數(shù)調(diào)整都記錄到一張實(shí)驗(yàn)表里連同 prompt 版本、結(jié)果示例一起保存方便對(duì)比和回滾。沒(méi)有這套記錄你會(huì)陷入“這個(gè)參數(shù)好像是上上周調(diào)的”的困境。3. 多 Agent 編排才是拉開(kāi)距離的地方3.1 單次調(diào)用到多 Agent 協(xié)作的躍遷單接口調(diào)用做到極致也就是一個(gè)“高級(jí)搜索引擎”。要把 AI 應(yīng)用做出真正的產(chǎn)品價(jià)值得進(jìn)入多 Agent 編排這個(gè)階段。所謂 Agent簡(jiǎn)單理解就是一個(gè)能使用工具的智能流程它可以調(diào)用搜索 API 獲取實(shí)時(shí)信息可以調(diào)用代碼解釋器做計(jì)算可以操作內(nèi)部系統(tǒng)的接口完成任務(wù)。而多 Agent 編排就是讓多個(gè)角色分工協(xié)作各管一段最后把結(jié)果匯合。我做一個(gè)行業(yè)研究報(bào)告生成器的時(shí)候最初就是一次調(diào)用大模型生成整篇報(bào)告。效果很感人模型輸出結(jié)構(gòu)完整但內(nèi)容空洞數(shù)據(jù)滯后引用格式是編的。后來(lái)改成三個(gè) Agent 協(xié)作研究 Agent 負(fù)責(zé)查資料分析師 Agent 負(fù)責(zé)提煉觀點(diǎn)寫(xiě)作 Agent 負(fù)責(zé)成稿。每個(gè) Agent 各有自己的 prompt 和工具權(quán)限按順序工作質(zhì)量上了一個(gè)大臺(tái)階。但這個(gè)架構(gòu)也有代價(jià)總耗時(shí)變長(zhǎng)、整體成本變高、各環(huán)節(jié)之間如何傳遞信息成了一個(gè)新問(wèn)題。我見(jiàn)過(guò)有人設(shè)計(jì)了一個(gè)特別復(fù)雜的 Agent 網(wǎng)絡(luò)每個(gè)環(huán)節(jié)都調(diào)用最強(qiáng)模型結(jié)果是單次生成成本高到用戶根本付不起。多 Agent 不是裝飾品不是為了顯得技術(shù)先進(jìn)。每增加一個(gè) Agent必須對(duì)應(yīng)一個(gè)明確的職責(zé)邊界和用戶可感知的價(jià)值提升這算是我做編排最深刻的體會(huì)。3.2 工具調(diào)用與結(jié)構(gòu)化輸出的正確姿勢(shì)Agent 要執(zhí)行具體操作依賴的是 function calling。這個(gè)能力讓模型可以輸出一個(gè)結(jié)構(gòu)化指令比如“調(diào)用 get_stock_price 這個(gè)函數(shù)參數(shù)是 600519”你的代碼接住這個(gè)指令后真正執(zhí)行函數(shù)、拿到結(jié)果、再回傳給模型。實(shí)現(xiàn)這一步時(shí)你寫(xiě)的不只是調(diào)用大模型接口的代碼而是一個(gè)完整的人機(jī)協(xié)作協(xié)議。做這塊我最大的坑是定義函數(shù)時(shí)的描述太馬虎。function calling 能不能準(zhǔn)確觸發(fā)不是看函數(shù)名而是看描述文本寫(xiě)得好不好。比如你定義了一個(gè)查詢天氣的函數(shù)描述寫(xiě)“查詢天氣”模型就經(jīng)常不知道該在什么時(shí)候調(diào)用它。寫(xiě)成“查詢某城市當(dāng)日的實(shí)時(shí)天氣情況包括溫度、濕度、降水概率參數(shù)city為城市中文名”模型觸發(fā)準(zhǔn)確率會(huì)高非常多。這個(gè)現(xiàn)象叫提示詞敏感本質(zhì)上是模型通過(guò)上下文語(yǔ)義匹配來(lái)理解函數(shù)用途。function calling 還有一個(gè)容易踩的坑是返回?cái)?shù)據(jù)過(guò)大。函數(shù)返回了一個(gè)很大的 JSON如果直接拼進(jìn)上下文下一輪調(diào)用的 token 消耗會(huì)暴漲。我的做法是返回?cái)?shù)據(jù)先做字段裁剪只保留模型做決策真正需要的字段或者先用代碼把結(jié)構(gòu)化數(shù)據(jù)壓縮成一段自然語(yǔ)言摘要再回傳模型。這一步不做好模型很容易被大量無(wú)關(guān)字段干擾反而抓不住重點(diǎn)。3.3 編排鏈路的穩(wěn)定性設(shè)計(jì)多 Agent 編排鏈路一長(zhǎng)任何一個(gè)環(huán)節(jié)失敗都會(huì)拖垮整個(gè)流程。模型接口偶爾超時(shí)、偶爾返回空內(nèi)容、偶爾輸出格式錯(cuò)亂這些在單次調(diào)用場(chǎng)景里只是小概率事件放到編排鏈路里就會(huì)變成大概率事件。你有一個(gè)五個(gè)環(huán)節(jié)的流水線每個(gè)環(huán)節(jié) 95% 的概率成功整條鏈路成功率就是 77%四舍五入大概每五次就有一次失敗。應(yīng)對(duì)策略無(wú)非三件套第一步每個(gè)環(huán)節(jié)都加超時(shí)控制和重試邏輯重試次數(shù)根據(jù)接口錯(cuò)誤碼區(qū)分——限流類的錯(cuò)誤值得重試鑒權(quán)類的錯(cuò)誤重試也沒(méi)用。第二步在關(guān)鍵節(jié)點(diǎn)做降級(jí)方案比如某個(gè) Agent 多次失敗就降級(jí)到單次模型直出哪怕效果差一點(diǎn)也比流程失敗強(qiáng)。第三步全鏈路盡量保留過(guò)程日志把每個(gè) Agent 的輸入輸出都記錄下來(lái)出現(xiàn)質(zhì)量問(wèn)題時(shí)能回溯到具體環(huán)節(jié)。工具選型上這類編排鏈路在技術(shù)上可以自己硬寫(xiě)也可以用編排框架。我自己比較過(guò)框架減少重復(fù)勞動(dòng)但是引入了學(xué)習(xí)成本和框架本身的抽象限制。小團(tuán)隊(duì)的話先用代碼直接寫(xiě)編排邏輯更可控。等到流程穩(wěn)定以后再考慮抽象和沉淀是比較務(wù)實(shí)的路線。4. 工程化落地效率和成本的持久戰(zhàn)4.1 token 成本的可觀測(cè)性建設(shè)做的 AI 應(yīng)用只要有點(diǎn)真實(shí)流量成本問(wèn)題就會(huì)浮出水面。我見(jiàn)過(guò)不止一個(gè)團(tuán)隊(duì)上線第一個(gè)月收到云賬單時(shí)人都傻了——調(diào)用量看起來(lái)不大費(fèi)用卻高得離譜。原因就一個(gè)之前根本沒(méi)做過(guò) token 成本的可觀測(cè)性建設(shè)每個(gè)功能模塊用了多少 token、花多少錢(qián)完全是黑盒。我自己的做法是在調(diào)用大模型接口的統(tǒng)一封裝層里強(qiáng)制記錄三個(gè)核心指標(biāo)每次請(qǐng)求的 prompt_tokens、completion_tokens、總耗時(shí)并打上業(yè)務(wù)標(biāo)簽。業(yè)務(wù)標(biāo)簽包括功能模塊、用戶等級(jí)、模型版本。這些數(shù)據(jù)匯總起來(lái)就能回答幾個(gè)關(guān)鍵問(wèn)題哪個(gè)功能最燒錢(qián)哪個(gè)用戶消耗最高上下文膨脹最嚴(yán)重的是哪個(gè)入口做這個(gè)過(guò)程不需要什么高大上的組件核心邏輯就是先記錄再聚合最后分析。一個(gè)項(xiàng)目如果從上線第一天就開(kāi)始記錄 token 消耗后面優(yōu)化成本時(shí)就會(huì)輕松很多。我在一次優(yōu)化中把對(duì)話歷史改成滑動(dòng)窗口和摘要機(jī)制后當(dāng)月 token 消耗降了 47%靠的就是幾行日志數(shù)據(jù)支撐的決策而不是拍腦袋。成本優(yōu)化如果沒(méi)有數(shù)據(jù)支持很容易做出一個(gè)傷害體驗(yàn)的愚蠢決定。4.2 延遲優(yōu)化感知速度和真實(shí)速度都很重要大模型生成文本是流式輸出的一句話可能要一兩秒才能完全結(jié)束。這在對(duì)話場(chǎng)景里尚可接受但放到 API 調(diào)用鏈里就非常致命。如果上游系統(tǒng)同步等待你的 AI 接口返回完整結(jié)果一次請(qǐng)求耗時(shí)就等于生成器耗時(shí)動(dòng)輒三五秒起步。優(yōu)化延遲有幾個(gè)方向。一是模型本身就是關(guān)鍵輕量模型輸出速度可能比最強(qiáng)模型快好幾倍二是控制輸出長(zhǎng)度max_tokens 不要給得太大方能短則短很多模型輸出到后半段速度也會(huì)變慢三是用流式輸出 SSE 把結(jié)果邊生成邊推給前端用戶能感知到的等待時(shí)間大幅下降四是做緩存相同的用戶問(wèn)題命中緩存直接返回命中率高的系統(tǒng)平均延遲可以大幅下降。我之前給一個(gè)內(nèi)部工具加了一層緩存普通問(wèn)題大概有 35% 的命中率平均延遲從 2.8 秒降到了 1.2 秒成本也一并降了。緩存設(shè)計(jì)的關(guān)鍵在于 key 的生成策略直接拿用戶輸入做 key 命中率很低因?yàn)橥粋€(gè)意思的表達(dá)方式太多了規(guī)范化后的輸入做 key配合一定的時(shí)效窗口是比較實(shí)用的方案。延遲優(yōu)化的另一個(gè)思路是“預(yù)先生成”。有些場(chǎng)景比如早報(bào)總結(jié)、每日推薦內(nèi)容是可以離線提前生成的。把這些任務(wù)改成定時(shí)任務(wù)提前把結(jié)果生成好存起來(lái)用戶訪問(wèn)時(shí)直接讀緩存響應(yīng)速度是毫秒級(jí)的成本也更可控。這類優(yōu)化思路來(lái)自傳統(tǒng)后端的空間換時(shí)間思想在 AI 應(yīng)用里依然適用。4.3 可觀測(cè)性與評(píng)測(cè)回歸體系傳統(tǒng)軟件開(kāi)發(fā)強(qiáng)調(diào)測(cè)試覆蓋率高AI 應(yīng)用開(kāi)發(fā)很難套用這套理論因?yàn)檩敵霾还潭āD悴荒軘嘌浴坝脩粽f(shuō)你好模型就必須回答你也好”哪怕十次里面有九次是這么回答的你也沒(méi)法用單測(cè)把這個(gè)行為釘死。所以 AI 項(xiàng)目要構(gòu)建的是評(píng)測(cè)集加評(píng)測(cè)流程。具體做法是準(zhǔn)備一個(gè)覆蓋典型場(chǎng)景的評(píng)測(cè)問(wèn)題集每條問(wèn)題標(biāo)注期望的行為描述或標(biāo)準(zhǔn)答案。每次改 prompt、換模型、調(diào)整參數(shù)時(shí)都跑一遍評(píng)測(cè)集把結(jié)果記錄下來(lái)人工或半自動(dòng)地評(píng)估質(zhì)量變化。這個(gè)流程說(shuō)起來(lái)不復(fù)雜但很多團(tuán)隊(duì)根本不做全靠感覺(jué)走。結(jié)果就是一個(gè)成熟的 prompt 改了十幾次之后沒(méi)人知道哪一版是最好的也沒(méi)人能說(shuō)清為什么改著改著效果變差了。我還發(fā)現(xiàn)除了模型輸出質(zhì)量可觀測(cè)性還應(yīng)該覆蓋另一層——用戶反饋。上線以后收集真實(shí)的用戶滿意度信號(hào)比如點(diǎn)贊點(diǎn)踩按鈕、復(fù)制結(jié)果按鈕、二次追問(wèn)行為。這些信號(hào)比離線評(píng)測(cè)集更真實(shí)。我給知識(shí)庫(kù)問(wèn)答系統(tǒng)加了一個(gè)簡(jiǎn)單的“回答是否有幫助”的反饋點(diǎn)拿回來(lái)之后發(fā)現(xiàn)離線評(píng)測(cè)完全預(yù)測(cè)不到的坑。4.4 接口冪等性與 AI 應(yīng)用的特殊要求“接口冪等性”這個(gè)詞在傳統(tǒng)后端開(kāi)發(fā)里是基本要求但在 AI 應(yīng)用里會(huì)被很多人忽略。AI 接口天然是非冪等的——同樣的請(qǐng)求模型可能返回不同的內(nèi)容而且它往往比較貴重復(fù)調(diào)用一筆就是一筆錢(qián)。更麻煩的是大模型接口的延遲方差很大前端超時(shí)了重試一次結(jié)果上一次請(qǐng)求其實(shí)也成功了用戶看到兩遍輸出系統(tǒng)里還扣了兩次錢(qián)。我在設(shè)計(jì) AI 應(yīng)用接口時(shí)對(duì)每個(gè)寫(xiě)入類操作都要求客戶端傳一個(gè)請(qǐng)求 ID服務(wù)端用這個(gè) ID 做冪等控制。如果同一個(gè)請(qǐng)求 ID 重復(fù)到達(dá)直接返回第一次的結(jié)果。這個(gè)方法在傳統(tǒng)后端是常規(guī)操作但在 AI 項(xiàng)目里經(jīng)常被忽略尤其是那些直接從 demo 改上線的項(xiàng)目。等你自己被重復(fù)扣費(fèi)和重復(fù)生成搞崩潰之后才知道這步有多重要。另外因?yàn)榇竽P徒涌诒旧碛胁淮_定性傳統(tǒng)接口那種“重試一次就好”的寫(xiě)法也要改。如果業(yè)務(wù)上允許我會(huì)給重試增加隨機(jī)擾動(dòng)換一個(gè)采樣溫度或者換一個(gè) prompt 版本避免連續(xù)兩次失敗。這是 AI 應(yīng)用接口設(shè)計(jì)跟傳統(tǒng)接口很不一樣的地方核心是處理好不確定性的傳播。5. 常見(jiàn)問(wèn)題排查與避坑實(shí)錄5.1 接口調(diào)用階段的典型問(wèn)題速查這個(gè)階段的問(wèn)題大多數(shù)發(fā)生在剛接接口的時(shí)候特征非常明顯排查起來(lái)也有章可循。第一個(gè)高頻問(wèn)題返回的 JSON 解析失敗。用戶讓模型輸出 JSON結(jié)果模型偶爾會(huì)在 JSON 外側(cè)包一層 Markdown 代碼塊標(biāo)記代碼直接解析失敗。我當(dāng)時(shí)的處理分三步首先在 prompt 里明確要求只輸出純 JSON 且不要使用 Markdown 標(biāo)記其次在代碼層做容錯(cuò)遇到包裹標(biāo)記就把外殼剝掉最后接一個(gè) JSON 修復(fù)工具比如解析失敗時(shí)用正則提取最像 JSON 的那段。這三層下來(lái)解析失敗率能壓到很低。第二個(gè)高頻問(wèn)題模型回答“編造事實(shí)”。大模型不知道的事會(huì)一本正經(jīng)地胡說(shuō)這在知識(shí)問(wèn)答場(chǎng)景尤其要命。解決思路不是指望模型不胡說(shuō)而是從架構(gòu)上做限制。給模型提供檢索到的參考材料并明確指示“只能基于給定的材料回答材料中沒(méi)有的內(nèi)容要直接說(shuō)明不知道”同時(shí)把 temperature 壓到低值。有了這一步幻覺(jué)問(wèn)題的發(fā)生率會(huì)明顯下降。第三個(gè)高頻問(wèn)題長(zhǎng)文本截?cái)唷ax_tokens 設(shè)置小了長(zhǎng)答案會(huì)被截?cái)鄟G一半內(nèi)容。排查方法是看返回結(jié)果的 finish_reason 字段如果是 length說(shuō)明命中了長(zhǎng)度限制如果是 stop說(shuō)明完整結(jié)束。這個(gè)字段是排查截?cái)鄦?wèn)題的第一線索很多人忽略了它。5.2 上下文膨脹與性能劣化排查上下文膨脹是 AI 應(yīng)用上線一段時(shí)間后最常見(jiàn)的問(wèn)題。表現(xiàn)是對(duì)話進(jìn)行到十幾輪的時(shí)候響應(yīng)變得很慢費(fèi)用明顯升高而且模型回答質(zhì)量下降。排查思路比較直接記錄每一輪請(qǐng)求的 token 統(tǒng)計(jì)對(duì)比早期和晚期的消耗差異基本就能確認(rèn)膨脹是否發(fā)生。解決辦法是按場(chǎng)景分兩類處理。對(duì)話場(chǎng)景用滑動(dòng)窗口加歷史摘要前面說(shuō)過(guò)效果好成本低。知識(shí)場(chǎng)景用向量檢索替代全文拼接把用戶問(wèn)題向量化在知識(shí)庫(kù)里召回最相關(guān)的片段代替把整個(gè)知識(shí)庫(kù)塞進(jìn)上下文的做法。這個(gè)思路可以類比搜索引擎的 “查詢-召回-排序” 流程而不是把整個(gè)數(shù)據(jù)庫(kù)發(fā)給模型讓它自己找。上下文優(yōu)化做得好最直接的收益是單次調(diào)用成本下降和響應(yīng)速度提升。我經(jīng)常拿這個(gè)數(shù)據(jù)說(shuō)服團(tuán)隊(duì)重視上下文設(shè)計(jì)因?yàn)榇蠹叶紝?duì)成本敏感數(shù)字?jǐn)[出來(lái)比空談架構(gòu)有用得多。5.3 Prompt 迭代失控的救火經(jīng)驗(yàn)Prompt 改崩這件事每個(gè)做 AI 應(yīng)用的人都經(jīng)歷過(guò)。改一個(gè)詞預(yù)期提高 A 場(chǎng)景效果結(jié)果 B 場(chǎng)景輸出全亂。最怕的是你沒(méi)有記錄習(xí)慣改完就忘幾天后發(fā)現(xiàn)問(wèn)題卻不知道是哪次改動(dòng)引入的。我自己后來(lái)規(guī)定團(tuán)隊(duì)里的 prompt 必須進(jìn)版本管理每條 prompt 變更都要寫(xiě)清楚改動(dòng)目的和預(yù)期影響。每次變更同步跑評(píng)測(cè)集把結(jié)果對(duì)比貼進(jìn)需求單里。沒(méi)有評(píng)測(cè)集的情況下至少要把變更前和變更后的典型輸出保存成樣本人工對(duì)比確認(rèn)不會(huì)劣化。另一個(gè)經(jīng)驗(yàn)是 prompt 要盡量“結(jié)構(gòu)化”而不是寫(xiě)一大段自然語(yǔ)言規(guī)則。把角色定義、任務(wù)目標(biāo)、輸出要求、示例、約束條件分段組織關(guān)鍵規(guī)則用編號(hào)列出。結(jié)構(gòu)化 prompt 的好處是當(dāng)某個(gè)環(huán)節(jié)出問(wèn)題時(shí)你能精準(zhǔn)定位是哪一行規(guī)則的責(zé)任而不是在一大段話里漫無(wú)目的地找。實(shí)測(cè)下來(lái)結(jié)構(gòu)化 prompt 的穩(wěn)定性和可維護(hù)性都遠(yuǎn)高于自由文本式的 prompt。5.4 資源受限場(chǎng)景下的降級(jí)設(shè)計(jì)AI 應(yīng)用一定會(huì)遇到資源受限的時(shí)候模型服務(wù)限流、賬戶余額不足、第三方接口故障。這些情況發(fā)生的時(shí)候好的應(yīng)用不會(huì)直接報(bào)錯(cuò)而是會(huì)降級(jí)。我給客服問(wèn)答系統(tǒng)設(shè)計(jì)了三級(jí)降級(jí)方案最優(yōu)方案走大模型加知識(shí)庫(kù)檢索這是完整鏈路當(dāng)模型接口不穩(wěn)定或成本受限時(shí)降級(jí)為純檢索匹配直接返回知識(shí)庫(kù)中最相似的 FAQ 條目雖然沒(méi)有生成感但能解決大部分常見(jiàn)問(wèn)題最后一層是人工客服表單入口把解決不了的問(wèn)題轉(zhuǎn)交人工。這套設(shè)計(jì)上線后第三方接口有幾次故障期間系統(tǒng)沒(méi)有出現(xiàn)過(guò)一次白屏報(bào)錯(cuò)。降級(jí)設(shè)計(jì)的核心原則是在資源受限時(shí)優(yōu)先保證“能用”再追求“好用”。這個(gè)原則在傳統(tǒng)后端是常識(shí)在 AI 項(xiàng)目里被很多人忽略。因?yàn)?demo 階段永遠(yuǎn)不會(huì)有限流和欠費(fèi)只有真實(shí)流量才會(huì)逼你面對(duì)。6. 寫(xiě)在最后的個(gè)人體會(huì)做了兩年多 AI 應(yīng)用開(kāi)發(fā)最大的體會(huì)是這個(gè)領(lǐng)域確實(shí)沒(méi)有傳統(tǒng)后端開(kāi)發(fā)那么成熟很多東西沒(méi)有標(biāo)準(zhǔn)答案踩坑幾乎就是學(xué)習(xí)的一部分。但正因?yàn)樗怀墒觳沤o了開(kāi)發(fā)者很大的空間去探索和創(chuàng)造。如果讓我給剛開(kāi)始做 AI 應(yīng)用的同學(xué)一個(gè)建議我會(huì)說(shuō)先別急著學(xué)那些復(fù)雜的框架和炫酷的 Agent 概念沉下心來(lái)把一次接口調(diào)用做到極致把上下文管理、成本控制、結(jié)構(gòu)化輸出、評(píng)測(cè)回歸這些基本功練扎實(shí)。這些基本功在名校課程和官方文檔里不會(huì)系統(tǒng)地教但它們才是支撐一個(gè) AI 應(yīng)用真正走向生產(chǎn)環(huán)境的基石。還有個(gè)小技巧是我后來(lái)總結(jié)出來(lái)的每次上線新功能前自己設(shè)一個(gè)“10 分鐘手動(dòng)回歸”流程把核心場(chǎng)景都點(diǎn)一遍同時(shí)觀察 token 消耗和失敗率。別小看這個(gè)笨辦法它幫我抓到了好多評(píng)測(cè)集跑不出來(lái)、用戶卻一定會(huì)踩到的隱形問(wèn)題。AI 應(yīng)用開(kāi)發(fā)這行走得快很重要但更重要的是每一步都得踩實(shí)。就分享到這兒。如果你也在做 AI 應(yīng)用開(kāi)發(fā)希望這些踩坑經(jīng)驗(yàn)?zāi)軒湍闵僮咭恍澛贰?