前九篇都在講怎麼讓外部引擎引用你。 這一篇反過來:你自己就是那個引擎。
有趣的地方在於——當你能看到檢索管線的每一層時, 會發現「內容不可引用」的原因,和公開網站一字不差。
一、情境
公司 B2B 金融科技,400 人(工程 180 人)
背景 2026 年初上線內部 AI 助手,接 Slack
資料源 GitHub Enterprise(86 個 private repo)、Confluence(4,200 頁)、
Notion(部分團隊)、Slack 歷史訊息、Jira、Google Drive
技術 GCP:Vertex AI Search(as RAG 檢索層)+ Gemini/Claude 生成
文件同步用 Cloud Run Job + Cloud Scheduler
使用者 全體員工,尖峰每日約 900 次查詢
問題:上線三個月後,工程團隊的使用率從第一週的 71% 掉到 12%。
內部滿意度調查(n=142)
──────────────────────────────────────────────
「答案正確」 23%
「答案有引用來源」 61%
「引用的來源是對的」 31% ← 關鍵
「比自己搜尋快」 34%
「我已經不用了」 58%
最常見的自由填答:
「它引用了一份 2022 年的舊 RFC,那個架構早就換掉了」
「它把 staging 的設定當成 production 講」
「它從一個廢棄的 repo 抓答案」
「引用連結點進去看不到它講的那句話」
「引用的來源是對的」只有 31% ——這和 Part 6 那家製造業的「描述正確率 31%」是同一個數字,也是同一類問題。
二、關鍵洞察:內部 RAG 的失敗模式,和公開 GEO 完全同構
把 Part 2 的六階段管線套上去:
階段 公開 GEO 的失敗 內部 RAG 的失敗
──────────────────────────────────────────────────────────────────────
① Query 用詞和使用者不同 內部黑話 vs 文件正式用語
fan-out (「風控引擎」vs「RiskSvc」)
② Retrieval crawler 被擋 / 未索引 權限過濾掉了 / 同步器沒接到
/ 二進位檔案沒解析
③ Rerank 整頁只有一段相關 Confluence 頁面 8,000 字,
主題混雜,chunk 被稀釋
④ Grounding 沒有可接地的事實 文件寫「參考 XXX 的做法」,
沒有具體值
⑤ Citation 被用了但沒標來源 引用到 anchor 不存在的位置
⑥ 查核 網域信任不足 沒有「哪份文件是權威版本」
的訊號 ← 內部特有、且最嚴重
第六項是內部 RAG 獨有的killer:公開網路上有 PageRank、有網域權威、有時間訊號;內部知識庫裡,一份 2022 年的過期 RFC 和一份昨天更新的架構文件,在檢索器眼中是平等的。
三、診斷:三個具體發現
發現 1:46% 的 chunk 來自「應該被刪掉的文件」
1# 分析:對 200 個真實查詢,記錄每次被檢索到的 top-10 chunk 來源
2# 然後標註每份來源文件的狀態
3
4來源文件狀態 被檢索到的 chunk 佔比
5────────────────────────────────────────────
6現行有效 54%
7已被取代但未刪除 21% ← 過期 RFC、舊架構文件
8草稿 / 未完成 12%
9個人筆記(誤放進共享空間) 8%
10已廢棄專案的 repo 5%
11────────────────────────────────────────────
12應該被排除的合計 46%
這不是 AI 的問題,是知識庫從來沒有人整理過。 上線 RAG 只是讓這個長期問題第一次變得可見。
發現 2:Confluence 頁面的平均長度是 6,200 字,且主題混雜
典型的 Confluence 頁面
「支付服務 - 架構與維運」(9,400 字)
├─ 架構圖與元件說明
├─ 本地開發環境設定
├─ 部署流程
├─ 監控與告警
├─ 常見故障排除
├─ 2023 年的遷移紀錄(歷史,已不適用)
└─ 待辦事項與 TODO
問題:任何一個具體問題(「支付服務怎麼部署?」)
在這頁的 rerank 分數都很低——因為整頁只有 12% 相關。
而且「2023 年遷移紀錄」那一段可能被當成現況引用。
發現 3:程式碼 repo 的 chunk 幾乎無法接地
被檢索到的 code chunk 的問題
──────────────────────────────────────────────
沒有檔案路徑上下文 chunk 只有函式本體,不知道在哪個 repo/服務
沒有版本 / branch 資訊 可能是三年前的 feature branch
註解稀少 純程式碼的語意檢索效果差
測試檔案與正式碼混在一起 引用到 mock 的設定值當成真實設定
四、解法:把 GEO 的四層模型翻譯成內部版本
公開 GEO 內部 RAG 對應
────────────────────────────────────────────────────────────────
Layer 1 技術層 同步管線與權限
「crawler 拿得到內容嗎」 → 「同步器接到了嗎、權限過濾對嗎」
Layer 2 結構層 metadata 與 chunk 邊界
「JSON-LD、標題層級」 → 「front matter、標題分節、來源標註」
Layer 3 內容層 文件寫作規範
「chunk 獨立、可接地」 → 完全相同的規則
Layer 4 實體層 權威訊號
「網域權威、sameAs」 → 「哪份是 source of truth」← 最關鍵
決策 1:建立「權威訊號」——這是內部 RAG 最重要的一件事
公開網路有 PageRank,內部沒有。所以你必須自己造一個。
做法:所有納入索引的文件必須有 front matter,並用它計算檢索加權。
1---
2doc_id: svc-payment-arch
3title: 支付服務架構
4status: active # active | deprecated | draft | historical
5authority: canonical # canonical | reference | notes
6owner: team-payments
7reviewed_at: 2026-06-18
8review_cycle_days: 180
9supersedes: [rfc-0042, svc-payment-arch-v1]
10applies_to:
11 env: [production]
12 version: ">=3.2"
13tags: [payment, architecture, backend]
14---
同步管線依此計算 boost,並直接排除不該出現的內容:
1# jobs/index_sync.py —— Cloud Run Job,每 6 小時跑一次
2from datetime import date, timedelta
3
4STATUS_BOOST = {
5 "active": 1.0,
6 "reference": 0.6,
7 "draft": 0.2,
8 "deprecated": 0.0, # 0 = 完全排除
9 "historical": 0.0,
10}
11AUTHORITY_BOOST = {"canonical": 1.0, "reference": 0.7, "notes": 0.35}
12
13
14def freshness_factor(reviewed_at: date, cycle_days: int) -> float:
15 """過了 review 週期就開始衰減,兩倍週期後降到 0.3。"""
16 age = (date.today() - reviewed_at).days
17 if age <= cycle_days:
18 return 1.0
19 over = (age - cycle_days) / cycle_days
20 return max(0.3, 1.0 - 0.7 * min(over, 1.0))
21
22
23def compute_boost(fm: dict) -> float:
24 s = STATUS_BOOST.get(fm.get("status", "draft"), 0.2)
25 if s == 0.0:
26 return 0.0
27 a = AUTHORITY_BOOST.get(fm.get("authority", "notes"), 0.35)
28 f = freshness_factor(fm["reviewed_at"], fm.get("review_cycle_days", 365))
29 return round(s * a * f, 3)
30
31
32def should_index(fm: dict, path: str) -> tuple[bool, str]:
33 if fm.get("status") in ("deprecated", "historical"):
34 return False, "status excluded"
35 if not fm.get("owner"):
36 return False, "no owner" # 無主文件一律不索引
37 if "/archive/" in path or "/wip/" in path:
38 return False, "archived path"
39 if compute_boost(fm) < 0.15:
40 return False, "boost below threshold"
41 return True, ""
「無主文件一律不索引」這一條爭議最大,效果也最大。 它強迫每份文件有人負責,也自動排除了個人筆記。
推到 Vertex AI Search 時把 boost 帶進 structData:
1from google.cloud import discoveryengine_v1 as de
2
3def to_document(doc_id, content, fm, boost):
4 return de.Document(
5 id=doc_id,
6 struct_data={
7 "title": fm["title"],
8 "status": fm["status"],
9 "authority": fm["authority"],
10 "owner": fm["owner"],
11 "reviewed_at": fm["reviewed_at"].isoformat(),
12 "boost": boost,
13 "env": fm.get("applies_to", {}).get("env", []),
14 "source_url": fm["source_url"],
15 },
16 content=de.Document.Content(raw_bytes=content.encode(), mime_type="text/plain"),
17 )
18
19# 查詢時套用 boost spec
20boost_spec = de.SearchRequest.BoostSpec(
21 condition_boost_specs=[
22 de.SearchRequest.BoostSpec.ConditionBoostSpec(
23 condition='status: ANY("active") AND authority: ANY("canonical")', boost=0.5),
24 de.SearchRequest.BoostSpec.ConditionBoostSpec(
25 condition='authority: ANY("notes")', boost=-0.3),
26 ]
27)
AWS Bedrock Knowledge Bases 的等價做法:把同樣的欄位寫進
.metadata.json伴隨檔,查詢時用retrievalConfiguration.vectorSearchConfiguration.filter過濾status,並在 rerank 階段用 metadata 排序。概念完全相同。
決策 2:文件切分——把 Confluence 大頁拆成單一主題頁
和 Part 7 電商、Part 8 landing page 的結論一字不差:主題單一的 1,000 字勝過主題混雜的 9,000 字。
改前
「支付服務 - 架構與維運」(9,400 字,7 個主題)
改後
svc-payment/architecture.md 架構與元件(1,200 字)
svc-payment/local-dev.md 本地開發環境怎麼設定?(800 字)
svc-payment/deploy.md 怎麼部署支付服務?(1,100 字)
svc-payment/monitoring.md 有哪些監控與告警?(900 字)
svc-payment/runbook.md 常見故障怎麼排除?(1,800 字)
archive/2023-migration.md status: historical(不索引)
runbook.md 的寫法特別重要——它是內部 RAG 命中率最高的文件類型:
1---
2doc_id: svc-payment-runbook
3status: active
4authority: canonical
5owner: team-payments
6reviewed_at: 2026-07-01
7review_cycle_days: 90
8---
9
10# 支付服務故障排除
11
12## 告警 `PaymentServiceHighLatency` 觸發時怎麼處理?
13
14**症狀**:P99 延遲超過 800ms 持續 5 分鐘。
15
16**最常見原因(依發生頻率)**
171. 下游銀行 API 逾時(佔歷史事件的 62%)
182. 資料庫連線池耗盡(21%)
193. 對帳批次作業與線上流量爭用(11%)
20
21**排查順序**
221. 看 Grafana dashboard `payment-svc-overview` 的
23 `downstream_bank_latency` panel。若該值同步升高 → 原因 1,
24 聯絡窗口見下方。
252. 檢查 `pgbouncer_pool_waiting` 是否 > 0 → 原因 2,
26 執行 `kubectl scale deploy/payment-svc --replicas=12`。
273. 確認當下是否為 02:00-04:00 的對帳窗口 → 原因 3,可等待。
28
29**升級路徑**:15 分鐘內未緩解 → #payment-oncall → Payments TL。
30
31**下游銀行窗口**:見 `svc-payment/contacts.md`(需 VPN)。
這份文件符合 Part 3 的九條規則裡的每一條——問句標題、答案前置、具體數字、明確主詞、可原句搬用。內部文件和公開內容的「可引用性」要求是相同的。
決策 3:程式碼索引要帶上下文
純程式碼 chunk 幾乎無法被有效檢索。解法是在索引前包裝上下文:
1# 索引 code chunk 前,前置一段自動生成的上下文標頭
2def wrap_code_chunk(repo, path, symbol, code, git_meta):
3 header = f"""[Repository] {repo}
4[File] {path}
5[Symbol] {symbol}
6[Branch] {git_meta['default_branch']}
7[Last modified] {git_meta['last_commit_date']} by {git_meta['last_author']}
8[Service] {infer_service(repo, path)}
9[Is test] {'yes' if is_test_path(path) else 'no'}
10"""
11 return header + "\n```" + lang_of(path) + "\n" + code + "\n```"
排除規則同樣重要:
1EXCLUDE_PATTERNS = [
2 "**/node_modules/**", "**/vendor/**", "**/*.min.js",
3 "**/test/fixtures/**", # mock 設定會被當成真實設定
4 "**/*.pb.go", "**/generated/**",
5 "**/migrations/**", # 歷史 migration 常誤導
6]
7EXCLUDE_REPOS_IF = lambda r: (
8 r["archived"] or
9 r["pushed_at"] < eighteen_months_ago or
10 r["default_branch_commits_last_90d"] == 0
11)
test/fixtures 這一條救了很多錯誤答案——助手原本會把測試用的假 API endpoint 當成真實設定講出來。
決策 4:讓答案帶出「權威等級」與「新鮮度」
這是內部 RAG 相對於公開引擎的優勢:你可以控制答案的呈現方式。
系統 prompt 中加入的規則:
「每個引用必須標註來源文件的 status、owner 與 reviewed_at。
若最高權重的來源 reviewed_at 距今超過其 review_cycle_days,
必須在答案開頭警告:『注意:主要來源已超過審閱週期』。
若找不到 authority=canonical 的來源,必須明說
『我找不到權威文件,以下答案來自參考性文件,請向 <owner> 確認』。」
實際輸出的樣子:
支付服務的部署流程是透過 ArgoCD,從 main 分支自動同步到 staging,
production 需要在 #payment-deploy 頻道由 TL 手動核准後才會 sync。
來源:
[1] svc-payment/deploy.md ✅ canonical · team-payments · 審閱於 2026-07-01(22 天前)
[2] platform/argocd.md 📘 reference · team-platform · 審閱於 2026-05-14(70 天前)
這個小改動讓「引用的來源是對的」這項滿意度從 31% 跳到 78%——不是因為檢索變準,而是因為使用者能自己判斷該不該信。降低錯誤的傷害,和降低錯誤率一樣有價值。
決策 5:把文件品質變成可量測的分數
複用 Part 4 的 geo-audit.py 概念,改成內部版本,跑在文件 repo 的 CI 上:
1# ci/doc_audit.py —— 對每份文件打分,低於門檻則 PR 失敗
2CHECKS = [
3 ("has_front_matter", lambda d: bool(d.fm), 25),
4 ("has_owner", lambda d: bool(d.fm.get("owner")), 20),
5 ("reviewed_recently", lambda d: d.days_since_review < d.cycle * 1.5, 15),
6 ("question_headings", lambda d: d.question_heading_ratio >= 0.4, 10),
7 ("single_topic", lambda d: d.word_count < 2500, 10),
8 ("has_concrete_data", lambda d: d.number_density >= 0.5, 10),
9 ("no_orphan_pronoun", lambda d: d.orphan_para_ratio < 0.25, 5),
10 ("links_resolve", lambda d: d.broken_link_count == 0, 5),
11]
搭配一個每週自動發到各 team channel 的報表:
📊 team-payments 文件健康度(2026-07-22)
文件數 34
平均分數 76(上週 71)
超過審閱週期 6 份 ← 需處理
無 owner 0 份
被 AI 助手引用次數 412 次(全公司第 2)
引用後被使用者標記為 4 次(1.0%,全公司平均 3.2%)
「答案錯誤」
⚠️ 超過審閱週期的文件:
svc-payment/contacts.md 逾期 41 天
svc-payment/local-dev.md 逾期 12 天
...
「被引用次數」讓文件維護第一次有了可見的回報。原本沒有人想寫文件,因為看不到效果;現在工程師會為了「我的 runbook 被引用了 412 次」而主動更新。
五、結果
指標 改前(2026-03) 改後(2026-08)
──────────────────────────────────────────────────────────
「答案正確」滿意度 23% 74%
「引用的來源是對的」 31% 78%
「我已經不用了」 58% 9%
每日查詢數 110(衰退中) 1,340
工程團隊週活躍率 12% 81%
檢索品質
來自 deprecated/draft 的 chunk 46% 3%
平均 chunk 字數 1,850 620
top-5 命中率(人工標註 200 題) 34% 81%
文件側
有 front matter 的文件 8% 94%
有 owner 的文件 22% 94%
超過審閱週期的文件 71% 18%
平均文件長度 6,200 字 1,400 字
商業側(估算)
平均每次查詢節省時間 — 8.5 分鐘(自陳)
每月節省人時 — 約 190 小時
新人 onboarding 到第一次 PR 18 天 11 天
投入分解
項目 人天
────────────────────────────────────────────
Front matter 規範 + 遷移工具 6
文件拆分(Confluence → repo) 28 ← 最大宗,各團隊分攤
Boost / 過濾邏輯 5
Code chunk 上下文包裝 4
答案呈現改造(權威標註) 2 ← 投報率最高
doc_audit CI + 週報 6
────────────────────────────────────────────
合計 51 人天(跨 6 個團隊,3 個月)
注意「答案呈現改造」只花了 2 人天,卻貢獻了滿意度提升的最大一塊。
六、三個 insight
1. 內部 RAG 的問題 95% 是文件問題,不是模型問題
團隊原本的假設:換更好的模型、調 chunk size、加 reranker
實際的根因: 46% 的內容根本不該被索引
投入 vs 回報
換模型 / 調參數 投入大,回報 < 5pp
文件治理與拆分 投入大,回報 > 40pp
答案呈現改造 投入 2 人天,回報 > 20pp
在動模型之前,先量「被檢索到的 chunk 有多少來自不該存在的文件」。 這個數字通常會讓人震驚。
2. 內部知識庫沒有 PageRank,你必須自己造權威訊號
這是公開 GEO 與內部 RAG 最大的差異,也是最容易被忽略的一點。
公開網路的天然訊號 內部要自己造的
────────────────────────────────────────────────
外部連結數量 status(active / deprecated)
網域年齡與權威 authority(canonical / reference / notes)
更新頻率 reviewed_at + review_cycle_days
被其他來源交叉引用 supersedes 關係鏈
搜尋點擊回饋 「答案錯誤」標記回饋
沒有這些,你的檢索器會平等對待一份 2022 年的廢棄 RFC 和昨天的架構文件——而 LLM 會很有自信地引用錯的那一份。
3. 「可引用性」是一種通用的內容品質,不是 SEO 技巧
這是整個系列最重要的收斂點。
同一組規則,在三種完全不同的場景下都成立:
公開網站 GEO 內部 RAG 給人讀
─────────────────────────────────────────────────────────
chunk 獨立成立 → chunk 獨立成立 → 段落自成一體
主詞明確 → 主詞明確 → 不用讀上文
問句式標題 → 問句式標題 → 好找
具體數字取代形容詞 → 具體數字取代形容詞 → 可執行
標註日期與方法 → reviewed_at → 知道還準不準
單一主題 → 單一主題 → 不用滑很久
比較表 → 比較表 → 一眼看懂
寫得好的文件,同時對人、對外部引擎、對自家 RAG 都好。 反過來說,如果你為了 GEO 做的事會讓人類讀者變痛苦,那多半是做錯了。
七、可以直接抄的清單
先量這三個數字(做任何事之前)
□ 被檢索到的 chunk 中,來自 deprecated/draft/無主文件的比例
□ 平均文件長度與主題數
□ 人工標註 100-200 題的 top-5 命中率
Layer 1:管線與權限
□ 確認每個資料源真的被同步到(不要相信設定,要抽查)
□ 排除 archived repo、18 個月未推送的 repo
□ 排除 test/fixtures、generated、migrations、vendor
□ 權限過濾在檢索層做,不要在生成層做
Layer 2:metadata 與 chunk
□ 強制 front matter:doc_id / status / authority / owner /
reviewed_at / review_cycle_days / supersedes / applies_to
□ 無 owner 的文件不索引
□ status 為 deprecated / historical 的完全排除
□ boost = status × authority × freshness
□ code chunk 前置上下文標頭(repo / path / branch / 最後修改)
Layer 3:文件寫作
□ 一份文件一個主題,目標 800-1,500 字
□ 標題用問句
□ 答案放段落第一句
□ 主詞明確(不要「這個服務」,要寫名字)
□ 數字、指令、參數具體到可以直接複製
□ runbook 用「症狀 → 最常見原因(附比例)→ 排查順序 → 升級路徑」
Layer 4:權威訊號與回饋
□ supersedes 建立取代關係鏈
□ 答案中標註 status / owner / reviewed_at
□ 找不到 canonical 來源時明說,並指出該問誰
□ 「答案錯誤」回報按鈕,回饋到文件健康度報表
□ 每週把「被引用次數」發給各 team——讓寫文件有回報
CI
□ doc_audit 分數門檻,低於則 PR 失敗
□ 超過審閱週期自動開 Jira ticket 給 owner
八、系列收尾
十篇走完,五個場景(企業官網、電商、Landing Page、課程平台、內部知識庫),技術堆疊從 AEM/AWS、Shopify/Vercel、Astro/Cloudflare、Django/GCP 到 Vertex AI Search,規模從 3 人到 3,000 人。
收斂出來的東西只有三句話:
先確認內容抵達得了。 五個案例中有四個,第一個發現的問題都是「內容根本沒被讀到」——WAF、Bot Control、CSR、付費牆、同步器漏接。這一層的修復成本通常是幾小時到幾天,效果卻是 0 到 1。
以 chunk 為單位寫作。 從 12,000 個商品頁到 4,200 頁 Confluence,同一條規則反覆勝出:主題單一的 1,000 字,勝過主題混雜的 8,000 字。
可接地才會被引用。 數字、日期、來源、明確的主詞。沒有這些,你的內容會在 grounding 階段被靜靜丟掉——而你永遠不會知道發生過。
剩下的都是這三件事在不同環境下的變形。
本系列文章:
- Part 1:概念篇 | Part 2:原理篇 | Part 3:方法篇 | Part 4:實作篇 | Part 5:量測與案例篇
- Part 6:實戰案例 — 大型企業官網
- Part 7:實戰案例 — 電商網站
- Part 8:實戰案例 — 單頁式產品 Landing Page
- Part 9:實戰案例 — 線上課程平台
- Part 10(本篇):實戰案例 — 私有 Repo 與內部知識庫
- 商業篇:Part 11 市場 | Part 12 GEO vs SEO 判斷 | Part 13 顧問方法論 | Part 14 產業劇本 | Part 15 工具與技術棧 | Part 16 規模化
