大多數人把 AI Agent 當成「會呼叫工具的 ChatBot」,
以為加幾個 function call 就完成了;
真正的 Agent 工程需要持久記憶、確定性狀態機、可觀測的思考迴圈,
差距不在 LLM,在你能不能讓它在第 20 步還知道自己在做什麼。
這篇給已經做過「單次 LLM 呼叫 + 幾個工具」、但還沒讓它跑到第 20 步的工程師看。讀完你會能畫出感知→思考→行動→觀察的主迴圈並設好步數與超時上限、為四層記憶(感官/工作/情節/語意)各選對存儲與 TTL、在滑動視窗與遞進摘要之間控制上下文成本,並用確定性狀態機把退款這類不可逆操作擋在 LLM 的決策之外。
一、核心問題:什麼是真正的 AI Agent
單次 LLM 呼叫(stateless call)與真正的 Agent之間有一道本質的鴻溝。
前者每次呼叫都是白紙一張,不知道剛才做了什麼,也不知道任務完成到哪裡;後者具備三個關鍵能力:
- 自主決策迴圈:在沒有人類介入的情況下,重複「感知→思考→行動→觀察」直到任務完成或確認無法完成。
- 跨步驟記憶:第 15 個步驟還能記得第 1 個步驟收集的用戶資料,不會重複詢問相同問題。
- 工具組合能力:可以依情境選擇不同工具,並根據工具回傳結果調整下一步計畫。
為什麼這很難?
LLM 本身是無狀態的(stateless)。每次 API 呼叫都是獨立的 HTTP 請求,沒有跨請求的記憶。Agent 框架必須在應用層解決:
- 上下文視窗有限:GPT-4o 128K tokens,換算約 96K 中文字。長任務無法全塞。
- 幻覺累積問題:步驟越多,錯誤累積越嚴重,必須設計檢查點。
- 成本爆炸:每步驟都傳完整歷史,128K token × $5/1M input = 每步 $0.64,100 步任務 = $64/次。
- 確定性需求:「取消訂單」這種有副作用的操作不能讓 LLM 隨機決定,必須用狀態機控制。
正確的 Agent 工程不是「把更多東西塞進 prompt」,而是設計一個可靠的資訊流系統,讓 LLM 在每個決策點都能拿到恰好足夠的上下文。
二、何時該升級:從 messages list 到持久記憶與 Worker Pool 的觸發條件
最小的 Agent 是「一個 ReAct prompt + Python dict 分派工具 + 把結果回填 messages list」,記憶就是那個 list,重啟即失。它足以證明 Agent 能完成任務,但有兩個硬限制:每步都把完整歷史傳給 LLM,成本隨步數近似平方成長;超過幾十步就撞 context window。該升級的訊號是:對話需要跨 session 延續(用戶明天回來還記得昨天的退款單號)、單次任務成本沒有上限、或是沒有 trace 能回答「它第 12 步為什麼呼叫這個工具」。對應動作是 Redis session store + 滑動視窗(§六)、PostgreSQL 情節記憶(§五)、每 session 的成本硬上限,以及把不可逆操作交給狀態機(§七)。
第二道門檻是水平擴展與記憶完整性:單一 Orchestrator 程序撐不住並發、長任務需要非同步執行(Celery)、或是用戶偏好與領域知識需要向量檢索而不是摘要注入時,才需要 Worker Pool、完整四層記憶(感官/工作/情節/語意)與 OpenTelemetry 追蹤。每一層記憶都有自己的存儲與 TTL,不要一開始就全部建起來。
| 從 | 到 | 觸發訊號 |
|---|---|---|
| in-memory messages list | Redis 工作記憶 + 滑動視窗 + 成本上限 | 單次任務成本不可預測;超過 20–40 步撞 context;重啟丟失對話 |
| 單 session 記憶 | PostgreSQL 情節記憶 + pgvector 語意記憶 | 需要跨 session 連續性(「上次你說…」);用戶偏好/FAQ 需要檢索而非塞進 prompt |
| 單一 Orchestrator | Agent Router + Worker Pool + Celery 長任務 | 並發讓單一程序成為瓶頸;有 > 30 秒的工具呼叫需要非同步 |
三、Agent 迴圈:Perceive → Think → Act → Observe
AI Agent 的核心是一個無限迴圈,每次迭代稱為一個「step」。
┌─────────────────────────────────────────────────────────────────┐
│ Agent 主迴圈 │
│ │
│ ┌─────────────┐ │
│ │ START │ │
│ │ task init │ │
│ └──────┬──────┘ │
│ │ │
│ ┌──────────────▼──────────────┐ │
│ │ PERCEIVE(感知) │ │
│ │ - 讀取用戶輸入 │ │
│ │ - 載入工作記憶 │ │
│ │ - 查詢情節/語意記憶 │ │
│ └──────────────┬──────────────┘ │
│ │ │
│ ┌──────────────▼──────────────┐ │
│ │ THINK(思考) │ │
│ │ - LLM 呼叫(含工具定義) │ │
│ │ - 解析 reasoning chain │ │
│ │ - 決定 next action │ │
│ └──────────────┬──────────────┘ │
│ │ │
│ ┌──────────────▼──────────────┐ │
│ │ ACT(行動) │ │
│ │ - 執行 tool call │ │
│ │ - 驗證參數(schema check) │ │
│ │ - 紀錄 action log │ │
│ └──────────────┬──────────────┘ │
│ │ │
│ ┌──────────────▼──────────────┐ │
│ │ OBSERVE(觀察) │ │
│ │ - 解析工具回傳結果 │ │
│ │ - 更新工作記憶 │ │
│ │ - 評估任務是否完成 │ │
│ └──────────────┬──────────────┘ │
│ │ │
│ ┌────────────▼────────────┐ │
│ │ 任務完成?/ 超步驟? │ │
│ │ / 遇到無法恢復的錯誤? │ │
│ └────┬──────────────┬─────┘ │
│ │ YES │ NO │
│ ▼ └──────────────────┐ │
│ ┌─────────┐ │ │
│ │ END │ 回到 PERCEIVE │ │
│ └─────────┘ │ │
│ ▼ │
└────────────────────────────────────────────────────────────────┘
關鍵設計決策
步驟上限(max_steps):必須設定,否則單次任務可能無限迴圈消耗資源。起步設 10,上線後設 25,之後依任務類型設 15–50。
步驟超時(step_timeout):每個 LLM 呼叫設 30s 超時,每個工具呼叫設 10s 超時。P99 超時觸發後應記錄中間狀態,允許用戶繼續。
狀態機整合:ACT 步驟不直接由 LLM 決定「要不要執行退款」,而是 LLM 提出意圖,狀態機驗證當前狀態允許該轉換後才實際執行。詳見第七節。
四、ReAct 框架:交錯推理與行動
ReAct(Reasoning + Acting)是目前最廣泛使用的 Agent 框架。核心思想是讓 LLM 在每個步驟先產出「思考」(Thought),再決定「行動」(Action),收到觀察結果(Observation)後繼續。
┌──────────────────────────────────────────────────────────────────┐
│ ReAct 流程圖 │
│ │
│ User: "幫我退訂單 #A123,原因是商品損壞" │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Step 1 │ │
│ │ Thought: 我需要先查訂單 #A123 的狀態,確認是否可退款 │ │
│ │ Action: order_lookup(order_id="A123") │ │
│ │ Observation: {status: "delivered", amount: 299, ...} │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Step 2 │ │
│ │ Thought: 訂單已送達,符合退款條件(14天內)。 │ │
│ │ 需確認用戶退款帳戶 │ │
│ │ Action: get_user_payment_info(user_id="U456") │ │
│ │ Observation: {method: "credit_card", last4: "1234"} │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Step 3 │ │
│ │ Thought: 資料齊全,執行退款。金額 $299 退回信用卡末4碼 │ │
│ │ 1234。需要用戶確認。 │ │
│ │ Action: request_user_confirmation( │ │
│ │ message="確認退款 $299 至信用卡 ****1234?") │ │
│ │ Observation: {confirmed: true} │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Step 4 │ │
│ │ Thought: 用戶確認。執行退款 API。 │ │
│ │ Action: process_refund(order_id="A123", amount=299, │ │
│ │ reason="damaged_goods") │ │
│ │ Observation: {refund_id: "R789", eta: "3-5 days"} │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Final Answer: 退款已成功申請(單號 R789),3-5 個工作天內 │
│ 退回您的信用卡 ****1234。 │
└──────────────────────────────────────────────────────────────────┘
Reflexion 擴展
Reflexion 在 ReAct 基礎上增加「反思」步驟:任務失敗後,Agent 生成一段自我批評(self-reflection),存入情節記憶,下次執行相似任務時注入 context,避免重複同樣錯誤。
Reflexion 論文(Shinn et al., 2023)在 HotpotQA 上報告了相對 ReAct 基線的明顯提升;具體數字依模型與設定而異,請以論文原表為準。代價是每次失敗都要多花一段反思生成的 tokens(示意:數百 tokens 量級)。
五、記憶四層架構:感官 / 工作 / 情節 / 語意
人類記憶的分層模型被直接應用於 Agent 設計。
┌──────────────────────────────────────────────────────────────────────┐
│ 四層記憶架構 │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Layer 1:感官記憶(Sensory Memory) │ │
│ │ 壽命 < 1s | 容量:當前 token buffer | 存儲:in-process │ │
│ │ 內容:原始輸入(用戶訊息、工具 raw output) │ │
│ │ 用途:尚未處理的即時資料 │ │
│ └──────────────────────────┬──────────────────────────────────┘ │
│ │ 重要資訊提取 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Layer 2:工作記憶(Working Memory) │ │
│ │ 壽命:session 期間 | 容量:~8K tokens | 存儲:Redis │ │
│ │ 內容:當前任務狀態、最近 N 步歷史、活躍工具結果 │ │
│ │ 用途:每次 LLM 呼叫都注入這層 │ │
│ │ TTL:2 小時(用戶閒置超過 2h 清除) │ │
│ └──────────────────────────┬──────────────────────────────────┘ │
│ │ 完成的任務/重要事件 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Layer 3:情節記憶(Episodic Memory) │ │
│ │ 壽命:永久 | 容量:無限 | 存儲:PostgreSQL │ │
│ │ 內容:過去對話摘要、任務結果、錯誤反思 │ │
│ │ 用途:跨 session 連續性;「上次你說…」 │ │
│ │ 查詢:by user_id + timestamp range │ │
│ └──────────────────────────┬──────────────────────────────────┘ │
│ │ 知識蒸餾 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Layer 4:語意記憶(Semantic Memory) │ │
│ │ 壽命:永久 | 容量:無限 | 存儲:pgvector / Pinecone │ │
│ │ 內容:用戶偏好、領域知識、FAQ 嵌入向量 │ │
│ │ 用途:RAG 檢索;相似情境參考 │ │
│ │ 查詢:cosine similarity top-K(K=5 常見起點) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 每次 LLM 呼叫的上下文組成: │
│ [System Prompt] + [語意記憶 top-3] + [情節摘要] + [工作記憶] │
│ 約 2K + 1.5K + 500 + 8K = 12K total │
└──────────────────────────────────────────────────────────────────────┘
各層實作要點
工作記憶(Redis):
- Key 格式:
agent:session:{session_id}:working_memory - Value:JSON 序列化的 messages list + task_state
- TTL:7200 秒
- 超出 8K tokens 時觸發「滑動視窗」策略(詳見第六節)
情節記憶(PostgreSQL):
1CREATE TABLE episode_memory (
2 id BIGSERIAL PRIMARY KEY,
3 user_id TEXT NOT NULL,
4 session_id TEXT NOT NULL,
5 summary TEXT, -- LLM 生成的對話摘要
6 outcome TEXT, -- success / failure / partial
7 key_facts JSONB, -- 結構化提取的重要資訊
8 created_at TIMESTAMPTZ DEFAULT NOW(),
9 INDEX (user_id, created_at DESC)
10);
語意記憶(pgvector):
- 使用
text-embedding-3-small(1536 dims)嵌入 - 每個知識片段 < 512 tokens
- 查詢時用
<=>cosine distance,閾值 < 0.3 才注入
六、上下文視窗管理:摘要 / 壓縮 / 選擇性遺忘
上下文視窗是 Agent 最稀缺的資源。以 GPT-4o 128K token 為例,input 費用 $5/1M tokens,若每步驟都傳滿,25 步任務的 input 成本 = 25 × 128K × $5/1M = $16/次,完全不可行。
三種壓縮策略
策略一:滑動視窗(Sliding Window)
保留最近 N 條訊息,丟棄更早的。簡單有效,但會遺失早期重要資訊。
適用:任務步驟少(< 10)、早期資訊不重要的場景。
N = 6(3 組 thought/action/observation)是常見的起點(示意,非實測)。
策略二:遞進摘要(Progressive Summarization)
每隔 K 步,呼叫 LLM 將「最舊的 K 步」壓縮為一段摘要,摘要替換原始訊息。
壓縮比:8K tokens → 500 tokens(約 16:1)。
代價:每次摘要多消耗一次 LLM 呼叫(約 $0.002/次)。
適用:長任務(10–30 步)、需要保留歷史摘要。
策略三:選擇性保留(Selective Retention)
由 LLM 標記每條訊息的「重要度分數」(0–1.0),低於 0.4 的訊息在視窗壓縮時優先丟棄。
高重要度標記的訊息類型:用戶明確說出的需求、工具回傳的關鍵 ID(訂單號/退款單號)、用戶確認的決策。
代價:需要額外標記步驟,或在 Observation 處理時即時評分。
上下文預算配置範例
| 區塊 | Token 預算 | 來源 |
|---|---|---|
| System prompt + tools | 2,000 | 靜態 |
| 語意記憶 RAG | 1,500 | 動態檢索 |
| 情節摘要 | 500 | 上次對話壓縮 |
| 工作記憶(滑動視窗) | 6,000 | 最近 6 步 |
| 輸出預留 | 2,000 | max_tokens |
| 總計 | 12,000 | — |
在 GPT-4o 的 128K 視窗內,這個配置有 10 倍的安全餘量,且成本每步僅 $0.06,25 步任務 = $1.50/次,可接受。
七、Agent 狀態機:確定性控制 vs LLM 決策的邊界
最常見的 Agent 設計錯誤:把所有決策都交給 LLM,包括「現在應不應該執行退款」這種有不可逆副作用的操作。
正確做法是用確定性狀態機控制業務流程,LLM 只負責「資訊蒐集」和「意圖提取」,狀態轉換由狀態機驗證。
┌─────────────────────────────────────────────────────────────────────┐
│ 退款 Agent 狀態機 │
│ │
│ INITIAL ──[用戶提出退款請求]──▶ GATHERING_INFO │
│ │ │
│ [所有必要資訊齊全] │
│ │ │
│ ▼ │
│ VALIDATING │
│ │ │ │
│ [驗證通過] │ │ [驗證失敗] │
│ ▼ ▼ │
│ AWAITING_ REJECTED │
│ CONFIRM (final) │
│ │ │
│ [用戶確認] │ [用戶取消] │
│ ▼ ▼ │
│ PROCESSING CANCELLED │
│ │ (final) │
│ [API 成功] │ [API 失敗] │
│ ▼ ▼ │
│ COMPLETED FAILED │
│ (final) (final) │
│ │
│ LLM 可執行的轉換:INITIAL→GATHERING、GATHERING→GATHERING(資訊收集)│
│ LLM 不可直接觸發的轉換:VALIDATING→PROCESSING(必須由系統驗證) │
│ AWAITING→PROCESSING(必須有明確用戶確認) │
└─────────────────────────────────────────────────────────────────────┘
邊界原則
| 決策類型 | 由誰決定 | 理由 |
|---|---|---|
| 下一步要問什麼問題 | LLM | 語言理解任務 |
| 如何組織回答 | LLM | 生成任務 |
| 是否滿足退款條件 | 規則引擎 | 確定性邏輯 |
| 是否執行退款 API | 狀態機 + 用戶確認 | 不可逆副作用 |
| 工具呼叫的參數值 | LLM | 語意提取任務 |
| 工具呼叫是否允許 | 權限系統 | 安全邊界 |
八、為什麼選 X 不選 Y
語意記憶該用哪個向量資料庫,見 Phase 11 Part 2 §八 ;Agent 框架(自建 vs LangChain / LlamaIndex / AutoGen)整篇在 Phase 14 Part 3 。這裡只留四個本篇真正要權衡的決策。
決策 1:工作記憶存儲
選擇 選 Redis 的理由 不選的理由
─────────────────────────────────────────────────────────────
Redis < 1ms 讀寫;原生 TTL; Memcached:無持久化,
vs Cluster 模式;支援 HASH/LIST 資料結構少
PostgreSQL structure Postgres:連接開銷 5ms+,
vs session 數萬時連接池壓力大
In-memory TTL 自動清理;多 Worker 共享 In-memory:Worker 重啟失憶,
dict 同一 session 狀態 無法水平擴展
Flip condition:若日均 session < 100,in-memory dict 足夠;Redis 的必要性從 1K sessions/day 開始顯現。
決策 2:ReAct vs Plan-and-Execute
選擇 選 ReAct 的理由 不選的理由
──────────────────────────────────────────────────────────────
ReAct 動態調整;每步根據觀察重新決策; Plan-and-Execute:
vs 適合不確定性高的任務; 預設計畫在中途失效時
Plan-and- 延遲低(不需先生成完整計畫) 難以動態調整;需要
Execute 額外的 re-planning 機制
示意:5 步以內任務 ReAct vs P&E P&E 在 > 10 步、結構
完成率相同(~82%),延遲 ReAct 低 明確的任務上完成率更高
30–40% (84% vs 79%)
Flip condition:任務步驟 > 10 且流程高度結構化(如:固定的 SOP)時,Plan-and-Execute 更可預測。
決策 3:記憶壓縮策略
選擇 選遞進摘要的理由 不選的理由
──────────────────────────────────────────────────────────────
遞進摘要 保留語意資訊; 滑動視窗:丟失早期
vs 壓縮比高(16:1); 重要資訊;< 10 步
滑動視窗 長任務(> 10 步)適用 任務可用
vs 選擇性保留:需要額外
選擇性保留 每次壓縮只多花 $0.002; LLM 呼叫打分,延遲
延遲影響 < 500ms 增加 800ms+
Flip condition:任務 < 8 步時,滑動視窗(保留最近 6 步)成本最低,無需摘要。
決策 4:用戶確認機制
選擇 選「明確確認 + 狀態機鎖定」的理由 不選的理由
──────────────────────────────────────────────────────────────
明確確認 不可逆操作(退款/刪除/下單) 隱式確認(LLM 判斷):
+ 狀態機 必須有用戶明確輸入; 幻覺風險;用戶說
vs 狀態機確保同一 session 不可 "好的隨便" 被誤判為
隱式確認 重複觸發同一退款 確認;無稽核日誌
Flip condition:對於可逆操作(查詢、摘要、推薦),不需要確認步驟,加確認反而降低 UX 流暢度。
九、系統效應:無記憶 vs 完整記憶系統
以電商客服 Agent 為基準,日均 80K 對話,每對話平均 4.2 步。
| 指標 | 無記憶(單次呼叫) | 完整記憶系統(四層架構) | 改善幅度 |
|---|---|---|---|
| 任務完成率 | 43% | 79% | +36pp |
| 需要人工接管率 | 57% | 21% | -36pp |
| 平均對話輪次 | 7.8 輪(重複問題多) | 4.2 輪 | -46% |
| 用戶重複提供資訊次數 | 2.3 次/對話 | 0.1 次/對話 | -96% |
| P99 回應延遲 | 3.2s | 5.8s(多了記憶查詢) | -2.6s |
| P50 回應延遲 | 1.8s | 2.4s | -0.6s |
| 每次對話成本 | $0.03 | $0.12 | +$0.09 |
| 月基礎設施成本 | $200 | $3,200 | +$3,000 |
| 用戶滿意度(CSAT) | 3.1 / 5 | 4.2 / 5 | +35% |
| 退款錯誤率 | 2.1% | 0.08% | -96% |
以上為示意估算,非實測數據。
成本效益分析(延續上表的示意數字)
月對話量:80K × 30 = 2.4M
每避免一次人工接管節省:~$2.5(人工客服成本)
月減少人工接管次數:2.4M × 0.36 = 864,000 次
月節省人工成本:864,000 × $2.5 = $2,160,000
月額外成本:基礎設施 $3,000 + LLM API 增量($0.09 × 2.4M = $216,000)= $219,000
ROI = ($2,160,000 − $219,000) / $219,000 ≈ 886%
與純人工方案(2.4M × $2.5 = $6M)相比,完整記憶系統的月總成本約為 LLM $288,000($0.12 × 2.4M)+ 基礎設施 $3,200 + 剩餘 21% 人工接管 $1,260,000 ≈ $1.55M,整體客服成本降低約 74%。
十、系列導航
本文是 AI 工程從零開始 系列 Phase 14 Part 1。
← Phase 13 Part 2:AI 工作流程編排 — LangChain、LlamaIndex 與生產管線
→ Phase 14 Part 2:Agent 規劃系統 — 從目標到行動計畫
系列索引:AI 工程從零開始 — 完整系列
