miararoy / pinecone-client

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool


Pinecone Client V3

A python client for Pinecone. For more information, see the docs at https://www.pinecone.io/docs/

Pinecone Client V3 is based on pre-compiled Rust code and gRPC, aimed at improving performance and stability. Using native gRPC transport, client V3 is able to achieve a 2x-3x speedup for vector upsert over the previous Rest-based client versions, as well as a 10-20% speedup for vector query latency. As the client installation is fully self-contained, it does not require any additional dependencies (e.g. grpcio), making it easier to install and use in any python environment.

⚠️ Warning

This is a public preview ("Beta") version. Please test it thoroughly before using it in production.
Some functionalities are not backwards compatible with the previous versions of the client, please refer to the cahangelog for more details.

Installation

Install public preview version from pip:

pip3 install pinecone-client==3.0.0rc2

Building from source and contributing

See CONTRIBUTING.md

Migrating from pinecone client V2

If you are migrating from pinecone client V2, here is the most minimal code change required to upgrade to V3:

# Pinecone client V2
import pinecone
pinecone.init() # One time init
...
index = pinecone.Index("example-index")
index.upsert(...)

# Pinecone client V3
from pinecone import Client
pinecone = Client() # This is now a `Client` instance
...
# Unchanged!
index = pinecone.Index("example-index")
index.upsert(...)

(For more API changes see the CHANGELOG.md)

Usage

Control plane opeations

Creating a Client instance

The Client is the main entry point for control operations like creating, deleting and configuring pinecone indexes.
Initializing a Client requires your Pinecone API key and a region, which can be passed as either environment variables or as parameters to the Client constructor.

import os
from pinecone import Client

# Initialize a client using environment variables
os.environ['PINECONE_API_KEY'] = 'YOUR_API_KEY'
os.environ['PINECONE_REGION'] = 'us-west1-gcp'
client = Client()

# Initialize a client using parameters
client = Client(api_key = 'YOUR_API_KEY', region = 'us-west1-gcp')

Creating an index

The following example creates an index without a metadata configuration. By default, Pinecone indexes all metadata.

from pinecone import Client

client = Client(api_key="YOUR_API_KEY", region="us-west1-gcp")

client.create_index("example-index", dimension=1024)

The following example creates an index that only indexes the "color" metadata field. Queries against this index cannot filter based on any other metadata field.

metadata_config = {
    "indexed": ["color"]
}

client.create_index("example-index-2", dimension=1024,
                      metadata_config=metadata_config)

Listing all indexes

The following example returns all indexes in your project.

active_indexes = client.list_indexes()

Getting index configuration

The following example returns information about the index example-index.

index_description = client.describe_index("example-index")

Deleting an index

The following example deletes example-index.

client.delete_index("example-index")

Scaling an existing index number of replicas

The following example changes the number of replicas for example-index.

new_number_of_replicas = 4
client.scale_index("example-index", replicas=new_number_of_replicas)

Data plane operations

Creating an Index instance

The index object is the entry point for data operations like upserting, querying and deleting vectors.

from pinecone import Client
client = Client(api_key="YOUR_API_KEY", region="us-west1-gcp")
index = client.get_index("example-index")

# Backwards compatibility
index = client.Index("example-index")

Printing index statistics

The following example returns statistics about the index example-index.

from pinecone import Client

client = Client(api_key="YOUR_API_KEY", region="us-west1-gcp")
index = client.Index("example-index")

print(index.describe_index_stats())

Upserting vectors

The following example upserts vectors to example-index.

from pinecone import Client, Vector, SparseValues
client = Client(api_key="YOUR_API_KEY", region="us-west1-gcp")
index = client.get_index("example-index")

upsert_response = index.upsert(
    vectors=[
        ("vec1", [0.1, 0.2, 0.3, 0.4], {"genre": "drama"}),
        ("vec2", [0.2, 0.3, 0.4, 0.5], {"genre": "action"}),
    ],
    namespace="example-namespace"
)

# Mixing different vector representations is allowed
upsert_response = index.upsert(
    vectors=[
        # Tuples 
        ("vec1", [0.1, 0.2, 0.3, 0.4]),
        ("vec2", [0.2, 0.3, 0.4, 0.5], {"genre": "action"}),
        # Vector objects
        Vector(id='id1', values=[1.0, 2.0, 3.0], metadata={'key': 'value'}),
        Vector(id='id3', values=[1.0, 2.0, 3.0], sparse_values=SparseValues(indices=[1, 2], values=[0.2, 0.4])),
        # Dictionaries
        {'id': 'id1', 'values': [1.0, 2.0, 3.0], 'metadata': {'key': 'value'}},
        {'id': 'id2', 'values': [1.0, 2.0, 3.0], 'sparse_values': {'indices': [1, 2], 'values': [0.2, 0.4]}},
    ],
    namespace="example-namespace"
)

