00核心心智模型
LLM 是一個無狀態的函數:文字進去,文字出來。它不記得你、沒有 session、 碰不到你的資料庫、也不會主動做任何事。整個 AI 架構裡其他所有元件的存在, 都是為了兩件事 —— 餵它該知道的,以及幫它做它想做的。
- 模型無狀態 → 每一輪都要重傳全部上下文 → 成本隨對話長度累積
- 模型碰不到外界 → 它只能「請求」你去做 → 控制權永遠在你的程式手上
- 模型是機率性的 → 同樣輸入可能不同輸出 → 輸出必須被驗證才能信任
01五層架構總覽
不管用什麼框架、什麼供應商,成熟的 LLM 應用都會長出這五層加上一組橫切關注。 層與層之間的關係是「上層指揮下層」,而不是「上層包含下層」。
控制權的形狀
這張圖比分層圖更重要。它回答一個關鍵問題:誰呼叫誰? 答案是 —— Orchestrator 呼叫所有人,沒有人呼叫 Orchestrator。
02模型層 — 引擎
| 元件 | 職責 | 可替換性 |
|---|---|---|
| LLM | 唯一會「思考」的東西。無狀態、機率性、只吃文字吐文字 | 高 — 但換模型要重測 prompt |
| Inference Provider | 實際跑模型的服務(雲端 API、自架推論伺服器) | 很高 — 多數 API 互相相容 |
| Tokenizer | 文字 ↔ token 轉換,決定計費與上下文佔用 | 綁定模型,不可自選 |
這層應該被抽象成一個介面。把模型當成可替換的零件,不要讓業務邏輯知道它在跟誰講話。 開源權重模型(可自架、可換供應商)與閉源 API 模型的最大差異就在這裡:後者換掉的成本高得多。
兩類常見的模型形態
直接生成答案
收到輸入就開始逐 token 產出回應。延遲低、可預測、便宜。適合抽取、分類、格式轉換、一般對話。
先思考再回答
先產出一段內部推理,再給最終答案。準確度較高,但延遲與費用都上升 —— 那段思考也是要付錢的 token。多數供應商提供「思考強度」參數讓你控制。
推理模型的內部思考內容不適用與最終答案相同的安全與品質標準,設計上不應直接顯示給終端使用者。多數 API 提供參數把它從回應中排除。
03上下文層 — 最被低估的一層
Context window 是整個系統唯一的工作記憶。所有能力層元件最終都在爭奪這塊空間。 Context engineering 已經取代 prompt engineering 成為主要的工程課題 —— 問題從「怎麼把話說好」變成「有限空間裡該放什麼、什麼時候放、什麼時候丟」。
三個必須理解的量
| 概念 | 是什麼 | 為什麼你要在意 |
|---|---|---|
| Token | 模型的最小處理單位。英文約 3–4 字元一個;中日韓文字約一字 0.6–1.5 個 | 計費單位。input 與 output 通常不同價,output 較貴 |
| Context window | 單次呼叫能容納的 token 上限(輸入+輸出合計) | 硬性限制。超過就報錯或被截斷 |
| Context rot | 上下文塞入過多不相關內容後,模型注意力被稀釋、表現下降 | 「還沒到上限」不等於「可以繼續塞」。塞越多不一定越準 |
Skills 的漸進式載入、Tool 的搜尋機制、RAG 的檢索、Sub-agent 的上下文隔離 —— 這四個看起來完全不同的技術,其實在解同一個問題: context window 是稀缺資源,必須按需分配。理解這件事之後,很多架構選擇會突然變得合理。
04Tools — 讓模型能影響外界
Tool 是你定義的一個可執行 function。你用結構化的 schema 把它「介紹」給模型, 模型判斷該不該用,然後 —— 關鍵在這裡 —— 由你執行,不是由模型執行。
一個 tool 的三個部分
{
"name": "search_orders",
// description 就是 prompt:模型靠讀這段文字決定要不要呼叫
"description": "當使用者要查詢訂單狀態或歷史時使用。
若使用者是要建立新訂單,改用 create_order。",
"input_schema": {
"type": "object",
"properties": {
"customer_id": { "type": "string" },
"status": { "type": "string", "enum": ["pending","shipped","done"] }
},
"required": ["customer_id"]
}
}
模型永遠不會真的執行你的 function —— 它做不到,它在別人的機器上, 碰不到你的資料庫。它能做的只有輸出一份「我想呼叫 X,參數是這些」的請求單。 Tool calling 的本質是一個約定好格式的請求,不是遠端執行。
完整的一次來回
並行工具呼叫
「Tool calling 一定要多輪來回」是常見誤解。回應裡的工具請求是一個陣列 —— 模型可以在同一次生成裡要求呼叫多個工具,這叫 parallel tool calling,不增加往返次數。
真正觸發多輪的條件只有一個:模型需要看到執行結果,才能決定下一步該做什麼。 如果執行成功後你只要回一句模板訊息,那永遠是一去一回。
05Skills — 讓模型知道「怎麼做」
Tool 回答「我能做什麼」,Skill 回答「我該怎麼做」。 它是一包打包好的流程知識,通常是一個資料夾:
my-skill/
├── SKILL.md ← frontmatter: name + description(常駐 context)
│ body: 完整步驟、規範、範例(按需載入)
├── scripts/ ← 輔助腳本,需要時才執行
└── references/ ← 參考文件,需要時才讀
核心機制:漸進式揭露
這解決一個真實的困境 —— 「怎麼做一件事」的知識量遠大於「能做什麼」。 你不可能把 30 頁的品牌規範塞進一個 tool 的 description, 但可以做成 skill,讓它只在相關時才進到上下文。
references/。為什麼值得這麼麻煩
| Skill 數量 | 全部載入 | 漸進式載入 |
|---|---|---|
| 4 個 | 12,000 | 200 |
| 20 個 | 60,000 | 1,000 |
| 100 個 | 300,000 · 爆了 | 5,000 |
而且這是每一輪都要重傳的固定成本。十輪對話就是十倍浪費。 更重要的是 context rot —— 塞太多不相關的指令,模型反而會變笨。
description 是整包 skill 最重要的一行
它是唯一常駐上下文的內容。寫爛了,這個 skill 就永遠不會被觸發。
描述「是什麼」
description: PDF 處理工具
模型看不出什麼情況該用。太模糊的版本則相反 —— 會在不該觸發時觸發,污染上下文。
描述「什麼時候用」
當需要讀取、拆分、合併 PDF 或填寫 PDF 表單時使用。觸發詞:PDF、表單、合併頁面。不適用於 Word/Excel,改用 office-docs。
有情境、有關鍵字、有邊界。
假裝你在寫一張給沒讀過內文的人看的索引卡:他必須只憑這張卡, 判斷現在該不該去翻那本書。加上「不適用於 X,改用 Y」特別有效 —— skill 一多,最常見的錯誤不是沒觸發,而是觸發了鄰居。
挑選 skill 的四種設計
| 做法 | 適用規模 | 取捨 |
|---|---|---|
| A. 全部塞進上下文 | < 5 個且都很短 | + 最簡單,沒有選錯問題 − 不可擴展 |
| B. 目錄常駐+模型判斷 主流做法 |
5 – 100 個 | + 便宜、判斷準(模型懂語意與上下文) − 目錄本身仍有固定成本 |
| C. 向量檢索先篩 | 100 個以上 | + 可擴展到無限多 − 檢索會漏(語意相似 ≠ 該用),多一層延遲 |
| D. 程式碼寫死規則 | 觸發條件明確可判斷 | + 零成本、完全可預測 − 只能處理你想到的情況 |
實務上常是混搭:用 D 處理明確的(副檔名、路徑、使用者角色),B 處理其餘。
06MCP — 工具的插座
MCP(Model Context Protocol)不是一種能力,而是一個標準化的傳輸協定。 它把工具從「寫死在你的程式裡」變成「可插拔的外部伺服器」。
每個 server 的工具定義都會佔上下文。接了 10 個 server、共 200 個工具, 光定義就可能吃掉十萬個 token。所以成熟的實作會加一層工具搜尋: 常駐只放工具名稱,模型需要時呼叫搜尋工具,才把那幾個相關工具的完整 schema 載入。 這和 Skill 的漸進式載入是同一個模式。
07Retrieval 與 Memory
Retrieval / RAG — 事實來源
模型的權重在訓練時就凍結了,而且不包含你的私有資料。 RAG 就是在呼叫模型之前(或之中)撈出相關資料塞進上下文。 關鍵是它有兩種模式,決定權的位置不同:
先檢索,再呼叫模型
Orchestrator 拿到問題就自己去檢索,把結果塞進上下文,然後才呼叫模型。
- 延遲固定、可預測
- 只查一次,查不到就沒了
- 模型沒有機會說「這不夠,再查一次」
把檢索包成工具
模型自己決定要不要查、查什麼、查幾次。檢索變成一個普通的 tool。
- 更聰明:可以查完發現不夠再查
- 更慢更貴:多輪往返
- 執行仍由 Orchestrator 做,只是決定權移給模型
典型的檢索管線
使用者問題
→ 查詢改寫 把口語問題轉成好的搜尋語句(一次 LLM 呼叫)
→ 混合檢索 向量(語意相似)+ 關鍵字(精確命中)並行
→ 重排序 用專門的小模型把最相關的排到前面
→ 取前 N 名塞進上下文
每一步都可以省略,也都可以換成更複雜的做法。 混合檢索幾乎總是勝過單一檢索 —— 向量找得到「意思接近」,關鍵字找得到「就是這個詞」。
Memory — 狀態
常被混為一談,其實是三種完全不同的東西:
| 短期 Working | 中期 Session | 長期 Persistent | |
|---|---|---|---|
| 是什麼 | 就是 context window 本身 | 這次任務的進度、決策、中間結果 | 跨 session 的事實:偏好、慣例、長期決策 |
| 生命週期 | 一次呼叫 | 一個任務 | 永久 |
| 實作 | 一個陣列 | KV store / DB checkpoint | 檔案或 DB + 按需檢索 |
| 難點 | 空間不夠 | 什麼時候存檢查點 | 什麼時候該「想起來」 |
不是「怎麼存」,而是「什麼時候該想起來」。 存了一萬條,這次對話該撈哪三條進上下文? 這本質上又變成一個檢索問題 —— 所以長期記憶的實作,最後幾乎都會長成一套 RAG。
08能力層對照 — 五個容易混淆的詞
這五個東西經常被放在一起講,但它們解決的是完全不同的問題。
| 回答的問題 | 本質 | 與模型的關係 | 有回傳值? | |
|---|---|---|---|---|
| Tools | 我能做什麼 | 能力 Capability | 模型請求 → 你執行 | 有 · 結果 |
| Skills | 我該怎麼做 | 知識 Know-how | 讀成文字 → 塞進上下文 | 沒有 · 是指令 |
| MCP | 從哪裡接進來 | 傳輸協定 Transport | 不直接參與,是通道 | 有 · 代理工具結果 |
| Retrieval | 我知道什麼 | 事實 Facts | 檢索結果 → 塞進上下文 | 有 · 文件片段 |
| Memory | 我記得什麼 | 狀態 State | 歷史/事實 → 塞進上下文 | 有 · 狀態 |
Tool 是被呼叫的,Skill 是被讀取的。 Skill 沒有回傳值 —— 它把文字搬進上下文,改變的是模型的行為, 而不是給你一份資料。兩者互補:skill 的內容裡經常會寫「第三步請用 X 工具執行」。
09控制層 — 誰在指揮
Agent Loop:「Agent」這個詞的定義
有迴圈就是 agent,沒迴圈就不是。整個概念可以壓縮成十行程式碼:
messages = [system_prompt, user_input]
while True:
response = llm(messages, tools) # 想
messages.append(response)
if not response.tool_calls: # 它不想再用工具了
return response.text # → 結束
for call in response.tool_calls:
result = dispatch(call) # 做
messages.append(tool_result(call.id, result))
# 迴圈繼續:把結果餵回去,讓它決定下一步
Workflow 還是 Agent?這是最重要的架構決策
絕大多數生產系統應該是 workflow,不是 agent。 只有當「你無法事先列舉所有可能路徑」時,才值得付 agent 的代價。 常見的成熟做法是混合:外層是固定階段的 workflow, 其中某一兩個階段內部才是 agent,讓它在有界的範圍內自己想辦法。
其他控制層元件
| 元件 | 做什麼 | 什麼時候需要 |
|---|---|---|
| Planner | 把大任務拆成可獨立執行的步驟,執行前先產出計畫 | 任務跨多步且順序重要時。直接丟進 agent loop 容易迷路 |
| Router | 判斷請求難度/意圖,分流到不同模型或路徑 | 想省錢時。簡單請求給小模型,或直接命中快取 |
| Dispatcher | 把工具名稱對應到實際 function 並執行 | 只要用了 tools 就需要。純程式,一個查表 |
| Sub-agents | 把子任務交給獨立的 agent,各有自己的上下文 | 任務會產生大量中間資料,而主流程只需要結論 |
是上下文隔離。一個子 agent 讀了 50 個檔案,主流程只需要它三行結論 —— 那 50 個檔案的 token 從來沒有進過主上下文。 代價是子 agent 之間看不到彼此的發現,容易重複勞動, 所以適合互相獨立的任務,不適合需要協商的任務。
10橫切關注 — 上線後才知道多重要
Guardrails 護欄
進到模型之前
- Prompt injection 偵測
- 個資遮罩
- 惡意內容過濾
- 速率限制與 token 上限
離開模型之後
- Schema 與型別驗證
- 對照來源檢查幻覺
- 敏感資料洩漏檢查
- 高風險操作要求人工確認
模型的輸出永遠是不可信的輸入。 它必須先經過驗證,才能寫進資料庫、才能被執行、才能顯示給使用者。 這條原則同時擋掉了 prompt injection 的大部分實際危害 —— 就算攻擊者成功操控了模型,它產出的請求依然要通過你的驗證層。
Observability 可觀測性
LLM 應用的除錯難度遠高於傳統程式,因為同樣輸入可能有不同輸出。 沒有完整紀錄,事後根本無法重現問題。每一次呼叫至少要存:
- 完整的輸入內容 —— 不只是使用者那句話,是整包送出去的東西
- 模型、參數、版本 —— 供應商靜默更新模型是真實會發生的事
- input/output token 數 —— 成本歸因的唯一依據
- 延遲,分解到每個階段 —— 慢在檢索、模型、還是工具執行
- 工具呼叫序列 —— agent 迷路時的唯一線索
- 失敗發生在哪一層 —— 模型層?驗證層?執行層?
Eval 評估
沒有 eval 就沒辦法改進。 改了 prompt 之後,你憑什麼說變好了?靠感覺試三個 case 不算。
| 評分方式 | 適用於 | 成本 |
|---|---|---|
| 精確比對 | 分類、格式轉換 —— 有唯一正解 | 幾乎為零 |
| 程式化檢查 | JSON 合法性、必填欄位、數值範圍 | 低 |
| LLM-as-judge | 語氣、完整性、有沒有答到問題 | 中 —— 要付模型費用 |
| 人工抽查 | 最終品質把關、判斷 judge 準不準 | 高,但不可完全省略 |
Sandbox 執行沙箱
一旦你給 agent 執行程式碼或操作檔案的能力,就需要隔離環境(容器、VM、獨立工作目錄)。 模型會寫出破壞性的指令,不是因為惡意,是因為它在解決問題 —— 而它不知道那個目錄裡有你三年的資料。
11誰是模型、誰是程式碼
架構圖上的一個框,不代表一個服務、一個模型、或一個進程。 很多框只是「同一顆 LLM 的第 N 次呼叫,換了不同的 system prompt」。
Planner、Router、Judge 這些聽起來像獨立系統的元件,實作起來往往就是一個函數, 裡面包一次 LLM 呼叫。理解這件事,能讓你看架構圖時不再被術語嚇到。
# 「Planner 元件」的全部實作
def plan(task: str) -> list[str]:
resp = llm(
system="你是任務規劃器。把使用者的目標拆解成 3-7 個"
"可獨立執行的步驟。只輸出 JSON 陣列,不要解釋。",
user=task,
)
return json.loads(resp)
需要「理解語意」或「開放式判斷」
- Planner — 分解任務
- Router — 判斷意圖與難度
- Query rewriter — 改寫檢索語句
- Reranker — 重排檢索結果(常用專門小模型)
- Summarizer — 壓縮過長的上下文
- Judge — 評估輸出品質
- 工具與 skill 的選擇
- Sub-agent — 就是模型加自己的迴圈
確定性的、可測試的
- Agent loop 本身 — 就是 while
- Dispatcher — 名稱查表
- Tool 實作 — 真正做事的部分
- Schema 驗證
- 向量檢索 — 相似度計算
- Memory 讀寫
- Guardrails — 格式、個資、速率
- Observability — 記錄與計費
- Cache — 命中判斷
需要理解人話或做開放式判斷的 → 交給 LLM。其他一律程式碼。 因為程式碼便宜、快、可預測、可測試。 每一個交給 LLM 的決定,都是在付錢買彈性 —— 要確認那份彈性真的需要。
三種呼叫模式的取捨
| 模式 | LLM 呼叫次數 | 延遲 | 適合 |
|---|---|---|---|
| 純解析 輸出用模板組 | 1 | 最快 | 格式轉換、分類、資料抽取 |
| 單步工具呼叫 +模板回覆 | 1 | 最快 | 要執行動作,但回覆不需模型潤飾 |
| 工具呼叫+模型潤飾 | 2 | 約兩倍 | 需要模型讀懂結果再用人話說明 |
| Agent loop | N(不可預測) | N 倍 | 多步推理,下一步取決於上一步結果 |
12成熟度階梯
實務上該按這個順序往上加。每往上一層,成本、延遲、除錯難度都是跳躍式增加 —— 往上加要因為「真的需要」,不是因為「比較潮」。
絕大多數應用停在 LV 1 到 LV 3 就已經足夠,而且往往是最佳解。 Agent 的價值只在「路徑無法事先窮舉」時才會浮現; 在路徑可窮舉的場景裡,它只會讓你付更多錢、等更久、更難除錯。
13常見誤解與術語
六個值得糾正的直覺
術語速查
- Token
- 模型的最小處理與計費單位
- Context window
- 單次呼叫能容納的 token 上限,輸入輸出合計
- Context engineering
- 決定有限上下文裡該放什麼、何時放、何時丟的工程實踐
- System prompt
- 開發者給模型的指令,權重高於使用者輸入,使用者看不到
- Structured output
- 在解碼層強制模型輸出符合指定 schema 的內容
- Tool / Function calling
- 模型輸出「想呼叫哪個函數、參數為何」的機制
- Parallel tool calling
- 同一次生成裡要求多個工具,不增加往返次數
- Skill
- 打包成資料夾的流程知識,透過漸進式揭露按需載入
- Progressive disclosure
- 只常駐簡短描述,判定相關後才載入完整內容
- MCP
- 把工具、資源、prompt 從外部伺服器插進 agent 的標準協定
- RAG
- 呼叫模型前後撈取外部資料塞進上下文,補足模型不知道的事
- Hybrid retrieval
- 向量檢索與關鍵字檢索並用,再重排序
- Agent loop
- 反覆「呼叫模型 → 執行工具 → 餵回結果」直到模型表示完成
- Workflow
- 路徑寫死在程式碼裡的流程,模型只是其中一個步驟
- Sub-agent
- 擁有獨立上下文的子代理,只把結論回報主流程
- Guardrails
- 模型前後的輸入輸出檢查層
- Prompt injection
- 使用者在輸入中夾帶指令,企圖覆蓋開發者的 system prompt
- LLM-as-judge
- 用模型評分另一個模型的輸出,作為自動化評估手段
- Context rot
- 上下文塞入過多不相關內容導致的表現退化
- Idempotency
- 同一請求重複送達只產生一次副作用,防止重放造成重複寫入
一句話收尾
LLM 是唯一會思考的元件,但它什麼都做不到。 其他所有元件的存在,都是為了餵它該知道的,和幫它做它想做的。
→延伸閱讀
這頁是全景總覽 —— 講元件之間的關係、取捨與邊界。 每個主題還有一篇專門深入的筆記:
內容為架構通論,不綁定特定框架或推論供應商。
術語隨產業演進,實作前請對照當前官方文件確認參數名稱與支援範圍。