Migrate your vector embeddings from Pinecone to Antfly.
This example also covers the Antfly-side pattern for other systems that already
have vectors, such as Milvus, Weaviate, pgvector exports, or custom offline
embedding pipelines. Only the fetch step in main.py is Pinecone-specific.
Use Cases
- Cost reduction: Move from cloud-based Pinecone to local Antfly
- Local development: Run your vector search locally without API calls
- Sunsetting Pinecone: Full migration with preserved embeddings
Prerequisites
- A running Antfly server (
antfly swarm) - Python 3.9+
- Your Pinecone API key
pip install -r requirements.txt
Vector Compatibility
Your source index stores embeddings at a specific dimension and uses a specific distance metric. To preserve search behavior in Antfly, create an external embeddings index with the same dimension and metric.
Check Your Pinecone Dimension
In the Pinecone dashboard, find your index and note its dimension. Or via API:
from pinecone import Pinecone
pc = Pinecone(api_key="...")
stats = pc.Index("your-index").describe_index_stats()
print(f"Dimension: {stats['dimension']}")
What must match
dimension: Vector length (e.g. 768, 1024, 1536)distance_metric: Usuallycosine,inner_product, orl2_squared
This example creates an Antfly embeddings index with:
external: true- the source vector
dimension - the configured
distance_metric
No embedder, field, or template is used for this migration index, because
the vectors are already computed upstream.
Schemaless Storage
Antfly tables are schemaless. When migrating from Pinecone:
- Each vector's metadata becomes top-level document fields
- The id becomes the document key
- Pre-computed embeddings go in the
_embeddingsfield for anexternal: trueindex
Example:
Pinecone: Antfly:
id: "doc-123" _id: "doc-123"
values: [0.1, 0.2, ...] → text: "Hello"
metadata: {text: "Hello"} _embeddings: {nomic_index: [0.1, ...]}
Configuration
Update these values in main.py for your migration:
# --- Configuration ---
# Customize these values for your migration
PINECONE_INDEX_NAME = "my-pinecone-index" # Your Pinecone index name
TABLE_NAME = PINECONE_INDEX_NAME # Antfly table name (using same name)
INDEX_NAME = "nomic_index" # Name for the vector index in Antfly
DISTANCE_METRIC = "cosine" # Match the metric used by your source index
ANTFLY_BASE_URL = os.getenv("ANTFLY_BASE_URL", "http://localhost:8080/api/v1")
Running the Migration
export PINECONE_API_KEY="your-key-here"
python main.py
How It Works
1. Initialize Clients
def main():
load_dotenv()
# --- Initialize Pinecone ---
pinecone_api_key = os.getenv("PINECONE_API_KEY")
if not pinecone_api_key:
print("FATAL: PINECONE_API_KEY not found in environment.", file=sys.stderr)
sys.exit(1)
pc = Pinecone(api_key=pinecone_api_key)
pinecone_index = pc.Index(PINECONE_INDEX_NAME)
print(f"Connected to Pinecone index '{PINECONE_INDEX_NAME}'.")
# --- Initialize Antfly ---
client = AntflyClient(base_url=ANTFLY_BASE_URL)
try:
print(f"Connecting to Antfly at {ANTFLY_BASE_URL}...")
client.list_tables()
print("Connected to Antfly.")
except Exception as e:
print(f"Could not connect to Antfly: {e}")
print("Ensure the Antfly server is running.")
sys.exit(1)
2. Create Table
Tables are schemaless - no schema definition needed:
# --- Create Antfly table ---
# Tables are schemaless - no schema definition required
try:
client.drop_table(TABLE_NAME)
print(f"Deleted existing table '{TABLE_NAME}'")
except AntflyException:
pass
try:
client.create_table(name=TABLE_NAME)
print(f"Created table '{TABLE_NAME}'")
except Exception as e:
print(f"Error creating table: {e}")
sys.exit(1)
print("Waiting for table shards to initialize...")
time.sleep(5)
3. Create Vector Index
The index is created before inserting data so Antfly knows where to store imported vectors.
This is an external index, meaning Antfly will accept vectors written through
_embeddings and will not try to generate them from document fields:
# --- Create vector index BEFORE inserting data ---
# This tells Antfly where to store pre-computed embeddings
try:
print(f"\nCreating vector index '{INDEX_NAME}' with dimension {dimension}...")
index_config = {
"name": INDEX_NAME,
"type": "embeddings",
"external": True,
"dimension": dimension,
"distance_metric": DISTANCE_METRIC,
}
print(f" -> Creating external index for imported vectors (metric: {DISTANCE_METRIC})")
resp = httpx.post(
f"{ANTFLY_BASE_URL}/tables/{TABLE_NAME}/indexes/{INDEX_NAME}",
json=index_config,
timeout=30.0
)
if resp.status_code in (200, 201):
print("Index created successfully.")
else:
print(f"Index creation response: {resp.status_code} - {resp.text}")
except Exception as e:
print(f"Error creating index: {e}")
print("Waiting for index to propagate...")
time.sleep(5)
4. Fetch Vectors from Pinecone
Uses a zero-vector query with high top_k - a common pattern since Pinecone doesn't have a "list all" API:
def fetch_all_vectors(pc_index, namespace: str, dim: int):
"""
Fetch all vectors from a Pinecone namespace.
Uses a zero-vector query with high top_k - a common pattern
since Pinecone doesn't have a direct "list all" API.
"""
print(f"Fetching vectors from namespace: '{namespace}'...")
# Query with zero vector to get all vectors
query_response = pc_index.query(
namespace=namespace,
vector=[0.0] * dim,
top_k=1000,
include_values=True,
include_metadata=True
)
vectors = query_response.get('matches', [])
if len(vectors) == 1000:
print(" WARNING: Hit 1000 limit - may have more vectors.", file=sys.stderr)
print(f" -> Found {len(vectors)} vectors.")
return vectors
# Fetch from all namespaces
all_vectors = {}
for namespace in namespaces:
vectors = fetch_all_vectors(pinecone_index, namespace, dimension)
if not vectors:
continue
all_vectors[namespace] = []
for v in vectors:
# Flatten: metadata becomes top-level fields
entry = {
'id': v['id'],
'values': v['values'],
**v.get('metadata', {}),
'namespace': namespace
}
all_vectors[namespace].append(entry)
5. Upsert to Antfly
Vectors are batch-upserted with the _embeddings field:
# --- Upsert into Antfly ---
for namespace, items in all_vectors.items():
try:
print(f"Upserting {len(items)} vectors for namespace '{namespace}'...")
inserts = {}
for item in items:
key = item['id']
properties = {k: v for k, v in item.items() if k != 'id'}
# Use _embeddings format for pre-computed embeddings
# Format: {"_embeddings": {"<index_name>": [vector values]}}
if 'values' in properties:
embedding_vector = properties.pop('values')
properties['_embeddings'] = {INDEX_NAME: embedding_vector}
inserts[key] = properties
resp = httpx.post(
f"{ANTFLY_BASE_URL}/tables/{TABLE_NAME}/batch",
json={"inserts": inserts},
timeout=60.0
)
if resp.status_code in (200, 201):
print(f" -> Successfully upserted {len(items)} vectors.")
else:
print(f" -> Batch failed: {resp.status_code} - {resp.text}")
except Exception as e:
print(f" -> ERROR: {e}")
break
print("\nMigration complete. Waiting for indexing...")
time.sleep(10)
6. Verify
# --- Verify migration ---
print("\n=== Verifying Migration ===")
try:
resp = httpx.post(
f"{ANTFLY_BASE_URL}/tables/{TABLE_NAME}/query",
json={"limit": 200},
timeout=30.0
)
if resp.status_code == 200:
data = resp.json()
hits = data.get('responses', [{}])[0].get('hits', {}).get('hits', [])
print(f"Total records in Antfly: {len(hits)}")
if hits:
print("\nSample records:")
for hit in hits[:3]:
key = hit.get('_id', 'unknown')
source = hit.get('_source', {})
text = source.get('text', '')[:80] if source else ''
print(f" - {key}: {text}...")
except Exception as e:
print(f"Verification failed: {e}")
# --- Test vector search using one of the imported vectors ---
sample_key = None
sample_vector = None
for items in all_vectors.values():
if items:
sample_key = items[0]['id']
sample_vector = items[0]['values']
break
if sample_vector is not None:
print(f"\nTesting vector search using imported vector from key '{sample_key}'...")
try:
resp = httpx.post(
f"{ANTFLY_BASE_URL}/tables/{TABLE_NAME}/query",
json={"embeddings": {INDEX_NAME: sample_vector}, "limit": 3},
timeout=30.0
)
if resp.status_code == 200:
data = resp.json()
hits = data.get('responses', [{}])[0].get('hits', {}).get('hits', [])
if hits:
print("Results:")
for hit in hits:
score = hit.get('_score', 0)
key = hit.get('_id', 'unknown')
print(f" [{score:.4f}] {key}")
else:
print(" (no results)")
else:
print(f" Search failed: {resp.status_code} - {resp.text}")
except Exception as e:
print(f" Search failed: {e}")
else:
print("\nSkipping verification search because no vectors were imported.")
print("\n=== Done ===")
print(f"Table: {TABLE_NAME}")
print(f"Index: {INDEX_NAME} (dimension: {dimension}, metric: {DISTANCE_METRIC})")
The verification query uses one of the imported vectors directly via the query
embeddings field. This keeps the example consistent with external: true
indexes, which do not have an embedder for text-to-vector conversion.
Troubleshooting
"manual _embeddings are not allowed"
The Antfly index was probably created as a managed index. For imported vectors,
create the index with external: true and do not set field, template, or
embedder.
"Hit 1000 limit"
Increase top_k in fetch_all_vectors() or implement pagination for larger datasets.
Adapting this example for Milvus / Weaviate / pgvector
Reuse the same Antfly-side steps:
- create the table
- create an
external: trueembeddings index - write vectors via
_embeddings - verify with a query-time
embeddingsvector
Only the export/fetch step changes based on your source system.