RDH AI AGENT
前言 總覽 報告 知識 記憶 RAG Cache 對照表
Architecture Review

RDH AI Agent
架構對照與建議

逐項檢視原始架構的每個模組,提出替代方案與待確認問題。
目標:讓 AI 從「被動接收資料」變成「主動使用工具」。

RAG 不是問題,過度依賴 RAG 才是

背景

RAG(Retrieval-Augmented Generation)在 2023-2024 年成為 AI 應用的標準做法。當時的模型 context window 小、推理能力有限,需要系統幫忙篩選資料再餵給模型。這個思路在當時是合理的。

但現在的模型已經不一樣了。Claude Sonnet / Opus 有 200K+ 的 context window,能理解複雜指令、使用工具、做多步推理。模型的能力已經超越「被動接收 chunks 再組合回答」的角色 — 它有能力自己判斷需要什麼資料、自己去找。

這份文件不是在說 RAG 沒用。RAG 的語意搜尋在某些場景仍然有價值。但當我們把 所有資料存取 都建立在 RAG 上面時,等於是在用 2023 年的假設去設計 2026 年的系統。

核心觀察

原架構每次請求都自動觸發三路向量搜尋,然後把結果全部塞進 context。這代表兩件事:

第一,AI 沒有選擇權。不管使用者問什麼,系統都會搜三個 collection、把命中的 chunks 注入。AI 看到的資料是系統幫它挑的,不是它自己判斷需要的。有時候使用者只是閒聊打招呼,系統還是會去撈基因報告和專科知識。

第二,上下文是不完整的。把一份基因報告切成 chunks 再用相似度撈,AI 拿到的只是跟 query 最像的幾個片段,不是完整的文件。它看不到前後文、看不到報告的整體結論、沒辦法比較不同指標之間的關係 — 因為那些資訊分散在其他 chunk 裡,而那些 chunk 這次沒有被搜到。AI 是在拼拼圖,但永遠只拿到幾塊。

這兩個問題加在一起,意味著我們在限制 AI 的能力,而不是在發揮它的能力。

替代思路:Harness 模式

與其幫 AI 決定該看什麼,不如建一個「環境」讓它自己運作。這個概念叫做 AI Harness — 包圍在 AI 外面的系統,提供工具和資料的存取介面,但不替 AI 做決策。

具體來說:AI 像一個登入系統的使用者。它有目錄可以瀏覽(ls)、有檔案可以讀取(cat)、有索引可以查閱、有技能可以載入、有子代理可以派遣。它根據使用者的問題,自己判斷這次需要什麼資料、用什麼工具去取。

這不是理論 — 這種模式已經被 Claude Code、Cursor、Windsurf 等 AI 編程工具驗證過了。它們讓 AI 在一個有工具的環境裡自主運作,效果遠好於把所有程式碼切 chunk 做 RAG。同樣的思路可以應用在健康諮詢場景。

一句話說清楚

原架構:系統替 AI 決定該看什麼(RAG 自動注入)。
建議方向:系統給 AI 工具,讓它自己決定該看什麼(Harness 模式)。

原架構的模式

每次請求 → 自動跑 3 路 RAG

使用者提問 → 系統自動把 query embed → 同時搜 3 個 Qdrant collection → 把搜到的 chunks 全部塞進 system prompt → Claude 回覆

建議的模式

每次請求 → AI 自己決定用什麼工具

使用者提問 → AI 看到問題 → 判斷需要什麼資料 → 用工具(bash / skill / 記憶檢索 / 語意搜尋)去取 → 基於讀到的內容回覆

// 原架構:AI 被動接收 Query → EmbedQdrant ×3 並行Chunks 注入 Context → Claude // 建議方向:AI 主動查找 Query → Claude 自己呼叫工具讀檔案 / 載入 Skill / 查記憶 → Claude 回覆

user_genes_report → 檔案系統 + 索引

原架構把使用者的基因報告 embed 進 Qdrant,用向量搜尋撈相關片段。
但如果報告本身不長,直接存成檔案讓 AI 讀完整版會更好。

