向量存储
本指南将帮助您了解如何作为开发者使用 Rememberizer 向量存储。
Rememberizer 向量存储简化了处理向量数据的过程,使您能够专注于文本输入,并利用向量的力量用于搜索和数据分析等各种应用。
介绍
Rememberizer 向量存储提供了一个易于使用的接口,用于处理向量数据,同时抽象掉向量嵌入的复杂性。由 PostgreSQL 和 pgvector 扩展驱动,Rememberizer 向量存储允许您直接处理文本。该服务处理文本数据的分块、向量化和存储,使您能够更专注于核心应用逻辑。
要深入了解向量嵌入和向量数据库背后的理论概念,请参见 什么是向量嵌入和向量数据库?。
技术概述
向量存储是如何工作的
Rememberizer 向量存储将文本转换为高维向量表示(嵌入),以捕捉语义意义。这使得:
语义搜索:根据意义而不仅仅是关键词查找文档
相似性匹配:识别概念上相关的内容
高效检索:快速从大型数据集中定位相关信息
关键组件
文档处理:文本被分割成最佳大小的块,具有重叠边界以保持上下文
向量化:块使用最先进的模型转换为嵌入
索引:专门的算法组织向量以实现高效的相似性搜索
查询处理:搜索查询被向量化并与存储的嵌入进行比较
架构
Rememberizer 使用以下方式实现向量存储:
带有 pgvector 扩展的 PostgreSQL:用于高效的向量存储和搜索
基于集合的组织:每个向量存储都有自己独立的集合
API 驱动的访问:所有操作的简单 RESTful 端点
开始使用
创建向量存储
在您的仪表板中导航到向量存储部分
点击“创建新向量存储”:
将出现一个表单,提示您输入详细信息。
填写详细信息:
名称:为您的向量存储提供一个唯一的名称。
描述:写一个简短的向量存储描述。
嵌入模型:选择将文本转换为向量的模型。
索引算法:选择向量如何组织以便搜索。
搜索度量:定义向量之间相似性的计算方式。
向量维度:向量嵌入的大小(通常为768-1536)。
提交表单:
点击“创建”按钮。您将收到成功通知,新存储将出现在您的向量存储列表中。

配置选项
嵌入模型
openai/text-embedding-3-large
1536
来自 OpenAI 的高精度嵌入模型
需要最大精度的生产应用
openai/text-embedding-3-small
1536
来自 OpenAI 的更小、更快的嵌入模型
具有更高吞吐量要求的应用
索引算法
IVFFLAT (默认)
反向文件与平面压缩
速度和准确性的良好平衡;适用于大多数数据集
HNSW
层次可导航小世界
对于大数据集更好的准确性;更高的内存要求
搜索指标
余弦 (默认)
测量向量之间的角度
通用相似性匹配
内积 (ip)
向量之间的点积
当向量大小重要时
L2 (欧几里得)
向量之间的直线距离
当空间关系重要时
管理向量存储
查看和编辑向量存储:
访问管理仪表板以查看、编辑或删除向量存储。
查看文档:
浏览特定向量存储中的单个文档及其相关元数据。
统计信息:
查看详细统计信息,例如存储的向量数量、查询性能和操作指标。

API 密钥管理
API 密钥用于验证和授权访问 Rememberizer 向量存储的 API 端点。正确管理 API 密钥对于维护您的向量存储的安全性和完整性至关重要。
创建 API 密钥
前往您的向量存储详细信息页面
导航到 API 密钥管理部分:
它可以在“配置”选项卡中找到
点击 “添加 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"找到 {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("向量相似性是如何工作的?")
// 向量存储 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));
*/
```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" %}
# 设置您的 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 在向量数量超过预定义阈值时会自动触发重新索引,但在以下情况下请考虑手动重新索引:
上传大量文档
更改嵌入模型
修改索引算法
查询增强
为了获得更好的搜索结果:
具体 搜索查询
包含上下文 如果可能的话
使用自然语言 而不是关键词
根据结果质量 调整参数
从其他向量数据库迁移
如果您当前正在使用其他向量数据库解决方案并希望迁移到 Rememberizer 向量存储,以下指南将帮助您高效地转移数据。
迁移概述
迁移向量数据涉及:
从您的源向量数据库导出数据
将数据转换为与 Rememberizer 兼容的格式
将数据导入到您的 Rememberizer 向量存储中
验证迁移是否成功
迁移到 Rememberizer 的好处
PostgreSQL 基础:建立在成熟的数据库技术上,具有内置的备份和恢复功能
集成生态系统:与其他 Rememberizer 组件的无缝连接
简化管理:统一的向量操作界面
高级安全性:行级安全和细粒度访问控制
可扩展架构:随着数据增长进行性能优化
从 Pinecone 迁移
{% tabs %} {% tab title="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" %}
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" %}
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" %}
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" %}
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();
迁移最佳实践
遵循以下建议以确保成功迁移:
提前规划:
估算迁移所需的数据量和时间
在低流量时段安排迁移
在开始大规模迁移之前增加磁盘空间
先进行测试:
在 Rememberizer 中创建一个测试向量存储
迁移一小部分数据(100-1000 个向量)
使用关键查询验证搜索功能
数据验证:
比较迁移前后的文档计数
运行基准查询以确保结果相似
验证元数据是否正确保留
优化性能:
使用批量操作以提高效率
考虑源数据库和目标数据库的地理共址
监控 API 速率限制并相应调整批量大小
迁移后的步骤:
验证在 Rememberizer 中创建索引
更新应用程序配置以指向新的向量存储
在迁移验证之前保留源数据库作为备份
有关详细的 API 参考和端点文档,请访问 https://github.com/skydeckai/rememberizer-docs/blob/production/zh-cn/developer/api-docs/vector-store/README.md 页面。
确保安全处理 API 密钥,并遵循 API 密钥管理的最佳实践。
Last updated