品架構(gòu)拆解:從AI Agent到消息推送與積分體系的完整設(shè)計(jì))
引言為什么我要把 AIFriends 的架構(gòu)和流程單獨(dú)寫成一份復(fù)習(xí)文檔如果你最近在準(zhǔn)備 AI 應(yīng)用方向的面試或者正在從零搭一個(gè)帶社交屬性的 AI 產(chǎn)品那 AIFriends 這個(gè)項(xiàng)目應(yīng)該是個(gè)不錯(cuò)的參考樣本。它不算特別復(fù)雜但麻雀雖小五臟俱全——涵蓋了用戶體系、AI Agent 交互、消息推送、積分經(jīng)濟(jì)系統(tǒng)、管理后臺(tái)這些典型模塊而且業(yè)務(wù)閉環(huán)是完整的。說實(shí)話我最初接到這個(gè)項(xiàng)目的技術(shù)梳理任務(wù)時(shí)第一反應(yīng)是“這不就是個(gè)套殼聊天應(yīng)用嗎”。但真正把架構(gòu)圖和業(yè)務(wù)流程逐層拆開之后我才發(fā)現(xiàn)里面的設(shè)計(jì)取舍比預(yù)想中多得多。比如 AI 人格的會(huì)話上下文怎么管理、異步任務(wù)隊(duì)列怎么設(shè)計(jì)、積分扣費(fèi)怎么避免并發(fā)超扣、管理后臺(tái)的審核流怎么和用戶側(cè)狀態(tài)機(jī)聯(lián)動(dòng)——這些都是實(shí)際業(yè)務(wù)里繞不開的細(xì)節(jié)也是面試官最喜歡深挖的點(diǎn)。這份文檔的定位是“復(fù)習(xí)用的”所以我會(huì)刻意把每個(gè)模塊的架構(gòu)決策、流程節(jié)點(diǎn)、數(shù)據(jù)流轉(zhuǎn)邏輯都講透而不是只羅列一堆技術(shù)名詞。不管你是想照著這個(gè)思路做自己的 AI 社交產(chǎn)品還是單純想理解 agent 類項(xiàng)目的通用架構(gòu)模式這篇內(nèi)容應(yīng)該都能幫你省下不少瞎琢磨的時(shí)間。1. 項(xiàng)目整體設(shè)計(jì)與架構(gòu)思路拆解1.1 項(xiàng)目定位與核心需求解析AIFriends 本質(zhì)上是一個(gè)“AI 虛擬好友社交平臺(tái)”。用戶注冊(cè)之后可以創(chuàng)建或者選擇不同的 AI 好友角色和這些角色進(jìn)行多輪對(duì)話。這些 AI 好友不是簡(jiǎn)單的問答機(jī)器人而是有固定人設(shè)、記憶能力、情感反饋的 agent 形態(tài)產(chǎn)品。從業(yè)務(wù)需求倒推技術(shù)需求核心要解決四件事多角色 AI 人格的管理每個(gè) AI 好友擁有獨(dú)立的 prompt 模板、知識(shí)庫、語氣風(fēng)格甚至記憶片段對(duì)話過程的穩(wěn)定性多輪對(duì)話不能丟上下文消息要能實(shí)時(shí)送達(dá)網(wǎng)絡(luò)抖動(dòng)不能直接吞消息商業(yè)化閉環(huán)免費(fèi)用戶每天有對(duì)話次數(shù)限制付費(fèi)用戶解鎖無限暢聊需要一套積分/會(huì)員系統(tǒng)內(nèi)容安全AI 生成內(nèi)容不能失控需要前置審核和后置舉報(bào)機(jī)制這個(gè)定位意味著架構(gòu)不能只考慮“能跑”還要考慮“能管”。很多 AI 應(yīng)用死在兩個(gè)地方一個(gè)是上下文管理亂導(dǎo)致體驗(yàn)崩塌另一個(gè)是商業(yè)化路徑不清晰導(dǎo)致產(chǎn)品沒法續(xù)命。AIFriends 的設(shè)計(jì)從一開始就把這兩條線拉了進(jìn)來。1.2 整體架構(gòu)分層與模塊劃分整個(gè)系統(tǒng)采用前后端分離 微服務(wù)化的架構(gòu)風(fēng)格但并沒有一上來就拆十幾二十個(gè)服務(wù)——那對(duì)小團(tuán)隊(duì)和快速迭代來說反而是災(zāi)難。它按照業(yè)務(wù)域拆成了六個(gè)核心服務(wù)服務(wù)模塊職責(zé)范圍關(guān)鍵技術(shù)點(diǎn)用戶服務(wù)注冊(cè)、登錄、個(gè)人資料、好友關(guān)系JWT 鑒權(quán)、Redis 會(huì)話緩存AI Agent 服務(wù)角色人格管理、對(duì)話生成、上下文管理LLM 接入層、Prompt 模板引擎、向量記憶庫消息服務(wù)實(shí)時(shí)消息收發(fā)、消息持久化、已讀回執(zhí)WebSocket 長(zhǎng)連接、消息隊(duì)列削峰訂單/積分服務(wù)積分充值、對(duì)話扣費(fèi)、會(huì)員套餐事務(wù)性扣費(fèi)、冪等性保障審核服務(wù)用戶輸入側(cè)敏感詞、AI 輸出側(cè)合規(guī)過濾異步審核隊(duì)列、人工審核工作臺(tái)管理后臺(tái)服務(wù)角色管理、用戶管理、數(shù)據(jù)看板、審核處理RBAC 權(quán)限模型、操作日志審計(jì)每個(gè)服務(wù)獨(dú)立部署、獨(dú)立數(shù)據(jù)庫用 HTTP/REST 作為服務(wù)間同步調(diào)用的主要協(xié)議異步場(chǎng)景走消息隊(duì)列。沒有引入太重的 Service Mesh也沒有強(qiáng)行上分布式事務(wù)而是盡量把跨服務(wù)的數(shù)據(jù)一致性控制在“最終一致”的范圍內(nèi)。提示這個(gè)架構(gòu)的聰明之處在于邊界劃分基本沿著“業(yè)務(wù)對(duì)象”走而不是沿著“技術(shù)層次”走——按用戶、對(duì)話、消息、錢、內(nèi)容安全來分每一個(gè)服務(wù)都能獨(dú)立演進(jìn)這是新手做架構(gòu)時(shí)最容易忽略的一點(diǎn)。1.3 為什么選這個(gè)架構(gòu)方案而不是單體應(yīng)用有人可能會(huì)問一個(gè) AI 聊天產(chǎn)品單體應(yīng)用不香嗎前期開發(fā)效率更高部署也更簡(jiǎn)單。這個(gè)問題的答案在于“業(yè)務(wù)預(yù)期的變化方向”。AI 對(duì)話類產(chǎn)品有三個(gè)天然特點(diǎn)第一LLM 調(diào)用的延遲和成本是波動(dòng)的需要獨(dú)立的服務(wù)來做限流、降級(jí)、重試策略第二對(duì)話上下文可能包含大量的個(gè)性化記憶數(shù)據(jù)存儲(chǔ)層需要能夠靈活擴(kuò)容第三消息實(shí)時(shí)通道和業(yè)務(wù) API 的負(fù)載特征完全不同——WebSocket 連接是長(zhǎng)駐型的而業(yè)務(wù) API 是突發(fā)型的放在同一個(gè)進(jìn)程里互相拖累就是必然的。所以在 AIFriends 的架構(gòu)里消息服務(wù)被單獨(dú)拆了出來這是一個(gè)非常正確的決策。長(zhǎng)連接服務(wù)不會(huì)被業(yè)務(wù)接口的突發(fā)流量打垮業(yè)務(wù)服務(wù)也不會(huì)因?yàn)橄V播的邏輯 bug 而全部宕機(jī)。你要做 AI 應(yīng)用消息通道和業(yè)務(wù)邏輯分離這條底線最好從一開始就守住。2. 核心業(yè)務(wù)流程拆解與設(shè)計(jì)邏輯2.1 用戶從注冊(cè)到首次對(duì)話的完整鏈路一個(gè)用戶從進(jìn)入產(chǎn)品到產(chǎn)生第一段對(duì)話背后走的是這樣一條鏈路用戶通過手機(jī)號(hào)或第三方賬號(hào)注冊(cè)用戶服務(wù)創(chuàng)建賬號(hào)并返回 JWT Token前端攜帶 Token 調(diào)用 AI Agent 服務(wù)拉取可選擇的 AI 好友列表用戶選擇某個(gè) AI 好友點(diǎn)擊“開始聊天”前端發(fā)起 WebSocket 連接消息服務(wù)完成連接鑒權(quán)和會(huì)話綁定用戶發(fā)送第一條消息消息服務(wù)先落庫再轉(zhuǎn)發(fā)給 AI Agent 服務(wù)AI Agent 服務(wù)加載該好友的人格 prompt、歷史記憶、當(dāng)前會(huì)話上下文調(diào)用 LLM 生成回復(fù)回復(fù)內(nèi)容先送審核服務(wù)做合規(guī)檢查通過后由消息服務(wù)推送給用戶這個(gè)流程看著不復(fù)雜但有兩個(gè)細(xì)節(jié)值得展開。第一個(gè)細(xì)節(jié)是“先落庫再轉(zhuǎn)發(fā)”。很多初版實(shí)現(xiàn)圖省事直接走內(nèi)存轉(zhuǎn)發(fā)消息一旦服務(wù)重啟就丟。AIFriends 的做法是用戶消息到達(dá)消息服務(wù)之后立刻寫數(shù)據(jù)庫狀態(tài)標(biāo)記為“已發(fā)送”等 AI 回復(fù)生成后再把這一輪會(huì)話的狀態(tài)更新為“已完成”。這樣即使中間任何一環(huán)掛了消息也不會(huì)丟用戶刷新頁面還能看到歷史記錄。第二個(gè)細(xì)節(jié)是 AI 回復(fù)的審核時(shí)機(jī)。這里是在“生成后、推送前”審核不是生成前攔截。原因很簡(jiǎn)單生成前的 prompt 審核只能攔住輸入側(cè)的問題但 LLM 的輸出不可完全預(yù)測(cè)所以輸出側(cè)必須有一道獨(dú)立檢查。雖然這樣會(huì)增加用戶等待時(shí)間但安全合規(guī)這條線不能省。2.2 AI 對(duì)話流程中的上下文管理與記憶機(jī)制AI 好友和普通聊天機(jī)器人的最大區(qū)別在于“記憶”。AIFriends 的 agent 設(shè)計(jì)里記憶分為三個(gè)層次短期會(huì)話記憶存儲(chǔ)在當(dāng)前 session 內(nèi)記錄最近的對(duì)話輪次保存在 Redis過期時(shí)間 30 分鐘長(zhǎng)期用戶記憶記錄用戶的基本喜好、性格標(biāo)簽、說過的重要信息寫入向量數(shù)據(jù)庫角色設(shè)定記憶AI 好友自身的人設(shè)背景、說話風(fēng)格、知識(shí)邊界作為系統(tǒng)級(jí) prompt每次用戶發(fā)消息時(shí)AI Agent 服務(wù)會(huì)執(zhí)行一個(gè)“記憶組裝”流程從 Redis 取出短期會(huì)話記錄從向量庫檢索與當(dāng)前話題相關(guān)的長(zhǎng)期記憶片段然后把角色設(shè)定、短期上下文、相關(guān)記憶拼裝成最終的 prompt 發(fā)送給 LLM。這個(gè)設(shè)計(jì)解決了一個(gè)核心矛盾LLM 的上下文窗口是有限的你不能把用戶全部歷史對(duì)話都塞進(jìn)去必須做有選擇性的提取。向量檢索在這里起到的作用就是“只挑和當(dāng)前話題有關(guān)的記憶”既控制 token 數(shù)量又讓回復(fù)看起來是“記得你”的。注意記憶檢索是 AI 社交產(chǎn)品體驗(yàn)的分水嶺。很多團(tuán)隊(duì)前期為了省事只往 prompt 里塞最近 N 輪對(duì)話結(jié)果 AI 聊了三天就把用戶的生日、喜歡的音樂類型全忘了用戶立刻就會(huì)覺得“這是個(gè)假 AI”。記憶機(jī)制不是錦上添花是產(chǎn)品能不能留住用戶的關(guān)鍵。2.3 積分扣費(fèi)與會(huì)員體系的流程設(shè)計(jì)商業(yè)模式部分AIFriends 走的是“免費(fèi)次數(shù) 積分充值 會(huì)員訂閱”三者結(jié)合的路子。積分扣費(fèi)流程是這里面最容易出并發(fā)問題的地方。每次用戶發(fā)送一條消息會(huì)先經(jīng)過積分服務(wù)的預(yù)扣費(fèi)操作。這個(gè)預(yù)扣費(fèi)不是直接扣余額而是先凍結(jié)對(duì)應(yīng)積分等 AI 回復(fù)成功推送給用戶后再轉(zhuǎn)正式扣費(fèi)如果 AI 生成失敗則解凍退回。這樣做的好處是避免“用戶發(fā)了消息但 AI 沒回復(fù)錢卻已經(jīng)扣了”的客訴。在技術(shù)實(shí)現(xiàn)上扣費(fèi)用的是 Redis 的 Lua 腳本做原子操作先檢查余額再扣減保證并發(fā)場(chǎng)景下不會(huì)超扣。數(shù)據(jù)庫層面記錄每一筆積分流水方便后續(xù)對(duì)賬和客服查詢。會(huì)員體系則簡(jiǎn)單一些會(huì)員用戶在有效期內(nèi)不限制對(duì)話次數(shù)但會(huì)有每日最大消息數(shù)的風(fēng)控上限防止接口被腳本刷爆。會(huì)員狀態(tài)存在用戶服務(wù)的緩存里AI Agent 服務(wù)每次收到對(duì)話請(qǐng)求時(shí)會(huì)校驗(yàn)會(huì)員資格。這個(gè)積分流程設(shè)計(jì)里最值得學(xué)習(xí)的一點(diǎn)是“預(yù)凍結(jié)”的思路。真實(shí)業(yè)務(wù)里凡是涉及“扣錢 異步結(jié)果”的場(chǎng)景都應(yīng)該考慮這個(gè)模式。直接先扣款再退款也不是不行但用戶體驗(yàn)和客服壓力完全不是一個(gè)量級(jí)。3. 關(guān)鍵模塊的技術(shù)實(shí)現(xiàn)與實(shí)操要點(diǎn)3.1 AI Agent 服務(wù)中 Prompt 模板的角色管理實(shí)現(xiàn)AI 好友的人格差異完全靠 Prompt 工程來實(shí)現(xiàn)。AIFriends 里每個(gè) AI 角色對(duì)應(yīng)一個(gè) Prompt 模板模板不是死字符串而是支持變量的結(jié)構(gòu)化配置。舉一個(gè)實(shí)際的模板片段你是{character_name}年齡{age}性格{personality}。 你正在和用戶進(jìn)行一場(chǎng){relationship_type}的對(duì)話。 【背景記憶】 {memory_snippets} 【近期對(duì)話】 {chat_history} 【用戶畫像】 {user_profile} 請(qǐng)用{style_guide}的風(fēng)格回復(fù)回復(fù)長(zhǎng)度控制在{max_tokens}字以內(nèi)。這里的變量分別從角色配置表、記憶檢索結(jié)果、會(huì)話上下文、用戶畫像服務(wù)中動(dòng)態(tài)填充。模板本身存放在數(shù)據(jù)庫里而不是硬編碼在代碼里這樣運(yùn)營(yíng)人員可以在管理后臺(tái)直接調(diào)整某個(gè) AI 角色的人設(shè)不需要重新發(fā)版。技術(shù)實(shí)現(xiàn)上有一個(gè)容易踩坑的點(diǎn)模板變量的注入順序會(huì)影響 LLM 的輸出質(zhì)量。AIFriends 的實(shí)測(cè)經(jīng)驗(yàn)是“角色設(shè)定放最前面用戶畫像放最后”效果最好因?yàn)?LLM 對(duì) prompt 開頭和結(jié)尾的信息注意力更強(qiáng)把最核心的人設(shè)約束放在開頭把對(duì)當(dāng)前回復(fù)影響最大的用戶信息放在結(jié)尾回復(fù)質(zhì)量的穩(wěn)定性會(huì)有明顯提升。3.2 消息服務(wù)的推送機(jī)制與接口設(shè)計(jì)消息服務(wù)需要同時(shí)支持 WebSocket 長(zhǎng)連接和 HTTP 回調(diào)兩種消息下發(fā)方式。WebSocket 用于實(shí)時(shí)推送HTTP 回調(diào)主要用于第三方渠道或者前端斷線重連后的消息補(bǔ)償拉取。實(shí)際的接口設(shè)計(jì)大概是這樣的// 發(fā)送消息請(qǐng)求 POST /api/v1/chat/message { session_id: uuid, session_type: ai_friend, content: 今天心情不太好, message_type: text } // 響應(yīng) { message_id: uuid, status: pending, estimated_reply_time_ms: 3500 }這里用 message_id 做全鏈路的追蹤標(biāo)識(shí)。消息從客戶端發(fā)出到 AI 回復(fù)返回中間經(jīng)過消息服務(wù)、Agent 服務(wù)、審核服務(wù)所有的狀態(tài)變更都通過這個(gè) message_id 關(guān)聯(lián)。前端可以輪詢或者通過 WebSocket 推送收到狀態(tài)更新當(dāng)狀態(tài)變?yōu)椤癱ompleted”時(shí)渲染 AI 回復(fù)。關(guān)于 WebSocket 連接管理AIFriends 用了 Redis Pub/Sub 做多實(shí)例消息廣播。單臺(tái)實(shí)例只維護(hù)自己節(jié)點(diǎn)上的客戶端連接但服務(wù)端要推送某條消息時(shí)通過 Redis 頻道廣播所有實(shí)例收到后只推送給本地持有對(duì)應(yīng) session 的連接。這個(gè)方案比自研一套消息路由協(xié)議簡(jiǎn)單得多實(shí)測(cè)在幾千并發(fā)連接下完全夠用。3.3 審核服務(wù)的異步處理鏈路審核服務(wù)在 AIFriends 里是獨(dú)立部署的沒有嵌在 Agent 服務(wù)的同步調(diào)用鏈里。設(shè)計(jì)成異步的原因很簡(jiǎn)單LLM 回復(fù)已經(jīng)要花 2-5 秒了如果審核再同步加 500 毫秒用戶體感會(huì)明顯變差。異步審核的流程是AI Agent 服務(wù)生成回復(fù)后把回復(fù)內(nèi)容投遞到審核消息隊(duì)列審核服務(wù)消費(fèi)隊(duì)列先做機(jī)器敏感詞過濾再調(diào)用內(nèi)容安全 API 做語義級(jí)檢測(cè)如果自動(dòng)審核通過直接把消息推送給用戶如果疑似違規(guī)進(jìn)入人工審核隊(duì)列人工審核完成后審核結(jié)果通過回調(diào)接口告知消息服務(wù)決定放行還是攔截這個(gè)鏈路里有個(gè)取舍問題用戶體驗(yàn)和合規(guī)風(fēng)險(xiǎn)怎么平衡。AIFriends 的做法是“低風(fēng)險(xiǎn)秒放行高風(fēng)險(xiǎn)進(jìn)人工”。機(jī)器審核認(rèn)為沒有問題的消息直接推送概率極低的高危內(nèi)容即使要等人工審核也絕對(duì)不能放出來。還有一個(gè)細(xì)節(jié)是審核服務(wù)的降級(jí)策略。如果內(nèi)容安全 API 調(diào)用超時(shí)不能無限阻塞消息推送可以設(shè)置一個(gè)超時(shí)閾值比如 800ms超時(shí)后先標(biāo)記為“待復(fù)核”放行消息后續(xù)在后臺(tái)異步補(bǔ)充審核。屬于業(yè)務(wù)向安全做適當(dāng)妥協(xié)的經(jīng)典做法不能因?yàn)閷徍讼到y(tǒng)的問題把整個(gè)對(duì)話功能拖死。4. 實(shí)操過程與核心環(huán)節(jié)實(shí)現(xiàn)復(fù)盤4.1 從零搭建環(huán)境與依賴服務(wù)如果你要復(fù)現(xiàn)這套架構(gòu)本地開發(fā)環(huán)境建議這樣搭建?;A(chǔ)設(shè)施部分Docker Compose 是起步的正確選擇。AIFriends 依賴的中間件主要包括PostgreSQL業(yè)務(wù)數(shù)據(jù)、Redis緩存和 Pub/Sub、RabbitMQ異步任務(wù)隊(duì)列、Milvus 或 Chroma向量記憶庫、MinIO對(duì)象存儲(chǔ)用于存用戶頭像和對(duì)話附件。按依賴順序啟動(dòng)容器# 1. 啟動(dòng)基礎(chǔ)設(shè)施 docker compose up -d postgres redis rabbitmq minio # 2. 啟動(dòng)向量數(shù)據(jù)庫無 GPU 需求CPU 模式即可 docker compose up -d chroma # 3. 初始化數(shù)據(jù)庫表結(jié)構(gòu) cd services/user-service alembic upgrade head cd services/message-service alembic upgrade head # 4. 啟動(dòng)各個(gè)微服務(wù) cd services/ai-agent python app.py --port 8001 cd services/message python app.py --port 8002 cd services/order python app.py --port 8003一個(gè)容易踩坑的點(diǎn)是數(shù)據(jù)庫初始化順序。user-service 和 message-service 之間有外鍵關(guān)聯(lián)嗎從架構(gòu)上看沒有——各服務(wù)庫都是獨(dú)立的所以不存在嚴(yán)格的建表順序依賴。但如果你的實(shí)現(xiàn)里跨庫引用了記得先起的服務(wù)要容忍關(guān)聯(lián)表暫不存在的異?;蛘哂檬录C(jī)制等依賴服務(wù)就緒。4.2 核心服務(wù)間的接口約定與聯(lián)調(diào)記錄服務(wù)間通信的接口約定是整個(gè)項(xiàng)目里最需要提前鎖死的東西。我在實(shí)際聯(lián)調(diào)中吃過虧兩個(gè)服務(wù)各自開發(fā)到聯(lián)調(diào)階段發(fā)現(xiàn)字段命名不一致、狀態(tài)碼語義不一致返工成本極高。AIFriends 的約定大致如下服務(wù)間 API 統(tǒng)一走 /api/v1/ 前綴內(nèi)部調(diào)用帶 internal-token 頭與用戶側(cè) JWT 區(qū)分用戶 ID 和會(huì)話 ID 統(tǒng)一用雪花算法生成不用數(shù)據(jù)庫自增 ID避免跨服務(wù)暴露業(yè)務(wù)量狀態(tài)碼統(tǒng)一使用 0 表示成功非 0 為業(yè)務(wù)錯(cuò)誤碼HTTP 層面只區(qū)分 2xx 和 5xx關(guān)鍵鏈路發(fā)送消息、扣費(fèi)必須打印鏈路 TraceID日志格式統(tǒng)一為 JSON聯(lián)調(diào)時(shí)最耗時(shí)間的是 WebSocket 消息時(shí)序問題。我在本地模擬了弱網(wǎng)環(huán)境做測(cè)試發(fā)現(xiàn)偶發(fā)的消息延遲會(huì)打亂前端渲染順序——用戶發(fā)了消息 A 和 BAI 先回復(fù)了 B 再回復(fù) A對(duì)話順序就亂了。解決辦法是在消息體里加一個(gè) client_msg_seq 字段前端本地維護(hù)遞增序號(hào)渲染時(shí)先按 seq 排序再展示。這個(gè)字段在初版設(shè)計(jì)里完全沒考慮屬于聯(lián)調(diào)時(shí)踩坑后補(bǔ)的。4.3 LLM 接入層設(shè)計(jì)與 Key 池管理AI Agent 服務(wù)的核心是 LLM 接入層這塊做得不好再好的 prompt 也白搭。AIFriends 沒有直接在各處硬編碼 LLM API 調(diào)用而是抽象出了一個(gè)統(tǒng)一的 LLM Gateway。這個(gè) Gateway 做了三件事多廠商模型路由不同 AI 角色可以配置不同的模型比如知識(shí)型角色用更強(qiáng)的模型閑聊型角色用更快更便宜的模型Gateway 根據(jù)角色配置做路由Key 池化管理多個(gè) API Key 輪詢使用某個(gè) Key 觸發(fā)限流時(shí)自動(dòng)切換下一個(gè)并標(biāo)記該 Key 冷卻超時(shí)重試與降級(jí)單次 LLM 調(diào)用最長(zhǎng)等待 15 秒超時(shí)后自動(dòng)重試一次切換 Key仍失敗則返回友好話術(shù)給用戶并把這條消息標(biāo)記為“ai 不可用”Key 池管理是很多團(tuán)隊(duì)會(huì)忽視的模塊。LLM 供應(yīng)商的限流策略和成本控制直接關(guān)系到項(xiàng)目能跑多久Key 被限流了沒有自動(dòng)切換機(jī)制整個(gè)對(duì)話功能就是癱瘓的。這個(gè)模塊用最簡(jiǎn)單的輪詢 冷卻期就夠了不必引入太復(fù)雜的負(fù)載均衡策略。4.4 部署架構(gòu)與配置管理的實(shí)戰(zhàn)建議部署方面AIFriends 用 Docker Compose 做單機(jī)編排生產(chǎn)環(huán)境建議升級(jí)到 Kubernetes。但要注意的是微服務(wù)架構(gòu)里服務(wù)拆得越多部署和排障成本越高。我的建議是如果你的團(tuán)隊(duì)只有兩三個(gè)人前期用 Docker Compose 部署在單臺(tái) 4C16G 的服務(wù)器上撐住幾千 DAU 完全沒問題。等用戶量起來之后再遷 K8s利用 namespace 和 deployment 的滾動(dòng)更新能力來降低發(fā)版風(fēng)險(xiǎn)。配置管理上強(qiáng)烈建議用環(huán)境變量 配置中心的方式不要硬編碼在代碼里或者寫死在配置文件中。至少要把數(shù)據(jù)庫連接串、Redis 地址、LLM API Key 這些敏感配置單獨(dú)抽離生產(chǎn)環(huán)境用 K8s Secret 或在配置中心加密存儲(chǔ)。5. 常見問題與排查技巧實(shí)錄5.1 消息丟失與重復(fù)推送問題這類問題在我實(shí)際運(yùn)行中遇到得最多尤其是消息丟失?,F(xiàn)象用戶發(fā)了一條消息客戶端狀態(tài)一直停在“發(fā)送中”數(shù)據(jù)庫里沒有這條記錄。排查路徑先看 Nginx 和網(wǎng)關(guān)的訪問日志確認(rèn)請(qǐng)求是否到達(dá)消息服務(wù)確認(rèn)消息服務(wù)日志里有沒有對(duì)應(yīng)的消息事件查數(shù)據(jù)庫對(duì)應(yīng)表看是否有記錄但狀態(tài)異常如果服務(wù)日志都沒有基本可以斷定是前端沒發(fā)出來或者網(wǎng)關(guān)丟包優(yōu)先查前端邏輯處理方案客戶端新增失敗重試機(jī)制超時(shí) 5 秒自動(dòng)重發(fā)并帶上 client_msg_seq 去重服務(wù)端在消息表增加唯一索引session_id client_msg_seq從根本上杜絕重復(fù)入庫。重復(fù)推送的問題更隱蔽。用戶發(fā)送一條消息AI 回復(fù)生成后 WebSocket 推送了一次前端斷線重連后 HTTP 補(bǔ)償拉取又拿到了一次導(dǎo)致對(duì)話界面出現(xiàn)兩條相同的回復(fù)。解決辦法是在前端維護(hù)已渲染消息的 ID 集合渲染前先查重。5.2 LLM 響應(yīng)超時(shí)與假死問題AI Agent 服務(wù)調(diào)用外部 LLM 時(shí)最常見的故障就是響應(yīng)超時(shí)。這里的超時(shí)場(chǎng)景和常規(guī)數(shù)據(jù)庫超時(shí)不一樣外部 LLM 服務(wù)可能因?yàn)樽陨碡?fù)載高而響應(yīng)極慢或者長(zhǎng)時(shí)間沒有返回任何流式數(shù)據(jù)。排查建議給 LLM 調(diào)用設(shè)置多級(jí)超時(shí)連接超時(shí)3 秒、首 token 等待超時(shí)10 秒、整體響應(yīng)超時(shí)30 秒對(duì)同時(shí)發(fā)起的并發(fā)請(qǐng)求數(shù)做信號(hào)量控制超出排隊(duì)等待防止 LLM API 被瞬時(shí)打滿流式模式下如果超過 30 秒沒有任何 token 返回直接中斷該次生成返回一個(gè)兜底文案給用戶還有一個(gè)容易被忽略的點(diǎn)LLM 調(diào)用線程池的大小設(shè)置不合理會(huì)導(dǎo)致服務(wù)整體線程阻塞。AIFriends 踩過這個(gè)坑最初設(shè)置的線程池太小某個(gè)時(shí)刻用戶量上來所有線程都阻塞在 LLM 調(diào)用上健康檢查接口都沒有空閑線程響應(yīng)了。解決方法是把線程池?fù)Q成 IO 密集型的 ThreadPool并且給健康檢查接口單獨(dú)留出線程配額。5.3 積分扣費(fèi)不一致的排查記錄積分扣費(fèi)不一致主要體現(xiàn)在三種情況用戶發(fā)送消息后余額扣了但 AI 回復(fù)沒有生成用戶并發(fā)發(fā)送多條消息只成功扣了部分積分對(duì)賬發(fā)現(xiàn)積分流水和訂單記錄金額不匹配第一種情況在預(yù)凍結(jié)模式下很少出現(xiàn)但如果出現(xiàn)大概率是凍結(jié)轉(zhuǎn)扣費(fèi)的流程沒走完。排查時(shí)看訂單表的狀態(tài)如果訂單停在“frozen”狀態(tài)超過 30 分鐘手動(dòng)補(bǔ)一個(gè)任務(wù)把它轉(zhuǎn)成“failed”并解凍。第二種情況一般是 Redis Lua 腳本在并發(fā)下執(zhí)行了正確性校驗(yàn)但 application 層把返回結(jié)果做了錯(cuò)誤的空值處理。這種問題需要看具體代碼但排查思路是在測(cè)試環(huán)境用并發(fā)工具比如 jmeter 或 go-wrk模擬 50 并發(fā)發(fā)消息看 Redis 的扣減記錄和數(shù)據(jù)庫的流水是否一致。5.4 審核服務(wù)堆積導(dǎo)致消息延遲推送當(dāng)內(nèi)容安全 API 調(diào)用波動(dòng)時(shí)審核隊(duì)列會(huì)積壓導(dǎo)致 AI 回復(fù)遲遲推不到用戶端。處理優(yōu)先級(jí)從高到低檢查內(nèi)容安全 API 的調(diào)用成功率如果 API 本身降級(jí)了自動(dòng)切換備用供應(yīng)商調(diào)整審核服務(wù)的消費(fèi)并發(fā)數(shù)加大消費(fèi)線程池把積壓消化掉動(dòng)態(tài)調(diào)整超時(shí)閾值把自動(dòng)放行的判斷標(biāo)準(zhǔn)適當(dāng)放寬比如超時(shí) 1 秒就標(biāo)記待復(fù)核放行人工審核隊(duì)列單獨(dú)限流防止操作員處理不過來之后積壓到爆這個(gè)問題的核心是“審核不能阻塞主流程”。AI 對(duì)話產(chǎn)品對(duì)實(shí)時(shí)性的要求很高一條消息 10 秒內(nèi)不回復(fù)用戶基本就流失了。所以審核鏈路一定要和業(yè)務(wù)主鏈路解耦寧可放進(jìn)來再處理也不能讓用戶一直等。5.5 新增 AI 角色的配置冷啟動(dòng)運(yùn)營(yíng)在管理后臺(tái)新建一個(gè) AI 角色用戶端立刻就能看到并開始聊天但實(shí)際體驗(yàn)可能會(huì)很差——因?yàn)檫@個(gè)角色還沒有積累任何記憶也缺少對(duì)話樣本。這就需要一個(gè)冷啟動(dòng)策略。AIFriends 的做法是給新角色填充“種子對(duì)話數(shù)據(jù)”在角色上線前運(yùn)營(yíng)配置 20-30 組預(yù)設(shè)問答對(duì)寫入角色的記憶庫作為初始記憶。這樣用戶第一次和新角色聊天時(shí)角色就已經(jīng)“知道”一些關(guān)于自己的背景信息不會(huì)出現(xiàn)一問三不知的情況。技術(shù)上實(shí)現(xiàn)很簡(jiǎn)單就是往向量庫寫入一批初始文檔同時(shí)更新角色配置表里的 greeting_message 和 初始人設(shè)關(guān)鍵詞。這里有一個(gè)典型坑新角色上線后由于沒有歷史對(duì)話數(shù)據(jù)向量檢索可能返回空結(jié)果Prompt 模板里的 memory_snippets 變量為空導(dǎo)致 LLM 生成的回復(fù)極其干癟。所以模板渲染時(shí)要兼容空變量的情況為空時(shí)直接省略該段提示而不是輸出一行空字段。5.6 問題排查速查表癥狀可能原因快速排查方向用戶消息發(fā)送失敗WebSocket 連接斷開檢查實(shí)例連接數(shù)、Redis Pub/Sub 頻道是否存在消息收到但 AI 未回復(fù)Agent 服務(wù)調(diào)用 LLM 超時(shí)查看 LLM Gateway 的日志和耗時(shí)指標(biāo)AI 回復(fù)了但用戶沒收到審核服務(wù)攔截或推送失敗查審核隊(duì)列狀態(tài)和消息服務(wù)推送日志積分被扣但 AI 沒回復(fù)預(yù)凍結(jié)后未正常轉(zhuǎn)扣費(fèi)查訂單狀態(tài)手動(dòng)補(bǔ)償解凍用戶反饋 AI 記憶混亂向量檢索質(zhì)量低或上下文拼接錯(cuò)誤查記憶檢索排名情況和 Prompt 組裝結(jié)果管理后臺(tái)角色修改未生效角色配置緩存未刷新檢查 Redis 緩存 key 的過期策略和手動(dòng)刷新接口6. 踩坑記錄與架構(gòu)演進(jìn)復(fù)盤6.1 初版單體應(yīng)用到微服務(wù)拆分的遷移邏輯AIFriends 最初的核心對(duì)話功能其實(shí)是一個(gè)單體應(yīng)用代碼量到兩萬行左右時(shí)就明顯吃力了。最典型的問題是消息推送的 WebSocket 連接和業(yè)務(wù) API 共享進(jìn)程一旦某個(gè)業(yè)務(wù)接口出現(xiàn)慢查詢GC 停頓時(shí)間變長(zhǎng)WebSocket 心跳就會(huì)斷客戶端表現(xiàn)為“掉線”。遷移到微服務(wù)之后最直接的收益不是性能提升了多少而是故障隔離做得更好了。消息服務(wù)宕機(jī)用戶還能正常登錄瀏覽Agent 服務(wù)依賴的 LLM 供應(yīng)商故障其他服務(wù)完全不受影響。這種隔離能力對(duì)在線業(yè)務(wù)來說比單純的性能優(yōu)化重要得多。另外拆分之后每個(gè)服務(wù)的數(shù)據(jù)結(jié)構(gòu)可以做針對(duì)性優(yōu)化。用戶服務(wù)用關(guān)系型存儲(chǔ)用戶畫像Agent 服務(wù)用向量庫支撐記憶檢索訂單服務(wù)用事務(wù)性數(shù)據(jù)庫保證資金安全。數(shù)據(jù)模型跟著業(yè)務(wù)屬性走而不是被一個(gè)“大而全”的庫綁架。6.2 數(shù)據(jù)一致性與多服務(wù)事務(wù)的取舍微服務(wù)架構(gòu)里最煩人的問題是跨服務(wù)的數(shù)據(jù)一致性。AIFriends 沒有引入分布式事務(wù)框架而是用“本地事務(wù) 消息事件”的模式保證最終一致。舉例來說用戶充值的流程訂單服務(wù)在本地庫創(chuàng)建訂單狀態(tài)為“待支付”支付回調(diào)成功后訂單服務(wù)更新訂單狀態(tài)并發(fā)送“支付成功”事件到消息隊(duì)列積分服務(wù)消費(fèi)事件給用戶增加積分并寫入積分流水如果步驟 2 之后、步驟 3 之前積分服務(wù)恰好宕機(jī)用戶支付了但積分沒到賬。解決方式是引入一個(gè)對(duì)賬補(bǔ)償任務(wù)——定時(shí)掃描“支付成功但積分未增加”的訂單重新發(fā)送事件。這種模式的代碼量不大但能覆蓋絕大多數(shù)故障場(chǎng)景比引入 Seata 這類分布式事務(wù)中間件的成本低得多。6.3 我對(duì) AI 社交類項(xiàng)目架構(gòu)擴(kuò)展性的心得這類項(xiàng)目的擴(kuò)展路線大致可以分三個(gè)階段。第一個(gè)階段是把核心對(duì)話鏈路跑通重點(diǎn)在于 prompt 質(zhì)量和記憶機(jī)制第二個(gè)階段是完善商業(yè)化和安全體系積分、會(huì)員、審核、管理后臺(tái)一個(gè)都不能少第三個(gè)階段才是規(guī)?;敫嗟?AI 角色類型、多人聊天室、UGC 社區(qū)等。如果一開始就按第三個(gè)階段的標(biāo)準(zhǔn)做架構(gòu)設(shè)計(jì)大概率會(huì)過度設(shè)計(jì)。AIFriends 的做法是我比較認(rèn)同的按業(yè)務(wù)自然增長(zhǎng)逐步演進(jìn)但每個(gè)階段都預(yù)留好擴(kuò)展點(diǎn)——比如 Prompt 模板從一開始就可以配置化向量記憶庫一開始就獨(dú)立存儲(chǔ)消息服務(wù)從一開始就支持多實(shí)例廣播。我個(gè)人在實(shí)際項(xiàng)目中的體會(huì)是AI 產(chǎn)品的架構(gòu)難點(diǎn)從來不在“怎么調(diào)用大模型”而在于怎么把大模型的輸出安全、穩(wěn)定、商業(yè)化地融入到你現(xiàn)有的業(yè)務(wù)體系里。Prompt 寫得好可以讓一個(gè)角色討人喜歡但架構(gòu)設(shè)計(jì)得好才能讓一百萬個(gè)角色同時(shí)活著且不出亂子。