本文 AI 產出,尚未審核

LangChain 課程 – Memory:對話記憶

主題:ConversationSummaryMemory


簡介

在建構以大型語言模型(LLM)為核心的聊天機器人時,對話記憶(Memory) 是決定系統是否能持續保持上下文、提供自然流暢回應的關鍵。若沒有適當的記憶機制,模型只能依賴當前的輸入,導致資訊遺失、回答斷裂,使用者體驗大打折扣。

ConversationSummaryMemory 是 LangChain 中一種 自動摘要式記憶,它會在對話持續進行時,將歷史訊息濃縮成一段簡短的摘要,並將摘要作為「背景」傳遞給後續的 LLM 呼叫。這種方式兼具 長期記憶(透過摘要保存關鍵資訊)與 成本控制(減少 token 數量),非常適合需要長時間追蹤使用者需求的應用。

本篇文章將從概念、實作、常見陷阱與最佳實踐,乃至實務應用場景,完整說明 ConversationSummaryMemory 的使用方法,讓你能在自己的 LangChain 專案中快速上手、避免踩雷。


核心概念

1. 為什麼需要「摘要」記憶?

  • Token 限制:大多數 LLM(如 OpenAI 的 GPT‑4)都有每次呼叫的 token 上限,對話過長會被截斷。
  • 成本考量:每個 token 都會產生成本,直接傳入完整對話會導致費用飆升。
  • 資訊抽取:不是所有過往訊息都同等重要,摘要能把「關鍵事實」保留下來,省去冗餘資訊。

結論ConversationSummaryMemory 透過 LLM 自動產生摘要,讓你在保持關鍵上下文的同時,控制 token 數量與成本。

2. 工作原理概覽

  1. 初始化:建立 ConversationSummaryMemory 並設定摘要模型(預設使用同一個 LLM)。
  2. 每輪對話
    • 把使用者與模型的最新訊息加入「暫存緩衝」 (buffer).
    • 當緩衝長度超過 max_token_limit(或自訂條件)時,觸發 摘要生成
    • 生成的摘要會覆寫原有的 summary,同時清空緩衝。
  3. 呼叫 LLM:在每次產生回應時,將 summary 作為 systemassistant 前置訊息傳入,確保模型具備先前的關鍵資訊。

3. 主要參數說明

參數 類型 說明
llm BaseChatModel 用於產生摘要的 LLM,若未指定則使用主要聊天模型。
memory_key string 存放摘要的變數名稱,預設為 "history"
input_key string 取得使用者輸入的鍵值,通常是 "input"
return_messages boolean 是否在 load_memory_variables 時返回完整訊息陣列(供 ChatPromptTemplate 使用)。
max_token_limit number 緩衝累計 token 超過此值時觸發摘要。
summarize_prompt PromptTemplate 自訂摘要提示詞,控制摘要的風格與細節。

程式碼範例

以下範例皆以 LangChainJS (Node.js) 為基礎,使用 @langchain/openai 套件作為 LLM 實作。

1️⃣ 基本使用:快速建立 ConversationSummaryMemory

// 1. 安裝套件
// npm install @langchain/core @langchain/openai

import { ChatOpenAI } from "@langchain/openai";
import { ConversationSummaryMemory } from "@langchain/memory";

// 建立聊天模型 (使用 GPT-3.5-turbo)
const llm = new ChatOpenAI({ modelName: "gpt-3.5-turbo", temperature: 0 });

// 建立摘要記憶
const summaryMemory = new ConversationSummaryMemory({
  llm,                     // 使用同一個 LLM 產生摘要
  memoryKey: "summary",   // 存放摘要的鍵名
  maxTokenLimit: 500,     // 超過 500 token 時觸發摘要
});

export default summaryMemory;

這段程式碼展示了最小化的設定:只需要提供 LLM 與 maxTokenLimit,其餘參數會使用預設值。


2️⃣ 搭配 ChatPromptTemplate 使用摘要

import { ChatPromptTemplate, HumanMessagePromptTemplate } from "@langchain/prompts";
import { LLMChain } from "@langchain/chains";

// 建立 Prompt,將摘要作為系統訊息注入
const prompt = ChatPromptTemplate.fromPromptMessages([
  // 系統訊息:放入摘要(若有)
  SystemMessagePromptTemplate.fromTemplate("{summary}"),
  // 使用者最新輸入
  HumanMessagePromptTemplate.fromTemplate("{input}"),
]);

// 建立 Chain,將 Memory 注入
const chain = new LLMChain({
  llm,
  prompt,
  memory: summaryMemory, // 這裡直接接上一步的 memory
});

