ドラフト エージェント設計パターン - メモリ管理

aiagentsmemorylangchainlanggraphtypescriptvercel
By sko X opus 4.19/20/202512 min read

TypeScript、LangChain、LangGraph、Vercelのサーバーレスプラットフォームを使用して、過去のやり取りを記憶し、セッション間でコンテキストを維持し、経験から学習するAIエージェントを構築する方法。

メンタルモデル: メモリファーストなエージェントの構築

エージェントのメモリを、Webアプリケーションの状態管理システムのより強力版として考えてみましょう。ReactアプリがReduxを状態管理に、Context APIを共有データに、localStorageを永続化に使用するように、AIエージェントには作業メモリ(useStateのような)、エピソードメモリ(セッションストレージのような)、セマンティックメモリ(データベースのような)、手続きメモリ(キャッシュされた計算のような)が必要です。LangGraphはメモリオーケストレーター(Reduxに似た)として機能し、Redisやベクターデータベースなどの外部ストアが永続層として機能します。重要な違いは、エージェントのメモリは構造化データとセマンティック理解の両方を処理し、サーバーレス制約内で効率的に動作する必要があることです。

基本例: 会話バッファメモリ

1. シンプルなバッファメモリの実装

// lib/memory/buffer-memory.ts
import { BufferMemory } from 'langchain/memory';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { ConversationChain } from 'langchain/chains';
import { map, take } from 'es-toolkit';

export function createBasicMemoryChain() {
  const model = new ChatGoogleGenerativeAI({
    modelName: 'gemini-2.5-flash',
    temperature: 0.7,
  });

  const memory = new BufferMemory({
    returnMessages: true,
    memoryKey: 'history',
  });

  const chain = new ConversationChain({
    llm: model,
    memory,
    verbose: false, // Set to true for debugging
  });

  return chain;
}

// app/api/chat-memory/route.ts
import { createBasicMemoryChain } from '@/lib/memory/buffer-memory';
import { NextResponse } from 'next/server';

export const runtime = 'nodejs';
export const maxDuration = 60;

const chain = createBasicMemoryChain();

export async function POST(req: Request) {
  const { message } = await req.json();

  const result = await chain.call({ input: message });

  return NextResponse.json({
    response: result.response,
    memorySize: chain.memory.chatHistory.messages.length
  });
}

セッション中にメモリ内のすべてのメッセージを維持するBufferMemoryを使用した基本的な会話チェーンを作成します。

2. 限定コンテキストのウィンドウメモリ

// lib/memory/window-memory.ts
import { BufferWindowMemory } from 'langchain/memory';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { ConversationChain } from 'langchain/chains';
import { takeRight } from 'es-toolkit';

interface WindowMemoryConfig {
  windowSize?: number;
  returnMessages?: boolean;
}

export class WindowMemoryManager {
  private chain: ConversationChain;
  private windowSize: number;

  constructor(config: WindowMemoryConfig = {}) {
    const model = new ChatGoogleGenerativeAI({
      modelName: 'gemini-2.5-flash',
      temperature: 0.7,
    });

    this.windowSize = config.windowSize || 10;

    const memory = new BufferWindowMemory({
      k: this.windowSize,
      returnMessages: config.returnMessages ?? true,
    });

    this.chain = new ConversationChain({
      llm: model,
      memory,
    });
  }

  async processMessage(message: string) {
    const result = await this.chain.call({ input: message });

    // Get current window
    const messages = this.chain.memory.chatHistory.messages;
    const windowMessages = takeRight(messages, this.windowSize * 2); // User + AI messages

    return {
      response: result.response,
      windowSize: windowMessages.length,
      droppedMessages: Math.max(0, messages.length - windowMessages.length)
    };
  }
}

最後のKメッセージペアのみを保持し、制限内に収めるために古いコンテキストを自動的に削除するスライディングウィンドウメモリを実装します。

3. 長い会話のためのサマリーメモリ

// lib/memory/summary-memory.ts
import { ConversationSummaryMemory } from 'langchain/memory';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { ConversationChain } from 'langchain/chains';
import { debounce } from 'es-toolkit';

