diff --git a/codegen/apis b/codegen/apis index be7f2b2f3..103cc9d89 160000 --- a/codegen/apis +++ b/codegen/apis @@ -1 +1 @@ -Subproject commit be7f2b2f3f501661431790398ed9b39263ee9959 +Subproject commit 103cc9d8937de2e21c574d2868446aa141668ee0 diff --git a/pinecone/core/grpc/protos/db_data_2025_10_pb2.py b/pinecone/core/grpc/protos/db_data_2025_10_pb2.py index 83094d3dd..3f34d8cfb 100644 --- a/pinecone/core/grpc/protos/db_data_2025_10_pb2.py +++ b/pinecone/core/grpc/protos/db_data_2025_10_pb2.py @@ -27,7 +27,7 @@ from google.api import field_behavior_pb2 as google_dot_api_dot_field__behavior__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x64\x62_data_2025-10.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1cgoogle/api/annotations.proto\x1a\x1fgoogle/api/field_behavior.proto\"J\n\x0cSparseValues\x12\x1d\n\x07indices\x18\x01 \x03(\rB\x03\xe0\x41\x02R\x07indices\x12\x1b\n\x06values\x18\x02 \x03(\x02\x42\x03\xe0\x41\x02R\x06values\"\x9e\x01\n\x06Vector\x12\x13\n\x02id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x02id\x12\x16\n\x06values\x18\x02 \x03(\x02R\x06values\x12\x32\n\rsparse_values\x18\x04 \x01(\x0b\x32\r.SparseValuesR\x0csparseValues\x12\x33\n\x08metadata\x18\x03 \x01(\x0b\x32\x17.google.protobuf.StructR\x08metadata\"\xba\x01\n\x0cScoredVector\x12\x13\n\x02id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x02id\x12\x14\n\x05score\x18\x02 \x01(\x02R\x05score\x12\x16\n\x06values\x18\x03 \x03(\x02R\x06values\x12\x32\n\rsparse_values\x18\x05 \x01(\x0b\x32\r.SparseValuesR\x0csparseValues\x12\x33\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.StructR\x08metadata\"\xa1\x01\n\x0cRequestUnion\x12(\n\x06upsert\x18\x01 \x01(\x0b\x32\x0e.UpsertRequestH\x00R\x06upsert\x12(\n\x06\x64\x65lete\x18\x02 \x01(\x0b\x32\x0e.DeleteRequestH\x00R\x06\x64\x65lete\x12(\n\x06update\x18\x03 \x01(\x0b\x32\x0e.UpdateRequestH\x00R\x06updateB\x13\n\x11RequestUnionInner\"U\n\rUpsertRequest\x12&\n\x07vectors\x18\x01 \x03(\x0b\x32\x07.VectorB\x03\xe0\x41\x02R\x07vectors\x12\x1c\n\tnamespace\x18\x02 \x01(\tR\tnamespace\"7\n\x0eUpsertResponse\x12%\n\x0eupserted_count\x18\x01 \x01(\rR\rupsertedCount\"\x8f\x01\n\rDeleteRequest\x12\x10\n\x03ids\x18\x01 \x03(\tR\x03ids\x12\x1d\n\ndelete_all\x18\x02 \x01(\x08R\tdeleteAll\x12\x1c\n\tnamespace\x18\x03 \x01(\tR\tnamespace\x12/\n\x06\x66ilter\x18\x04 \x01(\x0b\x32\x17.google.protobuf.StructR\x06\x66ilter\"\x10\n\x0e\x44\x65leteResponse\"C\n\x0c\x46\x65tchRequest\x12\x15\n\x03ids\x18\x01 \x03(\tB\x03\xe0\x41\x02R\x03ids\x12\x1c\n\tnamespace\x18\x02 \x01(\tR\tnamespace\"\xe1\x01\n\x16\x46\x65tchByMetadataRequest\x12\x1c\n\tnamespace\x18\x01 \x01(\tR\tnamespace\x12\x34\n\x06\x66ilter\x18\x02 \x01(\x0b\x32\x17.google.protobuf.StructH\x00R\x06\x66ilter\x88\x01\x01\x12\x19\n\x05limit\x18\x03 \x01(\rH\x01R\x05limit\x88\x01\x01\x12.\n\x10pagination_token\x18\x04 \x01(\tH\x02R\x0fpaginationToken\x88\x01\x01\x42\t\n\x07_filterB\x08\n\x06_limitB\x13\n\x11_pagination_token\"\xab\x02\n\x17\x46\x65tchByMetadataResponse\x12?\n\x07vectors\x18\x01 \x03(\x0b\x32%.FetchByMetadataResponse.VectorsEntryR\x07vectors\x12\x1c\n\tnamespace\x18\x02 \x01(\tR\tnamespace\x12!\n\x05usage\x18\x03 \x01(\x0b\x32\x06.UsageH\x00R\x05usage\x88\x01\x01\x12\x30\n\npagination\x18\x04 \x01(\x0b\x32\x0b.PaginationH\x01R\npagination\x88\x01\x01\x1a\x43\n\x0cVectorsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x1d\n\x05value\x18\x02 \x01(\x0b\x32\x07.VectorR\x05value:\x02\x38\x01\x42\x08\n\x06_usageB\r\n\x0b_pagination\"\xd6\x01\n\rFetchResponse\x12\x35\n\x07vectors\x18\x01 \x03(\x0b\x32\x1b.FetchResponse.VectorsEntryR\x07vectors\x12\x1c\n\tnamespace\x18\x02 \x01(\tR\tnamespace\x12!\n\x05usage\x18\x03 \x01(\x0b\x32\x06.UsageH\x00R\x05usage\x88\x01\x01\x1a\x43\n\x0cVectorsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x1d\n\x05value\x18\x02 \x01(\x0b\x32\x07.VectorR\x05value:\x02\x38\x01\x42\x08\n\x06_usage\"\xbd\x01\n\x0bListRequest\x12\x1b\n\x06prefix\x18\x01 \x01(\tH\x00R\x06prefix\x88\x01\x01\x12\x19\n\x05limit\x18\x02 \x01(\rH\x01R\x05limit\x88\x01\x01\x12.\n\x10pagination_token\x18\x03 \x01(\tH\x02R\x0fpaginationToken\x88\x01\x01\x12\x1c\n\tnamespace\x18\x04 \x01(\tR\tnamespaceB\t\n\x07_prefixB\x08\n\x06_limitB\x13\n\x11_pagination_token\" \n\nPagination\x12\x12\n\x04next\x18\x01 \x01(\tR\x04next\"\x1a\n\x08ListItem\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\"\xbf\x01\n\x0cListResponse\x12#\n\x07vectors\x18\x01 \x03(\x0b\x32\t.ListItemR\x07vectors\x12\x30\n\npagination\x18\x02 \x01(\x0b\x32\x0b.PaginationH\x00R\npagination\x88\x01\x01\x12\x1c\n\tnamespace\x18\x03 \x01(\tR\tnamespace\x12!\n\x05usage\x18\x04 \x01(\x0b\x32\x06.UsageH\x01R\x05usage\x88\x01\x01\x42\r\n\x0b_paginationB\x08\n\x06_usage\"\xbd\x01\n\x0bQueryVector\x12\x16\n\x06values\x18\x01 \x03(\x02R\x06values\x12\x32\n\rsparse_values\x18\x05 \x01(\x0b\x32\r.SparseValuesR\x0csparseValues\x12\x13\n\x05top_k\x18\x02 \x01(\rR\x04topK\x12\x1c\n\tnamespace\x18\x03 \x01(\tR\tnamespace\x12/\n\x06\x66ilter\x18\x04 \x01(\x0b\x32\x17.google.protobuf.StructR\x06\x66ilter\"\xd1\x02\n\x0cQueryRequest\x12\x1c\n\tnamespace\x18\x01 \x01(\tR\tnamespace\x12\x18\n\x05top_k\x18\x02 \x01(\rB\x03\xe0\x41\x02R\x04topK\x12/\n\x06\x66ilter\x18\x03 \x01(\x0b\x32\x17.google.protobuf.StructR\x06\x66ilter\x12%\n\x0einclude_values\x18\x04 \x01(\x08R\rincludeValues\x12)\n\x10include_metadata\x18\x05 \x01(\x08R\x0fincludeMetadata\x12*\n\x07queries\x18\x06 \x03(\x0b\x32\x0c.QueryVectorB\x02\x18\x01R\x07queries\x12\x16\n\x06vector\x18\x07 \x03(\x02R\x06vector\x12\x32\n\rsparse_vector\x18\t \x01(\x0b\x32\r.SparseValuesR\x0csparseVector\x12\x0e\n\x02id\x18\x08 \x01(\tR\x02id\"[\n\x12SingleQueryResults\x12\'\n\x07matches\x18\x01 \x03(\x0b\x32\r.ScoredVectorR\x07matches\x12\x1c\n\tnamespace\x18\x02 \x01(\tR\tnamespace\"\xb6\x01\n\rQueryResponse\x12\x31\n\x07results\x18\x01 \x03(\x0b\x32\x13.SingleQueryResultsB\x02\x18\x01R\x07results\x12\'\n\x07matches\x18\x02 \x03(\x0b\x32\r.ScoredVectorR\x07matches\x12\x1c\n\tnamespace\x18\x03 \x01(\tR\tnamespace\x12!\n\x05usage\x18\x04 \x01(\x0b\x32\x06.UsageH\x00R\x05usage\x88\x01\x01\x42\x08\n\x06_usage\":\n\x05Usage\x12\"\n\nread_units\x18\x01 \x01(\rH\x00R\treadUnits\x88\x01\x01\x42\r\n\x0b_read_units\"\xb0\x02\n\rUpdateRequest\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x16\n\x06values\x18\x02 \x03(\x02R\x06values\x12\x32\n\rsparse_values\x18\x05 \x01(\x0b\x32\r.SparseValuesR\x0csparseValues\x12:\n\x0cset_metadata\x18\x03 \x01(\x0b\x32\x17.google.protobuf.StructR\x0bsetMetadata\x12\x1c\n\tnamespace\x18\x04 \x01(\tR\tnamespace\x12\x34\n\x06\x66ilter\x18\x06 \x01(\x0b\x32\x17.google.protobuf.StructH\x00R\x06\x66ilter\x88\x01\x01\x12\x1c\n\x07\x64ry_run\x18\x07 \x01(\x08H\x01R\x06\x64ryRun\x88\x01\x01\x42\t\n\x07_filterB\n\n\x08_dry_run\"R\n\x0eUpdateResponse\x12,\n\x0fmatched_records\x18\x01 \x01(\x05H\x00R\x0ematchedRecords\x88\x01\x01\x42\x12\n\x10_matched_records\"L\n\x19\x44\x65scribeIndexStatsRequest\x12/\n\x06\x66ilter\x18\x01 \x01(\x0b\x32\x17.google.protobuf.StructR\x06\x66ilter\"5\n\x10NamespaceSummary\x12!\n\x0cvector_count\x18\x01 \x01(\rR\x0bvectorCount\"\xa9\x01\n\x15ListNamespacesRequest\x12.\n\x10pagination_token\x18\x01 \x01(\tH\x00R\x0fpaginationToken\x88\x01\x01\x12\x19\n\x05limit\x18\x02 \x01(\rH\x01R\x05limit\x88\x01\x01\x12\x1b\n\x06prefix\x18\x03 \x01(\tH\x02R\x06prefix\x88\x01\x01\x42\x13\n\x11_pagination_tokenB\x08\n\x06_limitB\t\n\x07_prefix\"\xb1\x01\n\x16ListNamespacesResponse\x12\x35\n\nnamespaces\x18\x01 \x03(\x0b\x32\x15.NamespaceDescriptionR\nnamespaces\x12\x30\n\npagination\x18\x02 \x01(\x0b\x32\x0b.PaginationH\x00R\npagination\x88\x01\x01\x12\x1f\n\x0btotal_count\x18\x03 \x01(\x05R\ntotalCountB\r\n\x0b_pagination\"8\n\x18\x44\x65scribeNamespaceRequest\x12\x1c\n\tnamespace\x18\x01 \x01(\tR\tnamespace\"e\n\x16\x43reateNamespaceRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12,\n\x06schema\x18\x02 \x01(\x0b\x32\x0f.MetadataSchemaH\x00R\x06schema\x88\x01\x01\x42\t\n\x07_schema\"\'\n\rIndexedFields\x12\x16\n\x06\x66ields\x18\x01 \x03(\tR\x06\x66ields\"\xd5\x01\n\x14NamespaceDescription\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12!\n\x0crecord_count\x18\x02 \x01(\x04R\x0brecordCount\x12,\n\x06schema\x18\x03 \x01(\x0b\x32\x0f.MetadataSchemaH\x00R\x06schema\x88\x01\x01\x12:\n\x0eindexed_fields\x18\x04 \x01(\x0b\x32\x0e.IndexedFieldsH\x01R\rindexedFields\x88\x01\x01\x42\t\n\x07_schemaB\x11\n\x0f_indexed_fields\"6\n\x16\x44\x65leteNamespaceRequest\x12\x1c\n\tnamespace\x18\x01 \x01(\tR\tnamespace\"\xa6\x04\n\x1a\x44\x65scribeIndexStatsResponse\x12K\n\nnamespaces\x18\x01 \x03(\x0b\x32+.DescribeIndexStatsResponse.NamespacesEntryR\nnamespaces\x12!\n\tdimension\x18\x02 \x01(\rH\x00R\tdimension\x88\x01\x01\x12%\n\x0eindex_fullness\x18\x03 \x01(\x02R\rindexFullness\x12,\n\x12total_vector_count\x18\x04 \x01(\rR\x10totalVectorCount\x12\x1b\n\x06metric\x18\x05 \x01(\tH\x01R\x06metric\x88\x01\x01\x12$\n\x0bvector_type\x18\x06 \x01(\tH\x02R\nvectorType\x88\x01\x01\x12,\n\x0fmemory_fullness\x18\x07 \x01(\x02H\x03R\x0ememoryFullness\x88\x01\x01\x12.\n\x10storage_fullness\x18\x08 \x01(\x02H\x04R\x0fstorageFullness\x88\x01\x01\x1aP\n\x0fNamespacesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\'\n\x05value\x18\x02 \x01(\x0b\x32\x11.NamespaceSummaryR\x05value:\x02\x38\x01\x42\x0c\n\n_dimensionB\t\n\x07_metricB\x0e\n\x0c_vector_typeB\x12\n\x10_memory_fullnessB\x13\n\x11_storage_fullness\"9\n\x17MetadataFieldProperties\x12\x1e\n\nfilterable\x18\x01 \x01(\x08R\nfilterable\"\x9a\x01\n\x0eMetadataSchema\x12\x33\n\x06\x66ields\x18\x01 \x03(\x0b\x32\x1b.MetadataSchema.FieldsEntryR\x06\x66ields\x1aS\n\x0b\x46ieldsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x18.MetadataFieldPropertiesR\x05value:\x02\x38\x01\x32\x9c\x08\n\rVectorService\x12\x45\n\x06Upsert\x12\x0e.UpsertRequest\x1a\x0f.UpsertResponse\"\x1a\x82\xd3\xe4\x93\x02\x14\"\x0f/vectors/upsert:\x01*\x12X\n\x06\x44\x65lete\x12\x0e.DeleteRequest\x1a\x0f.DeleteResponse\"-\x82\xd3\xe4\x93\x02\'\"\x0f/vectors/delete:\x01*Z\x11*\x0f/vectors/delete\x12>\n\x05\x46\x65tch\x12\r.FetchRequest\x1a\x0e.FetchResponse\"\x16\x82\xd3\xe4\x93\x02\x10\x12\x0e/vectors/fetch\x12:\n\x04List\x12\x0c.ListRequest\x1a\r.ListResponse\"\x15\x82\xd3\xe4\x93\x02\x0f\x12\r/vectors/list\x12\x39\n\x05Query\x12\r.QueryRequest\x1a\x0e.QueryResponse\"\x11\x82\xd3\xe4\x93\x02\x0b\"\x06/query:\x01*\x12\x45\n\x06Update\x12\x0e.UpdateRequest\x1a\x0f.UpdateResponse\"\x1a\x82\xd3\xe4\x93\x02\x14\"\x0f/vectors/update:\x01*\x12\x88\x01\n\x12\x44\x65scribeIndexStats\x12\x1a.DescribeIndexStatsRequest\x1a\x1b.DescribeIndexStatsResponse\"9\x82\xd3\xe4\x93\x02\x33\"\x15/describe_index_stats:\x01*Z\x17\x12\x15/describe_index_stats\x12V\n\x0eListNamespaces\x12\x16.ListNamespacesRequest\x1a\x17.ListNamespacesResponse\"\x13\x82\xd3\xe4\x93\x02\r\x12\x0b/namespaces\x12\x66\n\x11\x44\x65scribeNamespace\x12\x19.DescribeNamespaceRequest\x1a\x15.NamespaceDescription\"\x1f\x82\xd3\xe4\x93\x02\x19\x12\x17/namespaces/{namespace}\x12\\\n\x0f\x44\x65leteNamespace\x12\x17.DeleteNamespaceRequest\x1a\x0f.DeleteResponse\"\x1f\x82\xd3\xe4\x93\x02\x19*\x17/namespaces/{namespace}\x12V\n\x0f\x43reateNamespace\x12\x17.CreateNamespaceRequest\x1a\x15.NamespaceDescription\"\x13\x82\xd3\xe4\x93\x02\r\"\x0b/namespaces\x12k\n\x0f\x46\x65tchByMetadata\x12\x17.FetchByMetadataRequest\x1a\x18.FetchByMetadataResponse\"%\x82\xd3\xe4\x93\x02\x1f\"\x1a/vectors/fetch_by_metadata:\x01*BS\n\x11io.pinecone.protoP\x01Z\n\x05\x46\x65tch\x12\r.FetchRequest\x1a\x0e.FetchResponse\"\x16\x82\xd3\xe4\x93\x02\x10\x12\x0e/vectors/fetch\x12:\n\x04List\x12\x0c.ListRequest\x1a\r.ListResponse\"\x15\x82\xd3\xe4\x93\x02\x0f\x12\r/vectors/list\x12\x39\n\x05Query\x12\r.QueryRequest\x1a\x0e.QueryResponse\"\x11\x82\xd3\xe4\x93\x02\x0b\"\x06/query:\x01*\x12\x45\n\x06Update\x12\x0e.UpdateRequest\x1a\x0f.UpdateResponse\"\x1a\x82\xd3\xe4\x93\x02\x14\"\x0f/vectors/update:\x01*\x12\x88\x01\n\x12\x44\x65scribeIndexStats\x12\x1a.DescribeIndexStatsRequest\x1a\x1b.DescribeIndexStatsResponse\"9\x82\xd3\xe4\x93\x02\x33\"\x15/describe_index_stats:\x01*Z\x17\x12\x15/describe_index_stats\x12V\n\x0eListNamespaces\x12\x16.ListNamespacesRequest\x1a\x17.ListNamespacesResponse\"\x13\x82\xd3\xe4\x93\x02\r\x12\x0b/namespaces\x12\x66\n\x11\x44\x65scribeNamespace\x12\x19.DescribeNamespaceRequest\x1a\x15.NamespaceDescription\"\x1f\x82\xd3\xe4\x93\x02\x19\x12\x17/namespaces/{namespace}\x12\\\n\x0f\x44\x65leteNamespace\x12\x17.DeleteNamespaceRequest\x1a\x0f.DeleteResponse\"\x1f\x82\xd3\xe4\x93\x02\x19*\x17/namespaces/{namespace}\x12V\n\x0f\x43reateNamespace\x12\x17.CreateNamespaceRequest\x1a\x15.NamespaceDescription\"\x13\x82\xd3\xe4\x93\x02\r\"\x0b/namespaces\x12k\n\x0f\x46\x65tchByMetadata\x12\x17.FetchByMetadataRequest\x1a\x18.FetchByMetadataResponse\"%\x82\xd3\xe4\x93\x02\x1f\"\x1a/vectors/fetch_by_metadata:\x01*BS\n\x11io.pinecone.protoP\x01Z None: ... + scan_factor: float + max_candidates: int + def __init__(self, namespace: _Optional[str] = ..., top_k: _Optional[int] = ..., filter: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ..., include_values: _Optional[bool] = ..., include_metadata: _Optional[bool] = ..., queries: _Optional[_Iterable[_Union[QueryVector, _Mapping]]] = ..., vector: _Optional[_Iterable[float]] = ..., sparse_vector: _Optional[_Union[SparseValues, _Mapping]] = ..., id: _Optional[str] = ..., scan_factor: _Optional[float] = ..., max_candidates: _Optional[int] = ...) -> None: ... class SingleQueryResults(_message.Message): __slots__ = () diff --git a/pinecone/core/openapi/db_data/model/query_request.py b/pinecone/core/openapi/db_data/model/query_request.py index f0ed26fea..31e5fa3d3 100644 --- a/pinecone/core/openapi/db_data/model/query_request.py +++ b/pinecone/core/openapi/db_data/model/query_request.py @@ -80,6 +80,8 @@ class QueryRequest(ModelNormal): ("queries",): {}, ("vector",): {}, ("id",): {"max_length": 512}, + ("scan_factor",): {"inclusive_maximum": 4.0, "inclusive_minimum": 0.5}, + ("max_candidates",): {"inclusive_maximum": 100000, "inclusive_minimum": 1}, } @cached_class_property @@ -114,6 +116,8 @@ def openapi_types(cls): "vector": ([float],), # noqa: E501 "sparse_vector": (SparseValues,), # noqa: E501 "id": (str,), # noqa: E501 + "scan_factor": (float,), # noqa: E501 + "max_candidates": (int,), # noqa: E501 } @cached_class_property @@ -130,6 +134,8 @@ def discriminator(cls): "vector": "vector", # noqa: E501 "sparse_vector": "sparseVector", # noqa: E501 "id": "id", # noqa: E501 + "scan_factor": "scanFactor", # noqa: E501 + "max_candidates": "maxCandidates", # noqa: E501 } read_only_vars: Set[str] = set([]) @@ -194,6 +200,8 @@ def _from_openapi_data(cls: Type[T], top_k, *args, **kwargs) -> T: # noqa: E501 vector ([float]): The query vector. This should be the same length as the dimension of the index being queried. Each `query` request can contain only one of the parameters `id` or `vector`. [optional] # noqa: E501 sparse_vector (SparseValues): [optional] # noqa: E501 id (str): The unique ID of the vector to be used as a query vector. Each request can contain either the `vector` or `id` parameter. [optional] # noqa: E501 + scan_factor (float): An optimization parameter for IVF dense indexes in dedicated read node indexes. It adjusts how much of the index is scanned to find vector candidates. Range: 0.5 – 4 (default). Keep the default (4.0) for the best search results. If query latency is too high, try lowering this value incrementally (minimum 0.5) to speed up the search at the cost of slightly lower accuracy. This parameter is only supported for dedicated (DRN) dense indexes. [optional] # noqa: E501 + max_candidates (int): An optimization parameter that controls the maximum number of candidate dense vectors to rerank. Reranking computes exact distances to improve recall but increases query latency. Range: top_k – 100000. Keep the default for a balance of recall and latency. Increase this value if recall is too low, or decrease it to reduce latency at the cost of accuracy. This parameter is only supported for dedicated (DRN) dense indexes. [optional] # noqa: E501 """ _enforce_allowed_values = kwargs.pop("_enforce_allowed_values", False) @@ -295,6 +303,8 @@ def __init__(self, top_k, *args, **kwargs) -> None: # noqa: E501 vector ([float]): The query vector. This should be the same length as the dimension of the index being queried. Each `query` request can contain only one of the parameters `id` or `vector`. [optional] # noqa: E501 sparse_vector (SparseValues): [optional] # noqa: E501 id (str): The unique ID of the vector to be used as a query vector. Each request can contain either the `vector` or `id` parameter. [optional] # noqa: E501 + scan_factor (float): An optimization parameter for IVF dense indexes in dedicated read node indexes. It adjusts how much of the index is scanned to find vector candidates. Range: 0.5 – 4 (default). Keep the default (4.0) for the best search results. If query latency is too high, try lowering this value incrementally (minimum 0.5) to speed up the search at the cost of slightly lower accuracy. This parameter is only supported for dedicated (DRN) dense indexes. [optional] # noqa: E501 + max_candidates (int): An optimization parameter that controls the maximum number of candidate dense vectors to rerank. Reranking computes exact distances to improve recall but increases query latency. Range: top_k – 100000. Keep the default for a balance of recall and latency. Increase this value if recall is too low, or decrease it to reduce latency at the cost of accuracy. This parameter is only supported for dedicated (DRN) dense indexes. [optional] # noqa: E501 """ _enforce_allowed_values = kwargs.pop("_enforce_allowed_values", True) diff --git a/pinecone/db_data/index.py b/pinecone/db_data/index.py index e33371819..c15cc2a0e 100644 --- a/pinecone/db_data/index.py +++ b/pinecone/db_data/index.py @@ -945,6 +945,8 @@ def query( include_values: bool | None = None, include_metadata: bool | None = None, sparse_vector: SparseValues | SparseVectorTypedDict | None = None, + scan_factor: float | None = None, + max_candidates: int | None = None, **kwargs, ) -> QueryResponse | ApplyResult: """Query a namespace using a query vector. @@ -970,6 +972,14 @@ def query( sparse_vector: Sparse values of the query vector. Expected to be either a SparseValues object or a dict of the form: ``{'indices': list[int], 'values': list[float]}``, where the lists each have the same length. [optional] + scan_factor: An optimization parameter for the IVF dense indexes in dedicated + read node indexes. It adjusts how much of the index is scanned to find + vector candidates. Range: 0.5 - 4 (default). This parameter is only + supported for dedicated (DRN) dense indexes. [optional] + max_candidates: An optimization parameter that controls the maximum number of + candidate dense vectors to rerank. Reranking computes exact distances to + improve recall but increases query latency. Range: top_k - 100000. This + parameter is only supported for dedicated (DRN) dense indexes. [optional] **kwargs: Additional keyword arguments for the API call. Returns: @@ -1033,6 +1043,8 @@ def query( include_values=include_values, include_metadata=include_metadata, sparse_vector=sparse_vector, + scan_factor=scan_factor, + max_candidates=max_candidates, **kwargs, ) @@ -1054,6 +1066,8 @@ def _query( include_values: bool | None = None, include_metadata: bool | None = None, sparse_vector: SparseValues | SparseVectorTypedDict | None = None, + scan_factor: float | None = None, + max_candidates: int | None = None, **kwargs, ) -> OpenAPIQueryResponse: if len(args) > 0: @@ -1073,6 +1087,8 @@ def _query( include_values=include_values, include_metadata=include_metadata, sparse_vector=sparse_vector, + scan_factor=scan_factor, + max_candidates=max_candidates, **kwargs, ) from typing import cast @@ -1092,6 +1108,8 @@ def query_namespaces( include_values: bool | None = None, include_metadata: bool | None = None, sparse_vector: SparseValues | SparseVectorTypedDict | None = None, + scan_factor: float | None = None, + max_candidates: int | None = None, **kwargs, ) -> QueryNamespacesResults: """Query multiple namespaces in parallel and combine the results. @@ -1116,6 +1134,14 @@ def query_namespaces( include_values: Boolean field indicating whether vector values should be included with results. Defaults to None. [optional] include_metadata: Boolean field indicating whether vector metadata should be included with results. Defaults to None. [optional] sparse_vector: If you are working with a dotproduct index, you can pass a sparse vector as part of your hybrid search. Defaults to None. [optional] + scan_factor: An optimization parameter for the IVF dense indexes in dedicated + read node indexes. It adjusts how much of the index is scanned to find + vector candidates. Range: 0.5 - 4 (default). This parameter is only + supported for dedicated (DRN) dense indexes. [optional] + max_candidates: An optimization parameter that controls the maximum number of + candidate dense vectors to rerank. Reranking computes exact distances to + improve recall but increases query latency. Range: top_k - 100000. This + parameter is only supported for dedicated (DRN) dense indexes. [optional] **kwargs: Additional keyword arguments for the API call. Returns: @@ -1171,6 +1197,8 @@ def query_namespaces( include_values=include_values, include_metadata=include_metadata, sparse_vector=sparse_vector, + scan_factor=scan_factor, + max_candidates=max_candidates, async_threadpool_executor=True, _preload_content=False, **kwargs, diff --git a/pinecone/db_data/index_asyncio.py b/pinecone/db_data/index_asyncio.py index 3016f031a..a7da5b30b 100644 --- a/pinecone/db_data/index_asyncio.py +++ b/pinecone/db_data/index_asyncio.py @@ -728,6 +728,8 @@ async def query( include_values: bool | None = None, include_metadata: bool | None = None, sparse_vector: (SparseValues | SparseVectorTypedDict) | None = None, + scan_factor: float | None = None, + max_candidates: int | None = None, **kwargs, ) -> QueryResponse: """ @@ -832,6 +834,14 @@ async def main(): sparse_vector: (Union[SparseValues, dict[str, Union[list[float], list[int]]]]): sparse values of the query vector. Expected to be either a SparseValues object or a dict of the form: {'indices': list[int], 'values': list[float]}, where the lists each have the same length. + scan_factor: An optimization parameter for the IVF dense indexes in dedicated + read node indexes. It adjusts how much of the index is scanned to find + vector candidates. Range: 0.5 - 4 (default). This parameter is only + supported for dedicated (DRN) dense indexes. [optional] + max_candidates: An optimization parameter that controls the maximum number of + candidate dense vectors to rerank. Reranking computes exact distances to + improve recall but increases query latency. Range: top_k - 100000. This + parameter is only supported for dedicated (DRN) dense indexes. [optional] Returns: QueryResponse object which contains the list of the closest vectors as ScoredVector objects, and namespace name. @@ -846,6 +856,8 @@ async def main(): include_values=include_values, include_metadata=include_metadata, sparse_vector=sparse_vector, + scan_factor=scan_factor, + max_candidates=max_candidates, **kwargs, ) return parse_query_response(response) @@ -861,6 +873,8 @@ async def _query( include_values: bool | None = None, include_metadata: bool | None = None, sparse_vector: (SparseValues | SparseVectorTypedDict) | None = None, + scan_factor: float | None = None, + max_candidates: int | None = None, **kwargs, ) -> OpenAPIQueryResponse: if len(args) > 0: @@ -877,6 +891,8 @@ async def _query( include_values=include_values, include_metadata=include_metadata, sparse_vector=sparse_vector, + scan_factor=scan_factor, + max_candidates=max_candidates, **kwargs, ) from typing import cast @@ -897,6 +913,8 @@ async def query_namespaces( include_metadata: bool | None = None, vector: list[float] | None = None, sparse_vector: (SparseValues | SparseVectorTypedDict) | None = None, + scan_factor: float | None = None, + max_candidates: int | None = None, **kwargs, ) -> QueryNamespacesResults: """The query_namespaces() method is used to make a query to multiple namespaces in parallel and combine the results into one result set. @@ -909,6 +927,14 @@ async def query_namespaces( include_values (Optional[bool], optional): Boolean field indicating whether vector values should be included with results. Defaults to None. include_metadata (Optional[bool], optional): Boolean field indicating whether vector metadata should be included with results. Defaults to None. sparse_vector (Optional[ Union[SparseValues, dict[str, Union[list[float], list[int]]]] ], optional): If you are working with a dotproduct index, you can pass a sparse vector as part of your hybrid search. Defaults to None. + scan_factor: An optimization parameter for the IVF dense indexes in dedicated + read node indexes. It adjusts how much of the index is scanned to find + vector candidates. Range: 0.5 - 4 (default). This parameter is only + supported for dedicated (DRN) dense indexes. [optional] + max_candidates: An optimization parameter that controls the maximum number of + candidate dense vectors to rerank. Reranking computes exact distances to + improve recall but increases query latency. Range: top_k - 100000. This + parameter is only supported for dedicated (DRN) dense indexes. [optional] Returns: QueryNamespacesResults: A QueryNamespacesResults object containing the combined results from all namespaces, as well as the combined usage cost in read units. @@ -965,6 +991,8 @@ async def main(): sparse_vector=sparse_vector, async_threadpool_executor=True, _preload_content=False, + scan_factor=scan_factor, + max_candidates=max_candidates, **kwargs, ) for ns in target_namespaces diff --git a/pinecone/db_data/request_factory.py b/pinecone/db_data/request_factory.py index c40c3e092..ba6c73a3a 100644 --- a/pinecone/db_data/request_factory.py +++ b/pinecone/db_data/request_factory.py @@ -56,6 +56,8 @@ def query_request( include_values: bool | None = None, include_metadata: bool | None = None, sparse_vector: SparseValues | SparseVectorTypedDict | None = None, + scan_factor: float | None = None, + max_candidates: int | None = None, **kwargs, ) -> QueryRequest: if vector is not None and id is not None: @@ -73,6 +75,8 @@ def query_request( ("include_values", include_values), ("include_metadata", include_metadata), ("sparse_vector", sparse_vector_normalized), + ("scan_factor", scan_factor), + ("max_candidates", max_candidates), ] ) diff --git a/pinecone/db_data/resources/asyncio/vector_asyncio.py b/pinecone/db_data/resources/asyncio/vector_asyncio.py index 92048331f..2813fa052 100644 --- a/pinecone/db_data/resources/asyncio/vector_asyncio.py +++ b/pinecone/db_data/resources/asyncio/vector_asyncio.py @@ -372,6 +372,8 @@ async def query( include_values: bool | None = None, include_metadata: bool | None = None, sparse_vector: (SparseValues | SparseVectorTypedDict) | None = None, + scan_factor: float | None = None, + max_candidates: int | None = None, **kwargs, ) -> QueryResponse: """Query the index. @@ -401,6 +403,14 @@ async def query( SparseValues object or a dict of the form {'indices': list[int], 'values': list[float]}, where the lists each have the same length. [optional] + scan_factor: An optimization parameter for the IVF dense indexes in dedicated + read node indexes. It adjusts how much of the index is scanned to find + vector candidates. Range: 0.5 - 4 (default). This parameters is only + supported for dedicated (DRN) dense indexes. [optional] + max_candidates: An optimization parameter that controls the maximum number of + candidate dense vectors to rerank. Reranking computes exact distances to + improve recall but increases query latency. Range: top_k - 100000. This + parameter is only supported for dedicated (DRN) dense indexes. [optional] **kwargs: Additional keyword arguments. Returns: @@ -423,6 +433,8 @@ async def query( include_values=include_values, include_metadata=include_metadata, sparse_vector=sparse_vector, + scan_factor=scan_factor, + max_candidates=max_candidates, **kwargs, ) # parse_query_response already returns QueryResponse @@ -439,6 +451,8 @@ async def _query( include_values: bool | None = None, include_metadata: bool | None = None, sparse_vector: (SparseValues | SparseVectorTypedDict) | None = None, + scan_factor: float | None = None, + max_candidates: int | None = None, **kwargs, ) -> OpenAPIQueryResponse: if len(args) > 0: @@ -455,6 +469,8 @@ async def _query( include_values=include_values, include_metadata=include_metadata, sparse_vector=sparse_vector, + scan_factor=scan_factor, + max_candidates=max_candidates, **kwargs, ) from typing import cast @@ -475,6 +491,8 @@ async def query_namespaces( include_metadata: bool | None = None, vector: list[float] | None = None, sparse_vector: (SparseValues | dict[str, list[float] | list[int]]) | None = None, + scan_factor: float | None = None, + max_candidates: int | None = None, **kwargs, ) -> QueryNamespacesResults: """Query across multiple namespaces. @@ -495,6 +513,14 @@ async def query_namespaces( include_metadata: Indicates whether metadata is included in the response. [optional] sparse_vector: Sparse values of the query vector. [optional] + scan_factor: An optimization parameter for the IVF dense indexes in dedicated + read node indexes. It adjusts how much of the index is scanned to find + vector candidates. Range: 0.5 - 4 (default). This parameters is only + supported for dedicated (DRN) dense indexes. [optional] + max_candidates: An optimization parameter that controls the maximum number of + candidate dense vectors to rerank. Reranking computes exact distances to + improve recall but increases query latency. Range: top_k - 100000. This + parameter is only supported for dedicated (DRN) dense indexes. [optional] **kwargs: Additional keyword arguments. Returns: @@ -530,6 +556,8 @@ async def query_namespaces( include_values=include_values, include_metadata=include_metadata, sparse_vector=sparse_vector, # type: ignore[arg-type] + scan_factor=scan_factor, + max_candidates=max_candidates, async_threadpool_executor=True, _preload_content=False, **kwargs, diff --git a/pinecone/db_data/resources/sync/vector.py b/pinecone/db_data/resources/sync/vector.py index 50b9a3038..c4f0ffbbe 100644 --- a/pinecone/db_data/resources/sync/vector.py +++ b/pinecone/db_data/resources/sync/vector.py @@ -416,6 +416,8 @@ def query( include_values: bool | None = None, include_metadata: bool | None = None, sparse_vector: (SparseValues | SparseVectorTypedDict) | None = None, + scan_factor: float | None = None, + max_candidates: int | None = None, **kwargs, ) -> QueryResponse | ApplyResult: """Query the index. @@ -445,6 +447,14 @@ def query( SparseValues object or a dict of the form {'indices': list[int], 'values': list[float]}, where the lists each have the same length. [optional] + scan_factor: An optimization parameter for the IVF dense indexes in dedicated + read node indexes. It adjusts how much of the index is scanned to find + vector candidates. Range: 0.5 - 4 (default). This parameters is only + supported for dedicated (DRN) dense indexes. [optional] + max_candidates: An optimization parameter that controls the maximum number of + candidate dense vectors to rerank. Reranking computes exact distances to + improve recall but increases query latency. Range: top_k - 100000. This + parameter is only supported for dedicated (DRN) dense indexes. [optional] **kwargs: Additional keyword arguments. Returns: @@ -467,6 +477,8 @@ def query( include_values=include_values, include_metadata=include_metadata, sparse_vector=sparse_vector, + scan_factor=scan_factor, + max_candidates=max_candidates, **kwargs, ) @@ -489,6 +501,8 @@ def _query( include_values: bool | None = None, include_metadata: bool | None = None, sparse_vector: (SparseValues | SparseVectorTypedDict) | None = None, + scan_factor: float | None = None, + max_candidates: int | None = None, **kwargs, ) -> OpenAPIQueryResponse: if len(args) > 0: @@ -508,6 +522,8 @@ def _query( include_values=include_values, include_metadata=include_metadata, sparse_vector=sparse_vector, + scan_factor=scan_factor, + max_candidates=max_candidates, **kwargs, ) from typing import cast @@ -526,6 +542,8 @@ def query_namespaces( include_values: bool | None = None, include_metadata: bool | None = None, sparse_vector: (SparseValues | SparseVectorTypedDict) | None = None, + scan_factor: float | None = None, + max_candidates: int | None = None, **kwargs, ) -> QueryNamespacesResults: """Query across multiple namespaces. @@ -546,6 +564,14 @@ def query_namespaces( include_metadata: Indicates whether metadata is included in the response. [optional] sparse_vector: Sparse values of the query vector. [optional] + scan_factor: An optimization parameter for the IVF dense indexes in dedicated + read node indexes. It adjusts how much of the index is scanned to find + vector candidates. Range: 0.5 - 4 (default). This parameters is only + supported for dedicated (DRN) dense indexes. [optional] + max_candidates: An optimization parameter that controls the maximum number of + candidate dense vectors to rerank. Reranking computes exact distances to + improve recall but increases query latency. Range: top_k - 100000. This + parameter is only supported for dedicated (DRN) dense indexes. [optional] **kwargs: Additional keyword arguments. Returns: @@ -581,6 +607,8 @@ def query_namespaces( include_values=include_values, include_metadata=include_metadata, sparse_vector=sparse_vector, + scan_factor=scan_factor, + max_candidates=max_candidates, async_threadpool_executor=True, _preload_content=False, **kwargs, diff --git a/pinecone/grpc/index_grpc.py b/pinecone/grpc/index_grpc.py index fd01e994f..a4a402c1c 100644 --- a/pinecone/grpc/index_grpc.py +++ b/pinecone/grpc/index_grpc.py @@ -501,6 +501,8 @@ def _query( include_values: bool | None = None, include_metadata: bool | None = None, sparse_vector: (SparseValues | GRPCSparseValues | SparseVectorTypedDict) | None = None, + scan_factor: float | None = None, + max_candidates: int | None = None, **kwargs, ) -> tuple[ProtoQueryResponse, dict[str, str] | None]: """ @@ -529,6 +531,8 @@ def _query( ("include_values", include_values), ("include_metadata", include_metadata), ("sparse_vector", sparse_vector), + ("scan_factor", scan_factor), + ("max_candidates", max_candidates), ] ) @@ -548,6 +552,8 @@ def query( include_values: bool | None = None, include_metadata: bool | None = None, sparse_vector: (SparseValues | GRPCSparseValues | SparseVectorTypedDict) | None = None, + scan_factor: float | None = None, + max_candidates: int | None = None, async_req: bool | None = False, **kwargs, ) -> "QueryResponse" | PineconeGrpcFuture: @@ -588,6 +594,13 @@ def query( sparse_vector: (Union[SparseValues, dict[str, Union[list[float], list[int]]]]): sparse values of the query vector. Expected to be either a SparseValues object or a dict of the form: {'indices': list[int], 'values': list[float]}, where the lists each have the same length. + scan_factor (float): An optimization parameter for IVF dense indexes in dedicated + read node indexes. Adjusts how much of the index is scanned to find + vector candidates. Range: 0.5 - 4 (default). Only supported for + dedicated (DRN) dense indexes. [optional] + max_candidates (int): An optimization parameter that controls the maximum number of + candidate dense vectors to rerank. Range: top_k - 100000. Only + supported for dedicated (DRN) dense indexes. [optional] Returns: QueryResponse object which contains the list of the closest vectors as ScoredVector objects, and namespace name. @@ -616,6 +629,8 @@ def query( ("include_values", include_values), ("include_metadata", include_metadata), ("sparse_vector", sparse_vector), + ("scan_factor", scan_factor), + ("max_candidates", max_candidates), ] ) @@ -637,6 +652,8 @@ def query( include_values=include_values, include_metadata=include_metadata, sparse_vector=sparse_vector, + scan_factor=scan_factor, + max_candidates=max_candidates, timeout=timeout, **kwargs, ) @@ -654,6 +671,8 @@ def query_namespaces( include_values: bool | None = None, include_metadata: bool | None = None, sparse_vector: (GRPCSparseValues | SparseVectorTypedDict) | None = None, + scan_factor: float | None = None, + max_candidates: int | None = None, **kwargs, ) -> QueryNamespacesResults: if namespaces is None or len(namespaces) == 0: @@ -675,6 +694,8 @@ def query_namespaces( include_values=include_values, include_metadata=include_metadata, sparse_vector=sparse_vector, + scan_factor=scan_factor, + max_candidates=max_candidates, **kwargs, ) for ns in target_namespaces diff --git a/pinecone/openapi_support/api_version.py b/pinecone/openapi_support/api_version.py index 426a6d99c..21ca4af73 100644 --- a/pinecone/openapi_support/api_version.py +++ b/pinecone/openapi_support/api_version.py @@ -2,4 +2,4 @@ # Do not edit this file manually. API_VERSION = "2025-10" -APIS_REPO_SHA = "c968f9da428c7cf3742db5a3d2802c0d1bd728e7" +APIS_REPO_SHA = "be7f2b2f3f501661431790398ed9b39263ee9959" diff --git a/tests/unit/data/test_request_factory.py b/tests/unit/data/test_request_factory.py index c88b6ed16..8334755f2 100644 --- a/tests/unit/data/test_request_factory.py +++ b/tests/unit/data/test_request_factory.py @@ -7,6 +7,7 @@ ) from pinecone.core.openapi.db_data.models import FetchByMetadataRequest +from pinecone.core.openapi.db_data.model.query_request import QueryRequest from pinecone import RerankModel from tests.fixtures import ( @@ -623,3 +624,37 @@ def test_update_request_without_dry_run_not_included(self): assert "dry_run" not in request._data_store # endregion + + +class TestQueryRequest: + def test_query_request_basic(self): + request = IndexRequestFactory.query_request(top_k=10, vector=[0.1, 0.2, 0.3]) + assert isinstance(request, QueryRequest) + assert request.top_k == 10 + assert request.vector == [0.1, 0.2, 0.3] + + def test_query_request_with_scan_factor(self): + request = IndexRequestFactory.query_request( + top_k=10, vector=[0.1, 0.2, 0.3], scan_factor=2.0 + ) + assert request.scan_factor == 2.0 + assert "max_candidates" not in request._data_store + + def test_query_request_with_max_candidates(self): + request = IndexRequestFactory.query_request( + top_k=10, vector=[0.1, 0.2, 0.3], max_candidates=500 + ) + assert request.max_candidates == 500 + assert "scan_factor" not in request._data_store + + def test_query_request_with_scan_factor_and_max_candidates(self): + request = IndexRequestFactory.query_request( + top_k=10, vector=[0.1, 0.2, 0.3], scan_factor=1.5, max_candidates=1000 + ) + assert request.scan_factor == 1.5 + assert request.max_candidates == 1000 + + def test_query_request_omits_scan_factor_and_max_candidates_when_none(self): + request = IndexRequestFactory.query_request(top_k=10, vector=[0.1, 0.2, 0.3]) + assert "scan_factor" not in request._data_store + assert "max_candidates" not in request._data_store diff --git a/tests/unit/test_index.py b/tests/unit/test_index.py index 6117d444a..a1fc68862 100644 --- a/tests/unit/test_index.py +++ b/tests/unit/test_index.py @@ -410,6 +410,60 @@ def test_query_with_positional_args(self, mocker): in str(e.value) ) + def test_query_with_scan_factor_and_max_candidates(self, mocker): + mocker.patch.object(self.index._vector_api, "query_vectors", autospec=True) + self.index.query(top_k=10, vector=self.vals1, scan_factor=2.0, max_candidates=500) + self.index._vector_api.query_vectors.assert_called_once_with( + oai.QueryRequest(top_k=10, vector=self.vals1, scan_factor=2.0, max_candidates=500) + ) + + def test_query_with_scan_factor_only(self, mocker): + mocker.patch.object(self.index._vector_api, "query_vectors", autospec=True) + self.index.query(top_k=10, vector=self.vals1, scan_factor=0.5) + request = self.index._vector_api.query_vectors.call_args[0][0] + assert request.scan_factor == 0.5 + assert "max_candidates" not in request._data_store + + def test_query_with_max_candidates_only(self, mocker): + mocker.patch.object(self.index._vector_api, "query_vectors", autospec=True) + self.index.query(top_k=10, vector=self.vals1, max_candidates=1000) + request = self.index._vector_api.query_vectors.call_args[0][0] + assert request.max_candidates == 1000 + assert "scan_factor" not in request._data_store + + # endregion + + # region: query_namespaces tests + + def test_query_namespaces_forwards_scan_factor_and_max_candidates(self, mocker): + import json + from concurrent.futures import Future + from unittest.mock import MagicMock + + def make_future(**kwargs): + raw = MagicMock() + raw.data = json.dumps( + {"matches": [], "namespace": kwargs.get("namespace", ""), "usage": {"readUnits": 0}} + ).encode() + f = Future() + f.set_result(raw) + return f + + mock_query = mocker.patch.object(self.index, "query", side_effect=make_future) + self.index.query_namespaces( + vector=self.vals1, + namespaces=["ns1", "ns2"], + metric="cosine", + top_k=5, + scan_factor=3.0, + max_candidates=750, + ) + calls = mock_query.call_args_list + assert len(calls) == 2 + for call in calls: + assert call.kwargs["scan_factor"] == 3.0 + assert call.kwargs["max_candidates"] == 750 + # endregion # region: delete tests @@ -719,6 +773,57 @@ async def test_asyncio_update_withDryRunAndAllParams_updateWithDryRunAndAllParam # endregion + # region: asyncio query tests + + @pytest.mark.asyncio + async def test_asyncio_query_with_scan_factor_and_max_candidates(self, mocker): + asyncio_index = _IndexAsyncio(api_key="asdf", host="https://test.pinecone.io") + mock_response = oai.QueryResponse(matches=[], namespace="test", _check_type=False) + mocker.patch.object( + asyncio_index._vector_api, + "query_vectors", + return_value=mock_response, + new_callable=mocker.AsyncMock, + ) + await asyncio_index.query(top_k=10, vector=self.vals1, scan_factor=2.0, max_candidates=500) + asyncio_index._vector_api.query_vectors.assert_called_once_with( + oai.QueryRequest(top_k=10, vector=self.vals1, scan_factor=2.0, max_candidates=500) + ) + + @pytest.mark.asyncio + async def test_asyncio_query_namespaces_forwards_scan_factor_and_max_candidates(self, mocker): + import json + from unittest.mock import MagicMock + from pinecone.openapi_support.rest_utils import RESTResponse + + asyncio_index = _IndexAsyncio(api_key="asdf", host="https://test.pinecone.io") + + async def async_query_side_effect(**kwargs): + raw = MagicMock(spec=RESTResponse) + raw.data = json.dumps( + {"matches": [], "namespace": kwargs.get("namespace", ""), "usage": {"readUnits": 0}} + ).encode() + return raw + + mock_query = mocker.patch.object( + asyncio_index, "_query", side_effect=async_query_side_effect + ) + await asyncio_index.query_namespaces( + vector=self.vals1, + namespaces=["ns1", "ns2"], + metric="cosine", + top_k=5, + scan_factor=3.0, + max_candidates=750, + ) + calls = mock_query.call_args_list + assert len(calls) == 2 + for call in calls: + assert call.kwargs["scan_factor"] == 3.0 + assert call.kwargs["max_candidates"] == 750 + + # endregion + # region: describe index tests def test_describeIndexStats_callWithoutFilter_CalledWithoutFilter(self, mocker): diff --git a/tests/unit_grpc/test_grpc_index_query.py b/tests/unit_grpc/test_grpc_index_query.py index 32a273b72..e531014b6 100644 --- a/tests/unit_grpc/test_grpc_index_query.py +++ b/tests/unit_grpc/test_grpc_index_query.py @@ -46,3 +46,72 @@ def test_query_byVecId_queryByVecId(self, mocker): def test_query_rejects_both_id_and_vector(self): with pytest.raises(ValueError, match="Cannot specify both `id` and `vector`"): self.index.query(top_k=10, id="vec1", vector=[1, 2, 3]) + + def test_query_with_scan_factor_forwarded_to_proto(self, mocker, vals1): + mock_response = QueryResponse() + mocker.patch.object(self.index.runner, "run", return_value=(mock_response, None)) + self.index.query(top_k=10, vector=vals1, scan_factor=2.0) + self.index.runner.run.assert_called_once_with( + self.index.stub.Query, + QueryRequest(top_k=10, vector=vals1, scan_factor=2.0), + timeout=None, + ) + + def test_query_with_max_candidates_forwarded_to_proto(self, mocker, vals1): + mock_response = QueryResponse() + mocker.patch.object(self.index.runner, "run", return_value=(mock_response, None)) + self.index.query(top_k=10, vector=vals1, max_candidates=500) + self.index.runner.run.assert_called_once_with( + self.index.stub.Query, + QueryRequest(top_k=10, vector=vals1, max_candidates=500), + timeout=None, + ) + + def test_query_with_scan_factor_and_max_candidates_forwarded_to_proto(self, mocker, vals1): + mock_response = QueryResponse() + mocker.patch.object(self.index.runner, "run", return_value=(mock_response, None)) + self.index.query(top_k=10, vector=vals1, scan_factor=1.5, max_candidates=1000) + self.index.runner.run.assert_called_once_with( + self.index.stub.Query, + QueryRequest(top_k=10, vector=vals1, scan_factor=1.5, max_candidates=1000), + timeout=None, + ) + + def test_query_async_with_scan_factor_and_max_candidates_forwarded_to_proto( + self, mocker, vals1 + ): + mock_future = mocker.MagicMock() + mocker.patch.object(self.index.runner, "run", return_value=(mock_future, None)) + self.index.query( + top_k=10, vector=vals1, scan_factor=0.5, max_candidates=200, async_req=True + ) + self.index.runner.run.assert_called_once_with( + self.index.stub.Query.future, + QueryRequest(top_k=10, vector=vals1, scan_factor=0.5, max_candidates=200), + timeout=None, + ) + + +class TestGrpcIndexQueryNamespaces: + def setup_method(self): + self.config = Config(api_key="test-api-key", host="foo.pinecone.io") + self.index = GRPCIndex( + config=self.config, index_name="example-name", _endpoint_override="test-endpoint" + ) + + def test_query_namespaces_forwards_scan_factor_and_max_candidates(self, mocker, vals1): + mock_response = QueryResponse() + mocker.patch.object(self.index, "_query", return_value=(mock_response, None)) + self.index.query_namespaces( + vector=vals1, + namespaces=["ns1", "ns2"], + metric="cosine", + top_k=5, + scan_factor=3.0, + max_candidates=750, + ) + calls = self.index._query.call_args_list + assert len(calls) == 2 + for call in calls: + assert call.kwargs["scan_factor"] == 3.0 + assert call.kwargs["max_candidates"] == 750