# 向量存储

Rememberizer 向量存储简化了处理向量数据的过程，使您能够专注于文本输入，并利用向量的力量用于搜索和数据分析等各种应用。

## 介绍

Rememberizer 向量存储提供了一个易于使用的接口，用于处理向量数据，同时抽象掉向量嵌入的复杂性。由 PostgreSQL 和 pgvector 扩展驱动，Rememberizer 向量存储允许您直接处理文本。该服务处理文本数据的分块、向量化和存储，使您能够更专注于核心应用逻辑。

要深入了解向量嵌入和向量数据库背后的理论概念，请参见 [什么是向量嵌入和向量数据库？](https://docs.rememberizer.ai/zh-cn/background/what-are-vector-embeddings-and-vector-databases)。

## 技术概述

### 向量存储是如何工作的

Rememberizer 向量存储将文本转换为高维向量表示（嵌入），以捕捉语义意义。这使得：

1. **语义搜索**：根据意义而不仅仅是关键词查找文档
2. **相似性匹配**：识别概念上相关的内容
3. **高效检索**：快速从大型数据集中定位相关信息

### 关键组件

* **文档处理**：文本被分割成最佳大小的块，具有重叠边界以保持上下文
* **向量化**：块使用最先进的模型转换为嵌入
* **索引**：专门的算法组织向量以实现高效的相似性搜索
* **查询处理**：搜索查询被向量化并与存储的嵌入进行比较

### 架构

Rememberizer 使用以下方式实现向量存储：

* **带有 pgvector 扩展的 PostgreSQL**：用于高效的向量存储和搜索
* **基于集合的组织**：每个向量存储都有自己独立的集合
* **API 驱动的访问**：所有操作的简单 RESTful 端点

## 开始使用

### 创建向量存储

1. 在您的仪表板中导航到向量存储部分
2. 点击“创建新向量存储”：
   * 将出现一个表单，提示您输入详细信息。
3. 填写详细信息：
   * **名称**：为您的向量存储提供一个唯一的名称。
   * **描述**：写一个简短的向量存储描述。
   * **嵌入模型**：选择将文本转换为向量的模型。
   * **索引算法**：选择向量如何组织以便搜索。
   * **搜索度量**：定义向量之间相似性的计算方式。
   * **向量维度**：向量嵌入的大小（通常为768-1536）。
4. 提交表单：
   * 点击“创建”按钮。您将收到成功通知，新存储将出现在您的向量存储列表中。

<figure><img src="https://1371168417-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F4gvX7KIUy0DhcQETj8Ux%2Fuploads%2Fgit-blob-72ea151df769d4ea4796d74ab2d2b335a2445a6f%2Fcreate_vector_DB_store.png?alt=media" alt="创建新向量存储"><figcaption><p>创建新向量存储</p></figcaption></figure>

### 配置选项

#### 嵌入模型

| 模型                            | 维度   | 描述                    | 最佳用途         |
| ----------------------------- | ---- | --------------------- | ------------ |
| openai/text-embedding-3-large | 1536 | 来自 OpenAI 的高精度嵌入模型    | 需要最大精度的生产应用  |
| openai/text-embedding-3-small | 1536 | 来自 OpenAI 的更小、更快的嵌入模型 | 具有更高吞吐量要求的应用 |

#### 索引算法

| 算法           | 描述        | 权衡                    |
| ------------ | --------- | --------------------- |
| IVFFLAT (默认) | 反向文件与平面压缩 | 速度和准确性的良好平衡；适用于大多数数据集 |
| HNSW         | 层次可导航小世界  | 对于大数据集更好的准确性；更高的内存要求  |

#### 搜索指标

| 指标        | 描述        | 最适合      |
| --------- | --------- | -------- |
| 余弦 (默认)   | 测量向量之间的角度 | 通用相似性匹配  |
| 内积 (ip)   | 向量之间的点积   | 当向量大小重要时 |
| L2 (欧几里得) | 向量之间的直线距离 | 当空间关系重要时 |

### 管理向量存储

1. 查看和编辑向量存储：
   * 访问管理仪表板以查看、编辑或删除向量存储。
2. 查看文档：
   * 浏览特定向量存储中的单个文档及其相关元数据。
3. 统计信息：
   * 查看详细统计信息，例如存储的向量数量、查询性能和操作指标。

<figure><img src="https://1371168417-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F4gvX7KIUy0DhcQETj8Ux%2Fuploads%2Fgit-blob-302b722a14296aab8a6021b660e14861024f454b%2Fvector_store_management.png?alt=media" alt="查看向量存储的详细信息"><figcaption><p>查看向量存储的详细信息</p></figcaption></figure>

## API 密钥管理

API 密钥用于验证和授权访问 Rememberizer 向量存储的 API 端点。正确管理 API 密钥对于维护您的向量存储的安全性和完整性至关重要。

### 创建 API 密钥

1. 前往您的向量存储详细信息页面
2. 导航到 API 密钥管理部分：
   * 它可以在“配置”选项卡中找到
3. 点击 **“添加 API 密钥”**：
   * 将出现一个表单，提示您输入详细信息。
4. 填写详细信息：
   * **名称**：为 API 密钥提供一个名称，以帮助您识别其用例。
5. 提交表单：
   * 点击“创建”按钮。新的 API 密钥将被生成并显示。确保复制并安全存储它。此密钥用于验证对该特定向量存储的请求。

<figure><img src="https://1371168417-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F4gvX7KIUy0DhcQETj8Ux%2Fuploads%2Fgit-blob-bff80d6c4bcf1467cd515d0c9848e7fce600765f%2Fvector_store_api_key.png?alt=media" alt="创建新的 API 密钥"><figcaption><p>创建新的 API 密钥</p></figcaption></figure>

### 撤销 API 密钥

如果不再需要 API 密钥，您可以删除它以防止任何潜在的滥用。

出于安全原因，您可能希望定期更换您的 API 密钥。这涉及生成一个新密钥并撤销旧密钥。

## 使用向量存储 API

在创建向量存储并生成 API 密钥后，您可以使用 REST API 与其进行交互。

### 代码示例

{% tabs %}
{% tab title="Python" %}

```python
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"找到 {len(results['matched_chunks'])} 个与 '{query}' 匹配的结果")
        
        # 打印顶部结果
        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("向量相似性是如何工作的？")
```

{% endtab %}

{% tab title="JavaScript" %}

```javascript
// 向量存储 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));
*/
```

{% endtab %}

{% tab title="Ruby" %}
\`\`\`ruby 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
```

{% endtab %}

{% tab title="cURL" %}

```bash
# 设置您的 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}"
```

{% endtab %} {% endtabs %}

### 性能考虑

即将推出：向量存储架构图

该技术架构图将说明：

* PostgreSQL + pgvector 基础架构
* 索引算法结构（IVFFLAT 与 HNSW）
* 向量空间中的搜索指标如何工作（视觉比较）
* 文档分块过程及重叠可视化
* 不同规模下的性能考虑可视化

#### 针对不同数据量的优化

| 数据量              | 推荐配置              | 备注                |
| ---------------- | ----------------- | ----------------- |
| 小型 (<10k 文档)     | IVFFLAT, 余弦相似度    | 简单配置提供良好性能        |
| 中型 (10k-100k 文档) | IVFFLAT, 确保定期重建索引 | 搜索速度与索引维护之间的平衡    |
| 大型 (>100k 文档)    | HNSW, 考虑增加向量维度    | 更高的内存使用，但在规模上保持性能 |

#### 分块策略

分块过程对搜索质量有显著影响：

* **分块大小**：Rememberizer 使用默认的分块大小为 1024 字节，重叠 200 字节
* **较小的分块**（512-1024 字节）：更精确的匹配，更适合特定问题
* **较大的分块**（1500-2048 字节）：每个匹配中有更多上下文，更适合广泛主题
* **重叠**：确保在分块边界处不会丢失上下文

#### 查询优化

* **上下文窗口**：使用 `prev_chunks` 和 `next_chunks` 来检索周围内容
* **结果数量**：从 3-5 个结果开始（`n` 参数），根据精确度需求进行调整
* **阈值**：调整 `t` 参数以根据相似度得分过滤结果

### 高级用法

#### 重新索引

Rememberizer 在向量数量超过预定义阈值时会自动触发重新索引，但在以下情况下请考虑手动重新索引：

* 上传大量文档
* 更改嵌入模型
* 修改索引算法

#### 查询增强

为了获得更好的搜索结果：

1. **具体** 搜索查询
2. **包含上下文** 如果可能的话
3. **使用自然语言** 而不是关键词
4. **根据结果质量** 调整参数

### 从其他向量数据库迁移

如果您当前正在使用其他向量数据库解决方案并希望迁移到 Rememberizer 向量存储，以下指南将帮助您高效地转移数据。

#### 迁移概述

迁移向量数据涉及：

1. 从您的源向量数据库导出数据
2. 将数据转换为与 Rememberizer 兼容的格式
3. 将数据导入到您的 Rememberizer 向量存储中
4. 验证迁移是否成功

#### 迁移到 Rememberizer 的好处

* **PostgreSQL 基础**：建立在成熟的数据库技术上，具有内置的备份和恢复功能
* **集成生态系统**：与其他 Rememberizer 组件的无缝连接
* **简化管理**：统一的向量操作界面
* **高级安全性**：行级安全和细粒度访问控制
* **可扩展架构**：随着数据增长进行性能优化

#### 从 Pinecone 迁移

{% tabs %} {% tab title="Python" %}

```python
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()
```

{% endtab %}

{% tab title="Node.js" %}

```javascript
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();
```

{% endtab %} {% endtabs %}

#### 从 Qdrant 迁移

{% tabs %} {% tab title="Python" %}

```python
import requests
import json
import time
from qdrant_client import QdrantClient
from qdrant_client.http import model

# 设置 Qdrant 客户端
QDRANT_URL = "http://localhost:6333"  # 或者你的 Qdrant 云 URL
QDRANT_API_KEY = "your_qdrant_api_key"  # 如果使用 Qdrant 云
QDRANT_COLLECTION_NAME = "your_collection"

qdrant_client = QdrantClient(
    url=QDRANT_URL,
    api_key=QDRANT_API_KEY  # 仅适用于 Qdrant 云
)

# 设置 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()
```

{% endtab %}

{% tab title="Node.js" %}

```javascript
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();
```

{% endtab %} {% endtabs %}

#### 从 Supabase pgvector 迁移

如果您已经在使用带有 pgvector 的 Supabase，迁移到 Rememberizer 特别简单，因为两者都使用带有 pgvector 扩展的 PostgreSQL。

{% tabs %} {% tab title="Python" %}

```python
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()
```

{% endtab %}

{% tab title="Node.js" %}

```

javascript
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();
```

{% endtab %}
{% endtabs %}

### 迁移最佳实践

遵循以下建议以确保成功迁移：

1. **提前规划**：
   * 估算迁移所需的数据量和时间
   * 在低流量时段安排迁移
   * 在开始大规模迁移之前增加磁盘空间
2. **先进行测试**：
   * 在 Rememberizer 中创建一个测试向量存储
   * 迁移一小部分数据（100-1000 个向量）
   * 使用关键查询验证搜索功能
3. **数据验证**：
   * 比较迁移前后的文档计数
   * 运行基准查询以确保结果相似
   * 验证元数据是否正确保留
4. **优化性能**：
   * 使用批量操作以提高效率
   * 考虑源数据库和目标数据库的地理共址
   * 监控 API 速率限制并相应调整批量大小
5. **迁移后的步骤**：
   * 验证在 Rememberizer 中创建索引
   * 更新应用程序配置以指向新的向量存储
   * 在迁移验证之前保留源数据库作为备份

有关详细的 API 参考和端点文档，请访问 [vector-store](https://docs.rememberizer.ai/zh-cn/kai-fa-zhe-zi-yuan/api-docs/vector-store "mention") 页面。

***

确保安全处理 API 密钥，并遵循 API 密钥管理的最佳实践。
