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 的基本步驟
- 匯入類別
const { ConversationBufferMemory } = require("langchain/memory"); - 初始化(可傳入
memoryKey、returnMessages等參數)const memory = new ConversationBufferMemory({ memoryKey: "chat_history", // 在 Prompt 中使用的變數名稱 returnMessages: true // 是否回傳訊息物件陣列 }); - 將 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 limit 或 Invalid request 錯誤。 |
使用 ConversationTokenBufferMemory 或 ConversationSummaryMemory,設定 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 等外部儲存分離。 |
最佳實踐小結:
- 先小後大:開發初期先用
ConversationBufferMemory快速驗證流程,再根據需求換成更節省資源的記憶類型。 - 分層管理:把「即時」與「長期」記憶分開(Hybrid),降低單一記憶的負擔。
- 監控 Token 用量:在每次呼叫前,使用
llm.getNumTokens或自行計算,確保不超過模型上限。 - 持久化:對於重要的使用者偏好,建議同步寫入資料庫或向量服務,以免伺服器重啟遺失。
實際應用場景
| 場景 | 記憶策略 | 為何適合 |
|---|---|---|
| 客服聊天機器人 | Hybrid(最近 Buffer + 向量檢索) | 即時問題需要最新對話,舊的訂單資訊、常見問題可透過向量快速找回。 |
| 個人助理(行事曆、備忘錄) | ConversationSummaryMemory | 長期對話會累積大量資訊,摘要能保留關鍵時間點,同時節省 token。 |
| 教育問答平台 | VectorStoreRetrieverMemory | 學生的問題與教材會形成龐大資料庫,向量搜尋能快速定位相關章節。 |
| 醫療諮詢機器人 | TokenBuffer + 手動標記重要訊息 | 醫療資訊高度敏感,需保留完整對話,同時限制 token 以降低成本。 |
| 遊戲 NPC 對話 | ConversationBufferMemory(短回合) | 遊戲對話通常在同一局內完成,訊息量小,直接保留即可。 |
總結
- 上下文記憶是提升 LLM 對話品質的關鍵,LangChain 提供多樣化的 Memory 模組,讓開發者能根據不同需求靈活選擇。
- 從最簡單的
ConversationBufferMemory到高階的VectorStoreRetrieverMemory、Hybrid Memory,每種策略都有其適用情境與成本考量。 - 實作時務必注意 token 限制、變數對應、資料持久化,並在開發流程中逐步升級記憶方案,以免因過度繁瑣或資源浪費影響使用者體驗。
掌握了記憶的設定與最佳實踐,你的 Chatbot 不僅能在短期內保持連貫,更能在長期服務中累積價值,為使用者提供更貼心、智慧的互動體驗。祝開發順利,玩得開心! 🚀