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.txt

Embedding 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#

ModelDimensionProvider
nomic-embed-text768Ollama
mxbai-embed-large1024Ollama
text-embedding-3-small1536OpenAI
all-minilm384Ollama

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 _embeddings field

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:

15 lines
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 name

Running the Migration#

export PINECONE_API_KEY="your-key-here"
python main.py

How It Works#

1. Initialize Clients#

23 lines
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:

17 lines
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:

27 lines
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}")
25

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:

46 lines
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)
25

5. Upsert to Antfly#

Vectors are batch-upserted with the _embeddings field:

34 lines
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#

50 lines
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.