// 執行對話
async function chat(userInput) {
  const response = await chain.call({ input: userInput });
  console.log("Bot:", response.text);
}

// 測試
await chat("我想預訂明天晚上七點的餐廳");
await chat("請幫我加一張窗邊座位");
await chat("還要一瓶紅酒");

說明

  • SystemMessagePromptTemplate 會在每次呼叫 LLM 時自動填入 summary(即摘要)。
  • maxTokenLimit 被觸發,摘要會自動更新,保留最新的關鍵資訊。

3️⃣ 自訂摘要提示詞:控制摘要風格

有時候你想要摘要更精簡或加入特定格式(例如 JSON),可以自行提供 summarizePrompt

import { PromptTemplate } from "@langchain/core/prompts";

const customSummarizePrompt = PromptTemplate.fromTemplate(`
以下是使用者與聊天機器人的對話紀錄,請將重點整理成一段不超過 150 個字的中文摘要,並以 JSON 格式回傳,欄位說明如下:
{
  "key_facts": "關鍵事實",
  "pending_tasks": "尚未完成的任務"
}
對話紀錄:
{history}
`);

const summaryMemoryCustom = new ConversationSummaryMemory({
  llm,
  maxTokenLimit: 400,
  summarizePrompt: customSummarizePrompt,
});

使用方式
只要把 summaryMemoryCustom 替換原本的 summaryMemory,Chain 會自動使用這個自訂提示產生 JSON 格式摘要,方便後續程式解析。


4️⃣ 持久化摘要:將記憶寫入資料庫

在多輪會話或跨 session 的情況下,需要把摘要保存到外部儲存(如 MongoDB、Redis):

import { MongoClient } from "mongodb";

const client = new MongoClient("mongodb://localhost:27017");
await client.connect();
const db = client.db("chatbot");
const collection = db.collection("memories");

// 包裝 ConversationSummaryMemory,加入 load/save 方法
class PersistentSummaryMemory extends ConversationSummaryMemory {
  async loadMemoryVariables(values) {
    const doc = await collection.findOne({ sessionId: values.sessionId });
    if (doc?.summary) {
      this.summary = doc.summary;
    }
    return super.loadMemoryVariables(values);
  }

  async saveContext(inputValues, outputValues) {
    await super.saveContext(inputValues, outputValues);
    // 每次更新後寫回資料庫
    await collection.updateOne(
      { sessionId: inputValues.sessionId },
      { $set: { summary: this.summary } },
      { upsert: true }
    );
  }
}

const persistentMemory = new PersistentSummaryMemory({
  llm,
  maxTokenLimit: 500,
});

說明

  • loadMemoryVariables 先從 DB 讀取先前的摘要。
  • saveContext 在每輪對話結束後把最新的摘要寫回 DB。
    這樣即使伺服器重啟或使用者換頁,對話的關鍵資訊仍能被保留。

5️⃣ 結合其他 Memory(如 ConversationBufferMemory)的混合使用

有時候你想同時保留 完整近期對話(方便回溯)與 長期摘要,可以把兩種 Memory 放在同一個 Chain:

import { ConversationBufferMemory } from "@langchain/memory";

const bufferMemory = new ConversationBufferMemory({
  memoryKey: "recent_history",
  inputKey: "input",
  outputKey: "output",
  returnMessages: true,
});

const chain = new LLMChain({
  llm,
  prompt: ChatPromptTemplate.fromPromptMessages([
    SystemMessagePromptTemplate.fromTemplate("{summary}"),
    // 最近的對話作為補充資訊
    HumanMessagePromptTemplate.fromTemplate("{recent_history}"),
    HumanMessagePromptTemplate.fromTemplate("{input}"),
  ]),
  memory: {
    // 結合兩個 memory,LangChain 會自動合併變數
    summary: summaryMemory,
    recent_history: bufferMemory,
  },
});

效果

  • summary 提供長期關鍵上下文。
  • recent_history 讓模型可以即時參照最近的幾輪對話,提升回應的細節度。

常見陷阱與最佳實踐