export class SummaryMemoryManager {
  private chain: ConversationChain;
  private summaryModel: ChatGoogleGenerativeAI;
  private updateSummary: Function;

  constructor() {
    const model = new ChatGoogleGenerativeAI({
      modelName: 'gemini-2.5-pro',
      temperature: 0.7,
    });

    this.summaryModel = new ChatGoogleGenerativeAI({
      modelName: 'gemini-2.5-flash',
      temperature: 0,
    });

    const memory = new ConversationSummaryMemory({
      llm: this.summaryModel,
      returnMessages: false,
      inputKey: 'input',
      outputKey: 'response',
    });

    this.chain = new ConversationChain({
      llm: model,
      memory,
    });

    // Debounce summary updates for performance
    this.updateSummary = debounce(
      () => this.chain.memory.predictNewSummary(
        this.chain.memory.chatHistory.messages,
        ''
      ),
      5000
    );
  }

  async processWithSummary(message: string) {
    const result = await this.chain.call({ input: message });

    // Trigger summary update (debounced)
    this.updateSummary();

    return {
      response: result.response,
      currentSummary: this.chain.memory.buffer
    };
  }
}

LLMを使用して会話を段階的に要約し、長いやり取りでトークン使用量を削減しながらコンテキストを維持します。

高度な例: LangGraphを使用したマルチティアメモリシステム

1. LangGraphによる状態ベースメモリ

// lib/memory/langgraph-memory.ts
import { StateGraph, Annotation } from '@langchain/langgraph';
import { BaseMessage, HumanMessage, AIMessage } from '@langchain/core/messages';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { MemorySaver } from '@langchain/langgraph';
import { groupBy, orderBy, take } from 'es-toolkit';

// Define the state structure
const StateAnnotation = Annotation.Root({
  messages: Annotation<BaseMessage[]>({
    reducer: (x, y) => [...x, ...y],
    default: () => [],
  }),
  summary: Annotation<string>({
    reducer: (x, y) => y || x,
    default: () => '',
  }),
  userProfile: Annotation<Record<string, any>>({
    reducer: (x, y) => ({ ...x, ...y }),
    default: () => ({}),
  }),
  sessionMetadata: Annotation<Record<string, any>>({
    reducer: (x, y) => ({ ...x, ...y }),
    default: () => ({ startTime: Date.now() }),
  }),
});

export function createStatefulMemoryAgent() {
  const model = new ChatGoogleGenerativeAI({
    modelName: 'gemini-2.5-pro',
    temperature: 0.7,
  });

  const summaryModel = new ChatGoogleGenerativeAI({
    modelName: 'gemini-2.5-flash',
    temperature: 0,
  });

  const workflow = new StateGraph(StateAnnotation)
    .addNode('processMessage', async (state) => {
      // Process with full context
      const contextMessages = [
        new SystemMessage(`User Profile: ${JSON.stringify(state.userProfile)}`),
        new SystemMessage(`Summary of past: ${state.summary}`),
        ...take(state.messages, 10), // Recent messages
      ];

      const response = await model.invoke(contextMessages);

      return {
        messages: [response],
      };
    })
    .addNode('updateProfile', async (state) => {
      // Extract user preferences from conversation
      const recentMessages = take(state.messages, 5);
      const analysis = await summaryModel.invoke([
        new SystemMessage('Extract user preferences and facts as JSON'),
        ...recentMessages,
      ]);

      try {
        const preferences = JSON.parse(analysis.content as string);
        return { userProfile: preferences };
      } catch {
        return {};
      }
    })
    .addNode('summarizeIfNeeded', async (state) => {
      // Summarize when messages exceed threshold
      if (state.messages.length > 20) {
        const oldMessages = state.messages.slice(0, -10);
        const summary = await summaryModel.invoke([
          new SystemMessage('Summarize this conversation concisely'),
          ...oldMessages,
        ]);

        return {
          summary: summary.content as string,
          messages: state.messages.slice(-10), // Keep only recent
        };
      }
      return {};
    });

  // Define the flow
  workflow.addEdge('__start__', 'processMessage');
  workflow.addEdge('processMessage', 'updateProfile');
  workflow.addEdge('updateProfile', 'summarizeIfNeeded');
  workflow.addEdge('summarizeIfNeeded', '__end__');

  // Add persistence
  const checkpointer = new MemorySaver();

  return workflow.compile({ checkpointer });
}

