Skip to content

GH-3540: Allow user-provided embeddings in VectorStore - #3541

Closed
aniketg-21 wants to merge 3 commits into
spring-projects:mainfrom
aniketg-21:GH-3540
Closed

aniketg-21 wants to merge 3 commits into
spring-projects:mainfrom
aniketg-21:GH-3540

Conversation

@aniketg-21

Copy link
Copy Markdown

🚀 Motivation

Current VectorStore implementations (e.g., ChromaVectorStore) automatically generate embeddings from Document.content using the configured EmbeddingModel. However, more advanced use cases require greater flexibility:

  1. Precomputed embeddings: Embeddings may be generated externally, such as via batch pipelines or fine-tuned models.
  2. Embedding semantic intent: In some cases, embeddings should reflect a prompt, summary, or intent—not the raw content.
  3. Structured data: When storing structured formats (e.g., JSON), embedding the full raw structure often reduces semantic quality.

🔧 Enhancement

This PR introduces support for user-supplied embeddings, allowing applications to decouple document storage from semantic representation. This separation enables more accurate and intentional embedding strategies for search and retrieval.

  • If no embedding is provided, the default behavior—auto-generating embeddings from content—is preserved.
  • This change improves semantic search relevance without compromising the integrity or structure of stored documents.

✅ Backward Compatibility

Fully maintained. The default pipeline continues to auto-generate embeddings when none are explicitly provided.

🧪 Testing & Limitations

This feature has been tested with SimpleVectorStore. Other implementations (e.g., Pinecone, Milvus) were not tested due to environment constraints and may require further validation or adapter adjustments.

✅ Issue Closed

Closes #3540

Signed-off-by: Aniket Gupta <70100745+aniketg-21@users.noreply.github.com>
@markpollack

Copy link
Copy Markdown
Contributor

had a brief look, thanks for the PR. design wise

void add(List<Document> documents, List<float[]> embeddings);

should instead be a pair object, so that there is no ambiguity of which document has which embedding vs. two separate args

@markpollack markpollack added the enhancement New feature or request label Jul 11, 2025
@aniketg-21

Copy link
Copy Markdown
Author

@markpollack thanks for your suggestion. @sobychacko its ready to be reviewed.

@sobychacko

Copy link
Copy Markdown
Contributor

@aniketg-21 Apologies for not getting back to this PR sooner — it wasn't in the roadmap for previous releases, but we're finally at a point where we're pulling the general themes you describe into a feature item for 2.1.x.

Thanks for putting this together, and for iterating on Mark's feedback — really appreciate the effort here.

The underlying need (bringing your own precomputed embeddings) is something we definitely want, and I've actually got a version of this in progress on our end as part of a broader set of vector store additions — so I'd like to bring this in through that. The tradeoff I'm weighing is backward compatibility: changing the doAdd signature and adding a non-default method to the VectorStore interface ends up breaking custom stores that extend AbstractObservationVectorStore, so I'm going with a purely additive path — a new upsert(List<EmbeddedDocument>) default method that stores opt into, leaving add/doAdd untouched.

Your direction carries straight over, though: the EmbeddedDocument pair object is exactly what Mark was pointing at, and the embedding validation you added (dimension consistency, NaN/Inf checks) is something I want to keep. I'm going with upsert (replace-by-id) rather than a plain add since it's retry-safe and a bit more useful as a primitive.

Since it's already in progress on our side, please hold off on any further changes here — I don't want you spending cycles on work that'd end up superseded. I'll keep this open and link back once it's up so the lineage is clear and you get credit. Thanks again — the proposal and discussion here genuinely helped shape it. 🙏

@aniketg-21

Copy link
Copy Markdown
Author

Thanks for the update and detailed context!

The additive upsert(List<EmbeddedDocument>) approach makes total sense—maintaining backward compatibility. I'm glad the discussion, validation checks, and general structure were useful in shaping the feature for 2.1.x.

