🧠 実践的な会話メモリ(Memory):カスタマーサポートボットにコンテキストを記憶させる

前章まででRAG(検索拡張生成)システムを構築した方は、次のようなテストをしてみたかもしれません:

あなた:「こんにちは、Vibe Tutorの年間料金を教えてください」 AI:「Vibe Tutorのプレミアムメンバーシップ年額料金は299米ドルです」 あなた:「今クレジットカードで支払う場合、割引はありますか?」 AI:「どの商品の割引についてお尋ねでしょうか?」

まったく話が通じない! 直前までVibe Tutorの話をしていたのに、次の瞬間には忘れてしまうのです! これはAIボット開発初心者が最初に直面する大きな壁です:LLM(大規模言語モデル)は本質的に「ステートレス(無状態)」であり、金魚のように7秒しか記憶を保持できません。

この章では、LangChainを使用してメモリ(Memory)機能を実装し、ボットを人間のような賢いカスタマーサポートに変える方法を学びます。


1. AIが忘れてしまう理由

openai.chat.completions.create()を呼び出すとき、APIは独立した処理を行います。 5分前に何を送信したかに関係なく、今回のリクエストに含まれていない情報は一切認識できません。

AIに「記憶」を持たせる唯一の方法は: 「毎回の会話で『過去の全ての会話記録』を添付ファイルのように一緒にAIに送ること」

これは原始的に思えるかもしれませんが、ChatGPTのウェブ版が実際にこの原理で動作しています。


2. LangChainのBufferMemory

もし自分で会話履歴を管理するコードを書こうとすると:

let history = [];
history.push({ role: 'user', content: '你好' });
history.push({ role: 'ai', content: '你好,我是客服' });
// 次回呼び出し時にこれらをmapしてpromptに埋め込む...

非常に面倒です。幸い、LangChainにはMemoryコンポーネントが用意されています。

最も基本的でよく使われるのがBufferMemoryです。過去の会話を長い文字列に変換し、Promptの{history}変数に自動的に埋め込みます。

メモリ機能付きカスタマーサポートの実装

import { ChatOpenAI } from "langchain/chat_models/openai";
import { BufferMemory } from "langchain/memory";
import { ConversationChain } from "langchain/chains";
import { PromptTemplate } from "langchain/prompts";

// 1. 言語モデルの設定
const llm = new ChatOpenAI({ temperature: 0.7, modelName: 'gpt-3.5-turbo' });

// 2. メモリオブジェクトの作成
// returnMessages: false は履歴を単一文字列に変換(文字列promptに適している)
const memory = new BufferMemory({
  memoryKey: "chat_history", 
  returnMessages: false
});

// 3. メモリを考慮したPromptテンプレートの設計
const prompt = PromptTemplate.fromTemplate(`
あなたは親切なカスタマーサポートです。以下はこのお客様との過去の会話記録です:

{chat_history}

お客様の最新の発言:{input}
親切に返答してください:
`);

// 4. LLM、Prompt、MemoryをChainに結合
const chain = new ConversationChain({
  llm: llm,
  memory: memory,
  prompt: prompt
});

// 5. 会話開始!
async function runConversation() {
  const res1 = await chain.call({ input: "こんにちは、私はKenです。ブリンという名前の犬を飼っています。" });
  console.log("AI:", res1.response);
  // AI: こんにちはKenさん!ブリンという名前の犬、とても可愛そうですね!今日はどのようなご用件でしょうか?

  const res2 = await chain.call({ input: "私の犬の名前を忘れてしまいました。教えてもらえますか?" });
  console.log("AI:", res2.response);
  // AI: もちろんです!あなたの犬の名前はブリンですよ!
}

runConversation();

これでAIは記憶を持つようになります!ConversationChainが裏で自動的にres1の会話をmemoryオブジェクトに保存し、res2呼び出し時にこの記録を{chat_history}に追加します。


3. メモリ爆発の危機:BufferWindowMemory

BufferMemoryには重大な欠点があります:トークン数が増え続け、すぐに制限を超えてしまう!

100回の会話をすると、これら全てがOpenAIに送信され、高額なトークン費用が発生するだけでなく、モデルの入力上限(例:8Kや16Kトークン)に達する可能性があります。

この問題を解決するため、業界では**BufferWindowMemory(スライディングウィンドウメモリ)**が最もよく使われています。

これは「直近N回の会話」のみを記憶し、古いものは破棄します。人間の会話にも似ており、カスタマーサポートが3時間前の挨拶を覚えている必要はないのと同じです。

import { BufferWindowMemory } from "langchain/memory";

// k: 5 は「直近5ターン(質問と回答で1ターン、計10発言)」を記憶
const slidingMemory = new BufferWindowMemory({
  k: 5, 
  memoryKey: "chat_history"
});

4. メモリとRAGの統合:ConversationalRetrievalChain

単なる「雑談」だけでなく、「知識ベース(RAG)を読み取り」かつ「記憶を持つ」ようにするにはどうすればよいでしょうか? この場合、状況は複雑になります: 「割引はありますか?」と質問しても、ベクトルデータベースで「割引はありますか?」だけを検索しても、何も見つからないからです(「Vibe Tutorの年額料金」というキーワードが欠けている)。

この問題を解決するため、LangChainは**ConversationalRetrievalChain**という強力なソリューションを提供しています。

その動作は2段階です:

  1. 「会話履歴」と「最新の曖昧な発言」を小さなモデルに渡し、この発言を**「言い換え(Condense Question)」**します。 例:「割引はありますか?」→「Vibe Tutorプレミアムメンバーシップの年額料金に割引はありますか?」
  2. 言い換えられた明確な質問で、ベクトルデータベースから関連文書を検索します。
  3. 最後に、文書と会話履歴を大規模モデルに渡して回答を生成します。

最終版RAGカスタマーサポートの実装

import { ConversationalRetrievalQAChain } from "langchain/chains";
import { BufferWindowMemory } from "langchain/memory";

// vectorStoreが既にあると仮定
const retriever = vectorStore.asRetriever();
const llm = new ChatOpenAI({ temperature: 0 });

const memory = new BufferWindowMemory({
  k: 5,
  memoryKey: "chat_history", // chat_historyでなければならない
  returnMessages: true,      // 会話配列形式で返す
});

// 会話メモリ付きRAG Chainの作成
const chatChain = ConversationalRetrievalQAChain.fromLLM(
  llm,
  retriever,
  {
    memory: memory,
    // (オプション)「質問言い換え」のPromptをカスタマイズ可能
    questionGeneratorChainOptions: {
      template: `以下の会話履歴と追質問が与えられたら、追質問を独立的で完全な意味を持つ単一の質問に言い換えてください。
      会話履歴:
      {chat_history}
      追質問:{question}
      独立した質問:`
    }
  }
);

// 実行!
const result = await chatChain.call({ question: "今クレジットカードで支払う場合、割引はありますか?" });
console.log(result.text);

ConversationalRetrievalQAChainを使えば、あなたのRAGシステムは「単なるQ&A検索エンジン」から、「温かみがあり、一貫性があり、深い対話が可能」なスーパーアシスタントに進化します! 今すぐこのメカニズムをVibe Tutorウェブサイトに導入しましょう!

完全なチュートリアルをロック解除

このチャプターは有料コンテンツです。プロジェクトに参加して、10以上の神レベルのPromptや実際のソースコード例を含む、5000字以上の深い分析をロック解除してください!