コンテンツにスキップ

ベクターストア

Rememberizer ベクターストアは、ベクターデータの取り扱いを簡素化し、テキスト入力に集中できるようにし、検索やデータ分析などのさまざまなアプリケーションのためにベクターの力を活用します。

はじめに

Rememberizer Vector Storeは、ベクトルデータを扱うための使いやすいインターフェースを提供し、ベクトル埋め込みの複雑さを抽象化します。pgvector拡張機能を備えたPostgreSQLによって支えられたRememberizer Vector Storeは、テキストを直接操作することを可能にします。このサービスは、テキストデータのチャンク化、ベクトル化、保存を処理し、コアアプリケーションロジックに集中できるようにします。

ベクトル埋め込みとベクトルデータベースの背後にある理論的概念をより深く理解するには、ベクトル埋め込みとベクトルデータベースとは?をご覧ください。

技術概要

ベクトルストアの仕組み

Rememberizer ベクトルストアは、テキストを高次元のベクトル表現(埋め込み)に変換し、意味的な意味を捉えます。これにより、以下が可能になります:

  1. 意味検索:キーワードだけでなく、意味に基づいて文書を見つける
  2. 類似性マッチング:概念的に関連するコンテンツを特定する
  3. 効率的な取得:大規模データセットから関連情報を迅速に見つける

主要コンポーネント

  • ドキュメント処理: テキストは、コンテキストを保持するために重複する境界を持つ最適なサイズのチャンクに分割されます
  • ベクトル化: チャンクは、最先端のモデルを使用して埋め込みに変換されます
  • インデックス作成: 専門的なアルゴリズムがベクトルを整理し、効率的な類似検索を実現します
  • クエリ処理: 検索クエリはベクトル化され、保存された埋め込みと比較されます

アーキテクチャ

Rememberizerは、次の方法でベクターストアを実装しています:

  • pgvector拡張を使用したPostgreSQL:効率的なベクターの保存と検索のため
  • コレクションベースの組織:各ベクターストアは独自の孤立したコレクションを持つ
  • API駆動のアクセス:すべての操作のためのシンプルなRESTfulエンドポイント

始めに

ベクトルストアの作成

  1. ダッシュボードのベクトルストアセクションに移動します
  2. 「新しいベクトルストアを作成」をクリックします:
  3. 詳細を入力するように促すフォームが表示されます。
  4. 詳細を入力します:
  5. 名前:ベクトルストアのユニークな名前を提供します。
  6. 説明:ベクトルストアの簡単な説明を書きます。
  7. 埋め込みモデル:テキストをベクトルに変換するモデルを選択します。
  8. インデックスアルゴリズム:ベクトルが検索のためにどのように整理されるかを選択します。
  9. 検索メトリック:ベクトル間の類似性がどのように計算されるかを定義します。
  10. ベクトル次元:ベクトル埋め込みのサイズ(通常は768-1536)。
  11. フォームを送信します:
  12. 「作成」ボタンをクリックします。成功通知が届き、新しいストアがベクトルストアリストに表示されます。
新しいベクトルストアを作成

新しいベクトルストアを作成

設定オプション

埋め込みモデル

モデル 次元 説明 最適
openai/text-embedding-3-large 1536 OpenAIの高精度埋め込みモデル 最大の精度を必要とするプロダクションアプリケーション
openai/text-embedding-3-small 1536 OpenAIのより小さく、より高速な埋め込みモデル より高いスループット要件を持つアプリケーション

インデクシングアルゴリズム

アルゴリズム 説明 トレードオフ
IVFFLAT (デフォルト) フラット圧縮の逆ファイル スピードと精度の良いバランス; ほとんどのデータセットでうまく機能
HNSW 階層的ナビゲーション可能なスモールワールド 大規模データセットに対してより良い精度; より高いメモリ要件

検索メトリクス

メトリック 説明 最適
コサイン (デフォルト) ベクトル間の角度を測定 一般的な類似性マッチング
内積 (ip) ベクトル間のドット積 ベクトルの大きさが重要な場合
L2 (ユークリッド) ベクトル間の直線距離 空間的関係が重要な場合

ベクトルストアの管理

  1. ベクトルストアの表示と編集:
  2. 管理ダッシュボードにアクセスして、ベクトルストアを表示、編集、または削除します。
  3. ドキュメントの表示:
  4. 特定のベクトルストア内の個々のドキュメントとその関連メタデータを閲覧します。
  5. 統計:
  6. 保存されたベクトルの数、クエリパフォーマンス、運用指標などの詳細な統計を表示します。
ベクトルストアの詳細を表示

ベクトルストアの詳細を表示

APIキー管理

APIキーは、RememberizerベクターストアのAPIエンドポイントへのアクセスを認証および承認するために使用されます。APIキーの適切な管理は、ベクターストアのセキュリティと整合性を維持するために不可欠です。

APIキーの作成

  1. ベクターストアの詳細ページに移動します
  2. APIキー管理セクションに移動します:
  3. 「設定」タブ内にあります
  4. 「APIキーを追加」をクリックします:
  5. 詳細を入力するように促すフォームが表示されます。
  6. 詳細を入力します:
  7. 名前: APIキーの使用ケースを特定するための名前を提供します。
  8. フォームを送信します:
  9. 「作成」ボタンをクリックします。新しいAPIキーが生成され、表示されます。必ずコピーして安全に保管してください。このキーは、その特定のベクターストアへのリクエストを認証するために使用されます。
新しいAPIキーを作成

新しいAPIキーを作成

APIキーの取り消し

APIキーがもはや必要ない場合は、潜在的な悪用を防ぐために削除できます。

セキュリティ上の理由から、APIキーを定期的にローテーションすることをお勧めします。これは、新しいキーを生成し、古いキーを取り消すことを含みます。

ベクトルストアAPIの使用

ベクトルストアを作成し、APIキーを生成した後、REST APIを使用してそれと対話できます。

コード例

import requests
import json

API_KEY = "your_api_key_here"
VECTOR_STORE_ID = "vs_abc123"  # あなたのベクトルストアIDに置き換えてください
BASE_URL = "https://api.rememberizer.a

