> For the complete documentation index, see [llms.txt](https://docs.rememberizer.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.rememberizer.ai/zh-cn/kai-fa-zhe-zi-yuan/integration-options/vector-stores.md).

# 向量存储

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

## 介绍

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

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

## 技术概述

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

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

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

### 关键组件

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

### 架构

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

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

## 开始使用

### 创建向量存储

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

<figure><img src="/files/8xKVROUZzWRhPirahouH" 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="/files/8efa9iit2V8PwZw3FRJK" alt="查看向量存储的详细信息"><figcaption><p>查看向量存储的详细信息</p></figcaption></figure>

## API 密钥管理

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

### 创建 API 密钥

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

<figure><img src="/files/eKCkKj6FdBzV1feLrm7t" 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 参考和端点文档，请访问 [向量存储 API](/zh-cn/kai-fa-zhe-zi-yuan/api-docs/vector-store.md) 页面。

***

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


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.rememberizer.ai/zh-cn/kai-fa-zhe-zi-yuan/integration-options/vector-stores.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
