本文 AI 產出,尚未審核

LangChain – 實戰專案:Chatbot

主題:上下文記憶設定


簡介

在對話式 AI 中,上下文記憶是讓使用者感受到「自然」與「連貫」的關鍵。沒有記憶的聊天機器人每一次回覆都只能依賴單一輪的輸入,會導致資訊斷層、重複提問或是前後不一致的回答。
LangChain 作為一套專為 LLM(Large Language Model)設計的框架,提供了多種記憶(Memory)模組,讓開發者可以輕鬆將對話歷史保存、摘要或向量化,進而在後續回覆中被模型參考。

本篇文章將以 LangChain.js(Node.js 版)為例,說明如何在聊天機器人中加入上下文記憶、選擇適合的記憶策略、避免常見陷阱,並提供完整的程式碼範例,幫助初學者快速上手、讓中級開發者更精進。


核心概念

1. 為什麼需要 Memory?

  • 保持對話連貫:模型能根據先前的問題與答案推理,避免重複或矛盾的回覆。
  • 減少 Token 消耗:直接把全部歷史訊息塞給模型會快速耗盡 token 限制,Memory 會自動裁剪或摘要,只保留關鍵資訊。
  • 支援長期記憶:某些應用需要「永久」保存使用者偏好或歷史紀錄,Memory 可以結合資料庫或向量搜尋實現。

2. LangChain 的 Memory 類別

類別 特色 適用情境
ConversationBufferMemory 把所有對話原文串在一起,適合短對話或測試。 小型聊天機器人、Demo。
ConversationSummaryMemory 使用 LLM 自動摘要舊的對話,只保留摘要與最近訊息。 中長對話、需要控制 token。
VectorStoreRetrieverMemory 把對話切片向量化,透過向量搜尋找出相關段落。 大型知識庫、需要精確檢索。
ConversationTokenBufferMemory 當前對話累計 token 超過上限時,自動裁剪最舊訊息。 嚴格受 token 限制的模型(如 gpt‑3.5‑turbo)。

小技巧:在開發階段可先使用 ConversationBufferMemory 快速驗證流程,正式上線再換成更節省資源的記憶類型。