I'll hold off on further changes here as requested.
Thanks again, and I look forward to seeing it land in 2.1.x! 🚀

sobychacko added a commit to sobychacko/spring-ai that referenced this pull request Sep 16, 2026
Fixes spring-projects#6989

Bring-your-own-vector write path: an EmbeddedDocument record pairing a
Document with a caller-supplied vector, and a default
VectorStore.upsert that throws so stores opt in. Wired into
AbstractObservationVectorStore (UPSERT observation + doUpsert), with
replace-by-id implementations for pgvector, Redis, Elasticsearch, and
Qdrant. Adds a shared AbstractVectorStoreUpsertTests TCK, the
DocumentMetadata.CONTENT_REF key for non-text content, and reference
docs.

See also spring-projects#3540 and spring-projects#3541

Signed-off-by: Soby Chacko <soby.chacko@broadcom.com>
sobychacko added a commit to sobychacko/spring-ai that referenced this pull request Sep 16, 2026
Fixes spring-projects#6989

User supplied embeddings write path: an EmbeddedDocument record pairing a
Document with a caller-supplied vector, and a default
VectorStore.upsert that throws so stores opt in. Wired into
AbstractObservationVectorStore (UPSERT observation + doUpsert), with
replace-by-id implementations for pgvector, Redis, Elasticsearch, and
Qdrant. Adds a shared AbstractVectorStoreUpsertTests TCK, the
DocumentMetadata.CONTENT_REF key for non-text content, and reference
docs.

See also spring-projects#3540 and spring-projects#3541

Signed-off-by: Soby Chacko <soby.chacko@broadcom.com>
@sobychacko

sobychacko commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

As discussed above we are closing this PR. The new PR based on the new design is here: #7002

Thanks @aniketg-21 for the initial inspiration for these changes.

@sobychacko sobychacko closed this Sep 16, 2026
sobychacko added a commit to sobychacko/spring-ai that referenced this pull request Sep 18, 2026
Fixes spring-projects#6989

User supplied embeddings write path: an EmbeddedDocument record pairing a
Document with a caller-supplied vector, and a default
VectorStore.upsert that throws so stores opt in. Wired into
AbstractObservationVectorStore (UPSERT observation + doUpsert), with
replace-by-id implementations for pgvector, Redis, Elasticsearch, and
Qdrant. Adds a shared AbstractVectorStoreUpsertTests TCK, the
DocumentMetadata.CONTENT_REF key for non-text content, and reference
docs.

See also spring-projects#3540 and spring-projects#3541

Signed-off-by: Soby Chacko <soby.chacko@broadcom.com>
sobychacko added a commit to sobychacko/spring-ai that referenced this pull request Sep 18, 2026
Every store computes embeddings inside add(), so there was no way to
write vectors you already have, whether they came from a batch API,
another team's pipeline, or a multimodal model over an image.

upsert takes a document and its vector together and stores the vector
as given. Writing the same id again replaces the row, so a job that
uses stable ids can be re-run without creating duplicates. Stores opt
in one at a time: the default implementation throws, and add() is
untouched.

- EmbeddedDocument pairs a Document with a float[], rejecting empty
  and non-finite vectors
- pgvector, Redis, Elasticsearch and Qdrant implement it, each
  checking the whole batch against its index width before writing
- Media documents are rejected, as add() rejects them, because every
  store needs text on the row; empty text is still allowed
- upsert appears in metrics as its own operation
- A shared test suite runs the same checks against all four stores,
  on Testcontainers and without an API key
- DocumentMetadata.CONTENT_REF holds a pointer to content kept
  outside the store, for a row whose vector came from an image or a
  file too large to inline. Nothing in the framework reads it
- Redis returns only the metadata fields declared when the store is
  built, and now warns when a document carries others

Fixes spring-projects#6989

See also spring-projects#3540 and spring-projects#3541

Signed-off-by: Soby Chacko <soby.chacko@broadcom.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support Custom User-Provided Embeddings in VectorStore add(...) Method

4 participants