Quering an index by a new unseen vector

The following example queries the index example-index with metadata filtering.

query_response = index.query(
    values=[1.0, 5.3, 8.9, 0.5], # values of a query vector
    sparse_values = None, # optional sparse values of the query vector
    top_k=10,
    namespace="example-namespace",
    include_values=True,
    include_metadata=True,
    filter={
        "genre": {"$in": ["comedy", "documentary", "drama"]}
    }
)

Quering an index by an existing vector ID

The following example queries the index example-index for the top_k=10 nearest neighbors of the vector with ID vec1.

query_response = index.query(
    id="vec1",
    top_k=10,
    namespace="example-namespace",
    include_values=True,
    include_metadata=True,
)

Deleting vectors

# Delete vectors by IDs 
index.delete(ids=["vec1", "vec2"], namespace="example-namespace")

# Delete vectors by metadata filters
index.delete(filter={"genre": {"$in": ["comedy", "documentary", "drama"]}}, namespace="example-namespace")

# Delete all vectors in a given namespace (use namespace="" to delete all vectors in the DEFAULT namespace)
index.delete_all(namespace="example-namespace")

Fetching vectors by ids

The following example fetches vectors by ID without querying for nearest neighbors.

fetch_response = index.fetch(ids=["vec1", "vec2"], namespace="example-namespace")

Update vectors

The following example updates vectors by ID.

update_response = index.update(
    id="vec1",
    values=[0.1, 0.2, 0.3, 0.4],
    set_metadata={"genre": "drama"},
    namespace="example-namespace"
)

Performance tuning for upsering large datasets

To upsert an entire dataset of vectors, we recommend using concurrent batched upsert requests. The following example shows how to do this using the asyncio library:

import asyncio
from pinecone import Client, Vector

def chunker(seq, batch_size):
    return (seq[pos:pos + batch_size] for pos in range(0, len(seq), batch_size))

async def async_upload(index, vectors, batch_size, max_concurrent=50):
    sem = asyncio.Semaphore(max_concurrent)
    async def send_batch(batch):
        async with sem:
            return await index.upsert(vectors=batch, async_req=True)
    
    await asyncio.gather(*[send_batch(chunk) for chunk in chunker(vectors, batch_size=batch_size)]) 

# To use it:
client = Client()
index = client.get_index("example-index")
asyncio.run(async_upload(index, vectors, batch_size=100))

# In a jypter notebook, asyncio.run() is not supported. Instead, use
await async_upload(index, vectors, batch_size=100)  

TODO: Decide if we want to suggest this longer, more verbose version, which includes a progress bar and return type:

from tqdm.asyncio import tqdm
import asyncio
from pinecone import UpsertResponse, Client, Vector

def chunker(seq, batch_size):
    return (seq[pos:pos + batch_size] for pos in range(0, len(seq), batch_size))

async def async_upload(index, vectors, batch_size, max_concurrent=50):
    sem = asyncio.Semaphore(max_concurrent)
    async def send_batch(batch):
        async with sem:
            return await index.upsert(vectors=batch, async_req=True)

    tasks = [send_batch(chunk) for chunk in chunker(vectors, batch_size=batch_size)]
    pbar = tqdm(total=len(vectors), desc="upserted vectors")
    total_upserted_count = 0
    for task in asyncio.as_completed(tasks):
        res = await task
        total_upserted_count += res.upserted_count
        pbar.update(res.upserted_count)
    return UpsertResponse(upserted_count=total_upserted_count)

# To use it:
client = Client()
index = client.get_index("example-index")
vectors = [Vector(id=f"vec{i}", values=[1.0, 2.0, 3.0]) for i in range(1000)]
res = asyncio.run(async_upload(index, vectors, batch_size=100))

# In a jypter notebook, asyncio.run() is not supported. Instead, use
res = await async_upload(index, vectors, batch_size=100)  

Limitations

Code completion and type hints

Due to limitations with the underlying pyo3 library, code completion and type hints are not available in some IDEs, or might require additional configuration.

  • Jupyter notebooks: Should work out of the box
  • VSCode: Change the languageServer setting to jedi
  • PyCharm: For the moment, all fucntion signatures would show (*args, **kwargs). We are working on a solution ASAP. (Function docstrings would still show full arguments and type hints)

About


Languages

Language:Rust 60.7%Language:Python 38.7%Language:Makefile 0.6%