知識庫:檢索與 Agent¶
把 in-house 文件變成「可被 LLM 引用的 [n] 段落」的混合檢索管線,外加消費它的 agent 工具表面。一句話:dense(specstar 原生向量查詢)+ sparse(BM25)→ RRF 融合 → MMR 多樣化 → parent-document 合併 → top-k RetrievedPassage,再選擇性套上 LLM 強化(multi-query / HyDE / rerank)與 #105 文件品質先驗。
看這篇之前:先讀 架構總覽 抓全貌。
職責與邊界¶
負責:
- 對一組 collection 做混合檢索(
Retriever.search),輸出去重、合併成段落的RetrievedPassage列表。 - agent 工具表面:
kb_search(檢索 leaf)、ask_knowledge_base(消費介面,委派給 throwaway KB 子 agent)、lookup_glossary(確定性 glossary 查詢)。 - 把答案文字裡的
[n]標記解析回Citation(parse_citations),並記錄引用分析事件(CitationEvent)。 - 純函式的 ranking 元件:RRF、MMR、BM25、query 轉換、rerank、provenance 聚合。
不負責:
- 產生被查的向量/文字/provenance — 那是 知識庫:攝取與索引(
kb/ingest.py、kb/chunker.py、kb/embedder.py)的事。本子系統只讀DocChunk.embedding/SourceDoc.text。 - 回合 / 取消 / SSE 的 pump — 那是 API 與回合引擎 的
ChatTurnEngine。本子系統只提供工具 impl 與answer_question一個非串流子 agent 入口。 - agent loop 本身(呼叫工具、重試)— 那是 Agent 執行時 的
AgentRunner。 - wiki 檢索是平行的另一條路:KB agent 呼叫
ask_wiki(#537),委派給 wiki reader 走 index-first 導覽(list_files/read_file/search_wiki/read_source),在拋棄式 context 內跑完,只回綜合答案 + 指回原始文件的[n]。沒有路由旗標;用不用由 agent 依問題性質決定,各來源有自己的每回合配額(0 = 該來源不發工具)。
核心模組¶
| 路徑 | 角色 |
|---|---|
src/workspace_app/kb/retriever.py |
編排者。Retriever.search() 跑整條管線;持有 Enhancements(per-call 覆寫)、_resolve_enhancements(caller>default,被 operator max 夾住)、LocationFilter(#263 結構化範圍,推進 dense+sparse 兩條查詢)、Overlay(#328 乾跑候選集覆寫)、_dense/_dense_order/_dense_order_mem(specstar 原生 cosine 向量查詢,選擇性 code-vector fan-out;overlay 時改 in-memory cosine)、_load_chunks(BM25/MMR corpus)、_apply_quality_prior(#105 加性先驗,smoothstep _saturate)、_canonical_text(parent 文字解析)。 |
src/workspace_app/kb/fusion.py |
純 rank 融合+多樣化。rrf_scores/reciprocal_rank_fusion(Reciprocal Rank Fusion,k=60,帶量值)與 mmr(Maximal Marginal Relevance,lambda=0.5)。 |
src/workspace_app/kb/bm25.py |
sparse 半邊。bm25_rank — in-process BM25(k1=1.5, b=0.75)over (id, text) corpus;丟掉零分文件;同分以 id 破題。 |
src/workspace_app/kb/query.py |
LLM 驅動 query 轉換。expand_queries(multi-query 改寫,原 query 永遠第一、去重)與 hypothetical_document(HyDE 偽文件)。prompt+parse 純,LLM 注入。 |
src/workspace_app/kb/rerank.py |
rerank_passages — LLM 依相關性重排段落;被略過的段落保留原本尾序;解析純(regex 抓整數)。 |
src/workspace_app/kb/merge.py |
parent-document 檢索。ScoredChunk + merge_passages 把同一文件中重疊/相鄰的 chunk 合成一個跨 [min start, max end] 的 RetrievedPassage,文字由注入的 text_of 提供,並聚合 provenance。 |
src/workspace_app/kb/provenance.py |
#254 結構位置聚合。aggregate_provenance union 各 chunk 的 provenance dict;format_location 渲染 agent-facing 的 p.3–4 · Section 標頭。泛用 — 從不特判某個 key。 |
src/workspace_app/kb/citations.py |
parse_citations — 把答案裡的 [n](ASCII/全形/CJK 括號)對著 per-turn 段落 registry 解析成 Citation;去重、僅 in-range。 |
src/workspace_app/kb/cited.py |
引用分析。record_citations 每個 [n] 寫一筆 CitationEvent;scoped push-down 聚合(doc_cited_for_ids、chunk_cited 等)算 cited 次數。 |
src/workspace_app/kb/llm.py |
ILlm ABC — 僅串流文字補全(stream() 為唯一原語,collect() 把它榨乾)。LitellmLlm 生產實作(LiteLLM,含 Ollama think=False 的 reasoning_effort 處理)。驅動 expand/HyDE/rerank 並串流即時進度。 |
src/workspace_app/kb/context_cards.py |
#106 確定性 glossary。norm(NFKC+casefold+collapse)、derive_norm_keys、lookup/find_cards_by_key — 對 norm_keys.contains 做精確 element membership(無 LLM、無 retriever)。撐起 lookup_glossary。 |
src/workspace_app/agent/tools.py |
agent 工具表面。kb_search_impl(檢索 leaf:#195 budget、#263 location 解析、#68 強化串接、append 進 per-turn kb_passages、用 on_exec_output 串流 retriever 工作)、ask_knowledge_base_impl(消費介面:#280 rank→tier、委派 run_subagent('kb_chat',...)、bucket 引用)、lookup_glossary_impl。_WORKSPACE_TOOLS 列 ask_knowledge_base 而非 kb_search。 |
src/workspace_app/agent/context.py |
AgentToolContext — RCA 與 KB 共用的 dual-flavour per-run context。KB flavour 設 retriever+collection_ids+kb_passages+kb_search_budget(KbSearchBudget 值物件:max_calls/used,.exhausted/.remaining;#334 起取代舊的 kb_search_max_calls/kb_search_calls)+collection_tiers+kb_enhancements+run_subagent。 |
src/workspace_app/api/kb_chat_routes.py |
KB agent 回合表面。answer_question 跑一次非串流 KB-agent 回合(給 ask_knowledge_base bridge 用),逐字捕捉工具錯誤/RunError,解析+加 Sources footer;kb_progress 把子 agent 事件渲成 parent 進度行;register_kb_chat_routes 串流即時 KB 聊天。 |
src/workspace_app/api/subagent_bridge.py |
SubagentBridge.run 泛用 bridge(purpose→AgentConfig、collection scope、引用 relay+logging、wiki 路由、budget= 共用),接進每個 app 回合的 run_subagent。api/app.py 只構造它並留薄別名 _run_subagent = subagent_bridge.run;#280 tier scope + composer depth/effort(#334 budget 串接)的包裝 _run_subagent_with_depth 是 src/workspace_app/api/chat_send.py 內 ChatSendService.send 的巢狀閉包。 |
介面與接縫¶
| 接縫 | 種類 | 定義位置 | 實作 |
|---|---|---|---|
ILlm |
ABC | src/workspace_app/kb/llm.py |
LitellmLlm(生產 LiteLLM);FallbackLlm(#196 failover 包裝,見原始碼);測試注入 fake |
Embedder |
Protocol/ABC | src/workspace_app/kb/embedder.py |
HashEmbedder(測試)、LitellmEmbedder(Ollama/hosted)。實作細節見 知識庫:攝取與索引 |
AgentRunner |
Protocol | src/workspace_app/api/runner.py |
LitellmAgentRunner(live LLM,驅 kb_search loop)、ScriptedAgentRunner(測試) |
run_subagent bridge |
callable 接縫 | src/workspace_app/api/subagent_bridge.py |
SubagentBridge.run(泛用 purpose→AgentConfig;api/app.py 只留別名 _run_subagent = subagent_bridge.run);_run_subagent_with_depth(#280 tier scope + kb_chat composer depth/effort)在 src/workspace_app/api/chat_send.py 的 ChatSendService.send 內 |
AgentToolContext(RCA vs KB dual-flavour) |
dataclass | src/workspace_app/agent/context.py |
RCA flavour(sandbox+files)、KB flavour(retriever+collection_ids+kb_passages) |
| Retriever 純函式注入點 | callable 注入 | src/workspace_app/kb/retriever.py |
text_of=_canonical_text(merge)、similarity=cosine over _chunk_vec(mmr)、on_progress sink(即時串流) |
ILlm 是這個子系統最關鍵的接縫:它只有 stream() 一個抽象方法,collect() 在其上實作(榨乾 stream、轉發每個 chunk 給 on_chunk、回傳串接後的非 reasoning 內容)。刻意沒有非串流 complete() — 詳見下方不變式。
運作方式 / 資料流¶
flowchart TD
subgraph consumer[消費 app agent:RCA / playground / topic-hub]
AKB[ask_knowledge_base 工具]
GL[lookup_glossary 工具]
end
AKB -->|run_subagent 'kb_chat', rank→tier scope| BR[_run_subagent / answer_question]
BR -->|throwaway KB AgentToolContext| KBA[KB 子 agent loop]
KBA -->|kb_search query, enh, location| KS[kb_search_impl]
KS --> RT[Retriever.search]
subgraph pipeline[Retriever.search 管線]
RT --> LC[_load_chunks BM25/MMR corpus]
RT --> EXP[expand_queries multi-query]
RT --> HY[HyDE hypothetical_document]
EXP --> DO[_dense_order specstar cosine 向量查詢]
HY --> DO
RT --> BM[bm25_rank sparse]
DO --> RRF[rrf_scores RRF]
BM --> RRF
RRF --> MMR[mmr 多樣化]
MMR --> QP[_apply_quality_prior #105]
QP --> MG[merge_passages parent-doc]
MG --> RR[rerank_passages LLM]
RR --> TOPK[top_k RetrievedPassage]
end
TOPK -->|append, 編號 n| REG[(per-turn kb_passages registry)]
KS -.->|on_exec_output / kb_progress| BR
BR --> ANS[合成的引用答案 + Sources]
REG --> PC[parse_citations n→Citation]
PC --> CE[(CitationEvent log)]
GL --> CC[context_cards.lookup 精確 norm_keys membership]
LOC[LocationFilter #263] -.->|indexed QB WHERE| DO
LOC -.-> LC
kb_search 路徑(leaf)¶
只有本身就是 KB agent 的 agent 會拿到 kb_search。kb_search_impl:
- 先看 budget(#195/#334):
ctx.context.kb_search_budget.exhausted為真就回 sentinel,叫模型用已取得的段落作答,不再跑昂貴的 retriever。max_calls == 0(composer 選「這次回覆不搜尋」)回自己一條的 steer 字串(「不允許任何 KB 搜尋…」),與一般 budget 耗盡的 sentinel 區分(#334 Q4)。 - 解析 location(#263):若帶了
page_*/sheet卻沒帶document,在花掉 budget 前回一個可復原訊息;有document就用resolve_document把檔名→不透明source_doc_id,組LocationFilter。 - 解析強化串接(#68):
caller (ctx.kb_enhancements)>LLM tool args>operator default,最後在Retriever裡被 operatormax夾住。 - 呼叫
Retriever.search。search內部:_load_chunks載入(選擇性 location-scoped 的)chunk 全集供 BM25/MMR;_resolve_enhancements算出 ints/bool(沒接 LLM 時三項全強制關掉);expand>0時expand_queries擴成 N 個改寫;每個 query 經_dense_order(specstar 原生 cosine 向量查詢 overembedding,接了 code embedder 時再對embedding_altfan-out)與bm25_rank各得一條 ranked list;hyde>0時每個hypothetical_document被 embed 成另一條 dense probe。所有 ranked list →rrf_scores(RRF)→ 取 topcandidates→mmr多樣化(cosine of 存的 chunk 向量)→_apply_quality_prior(#105:final = 正規化 RRF + w·(smoothstep(quality/100) − 0.5),soft demote,選擇性硬quality_floor)→merge_passages對 canonicaldoc.text重建 parent 段落 → 選擇性rerank_passages→ 取top_k。 - 回到工具:每個段落 append 進 per-turn
kb_passages,依(document_id, start, end)去重後編號[n](前綴format_location),帶剩餘 budget 註記回傳;retriever 的強化-LLM thinking 經on_exec_output即時串流。
答案產生後,parse_citations(answer, kb_passages) 把 [n] 解析成 Citation。
ask_knowledge_base 路徑(消費介面)¶
非 KB 的 app agent(RCA/playground/topic-hub)呼叫 ask_knowledge_base_impl(question, rank=0)。它把 rank→collection_tiers 的優先 tier(#280),再經 run_subagent('kb_chat', question, on_exec_output, investigation_id, scope) → answer_question 開一個丟棄式 KB 子 agent context(自己的 retriever+collection_ids+kb_passages),跑它自己的 kb_search loop。子 agent 的搜尋/推理經 kb_progress over on_exec_output relay 回 parent 串流成 tool-log 行,但吵雜的原始段落留在子 agent 的 context 裡。合成的引用答案(+Sources footer)回傳;解析後的引用依工具名 bucket 進 subagent_citations 並記成 CitationEvent。
answer_question 還會逐字捕捉子 agent 的工具錯誤與 RunError:若所有工具呼叫都失敗,回傳真正的錯誤(而非讓 LLM 合成出「我無法存取 KB」的禮貌幻覺)。
lookup_glossary 路徑¶
確定性 — lookup_glossary_impl 對 context cards 做一次精確 norm_keys.contains membership 查詢,無 retriever/LLM。因此可直接授予任何 app(不像 kb_search)。
關鍵不變式與眉角¶
kb_search 與 ask_knowledge_base 不可合併、不可互換(#270)
kb_search 是檢索 leaf — 需要 context 裡有 retriever+collection_ids,只授予本身就是 KB agent 的 agent(kb_chat presets、infer_modules 分類器)。其他每個 app agent 拿 ask_knowledge_base(委派型消費介面)。接新 app 時列 ask_knowledge_base,永遠不要列 kb_search/search_wiki(沒 retriever/wiki context 會直接失敗)。兩者不可約:ask_knowledge_base 會生出一個 KB agent,那個內層 agent 必須用非委派工具(kb_search)搜尋,否則會對自己無限呼叫 ask_knowledge_base。
_WORKSPACE_TOOLS 列 ask_knowledge_base,不是 kb_search
RCA 預設工具表(build_tools(None))含 ask_knowledge_base。kb_search 在 _IMPLS 裡可被查到,但是 opt-in only — 只在 KB context 才會被授予。
強化串接順序固定
Python caller(Enhancements/ctx.kb_enhancements)> LLM tool args > operator default,再被 operator max 夾(ints floored at 0/capped at max;rerank 有效值 = raw AND max,所以 max=False 是硬 kill switch)。kb_search_impl 先把 caller-set 的欄位疊在 LLM args 上(caller 非 None 者勝),剩下交給 Retriever 的 _resolve_enhancements 套 default+max。沒接 LLM 時三項一律強制關掉,不管解析出來的值是什麼(Retriever.search 在 self._llm is None 時把 resolved 重設為全 0/False)。
location filter 沒有 document 就無意義
kb_search 在帶了 page/sheet 卻沒帶檔名時,在花 budget 前回一個可復原訊息。location 以 indexed QB WHERE 推進 dense 向量查詢與 BM25/MMR corpus 兩邊(_scoped),絕非 Python post-filter — 所以該欄位必須維持 index 在 DocChunk 上。
budget 在搜尋前先扣
budget.used += 1(KbSearchBudget)發生在呼叫 retriever.search 之前,所以空手或出錯的搜尋一樣消耗一單位 — 一個什麼都 match 不到的小模型才不會無限重搜。
per-message kb_search 次數上限(#334)
composer picker 經 max_kb_searches 請求欄位(KB 聊天 _MsgBody 與 app 回合 _MessageBody 兩個 body 都有)帶進來;resolve_max_searches(requested, default=, ceiling=) 把它夾到 [0, kb.max_searches_ceiling],沒帶(None)時回退 operator 預設 kb.max_searches_per_turn(可為 None=不限)。cap == 0 代表「這次回覆不搜尋」。一個 app 回合內所有 ask_knowledge_base 子 agent 以 reference 共用同一個 KbSearchBudget(#334 Q6),由 ChatSendService 建好後 threaded → SubagentBridge.run(budget=) → answer_question;infer_modules/workflows 不共用,各自拿一個 operator-default 的新 budget。
retriever overlay 是預覽接縫(#328)
Retriever.search(overlay=Overlay(virtual_chunks, shadow_doc_id, virtual_text)) 是乾跑的候選集替換:把被影子的文件(shadow_doc_id)已存的 chunk 從候選集移除、把未持久化的 virtual_chunks 加入,再用同一條 hybrid 管線(BM25/MMR/#105 品質先驗/parent-merge/rerank)重跑,所以預覽不會漂移。唯一 overlay-specific 的步驟是 dense 排序改走 _dense/_dense_order_mem(in-memory cosine over 覆寫後的集合,與 store 同樣的 specstar cosine_distance metric)、以及 merge 對被影子文件改切 overlay.virtual_text。不 reindex、不持久化任何東西。
findability 探測(#328 P3–P7)= prompt 調校 playground
痛點:「怎麼給 AI 一個好的 prompt 讓它生出好 chunk」。Collection.parser_guidance(non-indexed,免 migration,第 5 個 per-collection prompt,append 到每個 prompt-driven parser 的 base prompt——VLM describer 系列)讓你下一段抽取引導(例:魚骨圖→JSON、表格→md)。IParser.uses_guidance() 宣告誰吃它;ingestor 兩處 parse site 都串。POST /kb/findability/probe(kb/findability.py probe_findability,唯讀)對一句代表性問題報告這份 doc 的 chunk 在真實 retriever 裡排第幾(before,用 search(depth=N) 撈深、不只 top-5);給候選 guidance 時,Ingestor.dry_run_chunks 不落地重 parse 這一份 doc(重用同一條 parse+split+embed),再經 Overlay 撈深排名(after,其餘 collection 不動)。只看 rank(無 hit@k/MRR/分數);FE FindabilityModal 從 KbDocIde 狀態列開,改 prompt 即時比 before/after,「Apply」才寫 parser_guidance(specstar 原生 PATCH)。自動逐 chunk 出題計分(#333 批次)刻意延後。
每次 ask_knowledge_base 必須剛好 append 一筆 bucket
引用依工具名 bucket(subagent_citations['ask_knowledge_base']),persist 時把第 N 筆 bucket 對到第 N 個同名 tool message。所有早退路徑(無 tier、rank 越界)都必須 bucket.append([]),否則配對會漂移。
per-turn kb_passages 是引用的 ground-truth
[n] 標記對回 passages[n-1];編號在一個回合內跨多次 kb_search 持續(以 (document_id, start, end) 去重)。parse_citations 支援 ASCII [1]、全形 [1]、CJK 【1】(Qwen 寫中文時會吐寬括號)。
所有 LLM 呼叫都串流
ILlm 沒有非串流 complete();collect() 在 stream() 上把它榨乾。新增任何非串流 KB LLM 呼叫都違反觀測性政策。
#105 品質先驗是 scoped 的
當沒有任何候選文件帶 quality 分數時,排序逐位元組維持原本的 rank-based 相關性(沒有 rubric 的 collection 完全不受影響)。先驗是加性、centered at 0.5(未評分 = +0 中性,永不墊底)、saturating、small-weighted(真相關永遠贏);soft demote,除非 operator 設了 quality_floor。
chunk offset 索引進 doc.text,不是原始 bytes
chunk offset 指向 parser 抽出的 doc.text(canonical text),絕非原始 bytes — 對 image/pdf/docx bytes 解碼會得到 U+FFFD 亂碼(#114);沒有抽出文字的 legacy 文件得到一個可讀的 marker。SourceDoc id 不透明且不含 /,永不解析它取 path/collection/user;顯示檔名是 posixpath.basename(doc.path)。
context-card 查詢是精確 element membership
對正規化的 norm_keys(NFKC+casefold+collapse)做精確 membership — 'M4' 永不 match 'M40' card;norm 函式必須能被外部 caller 重現(刻意保持簡單)。
設計決策與出處¶
| 決策 | 理由 | 出處 |
|---|---|---|
kb_search(leaf)與 ask_knowledge_base(consumer)是兩個不可合併的工具 |
Context isolation:吵雜檢索(multi-query/HyDE/rerank、原始段落、整頁 wiki)留在丟棄式子 agent context,讓消費者的視窗保持精簡 — 對小型本地模型最關鍵。且內層 KB agent 必須用非委派工具搜尋否則無限遞迴 — leaf 不可約。 | #270/CLAUDE.md |
| RRF 融合 + MMR 多樣化(兩者皆純、參數少) | 混合異質 scorer(dense+sparse+per-variant)的標準輕量做法;RRF 量值帶有 rank order 丟掉的資訊(dense+BM25 都高的文件勝過只一邊高的),#105 先驗會複用它。 | kb/fusion.py docstring;docs/plan-kb-retrieval-enhancements.md |
| 品質先驗是 second-phase 加性文件先驗,smoothstep 飽和、small weight | IR 文件先驗文獻(Craswell SIGIR'05、Kraaij SIGIR'02、Zhou & Croft CIKM'05):加性(非乘性,乘性會讓偏斜特徵壓過相關性)、centered at 0.5(未評分=中性、永不墊底)、飽和(95-vs-100 幾乎無差)、small w(真相關永遠贏)。recall 不變,只重排倖存者。 | #105;retriever.py _apply_quality_prior/_saturate 註解 |
LocationFilter 在排序前以 indexed QB WHERE 縮小兩條查詢的候選集 |
scoped query(「為什麼 X 失敗,據 30-90 頁」)應是在範圍內做向量排序,而非另一條路;每個檢索訊號都要看到同一個縮小集合;indexed provenance 欄位讓它是真 WHERE 而非 Python post-filter。 | #263;docs/plan-issue-263.md |
| 強化串接 caller > LLM-args > default,再被 max 夾 | KB-chat 使用者選了 depth 時 caller 權威設定,模型自己填更深的 args 也不能覆寫使用者的「quick」;operator max 是最終仲裁,max=False 是硬 kill switch。 |
#68/#195;kb_search_impl + _resolve_enhancements |
ask_knowledge_base rank → tier fallback,消費者自行判斷/升級 |
per-profile 預設 tier + agent 驅動的分層 fallback:消費者從 rank 0(最高優先)開始,只在未解決時才升級,比較各 tier 答案而非自動信任更廣 tier。 | #280;docs/plan-issue-280.md |
lookup_glossary 可直接授予任何 app(不像 kb_search) |
它是便宜、確定性、精確 key 的 context-card 查詢(無 LLM、無 retriever),沒有 context 依賴可失敗 — 是 never-grant-kb_search 規則的例外。 | #106;CLAUDE.md;context_cards.py |
ILlm 僅串流 |
一次性非串流呼叫會藏起模型的工作、傷害觀測性;需要最終文字的 caller 用 collect()(底層仍串流,即時 thinking 浮現於聊天,#10)。 |
feedback_always_stream_llm;kb/llm.py |
與其他子系統的關係¶
- 上游 — 知識庫:攝取與索引:產出本子系統查的
DocChunk向量(embedding/embedding_alt)+SourceDoc.text+ provenance;KB_EMBED_DIM必須與 embedder 寬度一致。 - 資料層 — 資料層(specstar):
DocChunk/SourceDoc/RetrievedPassage/ContextCard模型(src/workspace_app/resources/kb.py;Citation定義在src/workspace_app/resources/conversation.py,由kb.pyre-export)+CitationEvent(src/workspace_app/resources/citation_event.py)+原生向量查詢(QB cosineorder_by)+ retriever 依賴的 indexed provenance/collection 欄位。 - 驅動 — API 與回合引擎:共用的 per-conversation 回合 pump(
ChatTurnEngine)同時驅動 RCA 工作區聊天與 KB 聊天;KB 聊天建一個 KBAgentToolContext與把TurnMessage→KbMessage的on_complete。 - 執行 — Agent 執行時:跑呼叫
kb_search/ask_knowledge_base的 agent loop(AgentRunner/litellm_runner);同一 runner 透過 dual-flavourAgentToolContext服務兩種 flavour。 - 消費者 — App 平台:
apps/rca、apps/playground、apps/topic-hub(經app.jsonagent.tools)列ask_knowledge_base(topic-hub 另加lookup_glossary);經api/subagent_bridge.py的SubagentBridge.run(在api/app.py構造並 wire)這個run_subagentbridge 觸及 KB agent。 - 平行路徑 — wiki 子系統(
kb/wiki/、search_wiki/read_source工具):另一條檢索路;answer_question在wiki_query開時路由到 wiki-aware runner。read_source(read_source_impl)會容忍 code-wiki 的卡片路徑(/files/<src>.md):先試字面 source path,失敗才用_coerce_source_path去掉前綴/files/與尾綴.mdcoerce 回底層 source path(/files/app/queue.py.md→app/queue.py)當 fallback,maintainer 純文字與 reader 編號[n]兩種模式皆然(#281 P7)。詳見 知識庫:攝取與索引 與原始碼。 - 組裝 — 啟動與組裝根:生產
Retriever的構造點(candidates/top_k/quality_weight/code_embedder/enhancement_defaults)由工廠(factories.py/coordinators)注入;KBAgentConfig與EnhancementSettings來自 config catalog(agents.kb_chatpresets、kb.retrieval_llm、kb.retrieval.enhancements)。 - 下游 — 前端(web/):
CitationEvent餵 cited 次數(kb/cited.py);FE 在 SSEdone時 refetch thread 渲染持久化的[n]→Citation(可點,#221)。
原始碼錨點¶
接手者建議閱讀順序:
src/workspace_app/kb/retriever.py—Retriever.search(整條管線)、_resolve_enhancements、LocationFilter.conditions、_apply_quality_prior、_dense_order、_saturate。src/workspace_app/agent/tools.py—kb_search_impl、ask_knowledge_base_impl、lookup_glossary_impl、_WORKSPACE_TOOLS、_IMPLS、build_tools。src/workspace_app/agent/context.py—AgentToolContext(RCA vs KB 接縫)。src/workspace_app/kb/fusion.py—rrf_scores、mmr。src/workspace_app/kb/bm25.py—bm25_rank。src/workspace_app/kb/merge.py—merge_passages、ScoredChunk。src/workspace_app/kb/query.py—expand_queries、hypothetical_document;src/workspace_app/kb/rerank.py—rerank_passages。src/workspace_app/kb/provenance.py—aggregate_provenance、format_location。src/workspace_app/kb/citations.py—parse_citations;src/workspace_app/kb/cited.py—record_citations。src/workspace_app/kb/llm.py—ILlm、LitellmLlm.stream;src/workspace_app/kb/context_cards.py—norm、lookup。src/workspace_app/api/kb_chat_routes.py—answer_question、kb_progress、resolve_max_searches;src/workspace_app/api/subagent_bridge.py—SubagentBridge.run(api/app.py只留別名_run_subagent);src/workspace_app/api/chat_send.py—ChatSendService.send/_run_subagent_with_depth。