各位饕客,你們的 AI 滿漢全席吃到現在,有沒有遇到一個甜蜜的煩惱?那就是... Token 限制!
想像你的頂級廚房 (Kernel) 不斷收到來自服務生 (Chat History) 的點餐單,單子越來越長,長到快拖地了!每一次的互動,廚房的每一位員工(Agent)都必須重新閱讀整張單子,才能知道你最新點了什麼、做了哪些更改。
這就是「上下文(Context)」帶來的沉重負擔。當你的對話累積到數十輪,AI 就要花費更多的時間和昂貴的 Token 來處理冗長的歷史紀錄。更糟的是,一旦對話歷史超出模型的最大限制,部分舊的資訊就會被無情地截斷,導致 AI 像得了失憶症一樣,忘記你前面說過的重要細節。
我們需要一個更聰明、更有效率的方法來處理這些滾滾而來的資訊。這就是我們今天的秘密武器——WhiteboardProvider
,或者我稱它為「主廚的短期記憶白板」。
主廚(Kernel/Agent)不需要記住客人說的每一句話,他只需要在廚房的白板上寫下關鍵的決策、待辦事項和最終需求。
WhiteboardProvider
最厲害的地方在於,它並不需要你自己手動整理這些資訊,它是自動化的!它會利用背後的 AI 模型能力,在 Agent 每次接收到新的對話時,自動提煉出最重要的短期上下文,將其儲存並更新在白板上。
這樣一來,即使你為了節省 Token 必須截斷部分舊的對話歷史,最重要的「短期關鍵資訊」也不會丟失,Agent 永遠能以最經濟的方式掌握最新的狀況。
現在,讓我們透過一個實戰案例來看看這個「主廚白板」如何在實際的 Agent 應用中發揮作用。我們將建立一個 Agent 協調員,負責幫我們規劃旅遊行程,並觀察它是如何在來回更改需求時保持記憶的。
首先,我們需要引入 Microsoft.SemanticKernel.Agents.Whiteboard
套件。
Bash
dotnet add package Microsoft.SemanticKernel.Agents.Whiteboard
我們建立一個旅遊規劃師 Agent,並給它一個明確的指示:將關鍵資訊寫入白板。然後,我們在 Agent 的執行緒中啟用 WhiteboardProvider
。
using Azure.AI.OpenAI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Memory;
using OpenAI;
// 假設你已經設定好 Kernel
var config = new ConfigurationBuilder()
.AddUserSecrets<Program>()
.Build();
var builder = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
modelId: "gpt-5-mini",
apiKey: config["OpenAI:ApiKey"]!);
var kernel = builder.Build();
// 1. 建立 Agent:它必須知道要使用白板來記錄資訊
var agent = new ChatCompletionAgent
{
Kernel = kernel,
Instructions = "你是一位專業的旅遊規劃師。你的任務是從對話中提煉出旅行地點、時間、人數等關鍵資訊,並寫入白板。最終根據白板上的資訊,確認行程。你需要將白板內容作為對話的參考依據。",
Id = "TravelPlanner"
};
var chatClient = new OpenAIClient(config["OpenAI:ApiKey"]!).GetChatClient("gpt-5-mini").AsIChatClient();
// 2. 建立 WhiteboardProvider:這就是我們的「記憶白板」
var whiteboard = new WhiteboardProvider(chatClient);
// 3. 建立 Agent Thread (對話執行緒)
var thread = new ChatHistoryAgentThread();
// 4. 將 Whiteboard 加入 Thread 的 Arguments - 這是 Agent 能夠存取並更新白板的關鍵
thread.AIContextProviders.Add(whiteboard);
// --- 模擬多輪對話 ---
// 5. 第一輪對話:提出模糊需求
Console.WriteLine("--- 執行對話 1:我想規劃一趟國外旅遊。預計在聖誕節附近,目標是去東京,兩個人。 ---");
ChatMessageContent messages1 = await agent.InvokeAsync("我想規劃一趟國外旅遊。預計在聖誕節附近,目標是去東京,兩個人。", thread).FirstAsync();
Console.WriteLine($"\n[Agent 回覆]:\n{messages1.Content}");
// 檢查白板內容:它應該擷取了東京和聖誕節
Console.WriteLine($"\n========================\n*** 白板內容 (1) ***\n{whiteboard.CurrentWhiteboardContent}\n========================");
// 預期輸出會類似:
// Decisions: 目的地是東京。
// Requirements: 規劃國外旅遊,兩個人。
// Action Items: 需確認精確的日期範圍,並考慮聖誕節假期的價格。
// 6. 第二輪對話:需求變更 (最能體現白板價值的地方)
Console.WriteLine("\n--- 執行對話 2:東京太貴了,我們改成去大阪,時間提前到 11 月底,從 11/20 到 11/27。 ---");
ChatMessageContent messages2 = await agent.InvokeAsync("等一下,東京太貴了。我們改成去大阪,時間提前到 11 月底,從 11/20 到 11/27。", thread).FirstAsync();
Console.WriteLine($"\n[Agent 回覆]:\n{messages2.Content}");
// 檢查最新的白板內容:它應該被更新了
Console.WriteLine($"\n========================\n*** 白板內容 (2) ***\n{whiteboard.CurrentWhiteboardContent}\n========================");
// 預期輸出會類似:
// Decisions: 目的地已更改為大阪。
// Requirements: 規劃國外旅遊,兩個人。
// Action Items: 行程預訂時間為 11/20 到 11/27。
// 7. (可選) 模擬截斷歷史紀錄後,Agent 仍可透過白板確認資訊
// 假設我們現在丟棄了前兩輪的完整聊天歷史,只留下白板資訊給 Agent
Console.WriteLine("\n--- 執行對話 3 (僅依靠白板資訊):請為我確認一下最終的行程地點和時間。 ---");
ChatMessageContent messages3 = await agent.InvokeAsync("請為我確認一下最終的行程地點和時間。", thread).FirstAsync();
Console.WriteLine($"\n[Agent 回覆]:\n{messages3.Content}");
// Agent 會根據白板上的「大阪」和「11/20-11/27」來確認,而不是前面已被遺忘的「東京」和「聖誕節」。
看到了嗎?即使在對話中途,使用者完全推翻了之前的決定,Agent 也能透過這個精簡過的「主廚白板」迅速捕捉到最新的目的地 (大阪) 和時間 (11/20-11/27)。這就像主廚直接擦掉白板上的「東京」和「聖誕節」,寫上了最新的指令,快速又精準!
這不僅節省了大量的 Token 消耗(因為每次呼叫 AI 時,只需要傳遞精簡的白板內容作為系統提示詞,而不是整個聊天紀錄),還能確保 Agent 在處理長篇對話時,永遠不會忘記使用者最新的關鍵指示。
對於複雜的商業流程、長時間的客服對話,或是需要動態更新狀態的任務,WhiteboardProvider
絕對是你的 Token 預算和 AI 記憶的最佳救星!