AI Engineering Academy · レッスン

RedisとPostgreSQLへのチャット履歴の永続化

RedisChatMessageHistoryとPostgresChatMessageHistoryを使って会話履歴を外部に保存し、アプリケーションの再起動後もメモリを保持して複数インスタンスに拡張できるようにします。

レッスン 4/413 ステップ

「RedisとPostgreSQLへのチャット履歴の永続化」はCoddyKit上の無料AI Engineering Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAI Engineering Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 AI Engineering Academyコースには全4レッスンが含まれています。

チャット履歴を外部に永続化する理由

アプリケーションが再起動したり複数のインスタンスにスケールしたりすると、メモリ上の会話履歴は失われます。外部永続化により、3つの問題を解決できます。履歴がクラッシュ後も残ること、複数のサーバーインスタンスで同じ履歴を共有できること、再訪したユーザーの過去のセッションを読み込めることです。RedisとPostgreSQLは、この用途で最もよく使われる2つのバックエンドです。

LangChainのチャットメッセージ履歴インターフェース

LangChainは、add_message(message)とmessages(プロパティ)という2つのメソッドを持つBaseChatMessageHistoryインターフェースを定義しています。このインターフェースを実装するストレージバックエンドであれば、どのLangChainチェーンにもメモリとして組み込めます。RedisとPostgreSQLの実装は、どちらもこのインターフェースに従っています。

from langchain_community.chat_message_histories import RedisChatMessageHistory

# Each unique session_id gets its own history namespace
history = RedisChatMessageHistory(
    session_id='user:alice:session:42',
    url='redis://localhost:6379'
)

# Add messages
history.add_user_message('What is RAG?')
history.add_ai_message('RAG stands for Retrieval-Augmented Generation...')

# Retrieve stored messages
for msg in history.messages:
    print(msg.type, ':', msg.content[:50])

Redisの履歴をチェーンに接続する

チェーンをRunnableWithMessageHistoryでラップし、各セッションに適した履歴オブジェクトを返すファクトリ関数を指定します。LangChainは各呼び出しの前に履歴を自動的に読み込み、各レスポンスの後に新しいメッセージを保存します。

from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import RedisChatMessageHistory
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

llm = ChatOpenAI(model='gpt-4o-mini')
prompt = ChatPromptTemplate.from_messages([
    ('system', 'You are a helpful assistant.'),
    MessagesPlaceholder(variable_name='history'),
    ('human', '{input}')
])

chain = prompt | llm

chain_with_history = RunnableWithMessageHistory(
    chain,
    lambda session_id: RedisChatMessageHistory(
        session_id=session_id,
        url='redis://localhost:6379'
    ),
    input_messages_key='input',
    history_messages_key='history'
)

セッションIDを指定してチェーンを呼び出す

メッセージ履歴でラップしたチェーンを呼び出すときは、config辞書のconfigurableの下にセッションIDを渡します。LangChainは各呼び出しを正しい履歴ストアに自動的に振り分けるため、AliceとBobの会話履歴は完全に分離されます。

# First turn for session alice-001
response = chain_with_history.invoke(
    {'input': 'My name is Alice.'},
    config={'configurable': {'session_id': 'alice-001'}}
)
print(response.content)

# Second turn — the chain remembers Alice's name from Redis
response = chain_with_history.invoke(
    {'input': 'What is my name?'},
    config={'configurable': {'session_id': 'alice-001'}}
)
print(response.content)  # Should say 'Your name is Alice.'

チャット履歴のRedisキー設計

Redisは、セッションIDから生成したキーの下に、各セッションのメッセージをリストとして保存します。アプリケーション間のキーの衝突を避け、ユーザー単位で効率的にスキャンできるように、chat:<app>:<user_id>:<session_id>のような構造化されたキースキーマを使用します。古いセッションキーにはTTLを設定し、ストレージが際限なく増加するのを防ぎます。

import redis

r = redis.Redis.from_url('redis://localhost:6379')

# List all chat sessions for user alice
keys = r.keys('message_store:alice-*')
print('Active sessions:', len(keys))

# Set a 30-day TTL on a session to auto-expire old history
for key in keys:
    r.expire(key, 60 * 60 * 24 * 30)

PostgreSQLのチャット履歴テーブル設計

SQLベースで永続化する場合は、セッションID、メッセージのロール、内容、タイムスタンプ用のカラムを持つテーブルを作成します。チャット履歴を検索、監査したり、ユーザーアカウントのデータと結合したりする必要がある場合、PostgreSQLは最適です。langchain_postgresパッケージには、すぐに使える実装が用意されています。

