例:AI SDK useChat
フック
Mastraのメモリを、Vercel AI SDKのuseChat
フックを使ってReactのようなフロントエンドフレームワークと統合する際は、メッセージ履歴の重複を避けるために慎重な取り扱いが必要です。この例では、推奨されるパターンを示します。
useChat
を使用したメッセージの重複防止
useChat
のデフォルト動作では、リクエストごとにチャット履歴全体を送信します。MastraのメモリはthreadId
に基づいて自動的に履歴を取得するため、クライアントから完全な履歴を送信すると、コンテキストウィンドウとストレージにメッセージが重複して表示されます。
解決策: useChat
を設定して、最新のメッセージのみをthreadId
とresourceId
と一緒に送信するようにします。
// components/Chat.tsx (React Example)
import { useChat } from "ai/react";
export function Chat({ threadId, resourceId }) {
const { messages, input, handleInputChange, handleSubmit } = useChat({
api: "/api/chat", // Your backend endpoint
// Pass only the latest message and custom IDs
experimental_prepareRequestBody: (request) => {
// Ensure messages array is not empty and get the last message
const lastMessage = request.messages.length > 0 ? request.messages[request.messages.length - 1] : null;
// Return the structured body for your API route
return {
message: lastMessage, // Send only the most recent message content/role
threadId,
resourceId,
};
},
// Optional: Initial messages if loading history from backend
// initialMessages: loadedMessages,
});
// ... rest of your chat UI component
return (
<div>
{/* Render messages */}
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} placeholder="Send a message..." />
<button type="submit">Send</button>
</form>
</div>
);
}
// app/api/chat/route.ts (Next.js Example)
import { Agent } from "@mastra/core/agent";
import { Memory } from "@mastra/memory";
import { LibSQLStore } from "@mastra/libsql";
import { openai } from "@ai-sdk/openai";
import { CoreMessage } from "@mastra/core";
const agent = new Agent({
name: "ChatAgent",
instructions: "You are a helpful assistant.",
model: openai("gpt-4o"),
memory: new Memory({
storage: new LibSQLStore({
url: "file:../mastra.db", // Or your database URL
}),
});
});
export async function POST(request: Request) {
// Get data structured by experimental_prepareRequestBody
const { message, threadId, resourceId }: { message: CoreMessage | null; threadId: string; resourceId: string } = await request.json();
// Handle cases where message might be null (e.g., initial load or error)
if (!message || !message.content) {
// Return an appropriate response or error
return new Response("Missing message content", { status: 400 });
}
// Process with memory using the single message content
const stream = await agent.stream(message.content, {
threadId,
resourceId,
// Pass other message properties if needed, e.g., role
// messageOptions: { role: message.role }
});
// Return the streaming response
return stream.toDataStreamResponse();
}
詳細については、メッセージ永続化に関するAI SDKのドキュメント を参照してください。
基本的なスレッド管理UI
このページでは主に useChat
に焦点を当てていますが、スレッドの管理(一覧表示、作成、選択)用のUIも構築できます。これには通常、Mastraのメモリ機能である memory.getThreadsByResourceId()
や memory.createThread()
などと連携するバックエンドAPIエンドポイントが関与します。
// Conceptual React component for a thread list
import React, { useState, useEffect } from 'react';
// Assume API functions exist: fetchThreads, createNewThread
async function fetchThreads(userId: string): Promise<{ id: string; title: string }[]> { /* ... */ }
async function createNewThread(userId: string): Promise<{ id: string; title: string }> { /* ... */ }
function ThreadList({ userId, currentThreadId, onSelectThread }) {
const [threads, setThreads] = useState([]);
// ... loading and error states ...
useEffect(() => {
// Fetch threads for userId
}, [userId]);
const handleCreateThread = async () => {
// Call createNewThread API, update state, select new thread
};
// ... render UI with list of threads and New Conversation button ...
return (
<div>
<h2>Conversations</h2>
<button onClick={handleCreateThread}>New Conversation</button>
<ul>
{threads.map(thread => (
<li key={thread.id}>
<button onClick={() => onSelectThread(thread.id)} disabled={thread.id === currentThreadId}>
{thread.title || `Chat ${thread.id.substring(0, 8)}...`}
</button>
</li>
))}
</ul>
</div>
);
}
// Example Usage in a Parent Chat Component
function ChatApp() {
const userId = "user_123";
const [currentThreadId, setCurrentThreadId] = useState<string | null>(null);
return (
<div style={{ display: 'flex' }}>
<ThreadList
userId={userId}
currentThreadId={currentThreadId}
onSelectThread={setCurrentThreadId}
/>
<div style={{ flexGrow: 1 }}>
{currentThreadId ? (
<Chat threadId={currentThreadId} resourceId={userId} /> // Your useChat component
) : (
<div>Select or start a conversation.</div>
)}
</div>
</div>
);
}
関連
- はじめに:
resourceId
とthreadId
の基本的な概念について説明します。 - Memory リファレンス:
Memory
クラスのメソッドに関する API 詳細。