源AI助手WorkBuddy企業(yè)化改造:私有化部署與協(xié)作路由實(shí)戰(zhàn))
把開(kāi)源版 WorkBuddy 改造成企業(yè)版這件事起源于我們團(tuán)隊(duì)內(nèi)部很多人的一個(gè)共識(shí)公共大模型工具用起來(lái)確實(shí)爽但一涉及公司內(nèi)部數(shù)據(jù)心里總是沒(méi)底。一次模型供應(yīng)商的接口調(diào)整引發(fā)了幾次誤調(diào)用管理層干脆叫停了非白名單工具的使用整個(gè)團(tuán)隊(duì)的效率瞬間掉了一截。那陣子我正好在研究開(kāi)源版的 WorkBuddy看它有沒(méi)有可能在內(nèi)部私有化跑起來(lái)。折騰了大概一個(gè)月我不僅把模型服務(wù)換了公司內(nèi)部的私有化部署版本還順手把同事這個(gè)原本只是聊天功能的東西做成了一套能真正派活、能觸達(dá)真人的協(xié)作入口。這篇文章就把我完整的改造鏈路、選型理由、踩過(guò)的坑和最終收益一次性講清楚。如果你所在的團(tuán)隊(duì)也在做類似的嘗試比如打算把某個(gè)開(kāi)源 AI 助手私有化或者想在公司內(nèi)部模型服務(wù)和團(tuán)隊(duì)協(xié)作之間打通一條路那這篇文章應(yīng)該能讓你少走不少?gòu)澛?。它涵蓋的內(nèi)容包括開(kāi)源版 WorkBuddy 到底適不適合做改造基座、接公司模型時(shí) API 之外的坑、同事 功能的真實(shí)設(shè)計(jì)邏輯以及企業(yè)落地繞不開(kāi)的 SSO 和審計(jì)問(wèn)題。我不是什么大廠架構(gòu)師只是一個(gè)啃過(guò)源碼、翻過(guò)文檔、也對(duì)著日志懷疑過(guò)人生的普通開(kāi)發(fā)者寫出來(lái)的東西可能不夠理論化但一定都是從實(shí)際代碼和線上故障里得出的經(jīng)驗(yàn)。1. 先搞清楚一個(gè)根本問(wèn)題為什么是開(kāi)源版 WorkBuddy而不是自研或者直接用 SaaS1.1 開(kāi)源版原本擅長(zhǎng)什么在決定改造之前我把 WorkBuddy 的源碼和文檔完整過(guò)了一遍。它的定位是本地優(yōu)先的 AI 工作臺(tái)核心能力集中在對(duì)話管理、上下文記憶、插件機(jī)制和知識(shí)庫(kù)掛載這幾塊。它的前端寫得很清爽對(duì)話界面、會(huì)話歷史、附件上傳這些基礎(chǔ)體驗(yàn)都是現(xiàn)成的不需要我從零去畫界面。后端則走的是典型的模型不可知設(shè)計(jì)模型接口被抽象成一層 Provider這意味著我可以不碰業(yè)務(wù)代碼只改配置和新增適配器就能切換模型來(lái)源。這一點(diǎn)很關(guān)鍵。很多同類工具把模型適配寫死在代碼里換一個(gè)模型服務(wù)等于重寫一遍而 WorkBuddy 的設(shè)計(jì)天然允許你指定 Base URL、模型名稱、API Key 的讀取位置。底層數(shù)據(jù)存儲(chǔ)用的是本地文件和輕量數(shù)據(jù)庫(kù)的組合這讓我在改造數(shù)據(jù)模型、加入企業(yè)用戶字段的時(shí)候少了不少掣肘。另一點(diǎn)讓我下定決心的是它的許可證比較寬松允許內(nèi)部修改和二次分發(fā)省去了合規(guī)層面的周旋。坦白講WorkBuddy 的開(kāi)源版本身也有一堆毛病。它的多用戶支持約等于沒(méi)有基本是一個(gè)人用一個(gè)實(shí)例的邏輯。權(quán)限體系、組織架構(gòu)、審計(jì)流水線這些企業(yè)級(jí)詞匯它一個(gè)都不沾邊。但正因?yàn)楣羌茏銐蚋蓛舾脑炱饋?lái)反而比那種全家桶式軟件好操作。你給它一個(gè)單用戶的殼它還你一個(gè)靈活的底子剩下的就看你怎么填。1.2 我為什么不直接選 SaaS 產(chǎn)品市面上現(xiàn)成的企業(yè)級(jí) AI 協(xié)作平臺(tái)其實(shí)不少界面漂亮、功能完善開(kāi)箱即用穩(wěn)定性也有保障。但我當(dāng)時(shí)面臨一個(gè)實(shí)際限制公司內(nèi)部對(duì)數(shù)據(jù)出網(wǎng)有嚴(yán)格要求任何包含業(yè)務(wù)關(guān)鍵詞的對(duì)話記錄都不允許流向外部接口。這意味著所有經(jīng)過(guò) SaaS 平臺(tái)的對(duì)話內(nèi)容都需要過(guò)一遍外部接口這在合規(guī)上直接不成立。即便供應(yīng)商承諾數(shù)據(jù)隔離合同條款寫得多漂亮也架不住內(nèi)部安全團(tuán)隊(duì)那一關(guān)根本批不下來(lái)。另一層原因是模型成本不可控。SaaS 平臺(tái)通常按 token 計(jì)費(fèi)而且對(duì)私有化部署的模型能力支持有限。我們內(nèi)部正好有一支團(tuán)隊(duì)長(zhǎng)期維護(hù)一套基于開(kāi)源模型打造的推理服務(wù)與其繞道外部 API 再回來(lái)不如直接在內(nèi)網(wǎng)完成閉環(huán)。私有大模型雖然在某些任務(wù)上不如商業(yè) API 能力強(qiáng)但勝在可以針對(duì)團(tuán)隊(duì)自己的使用習(xí)慣做微調(diào)而且每次調(diào)用都在內(nèi)網(wǎng)完成延遲和成本都穩(wěn)定得多。還有個(gè)容易被忽略的點(diǎn)協(xié)作閉環(huán)。SaaS 產(chǎn)品哪怕做一萬(wàn)個(gè)功能也很難和公司內(nèi)部的 IM、郵件、會(huì)議室、任務(wù)系統(tǒng)完全打通。而 WorkBuddy 改造后我可以直接把它接進(jìn)公司現(xiàn)有的通知體系A(chǔ)I 生成的結(jié)果可以一鍵轉(zhuǎn)給具體的人去確認(rèn)這中間的價(jià)值遠(yuǎn)大于一個(gè)純粹聊天的 AI 工具。1.3 我給自己定的三個(gè)改造目標(biāo)整個(gè)項(xiàng)目立項(xiàng)的時(shí)候我沒(méi)有貪多只定了三條主線后來(lái)的開(kāi)發(fā)工作幾乎全部圍繞這三條線展開(kāi)。第一模型可控。讓 WorkBuddy 通過(guò)統(tǒng)一的適配層接入公司內(nèi)部署的模型服務(wù)所有對(duì)話記錄和上下文全部保留在內(nèi)網(wǎng)并且支持按模型維度做灰度切換。第二身份打通。接上公司統(tǒng)一的賬號(hào)認(rèn)證體系讓員工可以直接用已有的企業(yè)賬號(hào)登錄不用再單獨(dú)注冊(cè)一套賬號(hào)密碼。這也是后續(xù)權(quán)限管理的基礎(chǔ)。第三協(xié)作閉環(huán)。在原版對(duì)話能力之上增加同事的能力——在對(duì)話里提到某個(gè)人系統(tǒng)要把這個(gè)人識(shí)別出來(lái)把相關(guān)任務(wù)或問(wèn)題定向分配給 TA并通過(guò) IM 和站內(nèi)通知的形式觸達(dá)對(duì)方。這樣 AI 助手就不再是孤島工具而是一個(gè)能連接真人的協(xié)作節(jié)點(diǎn)。這三個(gè)目標(biāo)聽(tīng)起來(lái)都不算復(fù)雜但每一項(xiàng)在真正落地時(shí)都比預(yù)期多花了不少時(shí)間。下面我按改造順序逐個(gè)拆解。2. 接公司模型這步坑全在 API 之外的細(xì)節(jié)里2.1 公司模型服務(wù)到底長(zhǎng)什么樣說(shuō)到接公司模型很多人的第一反應(yīng)是改一個(gè) base_url把原來(lái)的 OpenAI 指向換成內(nèi)部地址。理論上是這樣但實(shí)際操作完全不是一回事。我們公司內(nèi)部的模型服務(wù)由另一個(gè)團(tuán)隊(duì)維護(hù)他們的對(duì)外接口是一個(gè)基于內(nèi)網(wǎng)網(wǎng)關(guān)的 OpenAI 兼容協(xié)議。這意味著大部分網(wǎng)絡(luò)請(qǐng)求格式和 OpenAI 是相似的但鑒權(quán)方式完全不同公共 API 用的是 API Key而內(nèi)部網(wǎng)關(guān)走的是內(nèi)部統(tǒng)一認(rèn)證服務(wù)簽發(fā)的臨時(shí)令牌令牌還有時(shí)效性過(guò)期后必須自動(dòng)續(xù)簽。另外內(nèi)部模型服務(wù)的部署形態(tài)也和公共 API 不一樣。公共 API 通常在全球有多個(gè)節(jié)點(diǎn)延遲穩(wěn)定在幾十毫秒級(jí)別而內(nèi)部服務(wù)只有幾個(gè) GPU 節(jié)點(diǎn)遇到高峰期會(huì)排隊(duì)有時(shí)還會(huì)因?yàn)槟P蛽Q版本導(dǎo)致接口參數(shù)出現(xiàn)細(xì)微變動(dòng)。這個(gè)差異直接影響了我后面對(duì)于超時(shí)、重試和隊(duì)列策略的設(shè)計(jì)。還有一個(gè)很容易忽略的點(diǎn)是模型列表的動(dòng)態(tài)性。公共 API 的模型清單相對(duì)固定基本每個(gè)月加一兩個(gè)新模型。而內(nèi)部服務(wù)因?yàn)榻?jīng)常做實(shí)驗(yàn)性部署模型名可能一周就換一次甚至同名的模型后面對(duì)應(yīng)的實(shí)際底座也會(huì)變化。所以我不能把模型名寫死在配置里必須做一個(gè)可動(dòng)態(tài)刷新的模型列表接口讓 WorkBuddy 每次讀取模型時(shí)都向網(wǎng)關(guān)拉取最新的可用清單。2.2 我在適配層里做的四件事為了讓 WorkBuddy 和內(nèi)部網(wǎng)關(guān)能夠穩(wěn)定協(xié)作我寫了一個(gè)獨(dú)立的模型適配服務(wù)放在 WorkBuddy 后端和公司模型網(wǎng)關(guān)之間。這個(gè)服務(wù)承擔(dān)了四件關(guān)鍵事情鑒權(quán)代理、協(xié)議歸一、流式轉(zhuǎn)發(fā)、限流熔斷。鑒權(quán)代理是最先要解決的。內(nèi)部網(wǎng)關(guān)要求每次請(qǐng)求都附帶有效的認(rèn)證令牌而這塊令牌的獲取邏輯不在 WorkBuddy 范圍內(nèi)也不能落在前端。適配服務(wù)作為中間人負(fù)責(zé)定時(shí)刷新令牌池并在每次向上游轉(zhuǎn)發(fā)時(shí)自動(dòng)注入頭部。對(duì) WorkBuddy 側(cè)來(lái)說(shuō)它只需要知道適配服務(wù)自己的地址和一套靜態(tài)內(nèi)部憑據(jù)就可以無(wú)感使用公司模型。這樣做還有一個(gè)額外好處令牌的刷新和失效完全對(duì)上層透明哪怕公司后續(xù)調(diào)整認(rèn)證協(xié)議也不用手動(dòng)去改 WorkBuddy 的配置。協(xié)議歸一其實(shí)做的是接口翻譯。盡管公司網(wǎng)關(guān)號(hào)稱兼容 OpenAI 格式但細(xì)節(jié)上總有出入。比如 OpenAI 版本對(duì)話參數(shù)里的 system 消息在新的模型版本里可能需要做特殊映射有些新模型對(duì)參數(shù)名極其敏感多傳一個(gè)廢棄字段直接報(bào)錯(cuò)。適配服務(wù)負(fù)責(zé)把這些差異吞掉讓 WorkBuddy 面對(duì)的是一個(gè)穩(wěn)定的、字段標(biāo)準(zhǔn)化的模型接口。流式轉(zhuǎn)發(fā)則是一個(gè)技術(shù)上不復(fù)雜但小坑很多的地方。AI 模型回復(fù)走的是 SSE 流式協(xié)議用戶在界面看到字體逐個(gè)蹦出來(lái)的效果就是前端在不斷消費(fèi)流式數(shù)據(jù)的結(jié)果。適配層需要原樣轉(zhuǎn)發(fā)這個(gè)流同時(shí)還要處理流異常中斷時(shí)的重連。如果適配層因?yàn)槌瑫r(shí)把流截?cái)嗔饲岸藭?huì)表現(xiàn)為模型回了一半字突然停住這個(gè)現(xiàn)象非常勸退用戶。限流熔斷是我后來(lái)補(bǔ)上的。內(nèi)部 GPU 節(jié)點(diǎn)資源有限一個(gè)用戶的大段上下文請(qǐng)求可能會(huì)占住顯存很長(zhǎng)時(shí)間多個(gè)請(qǐng)求同時(shí)打過(guò)去會(huì)導(dǎo)致排隊(duì)越來(lái)越嚴(yán)重。適配服務(wù)參考了網(wǎng)關(guān)的返回狀態(tài)一旦發(fā)現(xiàn)上游負(fù)載過(guò)高就主動(dòng)放慢新請(qǐng)求的接入節(jié)奏同時(shí)對(duì)已經(jīng)進(jìn)隊(duì)列的請(qǐng)求做超時(shí)保護(hù)避免讓用戶無(wú)限等待。2.3 從單用戶 API Key到按人按組授權(quán)原版 WorkBuddy 理論上只需要一個(gè)全局 API Key所有對(duì)話都從這個(gè) Key 出發(fā)。但企業(yè)內(nèi)部使用場(chǎng)景不允許這種粗放模式不同部門的人對(duì)模型的使用權(quán)限是不同的有的部門可以用更強(qiáng)的模型跑長(zhǎng)文本有的部門只能跑基礎(chǔ)模型還有人根本不應(yīng)該被允許使用某些功能。我在適配層里引入了一套簡(jiǎn)單的授權(quán)策略。規(guī)則不復(fù)雜每個(gè)用戶在登錄后WorkBuddy 后端會(huì)拿到他的用戶信息和部門歸屬適配服務(wù)每轉(zhuǎn)發(fā)一次請(qǐng)求都會(huì)帶上這些身份屬性。網(wǎng)關(guān)側(cè)的規(guī)則引擎會(huì)判斷這個(gè)身份可以調(diào)哪些模型配額是多少超出的部分直接拒絕。這等于把權(quán)限控制前置到了模型調(diào)用這個(gè)環(huán)節(jié)而不是等對(duì)話記錄落庫(kù)以后再做過(guò)濾。這個(gè)改造的難點(diǎn)不在于代碼量而在于用戶身份要在請(qǐng)求鏈路里完整傳遞。WorkBuddy 原本的請(qǐng)求上下文里只有比較簡(jiǎn)單的用戶標(biāo)識(shí)我在改造時(shí)專門梳理了一遍請(qǐng)求鏈路確保從瀏覽器端到適配層再到網(wǎng)關(guān)用戶的身份屬性不會(huì)丟。丟了的話權(quán)限控制就會(huì)變成空話。我后來(lái)做了一個(gè)調(diào)試頁(yè)面專門展示每次模型請(qǐng)求對(duì)應(yīng)的用戶上下文和授權(quán)結(jié)果排查權(quán)限相關(guān)問(wèn)題時(shí)非常有用。2.4 幾個(gè)必須配置好的參數(shù)跑通基本鏈路后我整理了一份適配層的推薦配置清單踩過(guò)坑之后再回看這幾個(gè)參數(shù)每一個(gè)都是血淚教訓(xùn)參數(shù)推薦值說(shuō)明上游超時(shí)300s長(zhǎng)上下文生成任務(wù)經(jīng)常超過(guò) 60s默認(rèn) 30s 一定會(huì)斷流式空閑時(shí)間60s如果 60s 內(nèi)上游沒(méi)有任何流式數(shù)據(jù)返回觸發(fā)重連失敗重試次數(shù)2超過(guò) 2 次直接返回錯(cuò)誤給用戶避免排隊(duì)雪崩令牌刷新提前量5min在令牌真正過(guò)期前 5 分鐘就刷新避免邊界競(jìng)態(tài)隊(duì)列最大長(zhǎng)度200超出后新請(qǐng)求直接拒絕返回當(dāng)前模型繁忙提示我第一次部署時(shí)就是沒(méi)把超時(shí)和重試這兩個(gè)參數(shù)調(diào)好結(jié)果用戶體驗(yàn)非常糟糕問(wèn)一個(gè)復(fù)雜問(wèn)題生成到一半就斷了重新問(wèn)一次又可能觸發(fā)兩次并發(fā)請(qǐng)求把 GPU 顯存直接擠爆。后來(lái)我把超時(shí)拉長(zhǎng)同時(shí)把失敗重試改成指數(shù)退避策略狀況才明顯好轉(zhuǎn)。這些參數(shù)不會(huì)寫在 WorkBuddy 的默認(rèn)配置里需要自己在適配層管理但它們的價(jià)值遠(yuǎn)大于寫一百行業(yè)務(wù)代碼。3. 同事 不是聊天功能而是協(xié)作路由機(jī)制3.1 從所有事情都問(wèn) AI到AI 也能找人幫忙團(tuán)隊(duì)剛開(kāi)始使用 WorkBuddy 時(shí)大家都拿它當(dāng)增強(qiáng)版搜索引擎。比如讓 AI 總結(jié)會(huì)議紀(jì)要、續(xù)寫文案、查代碼邏輯基本是單人操作。但很快我就發(fā)現(xiàn)一個(gè)真實(shí)需求AI 生成的很多內(nèi)容需要人工確認(rèn)或者跑完一步后必須由特定角色來(lái)接手。這時(shí)候用戶得復(fù)制一堆內(nèi)容切到 IM找到對(duì)應(yīng)的同事粘貼發(fā)送然后再切回來(lái)。整個(gè)流程非常割裂。于是AI 對(duì)話框里直接找出這個(gè)人就成了一個(gè)自然而然的產(chǎn)品需求。這里要說(shuō)明我在規(guī)劃 同事 功能時(shí)并沒(méi)有打算做一個(gè)傳統(tǒng) IM 那樣的聊天提醒。我希望的是當(dāng)我在 WorkBuddy 對(duì)話框里輸入把這份需求的驗(yàn)收標(biāo)準(zhǔn)整理好阿哲 幫忙確認(rèn)系統(tǒng)能夠完成三件事識(shí)別出阿哲這個(gè)人把這段對(duì)話和上下文形成一個(gè)待辦事項(xiàng)通過(guò)公司內(nèi)部 IM 的機(jī)器人把通知發(fā)到阿哲那里阿哲點(diǎn)開(kāi)通知后直接看到這條對(duì)話的完整上下文可以在 WorkBuddy 里回復(fù)并且回復(fù)內(nèi)容會(huì)作為同事協(xié)作記錄沉淀回同一個(gè)會(huì)話里。這套邏輯從產(chǎn)品角度叫協(xié)作閉環(huán)從實(shí)現(xiàn)角度其實(shí)就是一整套路由機(jī)制誰(shuí)提到誰(shuí)、誰(shuí)確認(rèn)哪個(gè)任務(wù)、回復(fù)去哪兒。整個(gè)功能并不存在什么深?yuàn)W的技術(shù)難點(diǎn)但確實(shí)是 WorkBuddy 原生代碼里完全不存在的東西需要從零設(shè)計(jì)。3.2 解析層怎么把幫我 阿哲 看看這個(gè)變成結(jié)構(gòu)化數(shù)據(jù)市面上解析 的常規(guī)做法是正則匹配但這在中文場(chǎng)景下有個(gè)天然痛點(diǎn)同事的名字千奇百怪有的是三個(gè)字有的是兩個(gè)字有的是英文名加花名。直接靠正則去抓 開(kāi)頭、直到空格或標(biāo)點(diǎn)結(jié)束的字符串很容易把無(wú)關(guān)內(nèi)容截進(jìn)來(lái)。我最后的實(shí)現(xiàn)思路是雙通道解析先讓 WorkBuddy 自己通過(guò)一個(gè)輕量模型做語(yǔ)義識(shí)別把對(duì)話中指名道姓的部分提取成列表然后用規(guī)則引擎做兜底校驗(yàn)。為什么用雙通道因?yàn)槲乙呀?jīng)有模型網(wǎng)關(guān)可用讓模型做一次實(shí)體抽取實(shí)在是很順手的事準(zhǔn)確率遠(yuǎn)比正則高。但模型抽取有概率抽取出不存在的人名或者把非 場(chǎng)景的張總王哥這類稱呼也抓進(jìn)來(lái)這時(shí)候就需要規(guī)則引擎去比對(duì)用戶目錄過(guò)濾掉無(wú)效實(shí)體。放在實(shí)際流程里是這樣的用戶發(fā)送一條消息后WorkBuddy 后端先去用戶中心拉一次組織架構(gòu)快照然后用快照做正則初篩找出所有疑似 的片段接著把整個(gè)消息文本和候選名單交給模型讓模型確認(rèn)哪些是真實(shí)指向某個(gè)同事的并給出候選人的部門信息用來(lái)消歧。最后把確認(rèn)過(guò)的候選人集合返回前端前端彈一個(gè)確認(rèn) 誰(shuí)的下拉框用戶點(diǎn)選確認(rèn)后消息才正式發(fā)送。這個(gè)人工確認(rèn)步驟一開(kāi)始被產(chǎn)品經(jīng)理吐槽繁瑣但實(shí)際上它解決了大問(wèn)題同一家公司如果存在兩個(gè)同名阿哲系統(tǒng)直接列出部門和頭像讓用戶選擇避免發(fā)錯(cuò)人的尷尬。上線以來(lái)因?yàn)檫@個(gè)確認(rèn)步驟引起的誤發(fā)消息數(shù)是零。3.3 路由層任務(wù)如何派發(fā)、通知怎么送達(dá)解析層只負(fù)責(zé)認(rèn)出人真正讓 同事 生效的是路由層。當(dāng)一條消息確認(rèn) 了某個(gè)人之后WorkBuddy 后端會(huì)執(zhí)行以下幾件事在會(huì)話對(duì)象下創(chuàng)建一個(gè)協(xié)作待辦記錄字段包括發(fā)起人、接收人、原消息、會(huì)話鏈接、狀態(tài)、截止時(shí)間然后通過(guò)內(nèi)部 IM 機(jī)器人的 webhook 向接收人發(fā)送一條消息消息卡片里附帶該會(huì)話的鏈接和簡(jiǎn)要上下文同時(shí)把這條協(xié)作記錄寫入對(duì)應(yīng)會(huì)話的協(xié)作時(shí)間線方便后續(xù)追溯。這里面相對(duì)棘手的是通知渠道。公司內(nèi)部 IM 的消息機(jī)器人接口通常比較穩(wěn)定但發(fā)送頻率有限制瞬時(shí)發(fā)太多會(huì)被限流。我在路由層里加了一個(gè)進(jìn)程內(nèi)隊(duì)列所有協(xié)作通知先進(jìn)隊(duì)列再按 5 條每秒的節(jié)奏勻速發(fā)送避免觸發(fā)限流。通知失敗不要緊失敗的消息會(huì)重試三次最終仍然失敗就轉(zhuǎn)成站內(nèi)信待辦用戶登錄 WorkBuddy 后會(huì)看到你有未處理協(xié)作請(qǐng)求的橫幅。還有一個(gè)細(xì)節(jié)是通知內(nèi)容的隱私性。機(jī)器人推送的消息卡片不能包含完整對(duì)話明文否則任何路過(guò)的人瞄一眼屏幕都可能看到業(yè)務(wù)內(nèi)容。我最終的實(shí)現(xiàn)方式是卡片里只包含一段摘要發(fā)起人、會(huì)話主題、 你的那句話再附上點(diǎn)擊查看詳情。完整內(nèi)容必須通過(guò) WorkBuddy 的會(huì)話打開(kāi)查看同時(shí)校驗(yàn)接收人是否有權(quán)限進(jìn)入該會(huì)話。這個(gè)設(shè)計(jì)兼顧了通知的即時(shí)性和內(nèi)容的安全性。3.4 權(quán)限與邊界能 誰(shuí)誰(shuí)能看這段對(duì)話同事 功能有三個(gè)權(quán)限邊界如果不設(shè)清楚功能上線后一定會(huì)出亂子。第一個(gè)邊界是可見(jiàn)性。并非所有員工都能出現(xiàn)在 候選列表里。我的規(guī)則是只有當(dāng)前會(huì)話的參與人、組織架構(gòu)上屬于同一部門或協(xié)作項(xiàng)目組的成員才會(huì)出現(xiàn)在候選名單避免用戶隨手 到完全不相關(guān)的人。這個(gè)規(guī)則不是硬限制管理員可以在后臺(tái)調(diào)整但默認(rèn)配置偏向保守。第二個(gè)邊界是會(huì)話權(quán)限。原版 WorkBuddy 的會(huì)話本質(zhì)上是私有數(shù)據(jù)只有創(chuàng)建者能看到。改造后我引入了會(huì)話成員的顯式授權(quán)一個(gè)會(huì)話可以被發(fā)起人添加一個(gè)或多個(gè)協(xié)作者協(xié)作者有權(quán)查看會(huì)話歷史、在會(huì)話內(nèi)回復(fù)、在對(duì)話里追加 其他成員。非成員即使拿到會(huì)話鏈接也會(huì)看到您無(wú)權(quán)訪問(wèn)該會(huì)話的提示頁(yè)。這個(gè)邏輯聽(tīng)起來(lái)簡(jiǎn)單但原版代碼里并沒(méi)有會(huì)話成員表我需要額外建表并修改前端的路由守衛(wèi)工作量比想象中多。第三個(gè)邊界是操作審計(jì)。用戶發(fā)出的每一個(gè)協(xié)作指令都會(huì)被記錄誰(shuí)在什么時(shí)候 了誰(shuí)接收人做了什么處理會(huì)話最終有沒(méi)有閉環(huán)。這些記錄一方面滿足合規(guī)要求另一方面也方便復(fù)盤哪類問(wèn)題總在同事間反復(fù)流轉(zhuǎn)但沒(méi)解決對(duì)后續(xù)優(yōu)化團(tuán)隊(duì)協(xié)作流程很有價(jià)值。審計(jì)記錄不外發(fā)只在 WorkBuddy 管理后臺(tái)可見(jiàn)只有管理員賬號(hào)能夠查詢。4. 企業(yè)落地繞不開(kāi)的三座大山SSO 登錄、審計(jì)日志、部署運(yùn)維4.1 SSO 集成把 WorkBuddy 的登錄框整個(gè)換掉原版 WorkBuddy 的登錄模塊非常簡(jiǎn)單就是注冊(cè)手機(jī)號(hào)或郵箱然后輸密碼。企業(yè)內(nèi)部部署顯然不能用這套邏輯否則賬號(hào)體系就是第二個(gè)孤島。我的做法是把登錄入口改成公司統(tǒng)一門戶的 OIDC 認(rèn)證流程WorkBuddy 后端不再自己驗(yàn)證密碼而是把所有登錄請(qǐng)求都重定向到公司認(rèn)證中心用戶輸完企業(yè)賬號(hào)密碼后認(rèn)證中心回調(diào) WorkBuddy 的回調(diào)地址攜帶認(rèn)證碼后端再通過(guò)這個(gè)認(rèn)證碼換取用戶身份和部門信息。這個(gè)改造的難點(diǎn)在于 WorkBuddy 原本的會(huì)話管理是自研的一套 Session 機(jī)制它不會(huì)自動(dòng)理解 OIDC 的用戶身份。我需要在登錄成功之后做一次身份映射把認(rèn)證中心返回的企業(yè)用戶 ID 映射到 WorkBuddy 內(nèi)部的用戶 ID如果是第一次登錄自動(dòng)創(chuàng)建內(nèi)部用戶并標(biāo)記來(lái)源為 SSO。整個(gè)過(guò)程用到了原版代碼里幾乎沒(méi)碰過(guò)的 User 數(shù)據(jù)表還要處理用戶離職后賬號(hào)如何停用這種邊界場(chǎng)景。這里有個(gè)很小但極其容易踩的坑OIDC 回調(diào)地址的協(xié)議和端口必須和實(shí)際部署時(shí)的完全一致。我在測(cè)試環(huán)境用的是 HTTP 8080 端口回調(diào)地址寫的是 http://xxx 結(jié)果上了生產(chǎn)環(huán)境配了 HTTPS 443我忘了改回調(diào)地址導(dǎo)致用戶每次登錄都報(bào)回調(diào)地址不合法。當(dāng)時(shí)排查了幾個(gè)小時(shí)最后才發(fā)現(xiàn)是回調(diào)地址配置沒(méi)跟著環(huán)境同步。后來(lái)我把這個(gè)配置從代碼里抽出來(lái)放到環(huán)境變量里再也沒(méi)出過(guò)問(wèn)題。4.2 審計(jì)日志在夠用和過(guò)度記錄之間找到平衡企業(yè)環(huán)境做這種 AI 工具審計(jì)日志是躲不開(kāi)的。但審計(jì)日志怎么做很有講究。假設(shè)把所有對(duì)話明文都記錄下來(lái)一旦數(shù)據(jù)庫(kù)被拖走那危害不亞于對(duì)話內(nèi)容直接泄露到外部。假設(shè)什么都不記出了事想回溯又無(wú)從下手。我最后選擇的策略是分級(jí)記錄用戶身份、會(huì)話 ID、時(shí)間戳、調(diào)用的模型類型、對(duì)話消息量、本次請(qǐng)求的返回狀態(tài)全部記錄在審計(jì)表中但對(duì)話的完整明文不落庫(kù)或者只在會(huì)話自身的存儲(chǔ)里保留并且這個(gè)存儲(chǔ)也做了加密。如果確實(shí)需要對(duì)某條對(duì)話做具體內(nèi)容追溯可以通過(guò)一個(gè)管理員專屬的安全復(fù)盤接口單獨(dú)拉取指定會(huì)話的內(nèi)容并且拉取行為本身也會(huì)生成一條審計(jì)事件。這樣做的好處是日常審計(jì)日志的敏感度降到最低就算日志被別人讀到了也不會(huì)直接泄露對(duì)話內(nèi)容而真正需要內(nèi)容級(jí)追蹤的場(chǎng)景仍然有手段并且全程留痕。審計(jì)日志的保留周期我建議至少一年。內(nèi)部合規(guī)檢查時(shí)這件事被反復(fù)確認(rèn)過(guò)。同時(shí)日志表要按時(shí)間加索引否則數(shù)據(jù)量一上來(lái)查詢一個(gè)季度內(nèi)的操作記錄會(huì)慢到讓人懷疑人生。4.3 部署形態(tài)一套 Docker Compose 加反向代理整個(gè)系統(tǒng)最后以一套 Docker Compose 的形式跑在公司內(nèi)網(wǎng)的一臺(tái)服務(wù)器上。服務(wù)包括 WorkBuddy 后端、適配服務(wù)、前端靜態(tài)資源、數(shù)據(jù)庫(kù)。對(duì)外用 Nginx 做反向代理統(tǒng)一端口終止 HTTPS同時(shí)配置 WebSocket 支持因?yàn)?WorkBuddy 前端和后臺(tái)之間有實(shí)時(shí)通信通道。部署上最需要注意的一個(gè)點(diǎn)是 WorkBuddy 的前端構(gòu)建產(chǎn)物里經(jīng)常會(huì)內(nèi)置連接地址。我一開(kāi)始沒(méi)留意這個(gè)直接拿默認(rèn)配置構(gòu)建結(jié)果前端頁(yè)面請(qǐng)求的是 localhost后端服務(wù)明明在另一臺(tái)機(jī)器上頁(yè)面就是連不上。解決辦法是把后端地址配置為環(huán)境變量注入構(gòu)建時(shí)用相對(duì)路徑或者運(yùn)行時(shí)配置覆蓋。這類問(wèn)題在本地開(kāi)發(fā)時(shí)永遠(yuǎn)不可能暴露人家連的就是本機(jī)一切正常但一上服務(wù)器就把配置分離的問(wèn)題暴露無(wú)遺。備份策略也值得一提。數(shù)據(jù)庫(kù)每天自動(dòng)備份會(huì)話存儲(chǔ)目錄則通過(guò)定時(shí)任務(wù)同步到內(nèi)部存儲(chǔ)服務(wù)器?;謴?fù)流程我實(shí)際演練過(guò)一次從備份恢復(fù)到服務(wù)可用大概花了十幾分鐘這個(gè)恢復(fù)時(shí)長(zhǎng)在可接受范圍內(nèi)。更重要的收獲是演練過(guò)程中我發(fā)現(xiàn)備份腳本有一個(gè)隱患它沒(méi)有保留最近三份版本而是直接覆蓋那樣如果某天的備份是壞數(shù)據(jù)就會(huì)把之前的也沖掉。改掉之后每次備份都帶時(shí)間戳保留最近三十份。5. 上線 30 天踩坑實(shí)錄從能用到好用的距離5.1 流式輸出斷連前端等了 20 秒才開(kāi)始顯示第一個(gè)字上線初期最影響用戶信心的問(wèn)題是流式輸出不穩(wěn)定。用戶問(wèn)一個(gè)問(wèn)題界面要等很久才開(kāi)始顯示回復(fù)內(nèi)容有時(shí)干脆一個(gè)字都不出過(guò)一會(huì)兒提示網(wǎng)絡(luò)錯(cuò)誤。我沿著鏈路排查最終鎖定的根因是反向代理的緩沖機(jī)制。Nginx 默認(rèn)會(huì)緩沖后端返回的數(shù)據(jù)要攢到一定量才發(fā)給瀏覽器這對(duì) SSE 流式響應(yīng)非常致命。前端看到的現(xiàn)象就是首字延遲極高甚至超時(shí)。解決方案是給對(duì)應(yīng)的 location 加上關(guān)閉緩沖的配置同時(shí)把 HTTP 版本設(shè)為 1.1允許流式響應(yīng)逐塊傳遞。這個(gè)改動(dòng)只有一個(gè)參數(shù)但效果立竿見(jiàn)影流式輸出的首字延遲從十幾秒降到一秒以內(nèi)。排查這個(gè)問(wèn)題的過(guò)程本身也很值得分享。我先是在瀏覽器網(wǎng)絡(luò)面板看到響應(yīng)處于 pending 狀態(tài)以為是后端處理慢于是去查后端日志發(fā)現(xiàn)響應(yīng)早就開(kāi)始發(fā)送了說(shuō)明數(shù)據(jù)是從服務(wù)器出來(lái)后在某條鏈路被卡住了。再加了緊跟反代層的日志才發(fā)現(xiàn)是 Nginx 緩沖掉了。所以碰到流式問(wèn)題不要一上來(lái)就懷疑模型先從前端網(wǎng)絡(luò)請(qǐng)求面板出發(fā)逐步往后端、反代、上游方向排查。5.2 同名消歧兩個(gè)阿哲引發(fā)的 22 分鐘錯(cuò)亂有次上線后我收到一個(gè)真實(shí)反饋用戶發(fā)了一條 阿哲 那個(gè)客戶方案你再看下過(guò)了幾分鐘一個(gè)完全不是目標(biāo)同事的人回復(fù)了這不是我負(fù)責(zé)的客戶吧。原來(lái)公司真有兩個(gè)阿哲一個(gè)在銷售部一個(gè)在技術(shù)部。用戶選擇下拉框的時(shí)候手滑沒(méi)在意選成了技術(shù)部那個(gè)鬧出個(gè)不大不小的烏龍。解決方案分為兩檔。一檔是在下拉框里同時(shí)展示部門和頭像強(qiáng)制用戶看到足夠的上下文再選另一檔是為協(xié)同頻繁的同事之間增加最近協(xié)作的人是否排序把解析結(jié)果里頻率最高的候選人放到第一位。這兩點(diǎn)拼在一起誤選率降到了幾乎為零。我也意識(shí)到任何涉及人與人之間觸達(dá)的功能模糊匹配必須讓用戶顯式確認(rèn)任何自動(dòng)化都不能完全替代人的判斷。代碼再聰明一旦點(diǎn)錯(cuò)就涉及尷尬和信任問(wèn)題。5.3 重試風(fēng)暴一次模型過(guò)載引發(fā)的連鎖故障某個(gè)下午內(nèi)部模型服務(wù)因?yàn)榱硗庖粋€(gè)大團(tuán)隊(duì)的批處理任務(wù)導(dǎo)致推理響應(yīng)變慢。WorkBuddy 這邊的用戶發(fā)起新請(qǐng)求后遲遲收不到結(jié)果客戶端自動(dòng)重試邏輯被觸發(fā)。重試是好事可我不小心把重試范圍設(shè)得過(guò)大而且重試間隔是固定幾秒用戶一多重試請(qǐng)求像洪水一樣涌向適配層進(jìn)一步加重了上游的負(fù)擔(dān)。結(jié)果就是模型服務(wù)徹底過(guò)載新請(qǐng)求全部被拒頁(yè)面到處報(bào)錯(cuò)。這是典型的重試放大故障。我把重試策略徹底重寫了一遍首次重試延遲 1 秒之后每次乘以 2最大畫在 30 秒同一個(gè)用戶的同一會(huì)話并發(fā)請(qǐng)求數(shù)上限為 1多余的直接提示上一條還沒(méi)完成適配層看到上游返回 503 類錯(cuò)誤時(shí)不再轉(zhuǎn)發(fā)新請(qǐng)求而是直接返回友好錯(cuò)誤并記錄到面板中。調(diào)整之后哪怕上游再出問(wèn)題整個(gè)系統(tǒng)也只是變慢而不是崩潰。運(yùn)營(yíng)維護(hù) AI 服務(wù)重試策略的重要性真的不輸給模型本身的選型。5.4 改了配置沒(méi)生效瀏覽器緩存和鏡像緩存一起坑人開(kāi)發(fā)完新版本后我給測(cè)試環(huán)境部署了一版帶有新的模型列表和 功能配置的鏡像。測(cè)試反饋說(shuō)仍然看到舊的模型名也沒(méi)有 候選框。我檢查后端服務(wù)、環(huán)境變量、數(shù)據(jù)庫(kù)配置全都沒(méi)有問(wèn)題。最后才注意到最新構(gòu)建的 Docker 鏡像里Nginx 鏡像層把之前構(gòu)建過(guò)的舊靜態(tài)資源層原樣保留了瀏覽器那邊又緩存了更早的 JavaScript 文件。兩重緩存疊加導(dǎo)致我看到的永遠(yuǎn)是新代碼測(cè)試看到的永遠(yuǎn)是舊代碼。處理方式是雙管齊下構(gòu)建鏡像時(shí)清理全局緩存層靜態(tài)資源文件名加哈希指紋并配置 Nginx 對(duì)帶哈希的資源做長(zhǎng)效緩存、對(duì) HTML 入口做 no-cache。瀏覽器端也通過(guò)發(fā)版時(shí)換個(gè)靜態(tài)資源目錄路徑強(qiáng)制刷新。這些改動(dòng)本身不值錢但如果沒(méi)有遇到過(guò)這種明明對(duì)卻沒(méi)用的假象新手很難想到問(wèn)題出在緩存上。6. 改造之后的運(yùn)營(yíng)情況以及我還想繼續(xù)動(dòng)刀的方向6.1 團(tuán)隊(duì)真實(shí)使用情況上線到現(xiàn)在一個(gè)多月團(tuán)隊(duì)里的活躍使用人數(shù)大概穩(wěn)定在部門人數(shù)的六成左右日均對(duì)話量維持在幾百輪。主動(dòng) 同事 的協(xié)作消息量雖然只占全部消息的一成但每一輪都確實(shí)替代了一次 IM 上的消息搬運(yùn)。用戶反饋里最受歡迎的功能一個(gè)是對(duì)話中可以直接生成初稿然后 對(duì)應(yīng)人去確認(rèn)另一個(gè)是所有協(xié)作記錄都能在會(huì)話里找到不用再去聊天記錄里翻。這些反饋?zhàn)屛掖_定當(dāng)初定下的模型可控、身份打通、協(xié)作閉環(huán)三個(gè)目標(biāo)方向是對(duì)了。不過(guò)在普及率上還有很多問(wèn)題。不少人把它當(dāng)作別人的 AI 工具覺(jué)得要登錄內(nèi)網(wǎng)才能訪問(wèn)麻煩。我下一步計(jì)劃把 WorkBuddy 的入口接到公司門戶首頁(yè)去減少用戶尋找成本。另一個(gè)反饋是希望手機(jī)上也能方便使用目前 Web 端只在電腦瀏覽器上做了適配手機(jī)瀏覽器打開(kāi)體驗(yàn)一般。這個(gè)只能等后續(xù)安排專門做移動(dòng)端適配優(yōu)先級(jí)不算最高。6.2 現(xiàn)有邊界它能做什么和不做什么我得說(shuō)清楚這套企業(yè)版 WorkBuddy 目前的邊界。它不是一個(gè)全能協(xié)作平臺(tái)做不到像專業(yè)項(xiàng)目管理工具那樣完整地記錄人、事、時(shí)間的復(fù)雜依賴關(guān)系。同事 功能的核心是把 AI 產(chǎn)出轉(zhuǎn)交給真人而不是替代任務(wù)系統(tǒng)。它也不支持復(fù)雜的審批流比如需要主管確認(rèn)后才能把結(jié)果發(fā)送到群里這類流程目前需要外部系統(tǒng)配合。模型能力方面內(nèi)部模型在某些通用常識(shí)類任務(wù)上依然是短板團(tuán)隊(duì)使用時(shí)會(huì)明顯感覺(jué)到回答質(zhì)量在部分場(chǎng)景不如商業(yè) API。好在我當(dāng)時(shí)設(shè)計(jì)了模型適配層這條橋是搭好的如果后續(xù)內(nèi)部部署了更強(qiáng)的模型或者安全策略允許對(duì)接外部模型做兜底只需要調(diào)整路由策略就能實(shí)現(xiàn)切換不需要?jiǎng)訕I(yè)務(wù)代碼。這就是當(dāng)時(shí)堅(jiān)持把模型適配獨(dú)立出來(lái)的遠(yuǎn)期價(jià)值。6.3 下一步三個(gè)明確的方向接下來(lái)我打算動(dòng)刀的地方有三個(gè)。第一個(gè)是給會(huì)話增加智能標(biāo)簽讓系統(tǒng)自動(dòng)學(xué)習(xí)團(tuán)隊(duì)經(jīng)常討論的問(wèn)題類型比如需求評(píng)審、Bug 復(fù)現(xiàn)、方案設(shè)計(jì)并基于這些標(biāo)簽優(yōu)化對(duì)話和協(xié)作通知的分組展示。第二個(gè)是支持更細(xì)粒度的模型權(quán)限目前只是按部門和角色配置大概的可用模型范圍下一步希望做到某個(gè)具體用戶或某個(gè)具體會(huì)話可以綁定指定模型進(jìn)一步滿足不同業(yè)務(wù)場(chǎng)景的需求。第三個(gè)是把協(xié)作閉環(huán)做深不僅要能 人還希望一個(gè)協(xié)作事項(xiàng)被處理后系統(tǒng)能自動(dòng)整理處理結(jié)論并回填到原始會(huì)話這樣整個(gè)過(guò)程形成一個(gè)可追溯、可復(fù)盤的工作流。這些方向每一個(gè)都會(huì)花不少時(shí)間而且都需要持續(xù)收集用戶反饋來(lái)校準(zhǔn)。好在我已經(jīng)把基礎(chǔ)設(shè)施和代碼結(jié)構(gòu)打得夠穩(wěn)后續(xù)不管做哪個(gè)方向都不用擔(dān)心以前的功能崩掉。做這種企業(yè)內(nèi)部落地的項(xiàng)目酒香也怕巷子深基礎(chǔ)設(shè)施穩(wěn)、反饋快、迭代小步快跑比憋一個(gè)大版本更重要。