From 4f62ac1ceeb49e086251588028e22bdea3c6ec34 Mon Sep 17 00:00:00 2001
From: Laura Trotta <153528055+l-trotta@users.noreply.github.com>
Date: Mon, 9 Sep 2024 17:58:29 +0200
Subject: [PATCH] GH-1316: Elasticsearch vector store - Wrong error reported
 fix

Fixes: https://github.com/spring-projects/spring-ai/issues/1316

* Handling missing index case
* Docs update
* User header for observability
* Same exception management for delete
---
 .../pages/api/vectordbs/elasticsearch.adoc    |  2 ++
 .../vectorstore/ElasticsearchVectorStore.java | 25 ++++++++++---------
 2 files changed, 15 insertions(+), 12 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/elasticsearch.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/elasticsearch.adoc
index 1a70b2bd19c..a791e588b25 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/elasticsearch.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/elasticsearch.adoc
@@ -50,12 +50,14 @@ TIP: Refer to the xref:getting-started.adoc#repositories[Repositories] section t
 
 
 The vector store implementation can initialize the requisite schema for you, but you must opt-in by specifying the `initializeSchema` boolean in the appropriate constructor or by setting `...initialize-schema=true` in the `application.properties` file.
+Alternatively you can opt-out the initialization and create the index manually using the Elasticsearch client, which can be useful if the index needs advanced mapping or additional configuration.
 
 NOTE: this is a breaking change! In earlier versions of Spring AI, this schema initialization happened by default.
 
 
 
 Please have a look at the list of <<elasticsearchvector-properties,configuration parameters>> for the vector store to learn about the default values and configuration options.
+These properties can be also set by configuring the `ElasticsearchVectorStoreOptions` bean.
 
 Additionally, you will need a configured `EmbeddingModel` bean. Refer to the xref:api/embeddings.adoc#available-implementations[EmbeddingModel] section for more information.
 
diff --git a/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/ElasticsearchVectorStore.java b/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/ElasticsearchVectorStore.java
index 56db121a85f..ce4630cd738 100644
--- a/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/ElasticsearchVectorStore.java
+++ b/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/ElasticsearchVectorStore.java
@@ -105,6 +105,7 @@ public ElasticsearchVectorStore(ElasticsearchVectorStoreOptions options, RestCli
 		Objects.requireNonNull(embeddingModel, "EmbeddingModel must not be null");
 		this.elasticsearchClient = new ElasticsearchClient(new RestClientTransport(restClient, new JacksonJsonpMapper(
 				new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false))));
+		elasticsearchClient.withTransportOptions(t -> t.addHeader("user-agent", "spring-ai"));
 		this.embeddingModel = embeddingModel;
 		this.options = options;
 		this.filterExpressionConverter = new ElasticsearchAiSearchFilterExpressionConverter();
@@ -112,6 +113,11 @@ public ElasticsearchVectorStore(ElasticsearchVectorStoreOptions options, RestCli
 
 	@Override
 	public void doAdd(List<Document> documents) {
+		// For the index to be present, either it must be pre-created or set the
+		// initializeSchema to true.
+		if (!indexExists()) {
+			throw new IllegalArgumentException("Index not found");
+		}
 		BulkRequest.Builder bulkRequestBuilder = new BulkRequest.Builder();
 
 		for (Document document : documents) {
@@ -119,13 +125,8 @@ public void doAdd(List<Document> documents) {
 				logger.debug("Calling EmbeddingModel for document id = " + document.getId());
 				document.setEmbedding(this.embeddingModel.embed(document));
 			}
-			// We call operations on BulkRequest.Builder only if the index exists.
-			// For the index to be present, either it must be pre-created or set the
-			// initializeSchema to true.
-			if (indexExists()) {
-				bulkRequestBuilder.operations(op -> op
-					.index(idx -> idx.index(this.options.getIndexName()).id(document.getId()).document(document)));
-			}
+			bulkRequestBuilder.operations(op -> op
+				.index(idx -> idx.index(this.options.getIndexName()).id(document.getId()).document(document)));
 		}
 		BulkResponse bulkRequest = bulkRequest(bulkRequestBuilder.build());
 		if (bulkRequest.errors()) {
@@ -141,13 +142,13 @@ public void doAdd(List<Document> documents) {
 	@Override
 	public Optional<Boolean> doDelete(List<String> idList) {
 		BulkRequest.Builder bulkRequestBuilder = new BulkRequest.Builder();
-		// We call operations on BulkRequest.Builder only if the index exists.
 		// For the index to be present, either it must be pre-created or set the
 		// initializeSchema to true.
-		if (indexExists()) {
-			for (String id : idList) {
-				bulkRequestBuilder.operations(op -> op.delete(idx -> idx.index(this.options.getIndexName()).id(id)));
-			}
+		if (!indexExists()) {
+			throw new IllegalArgumentException("Index not found");
+		}
+		for (String id : idList) {
+			bulkRequestBuilder.operations(op -> op.delete(idx -> idx.index(this.options.getIndexName()).id(id)));
 		}
 		return Optional.of(bulkRequest(bulkRequestBuilder.build()).errors());
 	}