問題背景
大部分小公司都長得差不多:知識散落在 Wiki、Google Docs、Notion 三個地方,三個搜尋框、三套權限。新人在系統間切換找一份 SOP;資深同事每天被打斷回答早就寫過的問題;KM 變成「只寫不查」的系統。
我所在的電商公司就是這樣 —— 30 個人,HR 政策放在 Word 檔、SOP 在 Wiki.js、產品規格在 Notion。直到有同事一個月內第三次來問我「客訴升級的流程圖在哪?」,我決定動手做點什麼。

解決構想
把一個對話介面放進大家本來就會用的工具 —— Microsoft Teams —— 然後讓它一次搜遍所有來源。
這個 Bot 要做的是:
- 定時從 Wiki.js、Google Docs、Notion 同步內容
- 用 RAG(檢索增強生成)回答問題,並引用原文出處
- 讓貢獻新知識像傳訊息一樣簡單 —— 隨手記一段 → 一個指令發布成 KM 頁面
我設的標準:要做到比直接問資深同事還好用,不能只是「勉強堪用」。
動手前的三個決策
決策一:住進 Teams,不蓋新網站。 另一條路是做一個獨立的問答網站——介面自由度高很多。我沒走,因為知識工具的死因通常不是功能不夠,是沒有人記得去開它。放進大家每天本來就開著的 Teams,提問的成本趨近於零;順帶的紅利是身分驗證直接沿用公司帳號,我完全不用自己做登入系統——帳號密碼這種東西,自己刻的版本通常只做對一半。
決策二:回答一定附出處。 AI 會講錯話,這是前提不是意外。所以每個回答都附上引用的原文連結,使用者一鍵就能對原文——把「驗證 AI」的成本降到最低,信任才建立得起來。
決策三:讓它能寫,但寫的每一筆都留痕跡。 唯讀的知識庫會慢慢腐爛,因為發現錯字的人沒有權限改。我決定開放寫回,但每一次修改都自動留下稽核註記(Notion 沒有逐段編輯歷史,這筆註記就是事後追查的唯一線索)。能力可以開放,前提是出了事追得回來。
具體實作
帶查詢改寫的多來源 RAG
天真的 RAG 流程(把問題 embed → cosine 搜尋 → 生成答案)在簡報 demo 看起來很好,但碰到真實使用者就破功。真實使用者打的是「Line 推播設定位置?」這種模糊問句 —— 正確文件「Line 推播設定」被「位置」這個詞拉去 UI 設定類文件,分數連前五都進不了。
解法:搜尋前先用 GPT 把問題展開成 2–3 個語意變體,每個變體都搜,然後依每段內容的最高分合併排序。對使用者實際會打的那種短句問題,召回率有顯著提升。
後來又補上混合搜尋(語意 + 關鍵字加權)。起因是我自己踩到一個破口:一份需求文件明明在 Notion 裡,用口語問法就是搜不到。純語意檢索對專有名詞、產品代號這種詞真的弱 ——「粥寶寶」對模型來說只是一串字。加上關鍵字加權之後,同一個問題的排名從第 27 名升到第 5 名,寫得清楚的問題則穩定排第一。
但這裡有個誠實的限制:查詢展開是 LLM 做的,不是確定性的,所以同一個問題重問排名會抖。真正穩的做法不是把檢索調到完美,是讓 AI 同時拿到程式碼與文件兩邊去比對 —— 那個想法後來變成另一個專案。
優化的輸入永遠是真實使用者的問法,不是我想像中的問法。 每一次搜不到,都是一筆訓練材料;我把它餵回系統,下一個人就搜得到了。

把知識「寫回」知識庫
真正的關鍵功能不在搜尋 —— 而是 Bot 還能修改、附加、發布內容回到三個來源。
#修改 讓任何人不用學 Wiki.js 後台就能修一個錯字。#追加 讓他補一個 SOP 漏掉的步驟。#發布 把一段隨手記轉成排版整齊的 KM 頁面(AI 幫你補標題和結構)。每一次 Notion 修改還會自動加上一筆稽核註記 —— 因為 Notion 沒有 per-block 編輯歷史。
效果:知識庫被「真正注意到問題的人」持續修正,而不是等某個編輯被通知再去改。貢獻知識從「要排時間做的事」變成「順手就做」。