// app/api/stateful-chat/route.ts
import { createStatefulMemoryAgent } from '@/lib/memory/langgraph-memory';
import { HumanMessage } from '@langchain/core/messages';

export const runtime = 'nodejs';
export const maxDuration = 300;

const agent = createStatefulMemoryAgent();

export async function POST(req: Request) {
  const { message, threadId } = await req.json();

  const result = await agent.invoke(
    {
      messages: [new HumanMessage(message)],
    },
    {
      configurable: { thread_id: threadId || 'default' },
    }
  );

  return NextResponse.json({
    response: result.messages[result.messages.length - 1].content,
    profile: result.userProfile,
    messageCount: result.messages.length,
  });
}

LangGraphを使用して、自動要約、ユーザープロファイリング、スレッドベースの永続化を備えた高度なメモリシステムを実装します。

2. セマンティック検索によるベクターメモリ

// lib/memory/vector-memory.ts
import { MemoryVectorStore } from 'langchain/vectorstores/memory';
import { GoogleGenerativeAIEmbeddings } from '@langchain/google-genai';
import { VectorStoreRetrieverMemory } from 'langchain/memory';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { ConversationChain } from 'langchain/chains';
import { Document } from '@langchain/core/documents';
import { uniqBy, sortBy } from 'es-toolkit';

export class SemanticMemoryManager {
  private vectorStore: MemoryVectorStore;
  private memory: VectorStoreRetrieverMemory;
  private chain: ConversationChain;
  private embeddings: GoogleGenerativeAIEmbeddings;

  constructor() {
    this.embeddings = new GoogleGenerativeAIEmbeddings({
      modelName: 'embedding-001',
    });

    this.vectorStore = new MemoryVectorStore(this.embeddings);

    this.memory = new VectorStoreRetrieverMemory({
      vectorStoreRetriever: this.vectorStore.asRetriever(5),
      memoryKey: 'history',
      inputKey: 'input',
      outputKey: 'response',
      returnDocs: true,
    });

    const model = new ChatGoogleGenerativeAI({
      modelName: 'gemini-2.5-pro',
      temperature: 0.7,
    });

    this.chain = new ConversationChain({
      llm: model,
      memory: this.memory,
    });
  }

  async addMemory(content: string, metadata: Record<string, any> = {}) {
    const doc = new Document({
      pageContent: content,
      metadata: {
        ...metadata,
        timestamp: Date.now(),
      },
    });

    await this.vectorStore.addDocuments([doc]);
  }

  async searchMemories(query: string, k: number = 5) {
    const results = await this.vectorStore.similaritySearchWithScore(query, k);

    // Sort by relevance and recency
    const sorted = sortBy(results, [
      (r) => -r[1], // Score (descending)
      (r) => -r[0].metadata.timestamp, // Timestamp (descending)
    ]);

    return sorted.map(([doc, score]) => ({
      content: doc.pageContent,
      metadata: doc.metadata,
      relevanceScore: score,
    }));
  }

  async processWithSemanticMemory(message: string) {
    // Search for relevant past interactions
    const relevantMemories = await this.searchMemories(message, 3);

    // Add relevant context to the conversation
    const contextualMessage = relevantMemories.length > 0
      ? `Relevant context: ${relevantMemories.map(m => m.content).join('\n')}\n\nUser: ${message}`
      : message;

    const result = await this.chain.call({ input: contextualMessage });

    // Store the interaction
    await this.addMemory(
      `User: ${message}\nAssistant: ${result.response}`,
      { type: 'conversation' }
    );

    return {
      response: result.response,
      relevantMemories: relevantMemories.slice(0, 2),
      totalMemories: await this.vectorStore.memoryVectors.length,
    };
  }
}