陷阱 可能的影響 解決方案 / 最佳實踐
摘要過於簡略 重要資訊被遺失,模型無法正確回應。 - 使用 自訂 summarizePrompt,明確要求保留關鍵欄位(如任務、時間、人物)。
- 測試不同 maxTokenLimit,找出最適平衡點。
Token 限制設定過低 摘要頻繁觸發,導致摘要品質下降(模型被過度呼叫)。 - 依照所使用 LLM 的 token 上限(如 GPT‑4 為 8192)設定 合理的 maxTokenLimit(建議 1500~3000)。
摘要成本過高 每次產生摘要都會產生額外的 API 費用。 - 若對話量龐大,可改用 較小的模型(如 gpt-3.5-turbo)產生摘要,僅在需要高品質回應時使用大型模型。
忘記清除舊記憶 長時間運行的服務可能累積過多舊摘要,導致上下文混亂。 - 提供 clear() 方法或在會話結束時手動重置 summaryMemory
非同步競爭 多使用者同時使用同一 Memory 實例,造成摘要交叉污染。 - 為每個使用者或每個會話 建立獨立的 Memory 實例(或使用 sessionId 方式分離)。
摘要格式不符合下游需求 後續程式無法解析摘要(例如期待 JSON)。 - 使用 自訂 summarizePrompt 產生結構化摘要,並在程式中加入解析容錯處理。

其他最佳實踐

  1. 先行測試摘要品質
    在正式上線前,先跑幾段長對話,檢視摘要是否保留了「關鍵事實」與「未完成任務」。如果摘要過度概括,可調整 Prompt 或提升 maxTokenLimit

  2. 分層記憶結構

    • 短期記憶ConversationBufferMemory)保存最近 5~10 條訊息。
    • 長期摘要ConversationSummaryMemory)保存全局關鍵資訊。
      這樣既能在回應時使用最新細節,又不會因 token 限制失去全局觀。
  3. 使用回呼(callback)監控成本
    LangChain 支援 CallbackManager,可以在每次產生摘要時記錄 token 數量與花費,方便成本預算。

  4. 適時清除或重置
    在使用者明確結束對話(例如點擊「結束聊天」)時,呼叫 memory.clear(),避免舊資訊影響新會話。

  5. 結合外部知識庫
    若摘要中出現需要查證的資訊(如產品價格),可在 Prompt 中加入「若有不確定的事實,請先查詢資料庫」的指示,讓模型在生成回應前先檢索。


實際應用場景

場景 為何選擇 ConversationSummaryMemory 具體實作要點
客服助理 需要追蹤客戶問題的歷史、已處理的步驟與未完成的工單。 - 使用自訂 JSON 摘要,欄位包含 issue_id, status, last_action
- 結合資料庫持久化,確保跨會話持續追蹤。
教育輔導機器人 學生的學習進度、已解題目、待複習概念需要長期保存。 - 設定 maxTokenLimit 為較高值(3000),以保留較多細節。
- 在摘要中加入「已掌握」與「需要加強」的標籤。
行程規劃助手 使用者會分多輪提供日期、地點、偏好,需要在最後生成完整行程。 - 每輪對話後即產生摘要,保留 date, location, preference
- 在最終生成行程前,將摘要作為系統訊息給 LLM。
企業內部知識庫 Chatbot 員工問答涉及過往的討論、決策紀錄。 - 透過 ConversationSummaryMemory 把過去的決策要點濃縮,避免每次都把全部討論拉進 Prompt。
- 搭配向量搜尋,若摘要不足則自動查詢文件。
多語言客服 同一位使用者可能切換語言,需要保持語意一致。 - 在 summarizePrompt 中加入「請保留原始語言」的指示,讓摘要保持原語言或雙語格式。

總結

ConversationSummaryMemory 為 LangChain 提供了一個 高效、可擴充且成本友善 的對話記憶解決方案。透過自動摘要,我們能在 保持關鍵上下文 的同時,避免 token 爆炸與成本激增。本文從概念說明、完整程式碼範例、常見陷阱與最佳實踐,以及多樣化的實務應用場景,提供了從 入門進階優化 的全方位指南。

關鍵要點回顧

  1. 設定適當的 maxTokenLimit,讓摘要在不犧牲資訊的前提下觸發。
  2. 自訂摘要 Prompt,確保摘要格式與內容符合下游需求。
  3. 結合短期緩衝(Buffer)與長期摘要,取得最佳上下文平衡。
  4. 持久化與多會話隔離,避免資訊交叉污染。
  5. 持續監控 token 使用與成本,使用 Callback 或日誌追蹤。

掌握以上技巧後,你就能在自己的聊天機器人、客服系統或任何需要長對話追蹤的應用中,充分發揮 ConversationSummaryMemory 的威力,打造出 智慧、流暢且經濟 的對話體驗。祝開發順利,期待看到你利用 LangChain 創造出更多令人驚豔的 AI 產品! 🚀