3. 建立 Memory 的基本步驟

  1. 匯入類別
    const { ConversationBufferMemory } = require("langchain/memory");
    
  2. 初始化(可傳入 memoryKeyreturnMessages 等參數)
    const memory = new ConversationBufferMemory({
      memoryKey: "chat_history",   // 在 Prompt 中使用的變數名稱
      returnMessages: true        // 是否回傳訊息物件陣列
    });
    
  3. 將 Memory 注入 Chain(如 ChatOpenAI + PromptTemplate
    const chain = new LLMChain({
      llm: chatModel,
      prompt,
      memory,
    });
    

程式碼範例

以下示範 5 個實用範例,從最簡單的 Buffer 記憶到向量化長期記憶,皆附上註解說明。

3.1 基礎 Buffer 記憶(ConversationBufferMemory)

// 引入必要模組
const { ChatOpenAI } = require("langchain/chat_models/openai");
const { LLMChain } = require("langchain/chains");
const { PromptTemplate } = require("langchain/prompts");
const { ConversationBufferMemory } = require("langchain/memory");

// 建立 LLM(以 OpenAI gpt-3.5 為例)
const chatModel = new ChatOpenAI({ temperature: 0.7, modelName: "gpt-3.5-turbo" });

// Prompt 中使用 {{chat_history}} 變數
const prompt = PromptTemplate.fromTemplate(`
你是一位友善的客服助理,請根據以下對話歷史回覆使用者的問題。
對話歷史:
{{chat_history}}

使用者: {{question}}
助理:`);

// 初始化記憶
const memory = new ConversationBufferMemory({
  memoryKey: "chat_history",   // 與 Prompt 中的變數名稱對應
  returnMessages: true        // 讓 memory 回傳訊息物件
});

// 建立 Chain
const chain = new LLMChain({ llm: chatModel, prompt, memory });

// 執行一次對話
async function run() {
  const res1 = await chain.call({ question: "請問今天的天氣如何?" });
  console.log(res1.text);      // 第一次回覆
  const res2 = await chain.call({ question: "明天會下雨嗎?" });
  console.log(res2.text);      // 第二次回覆會自動帶入第一次的對話歷史
}
run();

說明ConversationBufferMemory 會把每一次的 question 與 LLM 的回覆全部保存在 chat_history,適合測試或短對話。

3.2 Token 限制記憶(ConversationTokenBufferMemory)

const { ConversationTokenBufferMemory } = require("langchain/memory");

// 設定最大 token 為 800(視模型上限而定)
const tokenMemory = new ConversationTokenBufferMemory({
  llm: chatModel,
  maxTokenLimit: 800,
  memoryKey: "chat_history",
});

// 直接套用到先前的 chain
const tokenChain = new LLMChain({
  llm: chatModel,
  prompt,
  memory: tokenMemory,
});

重點:當累積的 token 超過 maxTokenLimit 時,最舊的對話會被自動裁剪,避免觸發 OpenAI 的 token 限制錯誤。

3.3 自動摘要記憶(ConversationSummaryMemory)

const { ConversationSummaryMemory } = require("langchain/memory");

// 使用 LLM 產生摘要,每 5 條訊息自動摘要一次
const summaryMemory = new ConversationSummaryMemory({
  llm: chatModel,
  memoryKey: "chat_history",
  inputKey: "question",          // 需要摘要的輸入欄位
  outputKey: "answer",           // LLM 回覆欄位
  summaryPrompt: PromptTemplate.fromTemplate(`
以下是對話摘要,請用中文簡潔描述重點:
{history}
`),
});

const summaryChain = new LLMChain({
  llm: chatModel,
  prompt,
  memory: summaryMemory,
});

適用:對話長度可能超過 2,000 token 時,先把舊的對話壓縮成摘要,再與最新訊息一起送給模型,兼顧資訊完整與成本控制。

3.4 向量化長期記憶(VectorStoreRetrieverMemory)

const { OpenAIEmbeddings } = require("langchain/embeddings/openai");
const { MemoryVectorStore } = require("langchain/vectorstores/memory");
const { VectorStoreRetrieverMemory } = require("langchain/memory");

// 建立向量儲存(此範例使用記憶體儲存,正式環境可換成 Pinecone、Weaviate 等)
const embeddings = new OpenAIEmbeddings({ openAIApiKey: process.env.OPENAI_API_KEY });
const vectorStore = await MemoryVectorStore.fromTexts(
  [],                 // 初始沒有資料
  embeddings,
  { namespace: "chat_memory" }
);

// 設定檢索器:每次只取最相關的 3 個段落
const retriever = vectorStore.asRetriever(3);

const vectorMemory = new VectorStoreRetrieverMemory({
  retriever,
  memoryKey: "chat_history",
});

const vectorChain = new LLMChain({
  llm: chatModel,
  prompt: PromptTemplate.fromTemplate(`
以下是與使用者相關的過往對話(僅顯示最相關 3 條):
{{chat_history}}

使用者: {{question}}
助理:`),
  memory: vectorMemory,
});

說明:每次呼叫 vectorChain 時,VectorStoreRetrieverMemory 會先在向量資料庫中搜尋與本次問題最相似的過往對話,將結果注入 {{chat_history}}。這樣即使歷史對話超過數千條,模型仍只看到關鍵上下文。

3.5 結合多種記憶(Hybrid Memory)

有時候需要同時保留「最近對話」與「長期向量化摘要」:

const { CombinedMemory } = require("langchain/memory");

// 最近 5 條訊息使用 Buffer,較舊的使用向量檢索
const recentMemory = new ConversationTokenBufferMemory({
  llm: chatModel,
  maxTokenLimit: 500,
  memoryKey: "recent_history",
});

const hybridMemory = new CombinedMemory({
  memories: [recentMemory, vectorMemory], // 兩個記憶依序合併
  memoryKey: "chat_history",
});

const hybridChain = new LLMChain({
  llm: chatModel,
  prompt,
  memory: hybridMemory,
});

實務:這種 Hybrid 設計在客服系統中特別有用——即時對話使用最近記憶,舊的問題與偏好則透過向量檢索快速找回。


常見陷阱與最佳實踐

陷阱 說明 最佳做法
記憶過大導致 token 超限 把所有訊息直接塞給模型會瞬間超過 token 限制,產生 Rate limitInvalid request 錯誤。 使用 ConversationTokenBufferMemoryConversationSummaryMemory,設定 maxTokenLimit
忘記同步向量庫 使用 VectorStoreRetrieverMemory 時,若忘記把新對話寫入向量庫,檢索結果會越來越不完整。 每次 chain.call 後,呼叫 vectorStore.addDocuments([new Document({ pageContent: question + answer })])
Prompt 中變數名稱不一致 記憶的 memoryKey 必須與 Prompt 中的占位符名稱完全相同,否則模型收不到歷史資訊。 確認 memoryKey: "chat_history"{{chat_history}} 完全對應。
過度依賴摘要 摘要太過簡化會遺失關鍵細節,導致模型回答不正確。 設定合適的 summaryPrompt,或在關鍵情境(如法律、醫療)保留原始訊息。
多執行緒/多使用者共用同一 Memory 若把全局 Memory 直接放在 server 實例,會出現不同使用者的對話混雜。 為每個 Session 建立獨立的 Memory 實例,或使用 Redis、MongoDB 等外部儲存分離。

最佳實踐小結

  1. 先小後大:開發初期先用 ConversationBufferMemory 快速驗證流程,再根據需求換成更節省資源的記憶類型。
  2. 分層管理:把「即時」與「長期」記憶分開(Hybrid),降低單一記憶的負擔。
  3. 監控 Token 用量:在每次呼叫前,使用 llm.getNumTokens 或自行計算,確保不超過模型上限。
  4. 持久化:對於重要的使用者偏好,建議同步寫入資料庫或向量服務,以免伺服器重啟遺失。

實際應用場景

場景 記憶策略 為何適合
客服聊天機器人 Hybrid(最近 Buffer + 向量檢索) 即時問題需要最新對話,舊的訂單資訊、常見問題可透過向量快速找回。
個人助理(行事曆、備忘錄) ConversationSummaryMemory 長期對話會累積大量資訊,摘要能保留關鍵時間點,同時節省 token。
教育問答平台 VectorStoreRetrieverMemory 學生的問題與教材會形成龐大資料庫,向量搜尋能快速定位相關章節。
醫療諮詢機器人 TokenBuffer + 手動標記重要訊息 醫療資訊高度敏感,需保留完整對話,同時限制 token 以降低成本。
遊戲 NPC 對話 ConversationBufferMemory(短回合) 遊戲對話通常在同一局內完成,訊息量小,直接保留即可。

總結

  • 上下文記憶是提升 LLM 對話品質的關鍵,LangChain 提供多樣化的 Memory 模組,讓開發者能根據不同需求靈活選擇。
  • 從最簡單的 ConversationBufferMemory 到高階的 VectorStoreRetrieverMemoryHybrid Memory,每種策略都有其適用情境與成本考量。
  • 實作時務必注意 token 限制、變數對應、資料持久化,並在開發流程中逐步升級記憶方案,以免因過度繁瑣或資源浪費影響使用者體驗。

掌握了記憶的設定與最佳實踐,你的 Chatbot 不僅能在短期內保持連貫,更能在長期服務中累積價值,為使用者提供更貼心、智慧的互動體驗。祝開發順利,玩得開心! 🚀