個人助理功能
我順手在上面加了筆記和提醒功能 —— 反正 UI 都在了。Bot 同時也是個人的便利貼。每天 9/14/17 點的提醒推送背後有個小工程故事(見下面「學到的教訓」)—— 本來應該很簡單,結果並不。
全流程的圖片支援
使用者可以在筆記和提醒裡附圖。圖片存在私有 Azure Blob,每次顯示產生短期 SAS token,沒有任何公開 URL。筆記發布時,圖片會嵌入 KM(base64)或上傳到 Google Docs(透過 Drive),原始 blob 隨即刪除 —— 暫存生命週期刻意做短。
你沒有留的資料,永遠不會從你這裡外洩。 暫存檔活得越短,要保護的東西就越少。
實際運作狀況
從 2026 年 3 月上線至今。穩定維持 690+ 份文件、約 9,600 個索引切片、三個來源、每小時同步、回答時間 < 3 秒、每月 Azure 成本約 NT$2,500。7 月起補上使用遙測,開始能看見真實的使用分佈而不只是我的觀察。
最有意思的不是技術指標,是使用樣態:
- 客服用它在電話中即時查政策門檻
- 新人把它當自助 onboarding 工具 —— HR 少了很多「表單在哪裡」的中斷
- 一群進階使用者內化了 #發布 流程,這個 Bot 變成他們主要的知識貢獻入口
知識庫本身的成長速度比以前快。不是因為有人被要求要寫,而是因為門檻降低到讓「貢獻」變成「使用副作用」。
學到的教訓
幾個必須真實上線才會浮出的問題:
- node-cron 會悄悄失效。 長時間運行的 process 下,整點排程會「不知不覺停了」,但短週期排程(*/5)照樣活著。我第一次上的修法「理論上有用」,但只在重啟時生效 —— 而 Bot 沒重啟。真正的解法:用 10 分鐘輪詢做安全網,完全不依賴 cron。
- Azure App Service 預設用 32-bit worker,Node heap 被砍到 90MB,跟 —max-old-space-size 設多少無關。6000 個切片就 OOM。一行 CLI 就能切換。
- wwwroot 在 zip-deploy 時會被清空,包括 data/ 如果你把 vector store 放那。曾經因此整個知識庫消失。解法:用 env 變數把路徑指向持久化目錄。
- Teams 傳來的 content-type 是 image/*(通配符,不是實際 mime),而且 contentUrl 需要 Bearer 認證才能下載。一連串錯誤假設要慢慢拆掉。
- 為了省上傳時間做增量部署,把正式站弄掛兩次。 完整包 110MB、增量包只有 1.2MB,所以我只傳了改動的部分、沿用伺服器上的 node_modules。結果 app 起不來、crash loop 十分鐘。從 Kudu 撈 iisnode 的崩潰堆疊才看見真兇:遙測套件在載入一個第三方模組時,讀到一個「寫到一半」的檔案 —— 根因不是我的程式碼,是增量部署造成伺服器上的套件狀態不一致。改回完整 zip 一次替換就正常了。省下的上傳時間,用兩次生產中斷賠了回去。
- 完整部署會把不在包裡的東西刪掉。 修好上面那件事之後,完整包裡沒有 web/ 資料夾,部署時就把網頁版聊天介面的首頁刪了(Teams 端沒事,所以差點沒發現)。補一個 web-only 的包回去才恢復。
這串清單我照事故處理的規矩走完:每一次中斷都寫了根因分析,兩次部署事故的教訓變成一條硬規則——這個服務的部署永遠用完整包,一次替換,不做增量。規則不靠記性,寫進部署腳本裡。
完整的 postmortem 在 repo 裡,包含架構選擇、和我會重做的部分。
下一步計畫
- 圖片 OCR:很多 SOP 把關鍵資訊放在截圖裡,純文字 RAG 看不到。在同步階段對圖片做一次 vision pass。
- 可換的 LLM provider:現在綁 Azure OpenAI 是有實務理由的(亞洲直連 OpenAI / Anthropic 會 403),但抽象一層 interface 就能換到 OpenAI / Anthropic / Bedrock。
技術架構
TypeScript、Node.js、Azure OpenAI(gpt-4.1 + text-embedding-3-small)、Microsoft Bot Framework、Wiki.js GraphQL、Google Drive API + Docs API、Notion API、Azure Blob Storage、Azure SQL、Application Insights。部署在 Azure App Service。
原始碼在 GitHub —— README 有完整的架構說明,需要技術細節可以看。