為什麼不用 RAG 處理報告

基因報告是一份有結構的文件 — 有分類、有數值、有結論。使用者問「我的心血管風險高嗎?」的時候,AI 不只需要看到心血管那一段,它可能還需要看到相關的代謝指標、家族史風險、整體結論,才能給出完整的回答。

RAG 搜到的是跟 query 最相似的幾個 chunk。如果心血管風險的結論寫在報告的最後一段「整體評估」裡,但 chunk 切割剛好沒有把那段跟心血管關鍵字放在一起,這段就會被漏掉。

更直接的問題是:如果一份報告只有幾頁,為什麼要切?現代模型的 context window 足以裝下整份報告。讓 AI 直接讀完整報告,比讓它拼湊碎片更有效。

原做法

報告 → chunk → embed → Qdrant

報告被切成片段,embed 進 user_genes_report collection。每次搜尋靠 score threshold 0.72 撈相關的 chunks。AI 只看到片段,看不到完整報告。

建議做法

報告 → 檔案 + 索引 → AI 自己讀

每個使用者的報告存成獨立檔案,有一個 index/llms.txt 告訴 AI 有哪些報告。AI 看索引、判斷要讀哪份、然後讀完整內容。

/users/{user_id}/reports/ ├── llms.txt ← 索引檔 ├── report-gene-2025-03.md 首次全方位基因檢測 ├── report-gene-2025-09.md 半年追蹤 └── report-gene-2026-01.md 年度更新

AI 的行為流程

1
讀索引 AI 讀 llms.txt,看到使用者有 3 份報告,最新是 2026-01
2
判斷讀哪份 使用者問「心血管風險高嗎?」→ AI 判斷應該讀最新報告
3
讀完整報告 用工具讀整份報告 — 不是 chunk,是完整內容
4
回答並解釋 AI 基於完整報告回答,能引用具體數值、比較歷史資料
待確認

報告有多長?如果只有幾頁結構化內容,直接全文讀取最有效率。如果長達數十頁,可能需要分段或搭配摘要索引。

報告頻率?一年一次?每月?更頻繁?這影響索引的設計 — 如果報告很多,index 需要更豐富的 metadata(日期、類別、摘要)讓 AI 快速篩選。

報告格式統一嗎?如果不同類型的檢測(全方位 vs. 單項)格式差異大,索引裡需要標明類型,讓 AI 知道每份報告涵蓋哪些檢測項目。

specialist_knowledge → Skill 技能系統

原架構用向量相似度做「專科路由」— 本質上是用 embedding 當分類器。
但 AI 模型本身就有理解語意的能力,給它一份目錄就能自己判斷。

為什麼 Skill 比向量路由好

原架構用 embedding 相似度決定「這個問題屬於哪個專科」。但這其實是用一個比較笨的方法(向量距離)去做一個 LLM 本身就很擅長的事(理解語意分類)。

Claude 看到一份索引寫著「可用的專業領域:睡眠、營養、心血管、抗老」,它完全有能力判斷使用者問的「我最近一直睡不好,跟基因有關嗎?」應該對應到睡眠領域。不需要先 embed 這句話,再去跟四個 collection 算 cosine similarity。

Skill 系統還有一個實際的好處:維護成本低很多。新增一個領域 = 加幾個 markdown 檔案 + 更新索引。不需要建新的 Qdrant collection、設計 embedding pipeline、調 threshold。知識的更新也是直接改檔案,不用重新 embed。

另外,Skill 可以同時包含知識和回答指引(persona)。原架構把這兩個拆開 — specialist_knowledge 放知識,sysSpecialist 放 persona — Skill 系統可以把它們合在一起,因為「怎麼回答」跟「知道什麼」本來就是一體的。

原做法

向量路由 + 專科 persona prompt

query embed → 搜 specialist_knowledge collection (threshold 0.72) → 命中的專科知識注入 context → 另外載入該專科的 sysSpecialist persona prompt

