MeMemoは、ブラウザ上でベクトル検索とRetrieval Augmented Generation (RAG)を実現するJavaScriptライブラリです。ブラウザ環境でHNSW(Hierarchical Navigable Small World)アルゴリズムを実装し、クライアントサイドで効率的なベクトル検索を可能にします。
特徴
-
ブラウザネイティブ
- IndexedDBを使用したデータの永続化
- Web Workersによる並列処理
- クライアントサイドで完結
-
高速な検索
- HNSWアルゴリズムによる近似最近傍探索
- インデックスによる効率的な検索
- ブラウザの性能を最大限活用
-
プライバシー重視
- データはすべてクライアントサイドで処理
- サーバーへの送信不要
- オフライン動作可能
基本的な使い方
1. インストール
npmを使用してMeMemoをインストールします:
npm install mememo
2. インデックスの作成
HNSWインデックスを初期化します:
import { HNSW } from 'mememo';
const index = new HNSW({
// コサイン類似度を使用
distanceFunction: 'cosine-normalized',
// 各ノードの最大接続数(5-48の範囲を推奨)
m: 16,
// ブラウザのIndexedDBを使用してデータを永続化
useIndexedDB: true
});
3. ドキュメントの登録
テキストをチャンクに分割し、ベクトル化してインデックスに登録します:
// テキストをチャンクに分割
function splitIntoChunks(text: string, maxChunkSize = 512) {
const sentences = text.match(/[^.!?]+[.!?]+/g) || [];
let chunks = [];
let currentChunk = '';
for (const sentence of sentences) {
if (currentChunk.length + sentence.length > maxChunkSize) {
chunks.push(currentChunk.trim());
currentChunk = sentence;
} else {
currentChunk += ' ' + sentence;
}
}
if (currentChunk) chunks.push(currentChunk.trim());
return chunks;
}
// ドキュメントを登録
async function indexDocument(text: string) {
// テキストをチャンクに分割
const chunks = splitIntoChunks(text);
// チャンクをベクトル化(例:GTE-Smallモデルを使用)
const vectors = await model.embed(chunks);
// キーを生成
const keys = chunks.map((_, i) => `chunk_${i}`);
// インデックスに登録
await index.bulkInsert(keys, vectors);
return keys;
}
4. 検索の実行
登録したドキュメントから類似コンテンツを検索します:
// 基本的な検索
async function searchSimilar(query: string, k: number = 5) {
// クエリをベクトル化
const queryVector = await model.embed(query);
// k個の類似ドキュメントを検索
const { keys, distances } = await index.query(queryVector, k);
// 結果を類似度スコアと共に返す
return keys.map((key, i) => ({
key,
chunk: chunks[key], // オリジナルのテキストチャンク
similarity: 1 - distances[i] // 距離を類似度スコアに変換
}));
}
// メタデータを含む検索の実装例
interface DocumentChunk {
id: string;
text: string;
metadata: {
title: string;
source: string;
timestamp: string;
};
}
const documentStore = new Map<string, DocumentChunk>();
async function indexDocumentWithMetadata(
text: string,
metadata: { title: string; source: string }
) {
const chunks = splitIntoChunks(text);
const vectors = await model.embed(chunks);
// チャンク情報をストアに保存
const chunkDocuments = chunks.map((chunk, i) => ({
id: `doc_${metadata.title}_${i}`,
text: chunk,
metadata: {
...metadata,
timestamp: new Date().toISOString()
}
}));
// メタデータを含むチャンク情報を保存
for (const doc of chunkDocuments) {
documentStore.set(doc.id, doc);
}
// ベクトルインデックスに登録
await index.bulkInsert(
chunkDocuments.map(d => d.id),
vectors
);
return chunkDocuments.map(d => d.id);
}
5. 使用例
// ドキュメントの登録と検索
async function example() {
// ドキュメントを登録
await indexDocumentWithMetadata(
"長いテキストドキュメント...",
{
title: "サンプル文書",
source: "ウェブサイト"
}
);
// 検索を実行
const results = await searchWithMetadata("検索クエリ");
// 結果を表示
for (const result of results) {
console.log(`スコア: ${result.similarity}`);
console.log(`タイトル: ${result.metadata.title}`);
console.log(`ソース: ${result.metadata.source}`);
console.log(`テキスト: ${result.text}`);
console.log('---');
}
}
実装の注意点
-
チャンクサイズ
- 適切なサイズ(300-1000トークン程度)に分割
- 文脈を保持できる大きさを維持
-
ベクトル化
- 一貫した埋め込みモデルを使用
- 正規化されたベクトルを使用(cosine-normalizedの場合)
-
検索結果の処理
- 類似度スコアに基づくフィルタリング
- メタデータを活用した結果のグループ化
- 適切なスコアのしきい値設定
参考URL
デモサイトでは、以下の3つのユースケースを試すことができます:
- ML Paper Reviewer: 機械学習論文のレビュー支援
- Prompt Enhancer: 生成AIのプロンプト最適化
- Responsible AI Assistant: AIの倫理と安全性に関する知識提供
まとめ
MeMemoを使用することで、ブラウザ上で効率的なベクトル検索を実現できます。すべての処理がクライアントサイドで完結するため、プライバシーを保ちながらRAGシステムを構築できます。
基本的な実装に加えて、メタデータの活用や検索結果の後処理など、実用的な機能を追加することで、より高度な検索システムを構築することが可能です。