Eric TechBlog
AIAI Assistant

Context Window Management

上下文視窗管理,包含預算分配、資訊篩選策略、訊息視窗設計,以及 token 的隱藏消耗和成本模型。

設計問題

在 email AI Assistant 裡,context window 很快就會被吃光:最近對話、相關記憶、過去聊過的信件、搜尋結果 snippet、工具定義,全都想擠進去。

LLM 的 context window 是一個有限的資源,就像伺服器的記憶體一樣。你有 128K 個 token 的預算,但不代表你應該把 128K 全部用掉。

更多的 context 不一定 = 更好的回答。

研究顯示 LLM 有「Lost in the Middle」問題。放在 context 中間的資訊,比放在開頭和結尾的更容易被忽略。此外,token 用量直接影響延遲和費用。

核心問題:如何在有限的 token 預算內,塞進最多「有用」的資訊,同時排除「噪音」?

這篇在整個系列中的位置

Retrieval Pipeline 會告訴你怎麼把正確的 email 找出來; Memory Architecture 會告訴你哪些跨對話資訊值得保留; 這篇要解的是:找到之後,哪些內容真的值得塞進模型的 context。


預算分配

第一步是決定每個資訊來源應該分配多少預算:

刻意的節制

我們只用了不到 10K token。這不是因為 context window 不夠大,而是刻意的設計。每一塊都經過篩選,確保是高密度的有用資訊。


資訊篩選策略

策略一:全量 vs 篩選

資訊來源全量篩選後節省
用戶記憶(50 則)5,000 token300 token(top 3)94%
過去對話(20 段)40,000 token1,500 token(top 3)96%
歷史訊息(40 則)8,000 token2,000 token(top 10)75%
Email 搜尋結果5,000 token(10 封全文)1,500 token(snippet + 2 封全文)70%

篩選的手段:語意搜尋(找最相關的)+ 數量限制(取 top N)。

策略二:集中配置

所有的限制參數集中在一個配置中,方便調整:

LIMITS = {
  memoriesPerRequest: 3,       ← 每次注入幾則記憶
  recentMessageWindow: 10,     ← 保留最近幾則訊息
  olderMessagesToUse: 10,      ← 從舊訊息中搜出幾則
  rerankerCandidates: 30,      ← Reranking 的候選數量
  maxAgentSteps: 10,           ← Agent 最多執行幾步
}

這些數字是 trade-off 的具體化:

參數增大好處代價
memoriesPerRequest ↑更多個人化更多 token、可能引入無關記憶
recentMessageWindow ↑更連貫的對話更多 token
olderMessagesToUse ↑更好的長對話支持更多 token、更慢
rerankerCandidates ↑更好的搜尋品質更貴的 reranking
maxAgentSteps ↑更複雜的任務更長的延遲、更多 token

策略三:漸進式揭露

搜尋工具的三步工作流就是漸進式揭露的實踐:

搜尋

返回 metadata + snippet(~50 token/result)

Agent 判斷

哪些需要全文

getEmails

只取需要的全文(~500 token/email)

不是一次把所有資訊都塞進 context,而是讓 Agent 自己決定要深入哪些。


訊息視窗的設計

滑動視窗 + 語意搜尋

為什麼這樣切?

最近的訊息必須全量保留。它們是對話的當前脈絡;如果丟掉最近的訊息,LLM 就會不知道用戶在說什麼。

舊的訊息用語意搜尋。大部分舊訊息已經不相關了(「你好」「謝謝」),語意搜尋會找回真正重要的:

  • 對話開頭的背景設定
  • 中間做出的重要決定
  • 提到的約束條件

語意查詢的構造

查詢 = 所有最近訊息 + 最新訊息(重複一次加權)

最新訊息加權的原因:它最可能是決定「需要什麼上下文」的關鍵。如果用戶問「John 那封信最後怎麼說?」,最新訊息中的「John」「那封信」會決定應該把哪些舊訊息和搜尋結果找回來。


System Prompt 的結構

System prompt 的結構設計也影響 context 的有效性:

1. 角色定義(你是誰)
2. 工具使用規則(什麼時候用什麼工具)
3. 記憶(用戶的背景知識)
4. 關聯對話(過去的相關經驗)
5. 當前任務指引

把記憶和關聯對話放在 system prompt 而非 user message 中,原因:

  1. 位置優勢:System prompt 在 context 的開頭,不容易被 "Lost in the Middle"
  2. 角色區分:LLM 把 system prompt 當作「自己的知識」,而非「用戶說的話」
  3. 持久性:System prompt 在每輪對話中固定,不會隨著訊息增加而被推到 context 中間

Token 的隱藏消耗

除了你主動放進 context 的內容,還有一些隱藏的 token 消耗:

工具定義

每個工具的 schema(名稱、描述、參數定義)都佔 token。如果你註冊了 20 個工具,可能就吃掉 2,000+ token。

優化:App Selection 機制。用戶沒選中的 App 工具不會被註冊。如果用戶只選了 Calendar,Agent 只看到 Calendar 相關的工具,不會被 Tasks 的工具定義佔用 token。

工具呼叫歷史

Agent 的每次工具呼叫和結果都在 context 中累積。10 步的 Agent 循環,每步搜尋返回 10 個結果,可能累積數千 token。

優化maxAgentSteps 限制步數。搜尋工具返回 snippet 而非全文。

HITL 流程的文字化

HITL 的 data parts(確認請求、用戶決定、執行結果)在轉為 LLM 可理解的格式時,也佔用 token:

"The assistant requested to run findCalendarEvents with input: {...}"
"The user approved the tool."
"The tool returned this result: {...}"

生產考量

動態預算分配

靜態的限制參數(3 則記憶、10 則舊訊息)是最簡單的方案。更進階的做法:

  • 根據剩餘 token 預算動態調整
  • 根據查詢類型調整(簡單問答少給上下文,複雜分析多給)
  • 根據模型的 context window 大小調整

監控

需要監控的指標:

  • 每次請求的 input/output token 數量
  • Context 的利用率(實際用了多少 / window 大小)
  • 記憶命中率(注入的記憶有多少被 Agent 使用了)
  • 搜尋的點擊率(Agent 深入查看了幾個搜尋結果)

成本模型

每次對話的成本 ≈ input_tokens × input_price + output_tokens × output_price

假設 Gemini Flash 的定價:

  • Input: $0.15 / 1M token
  • Output: $0.60 / 1M token

一次典型的對話(~7K input + ~500 output):

  • Input: 7,000 × 0.00000015=0.00000015 = 0.00105
  • Output: 500 × 0.0000006=0.0000006 = 0.0003
  • 總計 ≈ $0.0014

如果每天 1000 次對話:$1.4/day。context 管理做得好,成本完全可控。


重點總結

設計原則實踐方式
少即是多語意搜尋篩選,只放最相關的
預算化思維每個來源有明確的 token 上限
漸進式揭露snippet → 判斷 → 全文
位置敏感重要資訊放 system prompt(開頭)
集中控制所有限制參數集中配置
成本可預測固定上限 = 可預測的費用

Last updated on

On this page