diff --git a/src/integration/java/io/pinecone/clients/ConnectionsMapTest.java b/src/integration/java/io/pinecone/clients/ConnectionsMapTest.java index 7600fd7..78bf3f6 100644 --- a/src/integration/java/io/pinecone/clients/ConnectionsMapTest.java +++ b/src/integration/java/io/pinecone/clients/ConnectionsMapTest.java @@ -1,5 +1,6 @@ package io.pinecone.clients; +import io.pinecone.configs.PineconeConfig; import io.pinecone.configs.PineconeConnection; import io.pinecone.exceptions.PineconeNotFoundException; import io.pinecone.helpers.RandomStringBuilder; @@ -57,6 +58,10 @@ public void testMultipleIndexesWithMultipleClients() throws InterruptedException // Get index1's host String host1 = indexModel1.getHost(); + // Create config1 for getting index connection and set the host + PineconeConfig config1 = new PineconeConfig(System.getenv("PINECONE_API_KEY")); + config1.setHost(host1); + // Create index-2 pinecone1.createServerlessIndex(indexName2, null, @@ -72,6 +77,11 @@ public void testMultipleIndexesWithMultipleClients() throws InterruptedException // Get index2's host String host2 = indexModel2.getHost(); + // Create config2 for getting index connection and set the host + PineconeConfig config2 = new PineconeConfig(System.getenv("PINECONE_API_KEY")); + config1.setHost(host2); + + // Establish grpc connection for index-1 Index index1_1 = pinecone1.getIndexConnection(indexName1); // Get connections map @@ -94,7 +104,7 @@ public void testMultipleIndexesWithMultipleClients() throws InterruptedException assertEquals(host2, connectionsMap1_2.get(indexName2).toString()); // Establishing connections with index1 and index2 using another pinecone client - pinecone2.getConnection(indexName1); + pinecone2.getConnection(indexName1, config1); ConcurrentHashMap connectionsMap2_1 = pinecone1.getConnectionsMap(); // Verify the new connections map is pointing to the same reference assert connectionsMap2_1 == connectionsMap1_2; @@ -103,7 +113,7 @@ public void testMultipleIndexesWithMultipleClients() throws InterruptedException // Verify the connection value for index1 is host1 assertEquals(host1, connectionsMap2_1.get(indexName1).toString()); - pinecone2.getConnection(indexName2); + pinecone2.getConnection(indexName2, config2); ConcurrentHashMap connectionsMap2_2 = pinecone1.getConnectionsMap(); // Verify the new connections map is pointing to the same reference assert connectionsMap2_1 == connectionsMap2_2; diff --git a/src/integration/java/io/pinecone/integration/controlPlane/serverless/DeletionProtectionTest.java b/src/integration/java/io/pinecone/integration/controlPlane/serverless/DeletionProtectionTest.java index dbabbb2..115802d 100644 --- a/src/integration/java/io/pinecone/integration/controlPlane/serverless/DeletionProtectionTest.java +++ b/src/integration/java/io/pinecone/integration/controlPlane/serverless/DeletionProtectionTest.java @@ -44,12 +44,12 @@ public void createPodIndexWithDeletionProtectionDisabled() { Map actualTags = indexModel.getTags(); Assertions.assertEquals(expectedTags, actualTags); // Configure index to enable deletionProtection - controlPlaneClient.configureServerlessIndex(indexName, DeletionProtection.ENABLED, expectedTags); + controlPlaneClient.configureServerlessIndex(indexName, DeletionProtection.ENABLED, expectedTags, null); indexModel = controlPlaneClient.describeIndex(indexName); deletionProtection = indexModel.getDeletionProtection(); Assertions.assertEquals(deletionProtection, DeletionProtection.ENABLED); // Configure index to disable deletionProtection - controlPlaneClient.configureServerlessIndex(indexName, DeletionProtection.DISABLED, expectedTags); + controlPlaneClient.configureServerlessIndex(indexName, DeletionProtection.DISABLED, expectedTags, null); // Delete index controlPlaneClient.deleteIndex(indexName); } diff --git a/src/integration/java/io/pinecone/integration/controlPlane/serverless/SparseIndexTest.java b/src/integration/java/io/pinecone/integration/controlPlane/serverless/SparseIndexTest.java index ee74997..fc5ff0e 100644 --- a/src/integration/java/io/pinecone/integration/controlPlane/serverless/SparseIndexTest.java +++ b/src/integration/java/io/pinecone/integration/controlPlane/serverless/SparseIndexTest.java @@ -63,7 +63,7 @@ public void configureSparseIndex() throws InterruptedException { waitUntilIndexIsReady(pinecone, indexName, 200000); // Disable deletion protection and add more index tags - pinecone.configureServerlessIndex(indexName, DeletionProtection.DISABLED, tags); + pinecone.configureServerlessIndex(indexName, DeletionProtection.DISABLED, tags, null); Thread.sleep(7000); // Describe index to confirm deletion protection is disabled diff --git a/src/integration/java/io/pinecone/integration/dataPlane/QueryErrorTest.java b/src/integration/java/io/pinecone/integration/dataPlane/QueryErrorTest.java index 5928357..e8146a8 100644 --- a/src/integration/java/io/pinecone/integration/dataPlane/QueryErrorTest.java +++ b/src/integration/java/io/pinecone/integration/dataPlane/QueryErrorTest.java @@ -34,7 +34,7 @@ public static void setUp() throws IOException, InterruptedException { when(connectionMock.getBlockingStub()).thenReturn(stubMock); when(connectionMock.getAsyncStub()).thenReturn(asyncStubMock); - index = new Index(connectionMock, "some-index-name"); + index = new Index(config, connectionMock, "some-index-name"); asyncIndex = new AsyncIndex(config, connectionMock, "some-index-name"); } diff --git a/src/integration/java/io/pinecone/integration/dataPlane/UpsertAndSearchRecordsTest.java b/src/integration/java/io/pinecone/integration/dataPlane/UpsertAndSearchRecordsTest.java new file mode 100644 index 0000000..d83d591 --- /dev/null +++ b/src/integration/java/io/pinecone/integration/dataPlane/UpsertAndSearchRecordsTest.java @@ -0,0 +1,95 @@ +package io.pinecone.integration.dataPlane; + +import io.pinecone.clients.Index; +import io.pinecone.clients.Pinecone; +import io.pinecone.helpers.RandomStringBuilder; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.openapitools.db_control.client.model.CreateIndexForModelRequest; +import org.openapitools.db_control.client.model.CreateIndexForModelRequestEmbed; +import org.openapitools.db_control.client.model.DeletionProtection; +import org.openapitools.db_data.client.ApiException; +import org.openapitools.db_data.client.model.SearchRecordsRequestQuery; +import org.openapitools.db_data.client.model.SearchRecordsRequestRerank; +import org.openapitools.db_data.client.model.SearchRecordsResponse; + +import java.util.*; + +public class UpsertAndSearchRecordsTest { + @Test + public void upsertAndSearchRecordsTest() throws ApiException, org.openapitools.db_control.client.ApiException, InterruptedException { + Pinecone pinecone = new Pinecone.Builder(System.getenv("PINECONE_API_KEY")).build(); + String indexName = RandomStringBuilder.build("inf", 8); + HashMap fieldMap = new HashMap<>(); + fieldMap.put("text", "chunk_text"); + CreateIndexForModelRequestEmbed embed = new CreateIndexForModelRequestEmbed() + .model("multilingual-e5-large") + .fieldMap(fieldMap); + pinecone.createIndexForModel(indexName, CreateIndexForModelRequest.CloudEnum.AWS, "us-west-2", embed, DeletionProtection.DISABLED, new HashMap<>()); + + // Wait for index to be created + Thread.sleep(10000); + + Index index = pinecone.getIndexConnection(indexName); + ArrayList> upsertRecords = new ArrayList<>(); + + HashMap record1 = new HashMap<>(); + record1.put("_id", "rec1"); + record1.put("category", "digestive system"); + record1.put("chunk_text", "Apples are a great source of dietary fiber, which supports digestion and helps maintain a healthy gut."); + + HashMap record2 = new HashMap<>(); + record2.put("_id", "rec2"); + record2.put("category", "cultivation"); + record2.put("chunk_text", "Apples originated in Central Asia and have been cultivated for thousands of years, with over 7,500 varieties available today."); + + HashMap record3 = new HashMap<>(); + record3.put("_id", "rec3"); + record3.put("category", "immune system"); + record3.put("chunk_text", "Rich in vitamin C and other antioxidants, apples contribute to immune health and may reduce the risk of chronic diseases."); + + HashMap record4 = new HashMap<>(); + record4.put("_id", "rec4"); + record4.put("category", "endocrine system"); + record4.put("chunk_text", "The high fiber content in apples can also help regulate blood sugar levels, making them a favorable snack for people with diabetes."); + + upsertRecords.add(record1); + upsertRecords.add(record2); + upsertRecords.add(record3); + upsertRecords.add(record4); + + index.upsertRecords("example-namespace", upsertRecords); + + String namespace = "example-namespace"; + HashMap inputsMap = new HashMap<>(); + inputsMap.put("text", "Disease prevention"); + SearchRecordsRequestQuery query = new SearchRecordsRequestQuery() + .topK(4) + .inputs(inputsMap); + + List fields = new ArrayList<>(); + fields.add("category"); + fields.add("chunk_text"); + + // Wait for vectors to be upserted + Thread.sleep(5000); + + SearchRecordsResponse recordsResponse = index.searchRecords(namespace, query, fields, null); + Assertions.assertEquals(upsertRecords.size(), recordsResponse.getResult().getHits().size()); + Assertions.assertEquals(record3.get("_id"), recordsResponse.getResult().getHits().get(0).getId()); + + recordsResponse = index.searchRecordsById(record1.get("_id"), namespace, fields, 1, null, null); + Assertions.assertEquals(1, recordsResponse.getResult().getHits().size()); + Assertions.assertEquals(record1.get("_id"), recordsResponse.getResult().getHits().get(0).getId()); + + SearchRecordsRequestRerank rerank = new SearchRecordsRequestRerank() + .model("bge-reranker-v2-m3") + .topN(2) + .rankFields(Arrays.asList("chunk_text")); + + recordsResponse = index.searchRecordsByText("Disease prevention", namespace, fields, 4, null, rerank); + Assertions.assertEquals(record3.get("_id"), recordsResponse.getResult().getHits().get(0).getId()); + + pinecone.deleteIndex(indexName); + } +} diff --git a/src/integration/java/io/pinecone/integration/dataPlane/UpsertErrorTest.java b/src/integration/java/io/pinecone/integration/dataPlane/UpsertErrorTest.java index bbe7351..df22aef 100644 --- a/src/integration/java/io/pinecone/integration/dataPlane/UpsertErrorTest.java +++ b/src/integration/java/io/pinecone/integration/dataPlane/UpsertErrorTest.java @@ -37,7 +37,7 @@ public static void setUp() throws IOException, InterruptedException { when(connectionMock.getBlockingStub()).thenReturn(stubMock); when(connectionMock.getAsyncStub()).thenReturn(asyncStubMock); - index = new Index(connectionMock, "some-index-name"); + index = new Index(config, connectionMock, "some-index-name"); asyncIndex = new AsyncIndex(config, connectionMock, "some-index-name"); } diff --git a/src/main/java/io/pinecone/clients/AsyncIndex.java b/src/main/java/io/pinecone/clients/AsyncIndex.java index 0851491..af04131 100644 --- a/src/main/java/io/pinecone/clients/AsyncIndex.java +++ b/src/main/java/io/pinecone/clients/AsyncIndex.java @@ -31,9 +31,9 @@ import static io.pinecone.clients.Pinecone.buildOkHttpClient; - /** - * A client for interacting with a Pinecone index via GRPC asynchronously. Allows for upserting, querying, fetching, updating, and deleting vectors. + * A client for interacting with a Pinecone index asynchronously. Allows for vector operations such as upserting, + * querying, fetching, updating, and deleting vectors along with records operations such as upsert and search records. * This class provides a direct interface to interact with a specific index, encapsulating network communication and request validation. *