建議做法

Skill 系統 — 知識包 + 回答指引

AI 有一份 skill index,列出所有可用的領域(睡眠、營養、心血管、抗老)。AI 看到使用者問題後,自己決定載入哪個 skill。Skill 本身包含知識和回答方式指引(取代獨立的 persona prompt)。

/skills/ ├── index.txt ← 所有可用技能的索引 │ ├── sleep/ 睡眠品質與基因 │ ├── knowledge.md 領域知識 │ └── guidance.md 回答風格 + 常見問題指引 │ ├── nutrition/ 營養與代謝 │ ├── knowledge.md │ └── guidance.md │ ├── cardiovascular/ 心血管風險 │ └── ... │ └── anti-aging/ 抗老化 └── ...

重要原則:如果 skill 知識庫沒有提到的內容,AI 仍然可以回答, 但必須標明「這是我的推論,不在目前的專業知識庫中」。 維持透明度,但不限制 AI 的能力。

附帶效果

Skill 吃掉 Persona。原架構的 sysSpecialist(專科 persona prompt)可以整合進 skill 的 guidance.md 裡。不需要額外維護一層 persona prompt,減少系統複雜度。

user_memory → Wiki 網路 + 子代理

原架構把使用者的對話記憶 embed 進 Qdrant,threshold 設 0(全撈)。
建議改成結構化的 wiki 網路 — 有索引、有連結、有層次。

為什麼記憶需要結構,不是向量

原架構的 user_memory collection 有一個耐人尋味的設定:score threshold 設為 0。這等於「不管相似度多低,全部都回傳」。如果向量搜尋的結果是不做篩選全部撈出來,那用向量搜尋的意義是什麼?

記憶的價值在於關聯性,不在於相似度。使用者上個月提過她在減肥,今天問「為什麼我一直好餓?」— 這兩件事的 embedding 距離可能很遠(減肥 vs. 飢餓感),但它們在語意上是高度相關的。向量搜尋不擅長處理這種跨主題的關聯。

Wiki 網路的做法是讓記憶之間有明確的連結。「減重紀錄」這份記憶連結到「飲食偏好」和「健康目標」。AI 看到使用者問飢餓感,從索引找到「減重紀錄」,順著連結就能找到飲食偏好的細節。這是結構化導航,不是機率性的相似度匹配。

而當記憶量累積到主代理沒辦法全部掃一遍的時候,子代理檢索機制可以幫忙。主代理把使用者的問題交給一個專門的記憶子代理,子代理掃描 wiki 網路後回傳最相關的幾個頁面路徑和摘要。主代理再自己決定要不要深入讀。

原做法

記憶 → embed → Qdrant (threshold: 0)

每段記憶存成 embedding。搜尋時 threshold 設 0,等於把所有記憶都撈出來塞進 context。隨著對話累積,context 會被不相關的記憶佔滿。

建議做法

Wiki 網路 + 常駐索引 + 子代理檢索

記憶存成互相連結的 Markdown 檔案(Obsidian 風格)。AI 常駐載入一個精簡的頂層索引。記憶量大時,派子代理去搜尋相關記憶,回傳路徑和摘要。

/users/{user_id}/memory/ ├── MEMORY.md ← 常駐索引(精簡,每次都載入) ├── health-goals.md → [[weight-loss]], [[sleep]] ├── weight-loss.md → [[health-goals]], [[nutrition-prefs]] ├── nutrition-prefs.md → [[weight-loss]] ├── sleep.md → [[health-goals]] └── family-history.md → [[cardiovascular]]

子代理檢索流程

主代理:「使用者問到減重進度,幫我找相關記憶」 │ ▼ 記憶子代理:掃描 wiki 網路 ├── weight-loss.md (直接相關) ├── nutrition-prefs.md (被 weight-loss 連結) └── health-goals.md (hub 頁面提及) │ ▼ 回傳:3 個路徑 + 摘要 │ ▼ 主代理:選擇讀取 weight-loss.md 完整內容

從「架構核心」降級為「工具之一」

