Migrate your vector embeddings from Pinecone to Antfly.
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.txtEmbedding Model Compatibility
Your Pinecone index stores embeddings at a specific dimension (e.g., 768 for nomic-embed-text). To enable semantic search in Antfly, configure an embedder that produces vectors of the same dimension.
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']}")Common Models and Dimensions
| Model | Dimension | Provider |
|---|---|---|
| nomic-embed-text | 768 | Ollama |
| mxbai-embed-large | 1024 | Ollama |
| text-embedding-3-small | 1536 | OpenAI |
| all-minilm | 384 | Ollama |
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
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:
1# --- Configuration ---
2# Customize these values for your migration
3PINECONE_INDEX_NAME = "my-pinecone-index" # Your Pinecone index name
4TABLE_NAME = PINECONE_INDEX_NAME # Antfly table name (using same name)
5INDEX_NAME = "nomic_index" # Name for the vector index in Antfly
6TEMPLATE = "{{text}}" # Handlebars template - which field(s) to embed
7QUERY_TEXT = "example search" # Test query for verification
8
9ANTFLY_BASE_URL = os.getenv("ANTFLY_BASE_URL", "http://localhost:8080/api/v1")
10
11# Embedder configuration (optional)
12# Set these if you have a local embedder matching your Pinecone vectors' dimension
13# Common options: ("ollama", "nomic-embed-text") for 768d, ("openai", "text-embedding-3-small") for 1536d
14EMBEDDER_PROVIDER = "ollama" # "ollama", "termite", or "openai"
15EMBEDDER_MODEL = "nomic-embed-text" # Model nameRunning the Migration
export PINECONE_API_KEY="your-key-here"
python main.pyHow It Works
1. Initialize Clients
1def main():
2 load_dotenv()
3
4 # --- Initialize Pinecone ---
5 pinecone_api_key = os.getenv("PINECONE_API_KEY")
6 if not pinecone_api_key:
7 print("FATAL: PINECONE_API_KEY not found in environment.", file=sys.stderr)
8 sys.exit(1)
9
10 pc = Pinecone(api_key=pinecone_api_key)
11 pinecone_index = pc.Index(PINECONE_INDEX_NAME)
12 print(f"Connected to Pinecone index '{PINECONE_INDEX_NAME}'.")
13
14 # --- Initialize Antfly ---
15 client = AntflyClient(base_url=ANTFLY_BASE_URL)
16 try:
17 print(f"Connecting to Antfly at {ANTFLY_BASE_URL}...")
18 client.list_tables()
19 print("Connected to Antfly.")
20 except Exception as e:
21 print(f"Could not connect to Antfly: {e}")
22 print("Ensure the Antfly server is running.")
23 sys.exit(1)2. Create Table
Tables are schemaless - no schema definition needed:
1 # --- Create Antfly table ---
2 # Tables are schemaless - no schema definition required
3 try:
4 client.drop_table(TABLE_NAME)
5 print(f"Deleted existing table '{TABLE_NAME}'")
6 except AntflyException:
7 pass
8
9 try:
10 client.create_table(name=TABLE_NAME)
11 print(f"Created table '{TABLE_NAME}'")
12 except Exception as e:
13 print(f"Error creating table: {e}")
14 sys.exit(1)
15
16 print("Waiting for table shards to initialize...")
17 time.sleep(5)3. Create Vector Index
The index is created before inserting data so Antfly knows where to store embeddings:
1 # --- Create vector index BEFORE inserting data ---
2 # This tells Antfly where to store pre-computed embeddings
3 try:
4 print(f"\nCreating vector index '{INDEX_NAME}' with dimension {dimension}...")
5 index_config = {
6 "name": INDEX_NAME,
7 "type": "aknn_v0",
8 "dimension": dimension,
9 "template": TEMPLATE,
10 "embedder": {"provider": EMBEDDER_PROVIDER, "model": EMBEDDER_MODEL}
11 }
12 print(f" -> Using embedder: {EMBEDDER_PROVIDER}/{EMBEDDER_MODEL}")
13
14 resp = httpx.post(
15 f"{ANTFLY_BASE_URL}/tables/{TABLE_NAME}/indexes/{INDEX_NAME}",
16 json=index_config,
17 timeout=30.0
18 )
19 if resp.status_code == 200:
20 print("Index created successfully.")
21 else:
22 print(f"Index creation response: {resp.status_code} - {resp.text}")
23 except Exception as e:
24 print(f"Error creating index: {e}")
254. 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:
1 def fetch_all_vectors(pc_index, namespace: str):
2 """
3 Fetch all vectors from a Pinecone namespace.
4
5 Uses a zero-vector query with high top_k - a common pattern
6 since Pinecone doesn't have a direct "list all" API.
7 """
8 print(f"Fetching vectors from namespace: '{namespace}'...")
9
10 stats = pc_index.describe_index_stats()
11 dim = stats['dimension']
12
13 # Query with zero vector to get all vectors
14 query_response = pc_index.query(
15 namespace=namespace,
16 vector=[0.0] * dim,
17 top_k=1000,
18 include_values=True,
19 include_metadata=True
20 )
21
22 vectors = query_response.get('matches', [])
23 if len(vectors) == 1000:
24 print(" WARNING: Hit 1000 limit - may have more vectors.", file=sys.stderr)
255. Upsert to Antfly
Vectors are batch-upserted with the _embeddings field:
1 # --- Upsert into Antfly ---
2 for namespace, items in all_vectors.items():
3 try:
4 print(f"Upserting {len(items)} vectors for namespace '{namespace}'...")
5
6 inserts = {}
7 for item in items:
8 key = item['id']
9 properties = {k: v for k, v in item.items() if k != 'id'}
10
11 # Use _embeddings format for pre-computed embeddings
12 # Format: {"_embeddings": {"<index_name>": [vector values]}}
13 if 'values' in properties:
14 embedding_vector = properties.pop('values')
15 properties['_embeddings'] = {INDEX_NAME: embedding_vector}
16
17 inserts[key] = properties
18
19 resp = httpx.post(
20 f"{ANTFLY_BASE_URL}/tables/{TABLE_NAME}/batch",
21 json={"inserts": inserts},
22 timeout=60.0
23 )
24 if resp.status_code in (200, 201):
25 print(f" -> Successfully upserted {len(items)} vectors.")6. Verify
1 # --- Verify migration ---
2 print("\n=== Verifying Migration ===")
3
4 try:
5 resp = httpx.post(
6 f"{ANTFLY_BASE_URL}/tables/{TABLE_NAME}/query",
7 json={"limit": 200},
8 timeout=30.0
9 )
10 if resp.status_code == 200:
11 data = resp.json()
12 hits = data.get('responses', [{}])[0].get('hits', {}).get('hits', [])
13 print(f"Total records in Antfly: {len(hits)}")
14
15 if hits:
16 print("\nSample records:")
17 for hit in hits[:3]:
18 key = hit.get('_id', 'unknown')
19 source = hit.get('_source', {})
20 text = source.get('text', '')[:80] if source else ''
21 print(f" - {key}: {text}...")
22 except Exception as e:
23 print(f"Verification failed: {e}")
24
25 # --- Test semantic search ---Troubleshooting
"No matching embedder"
Install the matching model. For Ollama:
ollama pull nomic-embed-text"Hit 1000 limit"
Increase top_k in fetch_all_vectors() or implement pagination for larger datasets.