メモリ機能付きのエージェントを頑張って作ってみた🧠

に公開

LangChainで会話エージェントに記憶を持たせてみた話

最近「AIエージェントとチャットでお話ししたい!」という衝動に駆られて、LangChainを使ってチャットボットを作ってみたい道半ば👒。

今回頑張ったのが、「メモリ機能の実装」。
今回は、LangChainを使ってエージェントに「会話の記憶」を持たせる方法と、
その効果を確認したテスト結果をご紹介します。


やりたいこと

  • エージェント実行のインスタンスに** 記憶機能(Memory) **を持たせる
  • 会話の履歴をプロンプトに反映させて、より文脈の通った返答を実現したい

実装概要

LangChainのConversationBufferMemoryを使う

from langchain.memory import ConversationBufferMemory
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder

memory = ConversationBufferMemory(return_messages=True)

prompt = ChatPromptTemplate.from_messages(
    [
        ("system", f"{system_message}"),
        MessagesPlaceholder(variable_name="history"),  # 会話履歴をプロンプトに挿入
        ("human", "{input}"),
        ("placeholder", "{agent_scratchpad}"),
    ]
)

このようにして、プロンプトテンプレート内にhistoryを挿入することで、過去の会話内容が次の出力に影響を与えるようになります。


テスト実験

実験内容

以下のプロンプトを順番に実行し、記憶なし記憶ありで返答がどう変わるかを比較しました。

入力シーケンス:

  1. 問1:1 + 2 =
  2. 問2:2 + 3 =
  3. 問1と問2の答えは?また、それらを足し算するといくつ?
  4. Pythonについて、最新のバージョンを教えて
  5. Pythonを使って、問1と問2を計算するスクリプトを書いて

使用したTOOLS

今回はシンプルに、、、

from langchain_community.tools import DuckDuckGoSearchRun
from langchain_core.tools import tool
# === カスタムツール定義 ===
# 足し算をするカスタムツール
@tool
def add_numbers(inputs: str) -> int:
    """
    2つの整数を「カンマ区切り」の文字列で受け取り、合計を返す。
    例: "3,5"
    """
    a, b = [int(x) for x in inputs.split(",")]
    return a + b

# web検索をしてくれるツール(langchain公式から拝借)
@tool
def duckduckgo_search(inputs: str) -> str:
    """DuckDuckGoSearchRunを使用して、web検索をします。"""
    search = DuckDuckGoSearchRun()
    return search.invoke(inputs)

# === 使用ツール一覧取得 ===
def setput_all_tools():
    # エージェントに登録するツールを管理する。
    return [
        add_numbers,
        duckduckgo_search,
    ]

用意したエージェント

# メモリ機能ありのエージェント
def create_memory_agent(
    api_key: str,
    model: str = deault_model, 
    system_message="You are a helpful assistant"
    ):
    llm = ChatOpenAI(
        model=model,
        openai_api_key="<api_key>",
    )
    tools = setput_all_tools()

    # メモリー作成
    memory = ConversationBufferMemory(memory_key="history", return_messages=True)

    # メモリー対応のプロンプトテンプレート
    prompt = ChatPromptTemplate.from_messages(
        [
            ("system", f"{system_message}"),
            MessagesPlaceholder(variable_name="history"), # プロンプトに過去の履歴を追加
            ("human", "{input}"),
            ("placeholder", "{agent_scratchpad}"),
        ]
    )
    agent = create_tool_calling_agent(
        llm=llm,
        tools=tools,
        prompt=prompt
    )
    return agent, memory

# メモリ機能搭載AIエージェントの実行
def create_memory_agent_executor(agent, memory):
    tools = setput_all_tools()
    return AgentExecutor(agent=agent, tools=tools, memory=memory, verbose=True)

結果比較

✅ メモリなしの場合:

問1:1 + 2 = → 「1 + 2 = 3 です。」
問2:2 + 3 = → 「2 + 3 = 5 です。」
問1と問2の答えは? → 「問1と問2の内容を教えていただけますか?」

→ 文脈を理解しておらず、会話が繋がっていない。(そりゃそう、、、💦)


✅ メモリありの場合:

問1:1 + 2 = → 「1 + 2 = 3 です。」
問2:2 + 3 = → 「2 + 3 = 5 です。」
問1と問2の答えは? → 「問1の答えは3で、問2の答えは5です。これらを足すと8になります。」
Pythonでスクリプト → 以下のスクリプトを出力。
# 問1と問2の計算
answer_1 = 1 + 2
answer_2 = 2 + 3

# 答えを表示
print("問1の答え:", answer_1)
print("問2の答え:", answer_2)

# 答えを足し算
total = answer_1 + answer_2
print("問1と問2の合計:", total)

→ 過去のやり取りを覚えており、会話がスムーズかつ一貫性のあるものに。


まとめ

  • LangChainのConversationBufferMemoryを使うことで、簡単に「記憶を持つエージェント」が実装できる
  • 実際にテストしてみると、メモリがあるかどうかで会話の質が大きく変わる
  • 一歩進んだチャットボットの開発には、メモリ実装が必須!

おわりに

これからチャットボットを作ろうと思っている方や、LangChainを試してみたい方の参考になれば幸いです。
今後は、記憶の形式を「要約ベース」にしたり、RAG(Retrieval-Augmented Generation)と組み合わせたりすることも試してみたいです!


最後まで読んでいただきありがとうございました。

Discussion