-- SQL table for storing chat messages
CREATE TABLE chat_messages (
    id          SERIAL PRIMARY KEY,
    session_id  TEXT NOT NULL,
    role        TEXT NOT NULL,   -- 'human' or 'ai'
    content     TEXT NOT NULL,
    created_at  TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_chat_session ON chat_messages (session_id, created_at);

LangChainのPostgresChatMessageHistory

langchain_postgresのPostgresChatMessageHistoryクラスはRedis版と同じように機能しますが、メッセージをPostgreSQLデータベースに保存します。接続文字列とセッションIDを渡すだけで、テーブルの作成とメッセージのシリアライズを自動的に処理します。

from langchain_postgres import PostgresChatMessageHistory
import psycopg

CONNECTION_STRING = 'postgresql://user:pass@localhost:5432/mydb'

# Create history object for a session
history = PostgresChatMessageHistory(
    table_name='chat_messages',
    session_id='alice-001',
    connection=psycopg.connect(CONNECTION_STRING)
)

history.add_user_message('Tell me about embeddings.')
history.add_ai_message('Embeddings are dense vector representations...')

print(f'Stored {len(history.messages)} messages')

再訪ユーザーの過去のセッションを読み込む

ユーザーがアプリを閉じた後に戻ってきたときは、RedisまたはPostgreSQLから履歴を読み込み、最後のセッションを復元できます。中断した箇所の概要をユーザーに提示することも、保存された履歴を次のプロンプトに注入してシームレスに会話を続けることもできます。

def get_or_create_history(user_id: str, session_id: str):
    history = RedisChatMessageHistory(
        session_id=f'{user_id}:{session_id}',
        url='redis://localhost:6379'
    )
    if history.messages:
        print(f'Resuming session with {len(history.messages)} messages.')
    else:
        print('Starting a new session.')
    return history

データベース内の履歴をトリミングする

長時間継続するセッションでは、数千件のメッセージが蓄積することがあります。カットオフより古いメッセージを削除するか、データベースに直近のN件のメッセージだけを残すトリミング戦略を実装します。これにより取得が高速になり、履歴を読み込んだときにコンテキストウィンドウがあふれるのを防げます。

# Keep only the last 20 messages per session in PostgreSQL
CLEAN_SQL = '''
    DELETE FROM chat_messages
    WHERE session_id = %s
    AND id NOT IN (
        SELECT id FROM chat_messages
        WHERE session_id = %s
        ORDER BY created_at DESC
        LIMIT 20
    )
'''

def trim_session(conn, session_id: str, keep_last: int = 20):
    with conn.cursor() as cur:
        cur.execute(CLEAN_SQL, (session_id, session_id))
    conn.commit()

RedisとPostgreSQLの選び方

どちらのバックエンドも本番環境で利用できますが、それぞれ強みが異なります。Redisはサブミリ秒の読み取りを実現できるため、レイテンシーが最も重要なリアルタイムチャットに適しています。PostgreSQLは、完全なSQLクエリ機能、ACIDトランザクション、既存のユーザーデータベースとの容易な統合を提供し、分析や監査に適しています。本番システムでは、Redisをプライマリキャッシュ、PostgreSQLを永続的なアーカイブとして使い分けることも多くあります。

セキュリティ:マルチテナントの履歴を分離する

マルチテナントアプリケーションでは、ユーザーがお互いの履歴にアクセスできないようにする必要があります。必ずセッションキーにユーザーIDを含め、履歴を読み込む前にセッションIDが認証済みユーザーに属していることを検証し、クライアント側のURLやCookieに生のセッションキーを公開しないでください。

  • 適切なキー:chat:app1:user:42:session:abc
  • 不適切なキー:session:abc(推測可能で、ユーザー単位のスコープがない)

理解度チェック

チャット履歴を外部に永続化する方法について、理解度を確認しましょう。

レッスンのまとめ

このレッスンでは、RedisChatMessageHistoryはセッションごとのキーを使ってRedisにチャット履歴を保存すること、PostgresChatMessageHistoryはSQLでクエリ可能な永続ストレージを実現すること、RunnableWithMessageHistoryはsession_idファクトリを介して任意のバックエンドをチェーンに接続することを学びました。次は、推論と行動を行うエージェントを構築するためのReActフレームワークについて学びます。

無料で開始

AI チューターと学ぶ Python — 無料

ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。

コース
30
レッスン
120

よくある質問

「RedisとPostgreSQLへのチャット履歴の永続化」レッスンは無料ですか?

はい。「RedisとPostgreSQLへのチャット履歴の永続化」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、AI Engineering Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 AI Engineering Academyコースには全4レッスンが含まれています。

「RedisとPostgreSQLへのチャット履歴の永続化」で何を学びますか?

RedisChatMessageHistoryとPostgresChatMessageHistoryを使って会話履歴を外部に保存し、アプリケーションの再起動後もメモリを保持して複数インスタンスに拡張できるようにします。 ブラウザで直接実行するハンズオンコードでAI Engineering Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

AI Engineering Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのAI Engineering Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「RedisとPostgreSQLへのチャット履歴の永続化」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このAI Engineering Academyレッスンでコードを書いて実行できますか?

はい。すべてのAI Engineering Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. StatelessなLLMに外部メモリが必要な理由
  2. Buffer MemoryとWindow Memory
  3. Summary MemoryとTokenを考慮した切り詰め
  4. RedisとPostgreSQLへのチャット履歴の永続化
← AI Engineering Academyに戻る