* Example: @@ -70,6 +70,7 @@ public class AsyncIndex implements IndexInterface * + * @param config The {@link PineconeConfig} configuration of the index. * @param connection The {@link PineconeConnection} configuration to be used for this index. * @param indexName The name of the index to interact with. The index host will be automatically resolved. * @throws PineconeValidationException if the connection object is null. diff --git a/src/main/java/io/pinecone/clients/Index.java b/src/main/java/io/pinecone/clients/Index.java index 974308c..665f8f0 100644 --- a/src/main/java/io/pinecone/clients/Index.java +++ b/src/main/java/io/pinecone/clients/Index.java @@ -2,16 +2,37 @@ import com.google.protobuf.Struct; import io.pinecone.commons.IndexInterface; +import io.pinecone.configs.PineconeConfig; import io.pinecone.configs.PineconeConnection; import io.pinecone.exceptions.PineconeValidationException; import io.pinecone.proto.*; +import io.pinecone.proto.DeleteRequest; +import io.pinecone.proto.DescribeIndexStatsRequest; +import io.pinecone.proto.FetchResponse; +import io.pinecone.proto.ListResponse; +import io.pinecone.proto.QueryRequest; +import io.pinecone.proto.UpdateRequest; +import io.pinecone.proto.UpsertRequest; +import io.pinecone.proto.UpsertResponse; import io.pinecone.unsigned_indices_model.QueryResponseWithUnsignedIndices; import io.pinecone.unsigned_indices_model.VectorWithUnsignedIndices; +import okhttp3.OkHttpClient; +import org.openapitools.db_data.client.ApiClient; +import org.openapitools.db_data.client.ApiException; +import org.openapitools.db_data.client.Configuration; +import org.openapitools.db_data.client.api.VectorOperationsApi; +import org.openapitools.db_data.client.model.*; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; + +import static io.pinecone.clients.Pinecone.buildOkHttpClient; /** - * A client for interacting with a Pinecone index via GRPC synchronously. Allows for upserting, querying, fetching, updating, and deleting vectors. + * A client for interacting with a Pinecone index synchronously. Allows for vector operations such as upserting, + * querying, fetching, updating, and deleting vectors along with records operations such as upsert and search records. * This class provides a direct interface to interact with a specific index, encapsulating network communication and request validation. *

* Example: @@ -34,6 +55,7 @@ public class Index implements IndexInterface * + * @param config The {@link PineconeConfig} configuration of the index. * @param connection The {@link PineconeConnection} configuration to be used for this index. - * @param indexName The name of the index to interact with. The index host will be automatically resolved. + * @param indexName The name of the index to interact with. The index host will be automatically resolved. * @throws PineconeValidationException if the connection object is null. */ - public Index(PineconeConnection connection, String indexName) { + public Index(PineconeConfig config, PineconeConnection connection, String indexName) { if (connection == null) { throw new PineconeValidationException("Pinecone connection object cannot be null."); } @@ -59,6 +82,16 @@ public Index(PineconeConnection connection, String indexName) { this.connection = connection; this.indexName = indexName; this.blockingStub = connection.getBlockingStub(); + + OkHttpClient customOkHttpClient = config.getCustomOkHttpClient(); + ApiClient apiClient = (customOkHttpClient != null) ? new ApiClient(customOkHttpClient) : new ApiClient(buildOkHttpClient(config.getProxyConfig())); + apiClient.setApiKey(config.getApiKey()); + apiClient.setUserAgent(config.getUserAgent()); + apiClient.addDefaultHeader("X-Pinecone-Api-Version", Configuration.VERSION); + + this.vectorOperations = new VectorOperationsApi(apiClient); + String protocol = config.isTLSEnabled() ? "https://" : "http://"; + vectorOperations.setCustomBaseUrl(protocol + config.getHost()); } /** @@ -783,7 +816,7 @@ public DescribeIndexStatsResponse describeIndexStats(Struct filter) { /** * {@inheritDoc} *

Example: - *

{@code
+     * 
{@code
      *     import io.pinecone.proto.ListResponse;
      *
      *     ...
@@ -802,7 +835,7 @@ public ListResponse list() {
     /**
      * {@inheritDoc}
      * 

Example: - *

{@code
+     * 
{@code
      *     import io.pinecone.proto.ListResponse;
      *
      *     ...
@@ -820,7 +853,7 @@ public ListResponse list(String namespace) {
     /**
      * {@inheritDoc}
      * 

Example: - *

{@code
+     * 
{@code
      *     import io.pinecone.proto.ListResponse;
      *
      *     ...
@@ -838,7 +871,7 @@ public ListResponse list(String namespace, int limit, String paginationToken) {
     /**
      * {@inheritDoc}
      * 

Example: - *

{@code
+     * 
{@code
      *     import io.pinecone.proto.ListResponse;
      *
      *     ...
@@ -856,7 +889,7 @@ public ListResponse list(String namespace, int limit) {
     /**
      * {@inheritDoc}
      * 

Example: - *

{@code
+     * 
{@code
      *     import io.pinecone.proto.ListResponse;
      *
      *     ...
@@ -875,7 +908,7 @@ public ListResponse list(String namespace, String prefix) {
     /**
      * {@inheritDoc}
      * 

Example: - *

{@code
+     * 
{@code
      *     import io.pinecone.proto.ListResponse;
      *
      *     ...
@@ -895,7 +928,7 @@ public ListResponse list(String namespace, String prefix, int limit) {
     /**
      * {@inheritDoc}
      * 

Example: - *

{@code
+     * 
{@code
      *     import io.pinecone.proto.ListResponse;
      *
      *     ...
@@ -920,22 +953,23 @@ public ListResponse list(String namespace, String prefix, String paginationToken
      * through a list of vector IDs. It then makes a synchronous RPC call to fetch the list of vector IDs.
      *
      * 

Example: - *

{@code
+     * 
{@code
      *     import io.pinecone.proto.ListResponse;
      *
      *     ...
      *
      *     ListResponse listResponse = index.list("example-namespace", "st-", "some-pagToken", 10);
      *   }
- * @param namespace The namespace that holds the vector IDs you want to retrieve. If namespace is not specified, - * the default namespace is used. - * @param prefix The prefix with which vector IDs must start to be included in the response. + * + * @param namespace The namespace that holds the vector IDs you want to retrieve. If namespace is not specified, + * the default namespace is used. + * @param prefix The prefix with which vector IDs must start to be included in the response. * @param paginationToken The token to paginate through the list of vector IDs. - * @param limit The maximum number of vector IDs you want to retrieve. + * @param limit The maximum number of vector IDs you want to retrieve. * @return {@link ListResponse} containing the list of vector IDs fetched from the specified namespace. - * The response includes vector IDs up to {@code 100} items. + * The response includes vector IDs up to {@code 100} items. * @throws RuntimeException if there are issues processing the request or communicating with the server. - * This includes network issues, server errors, or serialization issues with the request or response. + * This includes network issues, server errors, or serialization issues with the request or response. */ public ListResponse list(String namespace, String prefix, String paginationToken, int limit) { validateListEndpointParameters(namespace, prefix, paginationToken, limit, true, true, true, true); @@ -944,6 +978,269 @@ public ListResponse list(String namespace, String prefix, String paginationToken return blockingStub.list(listRequest); } + /** + *

Upserts records into a specified namespace within a Pinecone index. This operation + * will insert new records or update existing ones based on the provided data.

+ * + *

The method sends a list of {@link UpsertRecord} objects to the specified namespace + * in the Pinecone index, either inserting new records or updating existing records + * depending on whether the record IDs already exist.

+ * + *

Example: + *

{@code
+     *     List records = new ArrayList<>();
+     *     records.add(new UpsertRecord("rec1", "Apple's first product, the Apple I, was released in 1976.", "product"));
+     *     records.add(new UpsertRecord("rec2", "Apples are a great source of dietary fiber.", "nutrition"));
+     *
+     *     try {
+     *         index.upsertRecords("example-namespace", records);
+     *     } catch (ApiException e) {
+     *
+     *     }
+     * }

+ * + * @param namespace The namespace within the Pinecone index where the records will be upserted. + * The namespace must be an existing namespace or a valid one to create new records. + * @param upsertRecords A list of Map containing the records to be upserted. + * Each record must include a unique ID represented by the key "_id" along with the data to be + * stored. + * @throws ApiException If there is an issue with the upsert operation. This could include network errors, + * invalid input data, or issues communicating with the Pinecone service. + */ + public void upsertRecords(String namespace, List> upsertRecords) throws ApiException { + List records = new ArrayList<>(); + for(Map record: upsertRecords) { + UpsertRecord upsertRecord = new UpsertRecord(); + for (Map.Entry entry : record.entrySet()) { + if(entry.getKey().equals("_id")) { + upsertRecord.id(entry.getValue()); + } + else { + upsertRecord.putAdditionalProperty(entry.getKey(), entry.getValue()); + } + } + + records.add(upsertRecord); + } + vectorOperations.upsertRecordsNamespace(namespace, records); + } + + /** + *

Searches for records in a specified namespace within a Pinecone index by converting a query into a vector embedding. + * Optionally, a reranking operation can be applied to refine the results.

+ * + *

This method sends a search query along with specified fields to the Pinecone index, retrieves the relevant records, + * and applies an optional reranking operation if provided.

+ * + *

Example: + *

{@code
+     *     String namespace = "example-namespace";
+     *     HashMap inputsMap = new HashMap<>();
+     *     inputsMap.put("text", "Disease prevention");
+     *     SearchRecordsRequestQuery query = new SearchRecordsRequestQuery()
+     *             .topK(3)
+     *             .inputs(inputsMap);
+     *
+     *     List fields = new ArrayList<>();
+     *     fields.add("category");
+     *     fields.add("chunk_text");
+     *
+     *     SearchRecordsResponse recordsResponse = index.searchRecords(namespace, query, fields, null);
+     * }

+ * + * @param namespace The namespace within the Pinecone index where the search will be performed. + * The namespace must exist and contain records to search through. + * @param query The query to be converted into a vector embedding for the search operation. + * This query contains the input data for the search and parameters like topK for result limits. + * @param fields A list of fields to be searched within the records. These fields define which parts of the records + * are considered during the search. + * @param rerank (Optional) A reranking operation that can be applied to refine or reorder the search results. + * Pass null if no reranking is required. + * @return A {@link SearchRecordsResponse} object containing the search results, including the top matching records. + * @throws ApiException If there is an issue with the search operation. This could include network errors, + * invalid input data, or issues communicating with the Pinecone service. + */ + public SearchRecordsResponse searchRecords(String namespace, + SearchRecordsRequestQuery query, + List fields, + SearchRecordsRequestRerank rerank) throws ApiException { + SearchRecordsRequest request = new SearchRecordsRequest() + .query(query) + .fields(fields) + .rerank(rerank); + + return vectorOperations.searchRecordsNamespace(namespace, request); + } + + /** + *

Searches for records by a specific record ID within a Pinecone index. The search query will use the ID as a + * filter.

+ * + *

This method retrieves records from the Pinecone index by searching for a specific record ID. + * You can optionally apply a filter, limit the number of results with the topK parameter, + * and refine the results using an optional reranking operation.

+ * + *

Example: + *

{@code
+     *     String id = "12345";
+     *     String namespace = "example-namespace";
+     *     List fields = new ArrayList<>();
+     *     fields.add("category");
+     *     fields.add("chunk_text");
+     *     int topK = 3;
+     *
+     *     SearchRecordsResponse recordsResponse = index.searchRecordsById(id, namespace, fields, topK, null, null);
+     * }

+ * + * @param id The ID of the record to be searched within the Pinecone index. + * The ID must exist within the specified namespace for a valid search. + * @param namespace The namespace within the Pinecone index where the search will be performed. + * The namespace must exist and contain records to search through. + * @param fields A list of fields to be searched within the records. These fields define which parts of the records + * are considered during the search. + * @param topK The maximum number of results to be returned by the search. + * @param filter (Optional) A filter to apply to the search query. It can be used to narrow down the search + * based on specific criteria. + * @param rerank (Optional) A reranking operation that can be applied to refine or reorder the search results. + * Pass null if no reranking is required. + * @return A {@link SearchRecordsResponse} object containing the search results, including the top matching records. + * @throws ApiException If there is an issue with the search operation. This could include network errors, + * invalid input data, or issues communicating with the Pinecone service. + */ + public SearchRecordsResponse searchRecordsById(String id, + String namespace, + List fields, + int topK, + Map filter, + SearchRecordsRequestRerank rerank) throws ApiException { + SearchRecordsRequestQuery query = new SearchRecordsRequestQuery() + .id(id) + .topK(topK) + .filter(filter); + + SearchRecordsRequest request = new SearchRecordsRequest() + .query(query) + .fields(fields) + .rerank(rerank); + + return vectorOperations.searchRecordsNamespace(namespace, request); + } + + /** + *

Searches for records in a Pinecone index by a vector. The vector represents the search query in vector space.

+ * + *

This method converts the given vector into a query and searches for the most relevant records within the Pinecone index. + * You can limit the results with the topK parameter, apply a filter, and optionally apply a reranking operation.

+ * + *

The {@link SearchRecordsVector} class represents a vector in the query. It contains the following optional fields: + * - `values`: A list of floats representing the dense vector values. If this field is provided, it is used for the search. + * - `sparseValues`: An optional list of non-zero values for sparse vectors. If provided, the search will use sparse vector representation. + * - `sparseIndices`: An optional list of indices corresponding to the non-zero values in the sparse vector. If provided along with `sparseValues`, it will enable sparse vector search.

+ * + *

Example: + *

{@code
+     *     SearchRecordsVector vector = new SearchRecordsVector();
+     *     vector.setValues(Arrays.asList(1.0f, 2.0f, 3.0f)); // Dense vector values
+     *     // Or, for sparse vectors:
+     *     // vector.setSparseValues(Arrays.asList(1.0f, 2.0f));
+     *     // vector.setSparseIndices(Arrays.asList(0, 2));
+     *     String namespace = "example-namespace";
+     *     List fields = new ArrayList<>();
+     *     fields.add("category");
+     *     fields.add("chunk_text");
+     *     int topK = 3;
+     *
+     *     SearchRecordsResponse recordsResponse = index.searchRecordsByVector(vector, namespace, fields, topK, null, null);
+     * }

+ * + * @param vector The vector representing the search query, which is used to find the closest matching records. + * The vector can be a dense vector (via `values`) or a sparse vector (via `sparseValues` and `sparseIndices`). + * All fields in the vector are optional. If none are provided, the search will fail due to missing query data. + * @param namespace The namespace within the Pinecone index where the search will be performed. + * The namespace must exist and contain records to search through. + * @param fields A list of fields to be searched within the records. These fields define which parts of the records + * are considered during the search. + * @param topK The maximum number of results to be returned by the search. + * @param filter (Optional) A filter to apply to the search query. It can be used to narrow down the search + * based on specific criteria. + * @param rerank (Optional) A reranking operation that can be applied to refine or reorder the search results. + * Pass null if no reranking is required. + * @return A {@link SearchRecordsResponse} object containing the search results, including the top matching records. + * @throws ApiException If there is an issue with the search operation. This could include network errors, + * invalid input data, or issues communicating with the Pinecone service. + */ + public SearchRecordsResponse searchRecordsByVector(SearchRecordsVector vector, + String namespace, + List fields, + int topK, + Map filter, + SearchRecordsRequestRerank rerank) throws ApiException { + SearchRecordsRequestQuery query = new SearchRecordsRequestQuery() + .vector(vector) + .topK(topK) + .filter(filter); + + SearchRecordsRequest request = new SearchRecordsRequest() + .query(query) + .fields(fields) + .rerank(rerank); + + return vectorOperations.searchRecordsNamespace(namespace, request); + } + + /** + *

Searches for records in a Pinecone index using a text query. The text is converted into a vector for the search operation.

+ * + *

This method converts the given text into a vector representation and performs a search within the Pinecone index. + * You can limit the results with the topK parameter, apply a filter, and optionally apply a reranking operation.

+ * + *

Example: + *

{@code
+     *     String text = "Disease prevention";
+     *     String namespace = "example-namespace";
+     *     List fields = new ArrayList<>();
+     *     fields.add("category");
+     *     fields.add("chunk_text");
+     *     int topK = 3;
+     *
+     *     SearchRecordsResponse recordsResponse = index.searchRecordsByText(text, namespace, fields, topK, null, null);
+     * }

+ * + * @param text The text used in the query for searching. + * @param namespace The namespace within the Pinecone index where the search will be performed. + * The namespace must exist and contain records to search through. + * @param fields A list of fields to be searched within the records. These fields define which parts of the records + * are considered during the search. + * @param topK The maximum number of results to be returned by the search. + * @param filter (Optional) A filter to apply to the search query. It can be used to narrow down the search + * based on specific criteria. + * @param rerank (Optional) A reranking operation that can be applied to refine or reorder the search results. + * Pass null if no reranking is required. + * @return A {@link SearchRecordsResponse} object containing the search results, including the top matching records. + * @throws ApiException If there is an issue with the search operation. This could include network errors, + * invalid input data, or issues communicating with the Pinecone service. + */ + public SearchRecordsResponse searchRecordsByText(String text, + String namespace, + List fields, + int topK, + Map filter, + SearchRecordsRequestRerank rerank) throws ApiException { + HashMap inputs = new HashMap<>(); + inputs.put("text", text); + + SearchRecordsRequestQuery query = new SearchRecordsRequestQuery() + .inputs(inputs) + .topK(topK) + .filter(filter); + + SearchRecordsRequest request = new SearchRecordsRequest() + .query(query) + .fields(fields) + .rerank(rerank); + + return vectorOperations.searchRecordsNamespace(namespace, request); + } /** * {@inheritDoc} diff --git a/src/main/java/io/pinecone/clients/Pinecone.java b/src/main/java/io/pinecone/clients/Pinecone.java index 42a5f76..077db05 100644 --- a/src/main/java/io/pinecone/clients/Pinecone.java +++ b/src/main/java/io/pinecone/clients/Pinecone.java @@ -215,6 +215,50 @@ public IndexModel createSparseServelessIndex(String indexName, return indexModel; } + /** + * Creates a new serverless index with an associated embedding model. + *

+ * Example: + *

{@code
+     *     client.createIndexForModel("my-index", CreateIndexForModelRequest.CloudEnum.AWS,
+     *                                            "us-west-2", embedConfig, DeletionProtection.DISABLED, tags);
+     * }
+ * + * @param name The name of the index to be created. The name must be between 1 and 45 characters, + * start and end with an alphanumeric character, and consist only of lowercase alphanumeric + * characters or hyphens ('-'). + * @param cloud The cloud provider where the index will be hosted. Must be one of the supported cloud providers. + * @param region The cloud region where the index will be created. + * @param embed The embedding model configuration. Once set, the model cannot be changed, but configurations + * such as field map and parameters can be updated. + * @param deletionProtection Whether deletion protection is enabled for the index. If enabled, the index + * cannot be deleted. Defaults to disabled if not provided. + * @param tags A map of custom user tags to associate with the index. Keys must be alphanumeric or contain + * underscores ('_') or hyphens ('-'). Values must be alphanumeric, or contain characters such + * as ';', '@', '_', '-', '.', '+', or spaces. + * @return {@link IndexModel} representing the created serverless index with the associated embedding model. + * @throws PineconeException if the API encounters an error during index creation, or if any of the arguments + * are invalid. + * @throws ApiException if an error occurs while communicating with the API. + */ + public IndexModel createIndexForModel(String name, + CreateIndexForModelRequest.CloudEnum cloud, + String region, + CreateIndexForModelRequestEmbed embed, + DeletionProtection deletionProtection, + Map tags) throws PineconeException, ApiException { + + CreateIndexForModelRequest createIndexForModelRequest = new CreateIndexForModelRequest() + .name(name) + .cloud(cloud) + .region(region) + .embed(embed) + .deletionProtection(deletionProtection) + .tags(tags); + + return manageIndexesApi.createIndexForModel(createIndexForModelRequest); + } + /** * Overload for creating a new pods index with environment and podType, the minimum required parameters. *

@@ -716,8 +760,17 @@ public IndexModel configurePodsIndex(String indexName, DeletionProtection deleti * import org.openapitools.control.client.model.IndexModel; * ... * + * HashMap tags = new HashMap<>(); + * tags.put("env", "test); + * + * ConfigureIndexRequestEmbed embed = new ConfigureIndexRequestEmbed(); + * embed.model("multilingual-e5-large"); + * HashMap fieldMap = new HashMap<>(); + * fieldMap.put("text", "your-text-field"); + * embed.fieldMap(fieldMap); + * * // Make a configuration change - * IndexModel indexModel = client.configureServerlessIndex("YOUR-INDEX", DeletionProtection.ENABLED); + * IndexModel indexModel = client.configureServerlessIndex("YOUR-INDEX", DeletionProtection.ENABLED, tags, embed); * * // Call describeIndex to see the index status as the change is applied. * indexModel = client.describeIndex("YOUR-INDEX"); @@ -726,10 +779,16 @@ public IndexModel configurePodsIndex(String indexName, DeletionProtection deleti * @param indexName The name of the index to configure. * @param deletionProtection Enable or disable deletion protection for the index. * @param tags A map of tags to associate with the Index. + * @param embed Convert an existing index to an integrated index by specifying the embedding model and field_map. + * The index vector type and dimension must match the model vector type and dimension, and the index + * similarity metric must be supported by the model * @return {@link IndexModel} representing the configured index. * @throws PineconeException if an error occurs during the operation, the index does not exist, or if any of the arguments are invalid. */ - public IndexModel configureServerlessIndex(String indexName, DeletionProtection deletionProtection, Map tags) throws PineconeException { + public IndexModel configureServerlessIndex(String indexName, + DeletionProtection deletionProtection, + Map tags, + ConfigureIndexRequestEmbed embed) throws PineconeException { if (indexName == null || indexName.isEmpty()) { throw new PineconeValidationException("indexName cannot be null or empty"); } @@ -742,6 +801,10 @@ public IndexModel configureServerlessIndex(String indexName, DeletionProtection configureIndexRequest.tags(tags); } + if(embed != null) { + configureIndexRequest.embed(embed); + } + IndexModel indexModel = null; try { indexModel = manageIndexesApi.configureIndex(indexName, configureIndexRequest); @@ -949,8 +1012,11 @@ public Index getIndexConnection(String indexName) throws PineconeValidationExcep throw new PineconeValidationException("Index name cannot be null or empty"); } - PineconeConnection connection = getConnection(indexName); - return new Index(connection, indexName); + PineconeConfig perConnectionConfig = new PineconeConfig(config.getApiKey(), config.getSourceTag()); + perConnectionConfig.setHost(getIndexHost(indexName)); + + PineconeConnection connection = getConnection(indexName, perConnectionConfig); + return new Index(perConnectionConfig, connection, indexName); } /** @@ -978,7 +1044,10 @@ public AsyncIndex getAsyncIndexConnection(String indexName) throws PineconeValid throw new PineconeValidationException("Index name cannot be null or empty"); } - PineconeConnection connection = getConnection(indexName); + PineconeConfig perConnectionConfig = new PineconeConfig(config.getApiKey(), config.getSourceTag()); + perConnectionConfig.setHost(getIndexHost(indexName)); + + PineconeConnection connection = getConnection(indexName, perConnectionConfig); return new AsyncIndex(config, connection, indexName); } @@ -994,9 +1063,7 @@ public Inference getInferenceClient() { return new Inference(config); } - PineconeConnection getConnection(String indexName) { - PineconeConfig perConnectionConfig = new PineconeConfig(config.getApiKey(), config.getSourceTag()); - perConnectionConfig.setHost(getIndexHost(indexName)); + PineconeConnection getConnection(String indexName, PineconeConfig perConnectionConfig) { return connectionsMap.computeIfAbsent(indexName, key -> new PineconeConnection(perConnectionConfig)); } diff --git a/src/main/java/org/openapitools/db_data/client/ApiClient.java b/src/main/java/org/openapitools/db_data/client/ApiClient.java index 67632fb..b4f5b45 100644 --- a/src/main/java/org/openapitools/db_data/client/ApiClient.java +++ b/src/main/java/org/openapitools/db_data/client/ApiClient.java @@ -931,6 +931,18 @@ public RequestBody serialize(Object obj, String contentType) throws ApiException return RequestBody.create((File) obj, MediaType.parse(contentType)); } else if ("text/plain".equals(contentType) && obj instanceof String) { return RequestBody.create((String) obj, MediaType.parse(contentType)); + } else if ("application/x-ndjson".equals(contentType)) { + // Handle NDJSON (Newline Delimited JSON) + if (obj instanceof Iterable) { // If obj is a collection of objects + StringBuilder ndjsonContent = new StringBuilder(); + for (Object item : (Iterable) obj) { + String json = JSON.serialize(item); + ndjsonContent.append(json).append("\n"); + } + return RequestBody.create(ndjsonContent.toString(), MediaType.parse(contentType)); + } else { + throw new ApiException("NDJSON content requires a collection of objects."); + } } else if (isJsonMime(contentType)) { String content; if (obj != null) { diff --git a/src/test/java/io/pinecone/ListEndpointValidationTest.java b/src/test/java/io/pinecone/ListEndpointValidationTest.java index 01a1754..3576b6b 100644 --- a/src/test/java/io/pinecone/ListEndpointValidationTest.java +++ b/src/test/java/io/pinecone/ListEndpointValidationTest.java @@ -1,6 +1,7 @@ package io.pinecone; import io.pinecone.clients.Index; +import io.pinecone.configs.PineconeConfig; import io.pinecone.configs.PineconeConnection; import io.pinecone.exceptions.PineconeValidationException; import io.pinecone.proto.VectorServiceGrpc; @@ -18,21 +19,19 @@ @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class ListEndpointValidationTest { - private String indexName; - private PineconeConnection connectionMock; - private VectorServiceGrpc.VectorServiceBlockingStub stubMock; private Index index; @BeforeAll public void setUp() { - indexName = "test-index"; + String indexName = "test-index"; // Mock sync Pinecone connection - connectionMock = mock(PineconeConnection.class); - stubMock = mock(VectorServiceGrpc.VectorServiceBlockingStub.class); + PineconeConfig config = mock(PineconeConfig.class); + PineconeConnection connectionMock = mock(PineconeConnection.class); + VectorServiceGrpc.VectorServiceBlockingStub stubMock = mock(VectorServiceGrpc.VectorServiceBlockingStub.class); when(connectionMock.getBlockingStub()).thenReturn(stubMock); - index = new Index(connectionMock, indexName); + index = new Index(config, connectionMock, indexName); } @Test