ベクター埋め込みを使用して、最新性ではなく意味に基づいて関連する過去のやり取りを見つけるセマンティックメモリシステムを作成します。

3. サーバーレス用Redisによる永続メモリ

// lib/memory/redis-memory.ts
import { Redis } from '@upstash/redis';
import { BufferMemory, ChatMessageHistory } from 'langchain/memory';
import { AIMessage, HumanMessage, BaseMessage } from '@langchain/core/messages';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { ConversationChain } from 'langchain/chains';
import { memoize, chunk } from 'es-toolkit';

const redis = new Redis({
  url: process.env.UPSTASH_REDIS_URL!,
  token: process.env.UPSTASH_REDIS_TOKEN!,
});

export class RedisMemoryManager {
  private sessionTTL = 3600; // 1 hour
  private maxMessagesPerSession = 100;

  // Memoize session loading for performance
  private loadSession = memoize(
    async (sessionId: string) => {
      const data = await redis.get(`session:${sessionId}`);
      return data as any || { messages: [], metadata: {} };
    },
    {
      resolver: (sessionId) => sessionId,
      ttl: 5000, // Cache for 5 seconds
    }
  );

  async getMemory(sessionId: string): Promise<BufferMemory> {
    const session = await this.loadSession(sessionId);

    const messages = session.messages.map((msg: any) =>
      msg.type === 'human'
        ? new HumanMessage(msg.content)
        : new AIMessage(msg.content)
    );

    const chatHistory = new ChatMessageHistory(messages);

    return new BufferMemory({
      chatHistory,
      returnMessages: true,
      memoryKey: 'history',
    });
  }

  async saveMemory(sessionId: string, messages: BaseMessage[]) {
    // Keep only recent messages for serverless constraints
    const recentMessages = messages.slice(-this.maxMessagesPerSession);

    const serialized = recentMessages.map(msg => ({
      type: msg._getType(),
      content: msg.content,
    }));

    const session = {
      messages: serialized,
      metadata: {
        lastAccess: Date.now(),
        messageCount: serialized.length,
      },
    };

    await redis.setex(
      `session:${sessionId}`,
      this.sessionTTL,
      JSON.stringify(session)
    );

    // Update session index for cleanup
    await redis.zadd('sessions:active', {
      score: Date.now(),
      member: sessionId,
    });
  }

  async processWithPersistence(
    sessionId: string,
    message: string
  ) {
    const model = new ChatGoogleGenerativeAI({
      modelName: 'gemini-2.5-flash',
      temperature: 0.7,
    });

    const memory = await this.getMemory(sessionId);

    const chain = new ConversationChain({
      llm: model,
      memory,
    });

    const result = await chain.call({ input: message });

    // Save updated memory
    await this.saveMemory(
      sessionId,
      chain.memory.chatHistory.messages
    );

    // Get session stats
    const stats = await redis.get(`session:${sessionId}:stats`) as any || {};
    stats.messageCount = (stats.messageCount || 0) + 1;
    stats.lastActive = Date.now();

    await redis.setex(
      `session:${sessionId}:stats`,
      this.sessionTTL,
      JSON.stringify(stats)
    );

    return {
      response: result.response,
      sessionId,
      stats,
    };
  }

  async cleanupOldSessions() {
    // Remove sessions older than 24 hours
    const cutoff = Date.now() - 24 * 60 * 60 * 1000;
    const oldSessions = await redis.zrangebyscore(
      'sessions:active',
      0,
      cutoff
    );

    // Batch delete in chunks for efficiency
    const sessionChunks = chunk(oldSessions, 10);

    for (const batch of sessionChunks) {
      await Promise.all(
        batch.map(sessionId =>
          redis.del(`session:${sessionId}`)
        )
      );
    }

    await redis.zremrangebyscore('sessions:active', 0, cutoff);

    return oldSessions.length;
  }
}

自動クリーンアップとセッション管理を備えたVercelのサーバーレス環境に最適化されたRedisベースの永続メモリを実装します。

4. プロファイルストアによる階層メモリ

