
在單輪對話的應用中,狀態管理往往不是問題。但當 Agent 開始執行多步驟任務時,情況會迅速複雜化 —— 它必須記得前一步查到了什麼、目前處於哪個階段、以及哪些資訊只在這一輪有效。
以 Leave Copilot 的難點 ① 為例:「先用 search_leaves 取得真實 ID,再呼叫 update_leave_status」。這條規則表面上是順序問題,實際上牽涉到一個更基礎的能力 —— 模型必須把查到的 ID 正確地保存到下一次呼叫。
Google ADK 對此提供了一套相當完整的機制:用鍵名前綴區分作用域的 Session State,以及處理跨對話知識的 Memory Service。但其中有一個容易踩到、而且失敗時完全沒有錯誤訊息的陷阱。
以下的內容,將會說明 State 的四種作用域、正確的更新方式,以及短期記憶在 Token 成本上的實際限制。

Google ADK 透過鍵名前綴來決定 State 的作用域,這是相當實用的一個設計 —— 它讓開發者不需要額外管理多套儲存機制,就能區分資料的生命週期:

session.state['current_leave_id'] = 'LV-7f3a91' # 本次對話
session.state['user:default_assignee'] = 'u_2841' # 跨 session
session.state['app:maintenance_window'] = '02:00-04:00' # 全域
session.state['temp:raw_search_result'] = {...} # 用完即丟
temp: 特別值得注意。工具回傳的原始資料常常又大又雜,留在 state 裡只會佔上下文。用 temp: 存,這一輪結束就自動清掉。
這是文件裡標了警告的地方,而且失敗時不會報錯:
# ✗ 錯誤:這個修改不會被保存
retrieved_session = await session_service.get_session(...)
retrieved_session.state['key'] = value
透過 get_session() 取回的 Session 物件本質上是一份快照。直接修改它並不會拋出任何異常,變數的值也確實改變了 —— 然而下一次讀取時,那個修改會完全消失。
正確做法是透過事件更新:
from google.adk.events import Event, EventActions
state_changes = {
"task_status": "active",
"user:login_count": 1,
"temp:validation_needed": True,
}
actions_with_update = EventActions(state_delta=state_changes)
system_event = Event(
invocation_id="inv_login_update",
author="system",
actions=actions_with_update,
timestamp=current_time,
)
await session_service.append_event(session, system_event)
官方的說明是:state 應始終作為 Event 的一部分更新,以確保變更被追蹤、持久化正確運作,且更新是執行緒安全的。
這個設計對後續有直接影響:既然每次 state 變更都是一個 event,那麼 event stream 就是完整的狀態變遷紀錄。評測階段評估時可以檢查它,訓練資料階段萃取訓練資料時也可以用它還原每一步的上下文。
日常開發不必手動組 Event。ToolContext 和 CallbackContext 提供了直接的介面:
def my_callback_function(context: CallbackContext):
count = context.state.get("user_action_count", 0)
context.state["user_action_count"] = count + 1
context.state["temp:last_operation_status"] = "success"
框架會在背後把它包成 state_delta。

三者都支援 user: 和 app: 前綴,差別只在重啟後還在不在。
一個值得先知道的搭配關係:Google ADK 的 Tool Confirmation 目前標示為實驗性質,與 SessionService 的搭配有其適用範圍。而本系列把確認放在協定層(MCP Elicitation),好處之一正是它與 SessionService 的選擇彼此獨立 —— 換哪一種 session 都不影響那道防線。這是「規則跟著工具走」在架構上的又一次兌現。
這個系列在 Day 5 至 Day 11 都用 InMemorySessionService。評測階段跑評測時每個測試案例本來就要獨立的乾淨 session,記憶體版反而更合適。
「短期記憶」在 Google ADK 裡就是 session 內的 event 歷史。它會被組進每次請求的上下文,因此有兩個現實約束:
上下文長度。 工具回傳的 JSON 常常很大。search_leaves 回傳 50 張假單,那 50 張的完整內容都會進上下文。
成本。 上下文每一輪都重送。多輪對話的 token 消耗是累加的。
幾個實用的收斂手法:
search_leaves 回傳 id、title、status 就夠了,完整內容交給 get_leave。temp:,不要留在對話歷史裡。這一點會在訓練資料階段重新出現。 微調的目標之一正是「節省 token」——當模型把工具 schema 內化到權重裡,system Prompt 就不必每次注入完整的工具清單。對一個有九個工具、每個工具三五個參數的 Server 來說,這是每次請求都省下的固定成本。
跨 session 的記憶走 Memory Service,典型做法是把過往對話向量化後檢索。
對 Leave Copilot 來說,值得放進長期記憶的是規章知識:特休的遞延規則、病假需要哪些證明、加班補休怎麼換算。
但要提醒一件事:長期記憶不是這個系列要解決的問題。 四個難點都是「協定與領域規則的遵循」,跟記憶無關。RAG 檢索出來的相似案例,並不會讓模型記得「狀態不能跳級」。
分清楚哪些問題該用檢索解、哪些該用微調解,是評測階段的主題之一。
Session State 的設計看似只是 API 細節,但它背後反映的是一個重要的工程觀念:Agent 的狀態變更應該是可追蹤的事件,而不是可任意修改的變數。
總結來說,今天有三個重點值得記住:
temp: 前綴牽涉的是 Token 成本: 工具回傳的原始資料往往又大又雜,留在上下文裡只會佔用空間。用 temp: 存放,該輪結束即自動清除 —— 這個議題會在訓練資料階段討論微調的節省效益時重新出現。明天處理另一個相關的議題:如何讓模型在動手之前先想清楚順序。而那個選擇會在訓練資料階段準備訓練資料時,帶來意想不到的回報。

EventActions.state_delta、CallbackContext 用法、直接修改 session 的警告、三種 SessionService 對比查證日期:2026-08-19
大家好,我是 Simon 劉育維,是一位 AI 領域解決方案專家,目前也擔任 Google Cloud AI 領域開發者專家 (GDE),期待能夠幫助企業導入人工智慧相關技術解決問題。如果這篇文章對您有幫助,歡迎在我的 Linkedin 上留言提供意見,並與我一起討論有關人工智慧的主題,期待能夠對大家有所幫助!
我的個人部落格資訊:https://medium.com/@simon3458