# ベクターストアにドキュメントをアップロードする
def upload_document(file_path, document_name=None):
    if document_name is None:
        document_name = file_path.split("/")[-1]

    with open(file_path, "rb") as f:
        files = {"file": (document_name, f)}
        headers = {"x-api-key": API_KEY}

        response = requests.post(
            f"{BASE_URL}/vector-stores/{VECTOR_STORE_ID}/documents",
            headers=headers,
            files=files
        )

        if response.status_code == 201:
            print(f"ドキュメント '{document_name}' が正常にアップロードされました!")
            return response.json()
        else:
            print(f"ドキュメントのアップロード中にエラーが発生しました: {response.text}")
            return None

# ベクトルストアにテキストコンテンツをアップロードする
def upload_text(content, document_name):
    headers = {
        "x-api-key": API_KEY,
        "Content-Type": "application/json"
    }

    data = {
        "name": document_name,
        "content": content
    }

    response = requests.post(
        f"{BASE_URL}/vector-stores/{VECTOR_STORE_ID}/documents/text",
        headers=headers,
        json=data
    )

    if response.status_code == 201:
        print(f"テキストドキュメント '{document_name}' が正常にアップロードされました!")
        return response.json()
    else:
        print(f"テキストのアップロード中にエラーが発生しました: {response.text}")
        return None

# ベクトルストアを検索する
def search_vector_store(query, num_results=5, prev_chunks=1, next_chunks=1):
    headers = {"x-api-key": API_KEY}

    params = {
        "q": query,
        "n": num_results,
        "prev_chunks": prev_chunks,
        "next_chunks": next_chunks
    }

    response = requests.get(
        f"{BASE_URL}/vector-stores/{VECTOR_STORE_ID}/documents/search",
        headers=headers,
        params=params
    )

    if response.status_code == 200:
        results = response.json()
        print(f"'{query}' に対して {len(results['matched_chunks'])} 件の一致が見つかりました")

        # 上位の結果を表示
        if results['matched_chunks']:
            top_match = results['matched_chunks'][0]
            print(f"上位の一致 (距離: {top_match['distance']}):")
            print(f"ドキュメント: {top_match['document']['name']}")
            print(f"内容: {top_match['matched_content']}")

        return results
    else:
        print(f"検索中にエラーが発生しました: {response.text}")
        return None

# 使用例
# ドキュメントをアップロードする("path/to/document.pdf")
# upload_text("これはベクトル化されるサンプルテキストです", "sample-document.txt")
# search_vector_store("ベクトル類似性はどのように機能しますか?")
// ベクトルストアAPIクライアント
class VectorStoreClient {
  constructor(apiKey, vectorStoreId) {
    this.apiKey = apiKey;
    this.vectorStoreId = vectorStoreId;
    this.baseUrl = 'https://api.rememberizer.ai/api/v1';
  }

  // ベクトルストア情報を取得
  async getVectorStoreInfo() {
    const response = await fetch(`${this.baseUrl}/vector-stores/${this.vectorStoreId}`, {
      method: 'GET',
      headers: {
        'x-api-key': this.apiKey
      }
    });

    if (!response.ok) {
      throw new Error(`ベクトルストア情報の取得に失敗しました: ${response.statusText}`);
    }

    return response.json();
  }

  // テキストドキュメントをアップロード
  async uploadTextDocument(name, content) {
    const response = await fetch(`${this.baseUrl}/vector-stores/${this.vectorStoreId}/documents/text`, {
      method: 'POST',
      headers: {
        'x-api-key': this.apiKey,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        name,
        content
      })
    });

    if (!response.ok) {
      throw new Error(`テキストドキュメントのアップロードに失敗しました: ${response.statusText}`);
    }

    return response.json();
  }

  // ファイルをアップロード
  async uploadFile(file, onProgress) {
    const formData = new FormData();
    formData.append('file', file);

    const xhr = new XMLHttpRequest();

    return new Promise((resolve, reject) => {
      xhr.open('POST', `${this.baseUrl}/vector-stores/${this.vectorStoreId}/documents`);
      xhr.setRequestHeader('x-api-key', this.apiKey);

      xhr.upload.onprogress = (event) => {
        if (event.lengthComputable && onProgress) {
          const percentComplete = (event.loaded / event.total) * 100;
          onProgress(percentComplete);
        }
      };

      xhr.onload = () => {
        if (xhr.status === 201) {
          resolve(JSON.parse(xhr.responseText));
        } else {
          reject(new Error(`ファイルのアップロードに失敗しました: ${xhr.statusText}`));
        }
      };

      xhr.onerror = () => {
        reject(new Error('ファイルアップロード中のネットワークエラー'));
      };

      xhr.send(formData);
    });
  }

  // ベクトルストア内のドキュメントを検索
  async searchDocuments(query, options = {}) {
    const params = new URLSearchParams({
      q: query,
      n: options.numResults || 10,
      prev_chunks: options.prevChunks || 1,
      next_chunks: options.nextChunks || 1
    });

    if (options.threshold) {
      params.append('t', options.threshold);
    }

    const response = await fetch(
      `${this.baseUrl}/vector-stores/${this.vectorStoreId}/documents/search?${params}`,
      {
        method: 'GET',
        headers: {
          'x-api-key': this.apiKey
        }
      }
    );

    if (!response.ok) {
      throw new Error(`検索に失敗しました: ${response.statusText}`);
    }

    return response.json();
  }

  // ベクトルストア内のすべてのドキュメントをリスト
  async listDocuments() {
    const response = await fetch(
      `${this.baseUrl}/vector-stores/${this.vectorStoreId}/documents`,
      {
        method: 'GET',
        headers: {
          'x-api-key': this.apiKey
        }
      }
    );

    if (!response.ok) {
      throw new Error(`ドキュメントのリストに失敗しました: ${response.statusText}`);
    }

    return response.json();
  }

  // ドキュメントを削除
  async deleteDocument(documentId) {
    const response = await fetch(
      `${this.baseUrl}/vector-stores/${this.vectorStoreId}/documents/${documentId}`,
      {
        method: 'DELETE',
        headers: {
          'x-api-key': this.apiKey
        }
      }
    );

    if (!response.ok) {
      throw new Error(`ドキュメントの削除に失敗しました: ${response.statusText}`);
    }

    return true;
  }
}

