iT邦幫忙

2026 iThome 鐵人賽

DAY 10
1
Build on Google AI

Google ADK Agent 教戰:30 天從原型到可上線的 AI Agent 系統系列 第 10

Day 10 - 像管理原始碼一樣管理 Context:壓縮與 Token 最佳化

  • 分享至 

  • xImage
  •  

Day 10 | 像管理原始碼一樣管理 Context:壓縮與 Token 最佳化

讀完能做到:幫 agent 掛上自動化的 context 壓縮,看懂 token-based 跟 sliding window 兩種策略的優先序,也知道這個設定該掛在哪個物件上(提示:不是你以為的那個)。

對話越長,帳單越貴,這不是巧合

Day 9 講完 Session 跟 State,你已經知道 agent 怎麼記住一段對話。但「記住」是有代價的——官方文件講得很直白:agent 執行過程中會累積 context 資訊,包含使用者指令、檢索到的資料、工具回應、生成的內容;隨著這份 context 越變越大,每一輪都要把整包東西重新送給模型,處理時間跟著拉長,越來越多資料被送進生成模型,處理時間增加,回應變慢。

如果你曾經把一個對話型 agent 放上線,觀察過它聊到第二十輪之後回應速度明顯變慢,這不是模型突然變笨,是你送給它的 context 已經比第一輪大了好幾倍。Context Compaction 就是解這個問題的機制——在 agent 執行過程中,自動摘要較舊的 session 歷史(包含指令、輸入、模型回應),目標是「optimizes latency and reduces costs」,同時確保 agent 仍然保有存取近期重要互動的能力。

實作上,壓縮整合在 SingleFlow 的 CompactionRequestProcessor 裡,依你在 EventsCompactionConfig 設的規則自動觸發。

https://ithelp.ithome.com.tw/upload/images/20260907/20183762NRfXEojh02.png

兩種壓縮策略

官方給了兩種管理策略:

  • Token-Based(主要)——依實際消耗的 token 量觸發清理。這是一個絕對的安全網,特別適合不可預測的負載,比如使用者貼了一大塊程式碼或上傳了一個大檔案。
  • Sliding Window(依輪數)——固定的對話輪數後觸發清理,適合規律、可預期的文字對話。

這兩者不是各自獨立、可以並行的兩條規則——如果你兩種都設定了,系統會優先套用 token-based。當 session 長度超過你定義的 token 門檻,系統就觸發 token-based 壓縮,並跳過該輪的 sliding-window 壓縮。這個優先序的設計邏輯不難理解:token 數才是真正決定成本跟延遲的變數,輪數只是一個粗略的代理指標——如果某一輪使用者貼了五千行程式碼,單看「輪數」完全抓不到這輪其實已經超支了。

Token-based 壓縮:一個安全網

設定兩個參數:token_threshold(觸發壓縮的 token 安全上限)與 event_retention_size(觸發時要保留幾筆最近的原始未壓縮 event,用來維持代名詞指涉這類即時脈絡)。

from google.adk.apps.app import App, EventsCompactionConfig
from google.adk.agents import Agent

root_agent = Agent(
    name="my_root_agent",
    description="Main coordinating agent for the workflow."
)

compaction_config = EventsCompactionConfig(
    token_threshold=4000,     # 實際 token 數超過這個值就觸發壓縮
    event_retention_size=5    # 觸發時保留最近 5 筆原始 event,不壓縮
)

app = App(
    name="my_compacting_agent_app",
    root_agent=root_agent,
    events_compaction_config=compaction_config
)

注意設定是掛在 App 物件上,不是掛在 agent 上——這是這篇文章要特別強調的一點,因為官方大綱的標題「像管理 Source Code 一樣管理 Context」很好懂,但初次接觸的人很容易下意識去 Agent(...) 建構子裡找這個參數,結果找不到,因為它根本不在那裡。App 這個容器物件(Python v1.14.0 起)是專門用來放這類「整體運作基礎設施」設定的地方,跟個別 agent 的任務推理邏輯分開。

Sliding window:依輪數觸發

compaction_interval(間隔幾輪觸發一次)與 overlap_size(新一輪摘要要重疊保留前一輪的幾個 event)設定:

app = App(
    name='my-agent',
    root_agent=root_agent,
    events_compaction_config=EventsCompactionConfig(
        compaction_interval=3,  # 每 3 輪新的 invocation 觸發一次壓縮
        overlap_size=1          # 保留上一個壓縮視窗最後一輪,作為重疊脈絡
    ),
)

拿官方的具體例子攤開來看:compaction_interval=3, overlap_size=1 時,event 3 完成時把前 3 筆事件壓成一份摘要;event 6 完成時,壓縮範圍是事件 3 到 6(含 1 筆與前一份摘要重疊的事件);event 9 完成時同理壓 6 到 9。每一次新的壓縮,都會帶著上一輪最後一點點的原始脈絡,避免「摘要完全接不上剛才在講什麼」的斷裂感。設定好之後,Runner 會在背景自動處理壓縮,不需要你手動觸發。

自訂摘要模型

如果你不想用預設的摘要邏輯,可以透過 LlmEventSummarizer(Python)指定專門用來做摘要的模型——這個模型可以跟你 agent 主體用的模型不同,通常會選一個更便宜、更快的模型專門做這件事,因為摘要任務對推理能力的要求遠低於主線任務:

from google.adk.apps.app import App, EventsCompactionConfig
from google.adk.apps.llm_event_summarizer import LlmEventSummarizer
from google.adk.models import Gemini

summarization_llm = Gemini(model="gemini-flash-latest")
my_summarizer = LlmEventSummarizer(llm=summarization_llm)

app = App(
    name='my-agent',
    root_agent=root_agent,
    events_compaction_config=EventsCompactionConfig(
        compaction_interval=3,
        overlap_size=1,
        summarizer=my_summarizer,
    ),
)

想要更細緻的控制,還可以直接改 LlmEventSummarizerprompt_template——如果預設摘要邏輯把你關心的某類資訊(例如工具呼叫的具體參數)壓縮掉太多,這是修正的入口。

這個功能解決的是「文章讀起來會忘記自己在講什麼」的那種問題

值得強調一下 Context Compaction 跟 Day 9 講的 Rewind 是完全不同方向的機制——Rewind 是「主動退回」到某個時間點,通常是人為觸發、用來修正錯誤;Compaction 是「被動摘要」,跑在背景,目的是讓對話能無限延伸下去而不被 context 上限或成本壓垮。兩者都碰 session 的歷史資料,但一個是拿掉,一個是壓縮保留精華,行為模型完全不同。

銜接

壓縮解決的是「舊的對話怎麼變小」,但還有另一種省成本的思路:如果同一段長 instruction 或大型資料集要被重複送給模型,能不能讓模型端直接快取起來,不用每次都整包重傳?明天講 Context Caching,跟今天的壓縮正好是 token 最佳化的兩面。


Google ADK 官方網站
GitHub - Agent Development Kit (ADK) 2.0

GitHub 開源實作:https://github.com/SeanLinH/adk_tutor


上一篇
Day 09 - Sessions 管理:對話狀態、Rewind
下一篇
Day 11 - Caching 與 Artifacts:省錢與管檔案的兩把工具
系列文
Google ADK Agent 教戰:30 天從原型到可上線的 AI Agent 系統21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言