Skip to content

Commit 69200e6

Browse files
sobychackomarkpollack
authored andcommitted
Introduce Step Builder pattern for PineconeVectorStore
Replace the existing builder implementation with a type-safe Step Builder pattern to enforce required field initialization in a specific order. This change: - Introduces a new builder() method that guides users through required fields - Deprecates the old builder method with all parameters - Makes field configuration more explicit and type-safe - Updates all usage points to use the new builder pattern The required fields must now be provided in sequence: 1. embeddingModel 2. apiKey 3. projectId 4. environment 5. indexName This change improves API usability by preventing parameter confusion and ensuring all required fields are set before optional configuration.
1 parent 8162029 commit 69200e6

File tree

4 files changed

+191
-15
lines changed

4 files changed

+191
-15
lines changed

spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/pinecone/PineconeVectorStoreAutoConfiguration.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,11 @@ public PineconeVectorStore vectorStore(EmbeddingModel embeddingModel, PineconeVe
5454
ObjectProvider<VectorStoreObservationConvention> customObservationConvention,
5555
BatchingStrategy batchingStrategy) {
5656

57-
return PineconeVectorStore
58-
.builder(embeddingModel, properties.getApiKey(), properties.getProjectId(), properties.getEnvironment(),
59-
properties.getIndexName())
57+
return PineconeVectorStore.builder(embeddingModel)
58+
.apiKey(properties.getApiKey())
59+
.projectId(properties.getProjectId())
60+
.environment(properties.getEnvironment())
61+
.indexName(properties.getIndexName())
6062
.namespace(properties.getNamespace())
6163
.contentFieldName(properties.getContentFieldName())
6264
.distanceMetadataFieldName(properties.getDistanceMetadataFieldName())

vector-stores/spring-ai-pinecone-store/src/main/java/org/springframework/ai/vectorstore/pinecone/PineconeVectorStore.java

Lines changed: 175 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,48 @@ protected PineconeVectorStore(Builder builder) {
121121
/**
122122
* Creates a new builder instance for configuring a PineconeVectorStore.
123123
* @return A new PineconeBuilder instance
124+
* @deprecated use {@link #builder(EmbeddingModel)} instead.
124125
*/
126+
@Deprecated(forRemoval = true, since = "1.0.0-M6")
125127
public static Builder builder(EmbeddingModel embeddingModel, String apiKey, String projectId, String environment,
126128
String indexName) {
127129
return new Builder(embeddingModel, apiKey, projectId, environment, indexName);
128130
}
129131

132+
/**
133+
* Creates a new builder for constructing a PineconeVectorStore instance. This builder
134+
* implements a type-safe step pattern that guides users through the required
135+
* configuration fields in a specific order, followed by optional configurations.
136+
*
137+
* Required fields must be provided in this sequence:
138+
* <ol>
139+
* <li>embeddingModel (provided to this method)</li>
140+
* <li>apiKey</li>
141+
* <li>projectId</li>
142+
* <li>environment</li>
143+
* <li>indexName</li>
144+
* </ol>
145+
*
146+
* After all required fields are set, optional configurations can be added using the
147+
* fluent builder pattern.
148+
*
149+
* Example usage: <pre>{@code
150+
* PineconeVectorStore store = PineconeVectorStore.builder(embeddingModel)
151+
* .apiKey("your-api-key")
152+
* .projectId("your-project")
153+
* .environment("your-env")
154+
* .indexName("your-index")
155+
* .namespace("optional") // optional configuration
156+
* .build();
157+
* }</pre>
158+
* @param embeddingModel the embedding model to use for vector transformations
159+
* @return the first step of the builder requiring API key configuration
160+
* @throws IllegalArgumentException if embeddingModel is null
161+
*/
162+
public static Builder.BuilderWithApiKey builder(EmbeddingModel embeddingModel) {
163+
return Builder.StepBuilder.start(embeddingModel);
164+
}
165+
130166
/**
131167
* Adds a list of documents to the vector store based on the namespace.
132168
* @param documents The list of documents to be added.
@@ -339,18 +375,41 @@ public VectorStoreObservationContext.Builder createObservationContextBuilder(Str
339375
}
340376

341377
/**
342-
* Builder class for creating PineconeVectorStore instances.
378+
* Builder class for creating {@link PineconeVectorStore} instances. This implements a
379+
* type-safe step builder pattern to ensure all required fields are provided in a
380+
* specific order before optional configuration.
381+
*
382+
* The required fields must be provided in this sequence: 1. embeddingModel (via
383+
* builder method) 2. apiKey 3. projectId 4. environment 5. indexName
384+
*
385+
* After all required fields are set, optional configurations can be provided using
386+
* the fluent builder pattern.
387+
*
388+
* Example usage: <pre>{@code
389+
* PineconeVectorStore store = PineconeVectorStore.builder(embeddingModel)
390+
* .apiKey("your-api-key")
391+
* .projectId("your-project")
392+
* .environment("your-env")
393+
* .indexName("your-index")
394+
* .namespace("optional") // optional configuration
395+
* .build();
396+
* }</pre>
343397
*/
344398
public static class Builder extends AbstractVectorStoreBuilder<Builder> {
345399

400+
/** Required field for Pinecone API authentication */
346401
private final String apiKey;
347402

403+
/** Required field identifying the Pinecone project */
348404
private final String projectId;
349405

406+
/** Required field specifying the Pinecone environment (e.g. "gcp-starter") */
350407
private final String environment;
351408

409+
/** Required field specifying the Pinecone index name */
352410
private final String indexName;
353411

412+
// Optional fields with default values
354413
private String namespace = "";
355414

356415
private String contentFieldName = CONTENT_FIELD_NAME;
@@ -362,18 +421,127 @@ public static class Builder extends AbstractVectorStoreBuilder<Builder> {
362421
private Builder(EmbeddingModel embeddingModel, String apiKey, String projectId, String environment,
363422
String indexName) {
364423
super(embeddingModel);
365-
366-
Assert.hasText(apiKey, "ApiKey must not be null or empty");
367-
Assert.hasText(projectId, "ProjectId must not be null or empty");
368-
Assert.hasText(environment, "Environment must not be null or empty");
369-
Assert.hasText(indexName, "IndexName must not be null or empty");
370-
371424
this.apiKey = apiKey;
372425
this.projectId = projectId;
373426
this.environment = environment;
374427
this.indexName = indexName;
375428
}
376429

430+
/**
431+
* First step interface requiring API key configuration.
432+
*/
433+
public interface BuilderWithApiKey {
434+
435+
/**
436+
* Sets the Pinecone API key and moves to project ID configuration.
437+
* @param apiKey The Pinecone API key
438+
* @return The next builder step for project ID
439+
* @throws IllegalArgumentException if apiKey is null or empty
440+
*/
441+
BuilderWithProjectId apiKey(String apiKey);
442+
443+
}
444+
445+
/**
446+
* Second step interface requiring project ID configuration.
447+
*/
448+
public interface BuilderWithProjectId {
449+
450+
/**
451+
* Sets the project ID and moves to environment configuration.
452+
* @param projectId The Pinecone project ID
453+
* @return The next builder step for environment
454+
* @throws IllegalArgumentException if projectId is null or empty
455+
*/
456+
BuilderWithEnvironment projectId(String projectId);
457+
458+
}
459+
460+
/**
461+
* Third step interface requiring environment configuration.
462+
*/
463+
public interface BuilderWithEnvironment {
464+
465+
/**
466+
* Sets the environment and moves to index name configuration.
467+
* @param environment The Pinecone environment
468+
* @return The next builder step for index name
469+
* @throws IllegalArgumentException if environment is null or empty
470+
*/
471+
BuilderWithIndexName environment(String environment);
472+
473+
}
474+
475+
/**
476+
* Final step interface requiring index name configuration.
477+
*/
478+
public interface BuilderWithIndexName {
479+
480+
/**
481+
* Sets the index name and returns the builder for optional configuration.
482+
* @param indexName The Pinecone index name
483+
* @return The builder for optional configurations
484+
* @throws IllegalArgumentException if indexName is null or empty
485+
*/
486+
Builder indexName(String indexName);
487+
488+
}
489+
490+
/**
491+
* Internal implementation of the step builder pattern using records for
492+
* immutability. Each step maintains the state from previous steps and implements
493+
* the corresponding interface to ensure type safety and proper sequencing of the
494+
* build steps.
495+
*/
496+
public static class StepBuilder {
497+
498+
private record ApiKeyStep(EmbeddingModel embeddingModel) implements BuilderWithApiKey {
499+
@Override
500+
public BuilderWithProjectId apiKey(String apiKey) {
501+
Assert.hasText(apiKey, "ApiKey must not be null or empty");
502+
return new ProjectIdStep(embeddingModel, apiKey);
503+
}
504+
}
505+
506+
private record ProjectIdStep(EmbeddingModel embeddingModel, String apiKey) implements BuilderWithProjectId {
507+
@Override
508+
public BuilderWithEnvironment projectId(String projectId) {
509+
Assert.hasText(projectId, "ProjectId must not be null or empty");
510+
return new EnvironmentStep(embeddingModel, apiKey, projectId);
511+
}
512+
}
513+
514+
private record EnvironmentStep(EmbeddingModel embeddingModel, String apiKey,
515+
String projectId) implements BuilderWithEnvironment {
516+
@Override
517+
public BuilderWithIndexName environment(String environment) {
518+
Assert.hasText(environment, "Environment must not be null or empty");
519+
return new IndexNameStep(embeddingModel, apiKey, projectId, environment);
520+
}
521+
}
522+
523+
private record IndexNameStep(EmbeddingModel embeddingModel, String apiKey, String projectId,
524+
String environment) implements BuilderWithIndexName {
525+
@Override
526+
public Builder indexName(String indexName) {
527+
Assert.hasText(indexName, "IndexName must not be null or empty");
528+
return new Builder(embeddingModel, apiKey, projectId, environment, indexName);
529+
}
530+
}
531+
532+
/**
533+
* Initiates the step builder sequence with the embedding model.
534+
* @param embeddingModel The embedding model to use
535+
* @return The first step for API key configuration
536+
* @throws IllegalArgumentException if embeddingModel is null
537+
*/
538+
static BuilderWithApiKey start(EmbeddingModel embeddingModel) {
539+
Assert.notNull(embeddingModel, "EmbeddingModel must not be null");
540+
return new ApiKeyStep(embeddingModel);
541+
}
542+
543+
}
544+
377545
/**
378546
* Sets the Pinecone namespace. Note: The free-tier (gcp-starter) doesn't support
379547
* Namespaces.

vector-stores/spring-ai-pinecone-store/src/test/java/org/springframework/ai/vectorstore/pinecone/PineconeVectorStoreIT.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,8 +424,12 @@ public static class TestApplication {
424424
@Bean
425425
public VectorStore vectorStore(EmbeddingModel embeddingModel) {
426426
String apikey = System.getenv("PINECONE_API_KEY");
427-
return PineconeVectorStore
428-
.builder(embeddingModel, apikey, PINECONE_PROJECT_ID, PINECONE_ENVIRONMENT, PINECONE_INDEX_NAME)
427+
428+
return PineconeVectorStore.builder(embeddingModel)
429+
.apiKey(apikey)
430+
.projectId(PINECONE_PROJECT_ID)
431+
.environment(PINECONE_ENVIRONMENT)
432+
.indexName(PINECONE_INDEX_NAME)
429433
.namespace(PINECONE_NAMESPACE)
430434
.contentFieldName(CUSTOM_CONTENT_FIELD_NAME)
431435
.build();

vector-stores/spring-ai-pinecone-store/src/test/java/org/springframework/ai/vectorstore/pinecone/PineconeVectorStoreObservationIT.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,11 @@ public TestObservationRegistry observationRegistry() {
188188

189189
@Bean
190190
public VectorStore vectorStore(EmbeddingModel embeddingModel, ObservationRegistry observationRegistry) {
191-
return PineconeVectorStore
192-
.builder(embeddingModel, System.getenv("PINECONE_API_KEY"), PINECONE_PROJECT_ID, PINECONE_ENVIRONMENT,
193-
PINECONE_INDEX_NAME)
191+
return PineconeVectorStore.builder(embeddingModel)
192+
.apiKey(System.getenv("PINECONE_API_KEY"))
193+
.projectId(PINECONE_PROJECT_ID)
194+
.environment(PINECONE_ENVIRONMENT)
195+
.indexName(PINECONE_INDEX_NAME)
194196
.namespace(PINECONE_NAMESPACE)
195197
.contentFieldName(CUSTOM_CONTENT_FIELD_NAME)
196198
.observationRegistry(observationRegistry)

0 commit comments

Comments
 (0)