// 使用例
/*
const client = new VectorStoreClient('your_api_key', 'vs_abc123');

// ドキュメントを検索
client.searchDocuments('セマンティック検索はどのように機能しますか?')
  .then(results => {
    console.log(`見つかったマッチ: ${results.matched_chunks.length}`);
    results.matched_chunks.forEach(match => {
      console.log(`ドキュメント: ${match.document.name}`);
      console.log(`スコア: ${match.distance}`);
      console.log(`内容: ${match.matched_content}`);
      console.log('---');
    });
  })
  .catch(error => console.error(error));
*/
require 'net/http'
require 'uri'
require 'json'

class VectorStoreClient
  def initialize(api_key, vector_store_id)
    @api_key = api_key
    @vector_store_id = vector_store_id
    @base_url = 'https://api.rememberizer.ai/api/v1'
  end

  # ベクトルストアの詳細を取得
  def get_vector_store_info
    uri = URI("#{@base_url}/vector-stores/#{@vector_store_id}")
    request = Net::HTTP::Get.new(uri)
    request['x-api-key'] = @api_key

    response = send_request(uri, request)
    JSON.parse(response.body)
  end

  # テキストコンテンツをアップロード
  def upload_text(name, content)
    uri = URI("#{@base_url}/vector-stores/#{@vector_store_id}/documents/text")
    request = Net::HTTP::Post.new(uri)
    request['Content-Type'] = 'application/json'
    request['x-api-key'] = @api_key

    request.body = {
      name: name,
      content: content
    }.to_json

    response = send_request(uri, request)
    JSON.parse(response.body)
  end

  # ドキュメントを検索
  def search(query, num_results: 5, prev_chunks: 1, next_chunks: 1, threshold: nil)
    uri = URI("#{@base_url}/vector-stores/#{@vector_store_id}/documents/search")
    params = {
      q: query,
      n: num_results,
      prev_chunks: prev_chunks,
      next_chunks: next_chunks
    }

    params[:t] = threshold if threshold

    uri.query = URI.encode_www_form(params)
    request = Net::HTTP::Get.new(uri)
    request['x-api-key'] = @api_key

    response = send_request(uri, request)
    JSON.parse(response.body)
  end

  # ドキュメントをリスト
  def list_documents
    uri = URI("#{@base_url}/vector-stores/#{@vector_store_id}/documents")
    request = Net::HTTP::Get.new(uri)
    request['x-api-key'] = @api_key

    response = send_request(uri, request)
    JSON.parse(response.body)
  end

  # ファイルをアップロード(マルチパートフォーム)
  def upload_file(file_path)
    uri = URI("#{@base_url}/vector-stores/#{@vector_store_id}/documents")

    file_name = File.basename(file_path)
    file_content = File.binread(file_path)

    boundary = "RememberizerBoundary#{rand(1000000)}"

    request = Net::HTTP::Post.new(uri)
    request['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
    request['x-api-key'] = @api_key

    post_body = []
    post_body << "--#{boundary}\r\n"
    post_body << "Content-Disposition: form-data; name=\"file\"; filename=\"#{file_name}\"\r\n"
    post_body << "Content-Type: application/octet-stream\r\n\r\n"
    post_body << file_content
    post_body << "\r\n--#{boundary}--\r\n"

    request.body = post_body.join

    response = send_request(uri, request)
    JSON.parse(response.body)
  end

  private

  def send_request(uri, request)
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = (uri.scheme == 'https')

    response = http.request(request)

    unless response.is_a?(Net::HTTPSuccess)
      raise "APIリクエストに失敗しました: #{response.code} #{response.message}\n#{response.body}"
    end

    response
  end
end

使用例

=begin client = VectorStoreClient.new('your_api_key', 'vs_abc123')

ドキュメントを検索する

results = client.search('データセキュリティのベストプラクティスは何ですか?') puts "見つかった結果は #{results['matched_chunks'].length} 件です"

トップ結果を表示

if results['matched_chunks'].any? top_match = results['matched_chunks'].first puts "トップマッチ (距離: #{top_match['distance']}):" puts "ドキュメント: #{top_match['document']['name']}" puts "コンテンツ: #{top_match['matched_content']}" end =end ```

# APIキーとベクターストアIDを設定します
API_KEY="your_api_key_here"
VECTOR_STORE_ID="vs_abc123"
BASE_URL="https://api.rememberizer.ai/api/v1"

# ベクトルストア情報を取得
curl -X GET "${BASE_URL}/vector-stores/${VECTOR_STORE_ID}"   
  -H "x-api-key: ${API_KEY}"

# テキストドキュメントをアップロード
curl -X POST "${BASE_URL}/vector-stores/${VECTOR_STORE_ID}/documents/text"   
  -H "x-api-key: ${API_KEY}"   
  -H "Content-Type: application/json"   
  -d '{
    "name": "example-document.txt",
    "content": "これはベクトル化され、意味検索のためにベクトルデータベースに保存されるサンプルドキュメントです。"
  }'

# ファイルをアップロード
curl -X POST "${BASE_URL}/vector-stores/${VECTOR_STORE_ID}/documents"   
  -H "x-api-key: ${API_KEY}"   
  -F "file=@/path/to/your/document.pdf"

# ドキュメントを検索する
curl -X GET "${BASE_URL}/vector-stores/${VECTOR_STORE_ID}/documents/search?q=semantic%20search&n=5&prev_chunks=1&next_chunks=1"   
  -H "x-api-key: ${API_KEY}"

# すべてのドキュメントをリストする
curl -X GET "${BASE_URL}/vector-stores/${VECTOR_STORE_ID}/documents"   
  -H "x-api-key: ${API_KEY}"

# ドキュメントを削除する
curl -X DELETE "${BASE_URL}/vector-stores/${VECTOR_STORE_ID}/documents/123"   
  -H "x-api-key: ${API_KEY}"

パフォーマンスの考慮事項

近日公開: ベクトルストアアーキテクチャダイアグラム

この技術アーキテクチャダイアグラムは以下を示します:

  • PostgreSQL + pgvector 基盤アーキテクチャ
  • インデックスアルゴリズム構造 (IVFFLAT vs. HNSW)
  • ベクトル空間における検索メトリクスの動作 (視覚的比較)
  • オーバーラップの視覚化を伴うドキュメントチャンク処理
  • 異なるスケールで視覚化されたパフォーマンスの考慮事項
Rememberizer ベクトルストア実装の技術アーキテクチャ

異なるデータ量の最適化

データ量 推奨構成 注釈
小 (<10k ドキュメント) IVFFLAT, コサイン類似度 シンプルな構成で良好なパフォーマンスを提供
中 (10k-100k ドキュメント) IVFFLAT, 定期的な再インデックスを確保 検索速度とインデックス維持のバランス
大 (>100k ドキュメント) HNSW, ベクトル次元の増加を検討 メモリ使用量が増加するが、スケールでのパフォーマンスを維持

チャンク戦略

チャンク処理は検索品質に大きな影響を与えます:

  • チャンクサイズ: Rememberizerはデフォルトのチャンクサイズを1024バイト、200バイトのオーバーラップで使用します
  • 小さいチャンク (512-1024バイト): より正確な一致、特定の質問に適しています
  • 大きいチャンク (1500-2048バイト): 各一致により多くのコンテキスト、広範なトピックに適しています
  • オーバーラップ: チャンクの境界でコンテキストが失われないようにします

クエリ最適化

  • コンテキストウィンドウ: prev_chunksnext_chunks を使用して周囲のコンテンツを取得
  • 結果数: 3-5 件の結果(n パラメータ)から始め、精度のニーズに応じて調整
  • 閾値: 類似スコアで結果をフィルタリングするために t パラメータを調整

高度な使用法

再インデックス作成

Rememberizerは、ベクトル数が事前に定義された閾値を超えると自動的に再インデックス作成をトリガーしますが、以下の後に手動で再インデックス作成を検討してください:

  • 大量のドキュメントをアップロードした場合
  • 埋め込みモデルを変更した場合
  • インデックス作成アルゴリズムを変更した場合

クエリの強化

より良い検索結果のために:

  1. 具体的に 検索クエリを設定する
  2. 文脈を含める 可能な場合は
  3. 自然言語を使用する キーワードではなく
  4. 結果の質に基づいて パラメータを調整する

他のベクターデータベースからの移行

現在、他のベクターデータベースソリューションを使用していて、Rememberizer Vector Storeに移行したい場合は、以下のガイドがデータを効率的に移行するのに役立ちます。

移行の概要

ベクトルデータの移行には以下が含まれます: 1. ソースベクトルデータベースからデータをエクスポートする 2. データをRememberizerと互換性のある形式に変換する 3. データをRememberizerベクトルストアにインポートする 4. 移行が成功したことを確認する

Rememberizerへの移行の利点

  • PostgreSQL基盤: バックアップとリカバリ機能を備えた成熟したデータベース技術に基づいています
  • 統合エコシステム: 他のRememberizerコンポーネントとのシームレスな接続
  • 管理の簡素化: ベクトル操作のための統一インターフェース
  • 高度なセキュリティ: 行レベルのセキュリティと細かいアクセス制御
  • スケーラブルなアーキテクチャ: データが増えるにつれてパフォーマンスを最適化

Pineconeからの移行

import os
import pinecone
import requests
import json
import time

# Pineconeクライアントのセットアップ
pinecone.init(api_key="PINECONE_API_KEY", environment="PINECONE_ENV")
source_index = pinecone.Index("your-pinecone-index")

# Rememberizerベクターストアクライアントの設定
REMEMBERIZER_API_KEY = "your_rememberizer_api_key"
VECTOR_STORE_ID = "vs_abc123"  # あなたのRememberizerベクターストアID
BASE_URL = "https://api.rememberizer.ai/api/v1"

# 1. マイグレーションのバッチサイズを設定する(データサイズに基づいて調整)
BATCH_SIZE = 100

# 2. Pineconeからベクトルを取得する関数
def fetch_vectors_from_pinecone(index_name, batch_size, cursor=None):
    # Pineconeのバージョンで利用可能な場合はリスト操作を使用
    try:
        result = source_index.list(limit=batch_size, cursor=cursor)
        vectors = result.get("vectors", {})
        next_cursor = result.get("cursor")
        return vectors, next_cursor
    except AttributeError:
        # リスト操作のない古いPineconeバージョン用
        # これは簡略化されたアプローチであり、実際の実装はデータアクセスパターンに依存します
        query_response = source_index.query(
            vector=[0] * source_index.describe_index_stats()["dimension"],
            top_k=batch_size,
            include_metadata=True,
            include_values=True
        )
        return {item.id: {"id": item.id, "values": item.values, "metadata": item.metadata} 
                for item in query_response.matches}, None

# 3. Rememberizerにベクトルをアップロードする関数
def upload_to_rememberizer(vectors):
    headers = {
        "x-api-key": REMEMBERIZER_API_KEY,
        "Content-Type": "application/json"
    }

    for vector_id, vector_data in vectors.items():
        # PineconeのベクトルデータをRememberizer形式に変換
        document_name = vector_data.get("metadata", {}).get("filename", f"pinecone_doc_{vector_id}")
        content = vector_data.get("metadata", {}).get("text", "")

        if not content:
            print(f"{vector_id}をスキップします - メタデータにテキストコンテンツが見つかりません")
            continue

        data = {
            "name": document_name,
            "content": content,
            # オプション: 追加のメタデータを含める
            "metadata": vector_data.get("metadata", {})
        }

        response = requests.post(
            f"{BASE_URL}/vector-stores/{VECTOR_STORE_ID}/documents/text",
            headers=headers,
            json=data
        )

        if response.status_code == 201:
            print(f"ドキュメント '{document_name}' が正常にアップロードされました!")
        else:
            print(f"ドキュメント {document_name} のアップロード中にエラーが発生しました: {response.text}")

        # レート制限を防ぐために少し遅延を追加
        time.sleep(0.1)

# 4. メイン移行関数
def migrate_pinecone_to_rememberizer():
    cursor = None
    total_migrated = 0

    print("Pinecone から Rememberizer への移行を開始しています...")

    while True:
        vectors, cursor = fetch_vectors_from_pinecone("your-pinecone-index", BATCH_SIZE, cursor)

        if not vectors:
            break

        print(f"Pinecone から {len(vectors)} 個のベクトルを取得しました")
        upload_to_rememberizer(vectors)

        total_migrated += len(vectors)
        print(f"進捗: {total_migrated} 個のベクトルが移行されました")

        if not cursor:
            break

    print(f"移行が完了しました! {total_migrated} 個のベクトルが Rememberizer に移行されました")

# マイグレーションを実行する
# migrate_pinecone_to_rememberizer()
const { PineconeClient } = require('@pinecone-database/pinecone');
const axios = require('axios');

// Pineconeの設定
const pineconeApiKey = 'PINECONE_API_KEY';
const pineconeEnvironment = 'PINECONE_ENVIRONMENT';
const pineconeIndexName = 'YOUR_PINECONE_INDEX';

// Rememberizerの設定
const rememberizerApiKey = 'YOUR_REMEMBERIZER_API_KEY';
const vectorStoreId = 'vs_abc123';
const baseUrl = 'https://api.rememberizer.ai/api/v1';

// バッチサイズの設定
const BATCH_SIZE = 100;

// Pineconeクライアントの初期化
async function initPinecone() {
  const pinecone = new PineconeClient();
  await pinecone.init({
    apiKey: pineconeApiKey,
    environment: pineconeEnvironment,
  });
  return pinecone;
}

// Pineconeからベクトルを取得
async function fetchVectorsFromPinecone(pinecone, batchSize, paginationToken = null) {
  const index = pinecone.Index(pineconeIndexName);

  try {
    // 新しいPineconeバージョン用
    const listResponse = await index.list({
      limit: batchSize,
      paginationToken: paginationToken
    });

    return {
      vectors: listResponse.vectors || {},
      nextToken: listResponse.paginationToken
    };
  } catch (error) {
    // 古いPineconeバージョン用のフォールバック
    // これは簡略化されています; 実際の実装はデータアクセスパターンに依存します
    const stats = await index.describeIndexStats();
    const dimension = stats.dimension;

    const queryResponse = await index.query({
      vector: Array(dimension).fill(0),
      topK: batchSize,
      includeMetadata: true,
      includeValues: true
    });

    const vectors = {};
    queryResponse.matches.forEach(match => {
      vectors[match.id] = {
        id: match.id,
        values: match.values,
        metadata: match.metadata
      };
    });

    return { vectors, nextToken: null };
  }
}

// Rememberizerにベクトルをアップロード
async function uploadToRememberizer(vectors) {
  const headers = {
    'x-api-key': rememberizerApiKey,
    'Content-Type': 'application/json'
  };

  const results = [];

  for (const [vectorId, vectorData] of Object.entries(vectors)) {
    const documentName = vectorData.metadata?.filename || `pinecone_doc_${vectorId}`;
    const content = vectorData.metadata?.text || '';

    if (!content) {
      console.log(`スキップ中 ${vectorId} - メタデータにテキストコンテンツが見つかりません`);
      continue;
    }

    const data = {
      name: documentName,
      content: content,
      // オプション: 追加のメタデータを含める
      metadata: vectorData.metadata || {}
    };

    try {
      const response = await axios.post(
        `${baseUrl}/vector-stores/${vectorStoreId}/documents/text`,
        data,
        { headers }
      );

      if (response.status === 201) {
        console.log(`ドキュメント '${documentName}' が正常にアップロードされました!`);
        results.push({ id: vectorId, success: true });
      } else {
        console.error(`ドキュメント ${documentName} のアップロード中にエラーが発生しました: ${response.statusText}`);
        results.push({ id: vectorId, success: false, error: response.statusText });
      }
    } catch (error) {
      console.error(`ドキュメント ${documentName} のアップロード中にエラーが発生しました: ${error.message}`);
      results.push({ id: vectorId, success: false, error: error.message });
    }

    // レート制限を防ぐために小さな遅延を追加
    await new Promise(resolve => setTimeout(resolve, 100));
  }

  return results;
}

// メインの移行関数
async function migratePineconeToRememberizer() {
  try {
    console.log('PineconeからRememberizerへの移行を開始します...');

    const pinecone = await initPinecone();
    let nextToken = null;
    let totalMigrated = 0;

    do {
      const { vectors, nextToken: token } = await fetchVectorsFromPinecone(
        pinecone, 
        BATCH_SIZE, 
        nextToken
      );

      nextToken = token;

      if (Object.keys(vectors).length === 0) {
        break;
      }

      console.log(`Pineconeから ${Object.keys(vectors).length} ベクトルを取得しました`);

      const results = await uploadToRememberizer(vectors);
      const successCount = results.filter(r => r.success).length;

      totalMigrated += successCount;
      console.log(`進捗: ${totalMigrated} ベクトルが正常に移行されました`);

    } while (nextToken);

    console.log(`移行完了! ${totalMigrated} のベクトルがRememberizerに移行されました`);

  } catch (error) {
    console.error('移行に失敗しました:', error);
  }
}

// 移行を実行
// migratePineconeToRememberizer();

Qdrantからの移行

import requests
import json
import time
from qdrant_client import QdrantClient
from qdrant_client.http import models as rest

# Qdrantクライアントのセットアップ
QDRANT_URL = "http://localhost:6333"  # またはあなたのQdrantクラウドURL
QDRANT_API_KEY = "your_qdrant_api_key"  # Qdrant Cloudを使用する場合
QDRANT_COLLECTION_NAME = "your_collection"

qdrant_client = QdrantClient(
    url=QDRANT_URL,
    api_key=QDRANT_API_KEY  # Qdrant Cloud専用
)

# Rememberizerベクターストアクライアントの設定
REMEMBERIZER_API_KEY = "your_rememberizer_api_key"
VECTOR_STORE_ID = "vs_abc123"  # あなたのRememberizerベクターストアID
BASE_URL = "https://api.rememberizer.ai/api/v1"

# 処理のバッチサイズ
BATCH_SIZE = 100

# Qdrantからポイントを取得する関数
def fetch_points_from_qdrant(collection_name, batch_size, offset=0):
    try:
        # ベクトルの次元を決定するためにコレクション情報を取得
        collection_info = qdrant_client.get_collection(collection_name=collection_name)

        # ポイントをスクロール
        scroll_result = qdrant_client.scroll(
            collection_name=collection_name,
            limit=batch_size,
            offset=offset,
            with_payload=True,
            with_vectors=True
        )

        points = scroll_result[0]  # (points, next_offset) のタプル
        next_offset = scroll_result[1]

        return points, next_offset
    except Exception as e:
        print(f"Qdrantからポイントを取得中にエラーが発生しました: {e}")
        return [], None

# Rememberizerにベクトルをアップロードするための関数
def upload_to_rememberizer(points):
    headers = {
        "x-api-key": REMEMBERIZER_API_KEY,
        "Content-Type": "application/json"
    }

    results = []

    for point in points:
        # Qdrantポイントからデータを抽出
        point_id = point.id
        metadata = point.payload
        text_content = metadata.get("text", "")
        document_name = metadata.get("filename", f"qdrant_doc_{point_id}")

        if not text_content:
            print(f"スキップ中 {point_id} - ペイロードにテキストコンテンツが見つかりません")
            continue

        data = {
            "name": document_name,
            "content": text_content,
            # オプション: 追加のメタデータを含める
            "metadata": metadata
        }

        try:
            response = requests.post(
                f"{BASE_URL}/vector-stores/{VECTOR_STORE_ID}/documents/text",
                headers=headers,
                json=data
            )

            if response.status_code == 201:
                print(f"ドキュメント '{document_name}' が正常にアップロードされました!")
                results.append({"id": point_id, "success": True})
            else:
                print(f"ドキュメント {document_name} のアップロード中にエラーが発生しました: {response.text}")
                results.append({"id": point_id, "success": False, "error": response.text})
        except Exception as e:
            print(f"ドキュメント {document_name} のアップロード中に例外が発生しました: {str(e)}")
            results.append({"id": point_id, "success": False, "error": str(e)})

        # レート制限を防ぐために少し遅延を追加
        time.sleep(0.1)

    return results

# メイン移行関数
def migrate_qdrant_to_rememberizer():
    offset = None
    total_migrated = 0

    print("QdrantからRememberizerへの移行を開始します...")

    while True:
        points, next_offset = fetch_points_from_qdrant(
            QDRANT_COLLECTION_NAME, 
            BATCH_SIZE,
            offset
        )

        if not points:
            break

        print(f"Qdrantから{len(points)}ポイントを取得しました")

        results = upload_to_rememberizer(points)
        success_count = sum(1 for r in results if r.get("success", False))

        total_migrated += success_count
        print(f"進捗: {total_migrated}ポイントが正常に移行されました")

        if next_offset is None:
            break

        offset = next_offset

    print(f"移行完了! {total_migrated}ポイントがRememberizerに移行されました")

# マイグレーションを実行する
# migrate_qdrant_to_rememberizer()
const { QdrantClient } = require('@qdrant/js-client-rest');
const axios = require('axios');

// Qdrantの設定
const qdrantUrl = 'http://localhost:6333'; // またはあなたのQdrantクラウドURL
const qdrantApiKey = 'your_qdrant_api_key'; // Qdrant Cloudを使用する場合
const qdrantCollectionName = 'your_collection';

// Rememberizerの設定
const rememberizerApiKey = 'YOUR_REMEMBERIZER_API_KEY';
const vectorStoreId = 'vs_abc123';
const baseUrl = 'https://api.rememberizer.ai/api/v1';

// バッチサイズの設定
const BATCH_SIZE = 100;

// Qdrantクライアントの初期化
const qdrantClient = new QdrantClient({ 
  url: qdrantUrl,
  apiKey: qdrantApiKey // Qdrant Cloud専用
});

// Qdrantからポイントを取得する
async function fetchPointsFromQdrant(collectionName, batchSize, offset = 0) {
  try {
    // コレクション情報を取得
    const collectionInfo = await qdrantClient.getCollection(collectionName);

    // ポイントをスクロール
    const scrollResult = await qdrantClient.scroll(collectionName, {
      limit: batchSize,
      offset: offset,
      with_payload: true,
      with_vectors: true
    });

    return {
      points: scrollResult.points,
      nextOffset: scrollResult.next_page_offset
    };
  } catch (error) {
    console.error(`Qdrantからポイントを取得中にエラーが発生しました: ${error.message}`);
    return { points: [], nextOffset: null };
  }
}

// Rememberizerにベクトルをアップロードする
async function uploadToRememberizer(points) {
  const headers = {
    'x-api-key': rememberizerApiKey,
    'Content-Type': 'application/json'
  };

  const results = [];

  for (const point of points) {
    // Qdrantポイントからデータを抽出
    const pointId = point.id;
    const metadata = point.payload || {};
    const textContent = metadata.text || '';
    const documentName = metadata.filename || `qdrant_doc_${pointId}`;

    if (!textContent) {
      console.log(`スキップ中: ${pointId} - ペイロードにテキストコンテンツが見つかりません`);
      continue;
    }

    const data = {
      name: documentName,
      content: textContent,
      // オプション: 追加のメタデータを含める
      metadata: metadata
    };

    try {
      const response = await axios.post(
        `${baseUrl}/vector-stores/${vectorStoreId}/documents/text`,
        data,
        { headers }
      );

      if (response.status === 201) {
        console.log(`ドキュメント '${documentName}' が正常にアップロードされました!`);
        results.push({ id: pointId, success: true });
      } else {
        console.error(`ドキュメント ${documentName} のアップロード中にエラーが発生しました: ${response.statusText}`);
        results.push({ id: pointId, success: false, error: response.statusText });
      }
    } catch (error) {
      console.error(`ドキュメント ${documentName} のアップロード中にエラーが発生しました: ${error.message}`);
      results.push({ id: pointId, success: false, error: error.message });
    }

    // レート制限を防ぐために小さな遅延を追加
    await new Promise(resolve => setTimeout(resolve, 100));
  }

  return results;
}

// メインの移行関数
async function migrateQdrantToRememberizer() {
  try {
    console.log('QdrantからRememberizerへの移行を開始します...');

    let offset = null;
    let totalMigrated = 0;

    do {
      const { points, nextOffset } = await fetchPointsFromQdrant(
        qdrantCollectionName, 
        BATCH_SIZE, 
        offset
      );

      offset = nextOffset;

      if (points.length === 0) {
        break;
      }

      console.log(`Qdrantから ${points.length} ポイントを取得しました`);

      const results = await uploadToRememberizer(points);
      const successCount = results.filter(r => r.success).length;

      totalMigrated += successCount;
      console.log(`進捗: ${totalMigrated} ポイントが正常に移行されました`);

    } while (offset !== null);

    console.log(`移行完了! ${totalMigrated} ポイントがRememberizerに移行されました`);

  } catch (error) {
    console.error('移行に失敗しました:', error);
  }
}

// 移行を実行
// migrateQdrantToRememberizer();

Supabase pgvectorからの移行

すでにSupabaseをpgvectorで使用している場合、Rememberizerへの移行は特に簡単です。なぜなら、両者はpgvector拡張機能を持つPostgreSQLを使用しているからです。

import psycopg2
import requests
import json
import time
import os
from dotenv import load_dotenv

環境変数を読み込む

load_dotenv()

Supabase PostgreSQL 設定

SUPABASE_DB_HOST = os.getenv("SUPABASE_DB_HOST") SUPABASE_DB_PORT = os.getenv("SUPABASE_DB_PORT", "5432") SUPABASE_DB_NAME = os.getenv("SUPABASE_DB_NAME") SUPABASE_DB_USER = os.getenv("SUPABASE_DB_USER") SUPABASE_DB_PASSWORD = os.getenv("SUPABASE_DB_PASSWORD") SUPABASE_VECTOR_TABLE = os.getenv("SUPABASE_VECTOR_TABLE", "documents")

Rememberizerの設定

REMEMBERIZER_API_KEY = os.getenv("REMEMBERIZER_API_KEY") VECTOR_STORE_ID = os.getenv("VECTOR_STORE_ID") # 例: "vs_abc123" BASE_URL = "https://api.rememberizer.ai/api/v1"

処理のバッチサイズ

BATCH_SIZE = 100

Supabase PostgreSQLに接続する

def connect_to_supabase(): try: conn = psycopg2.connect( host=SUPABASE_DB_HOST, port=SUPABASE_DB_PORT, dbname=SUPABASE_DB_NAME, user=SUPABASE_DB_USER, password=SUPABASE_DB_PASSWORD ) return conn except Exception as e: print(f"Supabase PostgreSQLへの接続中にエラーが発生しました: {e}") return None

Supabase pgvectorからドキュメントを取得する

def fetch_documents_from_supabase(conn, batch_size, offset=0): try: cursor = conn.cursor()

    # テーブル構造に基づいてこのクエリを調整してください
    query = f"""
    SELECT id, content, metadata, embedding
    FROM {SUPABASE_VECTOR_TABLE}
    ORDER BY id
    LIMIT %s OFFSET %s
    """

    cursor.execute(query, (batch_size, offset))
    documents = cursor.fetchall()
    cursor.close()

    return documents
except Exception as e:
    print(f"Supabaseからドキュメントを取得中にエラーが発生しました: {e}")
    return []

Rememberizerにドキュメントをアップロードする

def upload_to_rememberizer(documents): headers = { "x-api-key": REMEMBERIZER_API_KEY, "Content-Type": "application/json" }

results = []

for doc in documents:
    doc_id, content, metadata, embedding = doc

    # メタデータがJSON文字列として保存されている場合は解析する
    if isinstance(metadata, str):
        try:
            metadata = json.loads(metadata)
        except:
            metadata = {}
    elif metadata is None:
        metadata = {}

    document_name = metadata.get("filename", f"supabase_doc_{doc_id}")

    if not content:
        print(f"{doc_id}をスキップします - コンテンツが見つかりません")
        continue

    data = {
        "name": document_name,
        "content": content,
        "metadata": metadata
    }

    try:
        response = requests.post(
            f"{BASE_URL}/vector-stores/{VECTOR_STORE_ID}/documents/text",
            headers=headers,
            json=data
        )

        if response.status_code == 201:
            print(f"ドキュメント '{document_name}' が正常にアップロードされました!")
            results.append({"id": doc_id, "success": True})
        else:
            print(f"ドキュメント {document_name} のアップロード中にエラーが発生しました: {response.text}")
            results.append({"id": doc_id, "success": False, "error": response.text})
    except Exception as e:
        print(f"ドキュメント {document_name} のアップロード中に例外が発生しました: {str(e)}")
        results.append({"id": doc_id, "success": False, "error": str(e)})

    # レート制限を防ぐために少し遅延を追加
    time.sleep(0.1)

return results

メイン移行関数

def migrate_supabase_to_rememberizer(): conn = connect_to_supabase() if not conn: print("Supabaseへの接続に失敗しました。移行を中止します。") return

offset = 0
total_migrated = 0

print("Supabase pgvectorからRememberizerへの移行を開始します...")

try:
    while True:
        documents = fetch_documents_from_supabase(conn, BATCH_SIZE, offset)

        if not documents:
            break

        print(f"Supabaseから{len(documents)}件のドキュメントを取得しました")

        results = upload_to_rememberizer(documents)
        success_count = sum(1 for r in results if r.get("success", False))

        total_migrated += success_count
        print(f"進捗: {total_migrated}件のドキュメントが正常に移行されました")

        offset += BATCH_SIZE

finally:
    conn.close()

print(f"移行完了!{total_migrated}件のドキュメントがRememberizerに移行されました")

マイグレーションを実行する

migrate_supabase_to_rememberizer()

```

const { Pool } = require('pg');
const axios = require('axios');
require('dotenv').config();

// Supabase PostgreSQL 設定
const supabasePool = new Pool({
  host: process.env.SUPABASE_DB_HOST,
  port: process.env.SUPABASE_DB_PORT || 5432,
  database: process.env.SUPABASE_DB_NAME,
  user: process.env.SUPABASE_DB_USER,
  password: process.env.SUPABASE_DB_PASSWORD,
  ssl: {
    rejectUnauthorized: false
  }
});

const supabaseVectorTable = process.env.SUPABASE_VECTOR_TABLE || 'documents';

// Rememberizer 設定
const rememberizerApiKey = process.env.REMEMBERIZER_API_KEY;
const vectorStoreId = process.env.VECTOR_STORE_ID; // 例: "vs_abc123"
const baseUrl = 'https://api.rememberizer.ai/api/v1';

// バッチサイズ設定
const BATCH_SIZE = 100;

// Supabase pgvector からドキュメントを取得
async function fetchDocumentsFromSupabase(batchSize, offset = 0) {
  try {
    // テーブル構造に基づいてこのクエリを調整
    const query = `
      SELECT id, content, metadata, embedding
      FROM ${supabaseVectorTable}
      ORDER BY id
      LIMIT $1 OFFSET $2
    `;

    const result = await supabasePool.query(query, [batchSize, offset]);
    return result.rows;
  } catch (error) {
    console.error(`Supabase からドキュメントを取得中にエラーが発生しました: ${error.message}`);
    return [];
  }
}

// Rememberizer にドキュメントをアップロード
async function uploadToRememberizer(documents) {
  const headers = {
    'x-api-key': rememberizerApiKey,
    'Content-Type': 'application/json'
  };

  const results = [];

  for (const doc of documents) {
    // メタデータが JSON 文字列として保存されている場合は解析
    let metadata = doc.metadata;
    if (typeof metadata === 'string') {
      try {
        metadata = JSON.parse(metadata);
      } catch (e) {
        metadata = {};
      }
    } else if (metadata === null) {
      metadata = {};
    }

    const documentName = metadata.filename || `supabase_doc_${doc.id}`;

    if (!doc.content) {
      console.log(`スキップ中: ${doc.id} - コンテンツが見つかりません`);
      continue;
    }

    const data = {
      name: documentName,
      content: doc.content,
      metadata: metadata
    };

    try {
      const response = await axios.post(
        `${baseUrl}/vector-stores/${vectorStoreId}/documents/text`,
        data,
        { headers }
      );

      if (response.status === 201) {
        console.log(`ドキュメント '${documentName}' が正常にアップロードされました!`);
        results.push({ id: doc.id, success: true });
      } else {
        console.error(`ドキュメント ${documentName} のアップロード中にエラーが発生しました: ${response.statusText}`);
        results.push({ id: doc.id, success: false, error: response.statusText });
      }
    } catch (error) {
      console.error(`ドキュメント ${documentName} のアップロード中にエラーが発生しました: ${error.message}`);
      results.push({ id: doc.id, success: false, error: error.message });
    }

    // レート制限を防ぐために少し遅延を追加
    await new Promise(resolve => setTimeout(resolve, 100));
  }

  return results;
}

// メイン移行関数
async function migrateSupabaseToRememberizer() {
  try {
    console.log('Supabase pgvector から Rememberizer への移行を開始します...');

    let offset = 0;
    let totalMigrated = 0;

    while (true) {
      const documents = await fetchDocumentsFromSupabase(BATCH_SIZE, offset);

      if (documents.length === 0) {
        break;
      }

      console.log(`Supabase から ${documents.length} ドキュメントを取得しました`);

      const results = await uploadToRememberizer(documents);
      const successCount = results.filter(r => r.success).length;

      totalMigrated += successCount;
      console.log(`進捗: ${totalMigrated} ドキュメントが正常に移行されました`);

      offset += BATCH_SIZE;
    }

    console.log(`移行完了! ${totalMigrated} 件のドキュメントが Rememberizer に移行されました`);

  } catch (error) {
    console.error('移行に失敗しました:', error);
  } finally {
    await supabasePool.end();
  }
}

// 移行を実行
// migrateSupabaseToRememberizer();

移行のベストプラクティス

成功する移行のために、以下の推奨事項に従ってください:

  1. 事前計画:
  2. 移行に必要なデータ量と時間を見積もる
  3. トラフィックが少ない時間帯に移行をスケジュールする
  4. 大規模な移行を開始する前にディスクスペースを増やす

  5. 最初にテスト:

  6. Rememberizer にテストベクトルストアを作成する
  7. 小さなデータセット(100-1000 ベクトル)を移行する
  8. 主要なクエリで検索機能を確認する

  9. データ検証:

  10. 移行前後のドキュメント数を比較する
  11. 同様の結果を確保するためにベンチマーククエリを実行する
  12. メタデータが正しく保持されていることを検証する

  13. パフォーマンスの最適化:

  14. 効率のためにバッチ操作を使用する
  15. ソースデータベースとターゲットデータベースの地理的な共同配置を考慮する
  16. API レート制限を監視し、バッチサイズを適宜調整する

  17. 移行後のステップ:

  18. Rememberizer でのインデックス作成を確認する
  19. 新しいベクトルストアを指すようにアプリケーション設定を更新する
  20. 移行が確認されるまでソースデータベースをバックアップとして保持する

詳細な API リファレンスとエンドポイントのドキュメントについては、vector-store ページを訪問してください。


API キーを安全に取り扱い、API キー管理のベストプラクティスに従ってください。