在前一篇文章中,我們學習了 RAG(檢索增強生成)系統,讓 AI 能夠基於文件知識庫回答問題,大幅提升回應的準確性和可靠性。RAG 解決了知識來源的問題,但在實際應用中,當對話持續進行,我們會面臨另一個挑戰:如何有效管理不斷增長的對話歷史?
想像你的客服 AI 代理已經和客戶對話了 50 輪,每一輪都包含問題和回答,還有 RAG 檢索到的文件內容,這時候歷史記錄可能已經包含數千個 token。這不僅會觸及模型的 context window 限制,更會讓 API 成本快速累積
今天我們要探討 Koog AI 框架的 History Compression(歷史記錄壓縮)
功能。這項技術會在 LLM session 內部自動將冗長的對話歷史壓縮成精簡的摘要,直接替換原本 prompt 中的訊息。與前面學到的 RAG 和 Embeddings 相輔相成——Embeddings 幫助我們理解內容的語意重要性,RAG 提供可靠的知識來源,而歷史壓縮則確保我們只保留最關鍵的資訊
當 Agent 完成一個任務階段,準備進入下一個階段時,這是進行壓縮的理想時機
val strategy = strategy<String, String>("multi-stage-agent") {
// 第一階段:收集資訊
val collectInformation by subgraph<String, String> {
// 收集用戶需求和相關資訊
}
// 壓縮歷史,為下一階段準備乾淨的上下文
val compressHistory by nodeLLMCompressHistory<String>(
strategy = HistoryCompressionStrategy.WholeHistory
)
// 第二階段:分析和決策
val analyzeAndDecide by subgraph<String, String> {
// 基於收集的資訊進行分析和決策
}
// 連接各階段
nodeStart then collectInformation then compressHistory then analyzeAndDecide
}
當對話歷史的訊息數量或 token 數超過設定的閾值時,系統會自動觸發壓縮
// 基於訊息數量判斷
private suspend fun AIAgentContextBase.shouldCompress(): Boolean {
return llm.readSession { prompt.messages.size > 20 }
}
// 基於內容長度判斷
private suspend fun AIAgentContextBase.shouldCompressByLength(): Boolean {
return llm.readSession {
prompt.messages.sumOf { it.content.length } > 8000
}
}
// 在策略中使用條件判斷
edge(executeTool forwardTo compressHistory onCondition { shouldCompress() })
選擇適當的壓縮時機非常重要
Koog AI 提供了四種不同的歷史記錄壓縮策略,我們來一一了解
這是預設策略,會將整個對話歷史壓縮成一個摘要
val compressHistory by nodeLLMCompressHistory<String>(
strategy = HistoryCompressionStrategy.WholeHistory
)
適用情況
保留最近的 N 條訊息,壓縮其餘部分
val compressHistory by nodeLLMCompressHistory<String>(
strategy = HistoryCompressionStrategy.FromLastNMessages(10)
)
適用情況
將歷史記錄分成多個區塊,分別壓縮
val compressHistory by nodeLLMCompressHistory<String>(
strategy = HistoryCompressionStrategy.Chunked(chunkSize = 10)
)
適用情況
這是最進階的策略,可以指定要從歷史記錄中提取哪些特定資訊
val compressHistory by nodeLLMCompressHistory<String>(
strategy = HistoryCompressionStrategy.RetrieveFactsFromHistory(
Concept(
keyword = "user_preferences",
description = "使用者的個人偏好設定,包括語言、主題風格等",
factType = FactType.MULTIPLE
),
Concept(
keyword = "issue_resolved",
description = "客戶的問題是否已經解決?",
factType = FactType.SINGLE
)
)
)
適用情況
有兩種主要的方式來實作歷史記錄壓縮
這是最常用和推薦的方式,使用 nodeLLMCompressHistory
節點在策略圖中定義壓縮邏輯
val strategy = strategy<String, String>("agent-with-compression") {
val processRequest by nodeLLMRequest()
val executeTool by nodeExecuteTool()
val sendToolResult by nodeLLMSendToolResult()
// 在策略圖中加入壓縮節點
val compressHistory by nodeLLMCompressHistory<ReceivedToolResult>(
strategy = HistoryCompressionStrategy.FromLastNMessages(10)
)
// 定義何時觸發壓縮
edge(executeTool forwardTo compressHistory onCondition {
llm.readSession { prompt.messages.size > 15 }
})
edge(compressHistory forwardTo sendToolResult)
}
優點
適用場景
在自定義節點內部使用 replaceHistoryWithTLDR()
函數進行壓縮,提供更精細的控制
特點
llm.writeSession { replaceHistoryWithTLDR() }
優點
適用場景
實作方式 | 適用情況 | 優勢 | 注意事項 |
---|---|---|---|
策略圖實作 | 一般應用、清晰流程 | 易維護、清晰可見 | 較少自定義空間 |
自定義節點實作 | 複雜邏輯、特殊需求 | 高度靈活、精細控制 | 實作複雜度較高 |
對於大多數應用,建議優先選擇策略圖實作方式,因為它提供了良好的可讀性和維護性
這個範例展示了策略圖實作方式的具體應用,讓我們看看如何在客服系統中整合歷史記錄壓縮功能
範例會展示壓縮是如何在 LLM session 內部自動進行的,以及如何診斷壓縮前後的差異
注意:下面是一個簡化版本的流程範例,主要用於說明策略圖實作的概念。在實際應用中,可能需要配合更複雜的工具和業務邏輯(如 Day 23 或 Day 24 的範例)才能觀察到明顯的壓縮效果
class CustomerServiceAgentWithHistoryCompression {
// 檢查歷史記錄是否過長(超過 10 條訊息就壓縮)
private suspend fun AIAgentContextBase.shouldCompressHistory(): Boolean {
return llm.readSession { prompt.messages.size > 10 }
}
private val agent = AIAgent(
executor = simpleOpenAIExecutor(ApiKeyManager.openAIApiKey!!),
systemPrompt = """
你是一個專業的客服助手,負責回答客戶問題。
請用正體中文回應客戶,保持友善和專業的態度。
""".trimIndent(),
llmModel = OpenAIModels.CostOptimized.GPT4_1Mini,
strategy = createStrategy()
)
private fun createStrategy() = strategy<String, String>("customer-service-with-compression") {
// 定義主要處理節點
val processRequest by nodeLLMRequest()
val executeTool by nodeExecuteTool()
val sendToolResult by nodeLLMSendToolResult()
// 歷史記錄壓縮節點 - 使用 FromLastNMessages 策略
val compressHistory by nodeLLMCompressHistory<ReceivedToolResult>(
strategy = HistoryCompressionStrategy.FromLastNMessages(5)
)
// 診斷節點 - 展示壓縮前後的狀態
val diagnosticNode by node<ReceivedToolResult, ReceivedToolResult>("diagnostic") { toolResult ->
println("=== 歷史記錄壓縮觸發 ===")
// 顯示壓縮將要發生
val beforeMessages = llm.readSession { prompt.messages.size }
println("壓縮前訊息數量: $beforeMessages 條")
println("即將觸發壓縮:保留最近 5 條訊息,將早期對話摘要化")
toolResult
}
// 壓縮後檢查節點
val postCompressionCheck by node<ReceivedToolResult, ReceivedToolResult>("post_compression") { toolResult ->
val afterMessages = llm.readSession { prompt.messages.size }
println("壓縮完成!目前訊息數量: $afterMessages 條")
toolResult
}
// 建立執行流程
edge(nodeStart forwardTo processRequest)
// 如果是助理回應,直接結束
edge(processRequest forwardTo nodeFinish onAssistantMessage { true })
// 如果需要使用工具,執行工具
edge(processRequest forwardTo executeTool onToolCall { true })
// 執行工具後檢查是否需要壓縮歷史
edge(executeTool forwardTo diagnosticNode onCondition { shouldCompressHistory() })
edge(diagnosticNode forwardTo compressHistory)
edge(compressHistory forwardTo postCompressionCheck)
edge(postCompressionCheck forwardTo sendToolResult)
// 如果不需要壓縮,直接發送工具結果
edge(executeTool forwardTo sendToolResult onCondition { !shouldCompressHistory() })
// 處理工具結果後的後續動作
edge(sendToolResult forwardTo executeTool onToolCall { true })
edge(sendToolResult forwardTo nodeFinish onAssistantMessage { true })
}
suspend fun handleCustomerQuery(query: String): String {
return agent.run(query)
}
}
// 一般客服:保留最近對話即可
HistoryCompressionStrategy.FromLastNMessages(15)
// 技術支援:需要保留完整上下文
HistoryCompressionStrategy.WholeHistory
// 銷售對話:提取客戶偏好和需求
RetrieveFactsFromHistory(
Concept("customer_needs", "客戶需求和偏好", FactType.MULTIPLE),
Concept("budget_range", "客戶預算範圍", FactType.SINGLE)
)
// 長期專案:分段保留不同階段資訊
HistoryCompressionStrategy.Chunked(20)
val compressHistory by nodeLLMCompressHistory<String>(
strategy = HistoryCompressionStrategy.FromLastNMessages(10),
preserveMemory = true // 保留 AgentMemory feature 的記憶訊息
)
透過本篇文章,我們深入學習了 Koog AI 框架的歷史記錄壓縮功能,這是建立高效能 AI 應用的關鍵技術。從四種壓縮策略到實際的範例說明,我們掌握了如何在長時間對話中自動維持效能與品質的平衡
明天我們將學習如何自定義 AI 模型設定,探討當新模型(如 GPT-5)發布時,如何快速在 Koog 框架中配置和使用。結合今天學到的歷史壓縮技術,我們將能夠充分發揮新模型的能力,同時控制使用成本,建立更強大的 AI 應用系統
圖片來源:AI 產生
同步刊登於 Blog 第一次學 Kotlin Koog AI 就上手 Day 32:歷史記錄壓縮:優化對話上下文與降低成本