// lib/memory/hierarchical-memory.ts
import { StateGraph, Annotation, MemorySaver } from '@langchain/langgraph';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { Redis } from '@upstash/redis';
import { pick, omit, merge } from 'es-toolkit';

interface UserProfile {
  preferences: Record<string, any>;
  facts: string[];
  interests: string[];
  lastUpdated: number;
}

interface SessionMemory {
  messages: any[];
  summary: string;
  topics: string[];
}

interface WorkingMemory {
  currentTopic: string;
  context: string[];
  activeGoal: string;
}

const HierarchicalStateAnnotation = Annotation.Root({
  // Working memory - immediate context
  working: Annotation<WorkingMemory>({
    reducer: (x, y) => ({ ...x, ...y }),
    default: () => ({
      currentTopic: '',
      context: [],
      activeGoal: '',
    }),
  }),
  // Session memory - current conversation
  session: Annotation<SessionMemory>({
    reducer: (x, y) => ({ ...x, ...y }),
    default: () => ({
      messages: [],
      summary: '',
      topics: [],
    }),
  }),
  // Long-term memory - user profile
  profile: Annotation<UserProfile>({
    reducer: (x, y) => merge(x, y),
    default: () => ({
      preferences: {},
      facts: [],
      interests: [],
      lastUpdated: Date.now(),
    }),
  }),
});

export class HierarchicalMemorySystem {
  private redis: Redis;
  private graph: ReturnType<typeof StateGraph.compile>;
  private model: ChatGoogleGenerativeAI;

  constructor() {
    this.redis = new Redis({
      url: process.env.UPSTASH_REDIS_URL!,
      token: process.env.UPSTASH_REDIS_TOKEN!,
    });

    this.model = new ChatGoogleGenerativeAI({
      modelName: 'gemini-2.5-pro',
      temperature: 0.7,
    });

    this.graph = this.buildMemoryGraph();
  }

  private buildMemoryGraph() {
    const workflow = new StateGraph(HierarchicalStateAnnotation)
      .addNode('processInput', async (state) => {
        // Extract topic and intent from input
        const analysis = await this.model.invoke([
          new SystemMessage('Extract the main topic and user intent as JSON'),
          new HumanMessage(state.working.context[state.working.context.length - 1]),
        ]);

        try {
          const { topic, intent } = JSON.parse(analysis.content as string);
          return {
            working: {
              currentTopic: topic,
              activeGoal: intent,
            },
          };
        } catch {
          return {};
        }
      })
      .addNode('retrieveRelevantMemory', async (state) => {
        // Get relevant long-term memories
        const profileKey = `profile:${state.working.currentTopic}`;
        const relevantProfile = await this.redis.get(profileKey) || {};

        // Get relevant session memories
        const sessionKey = `session:recent:${state.working.currentTopic}`;
        const relevantSessions = await this.redis.get(sessionKey) || [];

        return {
          working: {
            context: [
              ...state.working.context,
              `Profile context: ${JSON.stringify(relevantProfile)}`,
              `Previous discussions: ${JSON.stringify(relevantSessions)}`,
            ],
          },
        };
      })
      .addNode('generateResponse', async (state) => {
        // Generate response with full context
        const contextMessage = [
          new SystemMessage(`User profile: ${JSON.stringify(state.profile)}`),
          new SystemMessage(`Current topic: ${state.working.currentTopic}`),
          new SystemMessage(`Session context: ${state.session.summary}`),
          ...state.working.context.map(c => new SystemMessage(c)),
        ];

        const response = await this.model.invoke(contextMessage);

        return {
          session: {
            messages: [...state.session.messages, response.content],
          },
        };
      })
      .addNode('updateMemoryTiers', async (state) => {
        // Promote important information up the hierarchy
        const importantFacts = await this.extractImportantFacts(
          state.session.messages
        );

        if (importantFacts.length > 0) {
          return {
            profile: {
              facts: [...state.profile.facts, ...importantFacts],
              lastUpdated: Date.now(),
            },
          };
        }

        return {};
      })
      .addNode('persistMemory', async (state) => {
        // Save to Redis with different TTLs
        // Working memory - 5 minutes
        await this.redis.setex(
          `working:${state.working.currentTopic}`,
          300,
          JSON.stringify(state.working)
        );

        // Session memory - 1 hour
        await this.redis.setex(
          `session:${Date.now()}`,
          3600,
          JSON.stringify(state.session)
        );

        // Profile - permanent (or very long TTL)
        await this.redis.set(
          `profile:user`,
          JSON.stringify(state.profile)
        );

        return {};
      });

    workflow.addEdge('__start__', 'processInput');
    workflow.addEdge('processInput', 'retrieveRelevantMemory');
    workflow.addEdge('retrieveRelevantMemory', 'generateResponse');
    workflow.addEdge('generateResponse', 'updateMemoryTiers');
    workflow.addEdge('updateMemoryTiers', 'persistMemory');
    workflow.addEdge('persistMemory', '__end__');

    const checkpointer = new MemorySaver();
    return workflow.compile({ checkpointer });
  }