RAG 不是不能用。語意搜尋有時比關鍵字搜尋更準。
但它應該是 AI 工具箱裡的其中一把工具,由 AI 自己決定什麼時候用。

降級不是刪除,是回歸正確定位

這裡要強調的是:我們不是在說 RAG 沒用。語意搜尋在某些場景確實比關鍵字搜尋更準 — 比如使用者用口語描述一個症狀,跟知識庫裡的醫學術語可能字面上完全不同,但語意是相關的。

問題是原架構把 RAG 當成唯一的資料存取方式。每次請求都跑向量搜尋,不管這次搜尋有沒有意義。使用者問「你好,今天天氣不錯」,系統照樣去搜三個 collection。

把 RAG 變成一個 tool,意味著 AI 自己判斷什麼時候需要語意搜尋。很多時候 AI 已經知道要讀哪份報告(因為它看了索引),直接讀就好,不需要繞一圈做 embedding → cosine similarity → 取 top-k。語意搜尋留給真正需要模糊匹配的場景 — 比如在大量知識庫裡找一個使用者用不精確語言描述的概念。

原定位

RAG = 整個系統的骨架

每次請求都跑 RAG。三個 collection 並行搜尋。embedding + vector search 是唯一的資料存取方式。

建議定位

RAG = 工具箱裡的一把工具

提供 semantic_search 工具,AI 覺得需要模糊語意匹配時才呼叫。結果回傳來源路徑 + 片段,AI 可以進一步讀完整檔案。跟 bash grep 並列,不是凌駕其上。

AI 的完整工具箱 bash ls, cat, grep, head — 精確檔案操作 semantic_search 向量搜尋 — 模糊語意匹配,回傳路徑 read_skill 載入指定領域的技能知識 ask_user 向使用者提問釐清需求 sub_agent 派子代理做特定任務

操控權的轉移:不是系統每次都自動跑 RAG 塞東西給 AI, 而是 AI 自己評估「這個問題我需要語意搜尋嗎?還是直接讀檔就好?」 把決策權還給 AI。

不需要過度設計的部分

原做法 — 4 層 Prompt Cache

sysDefault → sysSpecialist → sysCustomer → requestCtx

手動設計 4 層 system prompt,前 3 層 cache(TTL 5min),最後 1 層動態。這是自己發明的 caching 策略。

建議 — 按官方做法就好

跟著供應商的 cache 機制走

Claude 手動標 cache breakpoint + TTL。OpenAI / Google 自動做。不需要自己設計分層策略 — 按照官方文件的做法,對話到哪就 cache 到哪。

Streaming

SSE Stream 回 Client

原架構有標示 SSE Stream,這是正確的。

沒有異議

Streaming 是基本功能

Vercel AI SDK 等框架內建支援。不是架構設計上的挑戰。

技術棧

Go + Hertz + PostgreSQL + Qdrant

原架構選擇 Go 語言和 Hertz 框架。

彈性

技術棧根據團隊熟悉度選擇

重點是架構設計思路。語言框架和資料庫的選擇可以保持彈性。

逐項對比

模組 原架構 建議方向
資料存取模式 系統自動 embed + 搜尋 + 注入 AI 透過工具自主查找
基因報告 chunk → embed → Qdrant
(threshold 0.72)
完整檔案 + llms.txt 索引
→ AI 自己讀完整報告
領域知識 specialist_knowledge collection
+ sysSpecialist persona
Skill 系統(知識 + 回答指引打包)
Persona 被 Skill 吸收
使用者記憶 user_memory collection
(threshold 0 = 全撈)
Wiki 網路 + 常駐索引
+ 子代理檢索
RAG 架構骨架(每次都跑) 工具之一(AI 決定用不用)
Prompt Cache 自行設計 4 層分層 依照供應商官方做法
AI 的角色 被動:吃什麼由系統決定 主動:自己判斷需要什麼
擴展新知識 新 Qdrant collection + embedding pipeline 新 skill 檔案 + 更新索引