  private async extractImportantFacts(messages: string[]): Promise<string[]> {
    if (messages.length === 0) return [];

    const extraction = await this.model.invoke([
      new SystemMessage('Extract important facts about the user as a JSON array'),
      ...messages.map(m => new HumanMessage(m)),
    ]);

    try {
      return JSON.parse(extraction.content as string);
    } catch {
      return [];
    }
  }

  async process(userId: string, message: string) {
    // Load existing profile
    const profile = await this.redis.get(`profile:${userId}`) as UserProfile || {
      preferences: {},
      facts: [],
      interests: [],
      lastUpdated: Date.now(),
    };

    const result = await this.graph.invoke(
      {
        working: {
          context: [message],
        },
        profile,
      },
      {
        configurable: { thread_id: userId },
      }
    );

    return {
      response: result.session.messages[result.session.messages.length - 1],
      memoryStats: {
        workingMemorySize: result.working.context.length,
        sessionMessages: result.session.messages.length,
        profileFacts: result.profile.facts.length,
      },
    };
  }
}

即座のコンテキストのための作業メモリ、会話のためのセッションメモリ、長期プロファイルストレージによる3層メモリ階層を実装します。

5. メモリ状態によるフロントエンド統合

// components/MemoryChat.tsx
'use client';

import { useState, useEffect } from 'react';
import { useMutation } from '@tanstack/react-query';
import { groupBy, debounce } from 'es-toolkit';

interface MemoryStats {
  workingMemory: number;
  sessionMessages: number;
  profileFacts: number;
  relevantMemories?: Array<{
    content: string;
    relevanceScore: number;
  }>;
}

export default function MemoryChat() {
  const [message, setMessage] = useState('');
  const [sessionId] = useState(() =>
    `session-${Date.now()}-${Math.random()}`
  );
  const [memoryStats, setMemoryStats] = useState<MemoryStats>({
    workingMemory: 0,
    sessionMessages: 0,
    profileFacts: 0,
  });
  const [messages, setMessages] = useState<Array<{
    role: 'user' | 'assistant';
    content: string;
    timestamp: number;
  }>>([]);

  const sendMessage = useMutation({
    mutationFn: async (text: string) => {
      const response = await fetch('/api/memory-chat', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          message: text,
          sessionId
        }),
      });
      return response.json();
    },
    onSuccess: (data) => {
      setMessages(prev => [
        ...prev,
        {
          role: 'user',
          content: message,
          timestamp: Date.now()
        },
        {
          role: 'assistant',
          content: data.response,
          timestamp: Date.now()
        },
      ]);
      setMemoryStats(data.memoryStats || memoryStats);
      setMessage('');
    },
  });

  // Auto-save session periodically
  useEffect(() => {
    const saveSession = debounce(async () => {
      await fetch('/api/memory-chat/save', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ sessionId, messages }),
      });
    }, 10000);

    if (messages.length > 0) {
      saveSession();
    }
  }, [messages, sessionId]);

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (message.trim()) {
      sendMessage.mutate(message);
    }
  };

  // Group messages by time period
  const messageGroups = groupBy(messages, (msg) => {
    const date = new Date(msg.timestamp);
    return date.toLocaleDateString();
  });

  return (
    <div className="flex flex-col h-screen max-h-[800px] bg-base-100">
      {/* Memory Status Bar */}
      <div className="navbar bg-base-200 px-4">
        <div className="flex-1">
          <h2 className="text-xl font-bold">AIメモリチャット</h2>
        </div>
        <div className="flex-none">
          <div className="stats stats-horizontal shadow">
            <div className="stat place-items-center py-2 px-4">
              <div className="stat-title text-xs">作業</div>
              <div className="stat-value text-lg">
                {memoryStats.workingMemory}
              </div>
            </div>
            <div className="stat place-items-center py-2 px-4">
              <div className="stat-title text-xs">セッション</div>
              <div className="stat-value text-lg">
                {memoryStats.sessionMessages}
              </div>
            </div>
            <div className="stat place-items-center py-2 px-4">
              <div className="stat-title text-xs">プロファイル</div>
              <div className="stat-value text-lg">
                {memoryStats.profileFacts}
              </div>
            </div>
          </div>
        </div>
      </div>

      {/* Messages Area */}
      <div className="flex-1 overflow-y-auto p-4 space-y-4">
        {Object.entries(messageGroups).map(([date, msgs]) => (
          <div key={date}>
            <div className="divider text-sm">{date}</div>
            {msgs.map((msg, idx) => (
              <div
                key={idx}
                className={`chat ${
                  msg.role === 'user' ? 'chat-end' : 'chat-start'
                }`}
              >
                <div className="chat-header">
                  {msg.role === 'user' ? 'あなた' : 'AI'}
                </div>
                <div
                  className={`chat-bubble ${
                    msg.role === 'user'
                      ? 'chat-bubble-primary'
                      : 'chat-bubble-secondary'
                  }`}
                >
                  {msg.content}
                </div>
              </div>
            ))}
          </div>
        ))}

        {/* Show relevant memories if available */}
        {memoryStats.relevantMemories &&
         memoryStats.relevantMemories.length > 0 && (
          <div className="alert alert-info">
            <div>
              <h4 className="font-bold">関連するメモリ:</h4>
              {memoryStats.relevantMemories.map((mem, idx) => (
                <div key={idx} className="text-sm mt-1">
                  • {mem.content} (関連度: {mem.relevanceScore.toFixed(2)})
                </div>
              ))}
            </div>
          </div>
        )}

        {sendMessage.isPending && (
          <div className="flex justify-start">
            <div className="chat-bubble">
              <span className="loading loading-dots loading-sm"></span>
            </div>
          </div>
        )}
      </div>

      {/* Input Area */}
      <form onSubmit={handleSubmit} className="p-4 bg-base-200">
        <div className="join w-full">
          <input
            type="text"
            value={message}
            onChange={(e) => setMessage(e.target.value)}
            placeholder="メッセージを入力してください..."
            className="input input-bordered join-item flex-1"
            disabled={sendMessage.isPending}
          />
          <button
            type="submit"
            className="btn btn-primary join-item"
            disabled={sendMessage.isPending || !message.trim()}
          >
            送信
          </button>
        </div>
      </form>
    </div>
  );
}

リアルタイムでメモリ統計を視覚化し、作業メモリ、セッションメッセージ、プロファイルファクトを表示するReactコンポーネントです。

結論

メモリ管理は、ステートレスなAIインタラクションを時間をかけて関係を構築するインテリジェントで文脈を理解する会話に変換します。このガイドでは、シンプルなバッファメモリからVercelのサーバーレス制約に最適化された高度な階層システムまでの進歩を示しました。主要なパターンには、複雑なメモリフローのためのLangGraphのStateGraphの使用、サーバーレス永続化のためのRedisの実装、セマンティック検索のためのベクターストアの活用、人間の認知システムを模倣するマルチティアアーキテクチャの構築が含まれます。作業メモリ、エピソードメモリ、セマンティックメモリ、手続きメモリの組み合わせにより、エージェントはコンテキストを維持し、相互作用から学習し、サーバーレス実行制限内で効率的に動作しながら、ますますパーソナライズされた支援を提供できます。