Repository: deepset-ai/haystack

Issue:
`OpenSearchDocumentStore` has wrong `embeddings_field_supports_similarity` if creating index
**Describe the bug**
If a query pipeline get's deployed before the index pipeline and has set `create_index` to false, then the `OpenSearchDocumentStore` doesn't set `embeddings_field_supports_similarity` to `True`. 
The issue is the lack of a `embeddings_field_supports_similarity=True` [here](https://github.com/deepset-ai/haystack/blob/374155fd5c2d990cf51573a6da50314c3a07f926/haystack/document_stores/opensearch.py#L379) in case the index doesn't exist.

Related issues:
- https://github.com/deepset-ai/haystack/issues/2802
- https://github.com/deepset-ai/haystack-hub-api/issues/1121

**Error message**
Error that was thrown (if available)

**Expected behavior**
`embeddings_field_supports_similarity ` is set correctly even if the index didn't exist before.

**Additional context**
Happed with a customer on Deepset Cloud.

**To Reproduce**
1. Deploy a dense query pipeline with `create_index=True`
2. Deploy a index pipeline 
3. Dense query pipeline has `embeddings_field_supports_similarity = False`





Discussion:
@wochinge if there is no index yet how should the code infer whether the embedding field supports the requested similarity type?
In your case you know that the embedding field will support it because you have some external context knowledge that there will definitely be an indexing pipeline that will create the index such that the embedding field supports the similarity function. Hence you would need to pass that additional piece of information explicitly.
So I'd suggest to simply set `embeddings_field_supports_similarity` after the constructor call. 
Adding an additional param for that wouldn't do anything else and so wouldn't be worth the overhead I guess. But maybe I'm missing something. WDYT?
Ah sorry, I missed it's not even set when creating the index. Will fix that, however supporting custom_mappings will be slightly more complicated. Probably it makes sense to separate the check from the index creation logic.
> Ah sorry, I missed it's not even set when creating the index

Yes, that was my fault. Our initial theory was somewhat reversed. The issue is indeed that we only set `embeddings_field_supports_similarity` _when_ we're _not_ creating an index.

> Probably it makes sense to separate the check from the index creation logic.

Agree 👍🏻 

Resolving patch:
diff --git a/haystack/document_stores/opensearch.py b/haystack/document_stores/opensearch.py
--- a/haystack/document_stores/opensearch.py
+++ b/haystack/document_stores/opensearch.py
@@ -427,26 +427,21 @@ def _create_document_index(self, index_name: str, headers: Optional[Dict[str, st
                         embedding_field_space_type = index_settings["knn.space_type"]
                     # embedding field with local space_type setting
                     else:
-                        # embedding field with global space_type setting
-                        if "method" not in mappings["properties"][self.embedding_field]:
-                            embedding_field_space_type = index_settings["knn.space_type"]
-                        # embedding field with local space_type setting
-                        else:
-                            embedding_field_space_type = mappings["properties"][self.embedding_field]["method"][
-                                "space_type"
-                            ]
-
-                        embedding_field_similarity = self.space_type_to_similarity[embedding_field_space_type]
-                        if embedding_field_similarity == self.similarity:
-                            self.embeddings_field_supports_similarity = True
-                        else:
-                            logger.warning(
-                                f"Embedding field '{self.embedding_field}' is optimized for similarity '{embedding_field_similarity}'. "
-                                f"Falling back to slow exact vector calculation. "
-                                f"Consider cloning the embedding field optimized for '{embedding_field_similarity}' by calling clone_embedding_field(similarity='{embedding_field_similarity}', ...) "
-                                f"or creating a new index optimized for '{self.similarity}' by setting `similarity='{self.similarity}'` the first time you instantiate OpenSearchDocumentStore for the new index, "
-                                f"e.g. `OpenSearchDocumentStore(index='my_new_{self.similarity}_index', similarity='{self.similarity}')`."
-                            )
+                        embedding_field_space_type = mappings["properties"][self.embedding_field]["method"][
+                            "space_type"
+                        ]
+
+                    embedding_field_similarity = self.space_type_to_similarity[embedding_field_space_type]
+                    if embedding_field_similarity == self.similarity:
+                        self.embeddings_field_supports_similarity = True
+                    else:
+                        logger.warning(
+                            f"Embedding field '{self.embedding_field}' is optimized for similarity '{embedding_field_similarity}'. "
+                            f"Falling back to slow exact vector calculation. "
+                            f"Consider cloning the embedding field optimized for '{embedding_field_similarity}' by calling clone_embedding_field(similarity='{embedding_field_similarity}', ...) "
+                            f"or creating a new index optimized for '{self.similarity}' by setting `similarity='{self.similarity}'` the first time you instantiate OpenSearchDocumentStore for the new index, "
+                            f"e.g. `OpenSearchDocumentStore(index='my_new_{self.similarity}_index', similarity='{self.similarity}')`."
+                        )
 
                 # Adjust global ef_search setting. If not set, default is 512.
                 ef_search = index_settings.get("knn.algo_param", {"ef_search": 512}).get("ef_search", 512)
@@ -498,6 +493,7 @@ def _create_document_index(self, index_name: str, headers: Optional[Dict[str, st
                 )
 
         try:
+            self.embeddings_field_supports_similarity = True
             self.client.indices.create(index=index_name, body=index_definition, headers=headers)
         except RequestError as e:
             # With multiple workers we need to avoid race conditions, where:


Test patch:
diff --git a/test/document_stores/test_opensearch.py b/test/document_stores/test_opensearch.py
--- a/test/document_stores/test_opensearch.py
+++ b/test/document_stores/test_opensearch.py
@@ -1,4 +1,3 @@
-import sys
 import logging
 
 from unittest.mock import MagicMock
@@ -390,9 +389,7 @@ def test__create_document_index_with_existing_mapping_but_no_method(self, mocked
         mocked_document_store.embedding_field = "vec"
 
         mocked_document_store._create_document_index(self.index_name)
-        # FIXME: when `method` is missing from the field mapping, embeddings_field_supports_similarity is always
-        # False but I'm not sure this is by design
-        assert mocked_document_store.embeddings_field_supports_similarity is False
+        assert mocked_document_store.embeddings_field_supports_similarity is True
 
     @pytest.mark.unit
     def test__create_document_index_with_existing_mapping_similarity(self, mocked_document_store, index):
@@ -494,6 +491,7 @@ def test__create_document_index_no_index_custom_mapping(self, mocked_document_st
         mocked_document_store._create_document_index(self.index_name)
         _, kwargs = mocked_document_store.client.indices.create.call_args
         assert kwargs["body"] == {"mappings": {"properties": {"a_number": {"type": "integer"}}}}
+        assert mocked_document_store.embeddings_field_supports_similarity is True
 
     @pytest.mark.unit
     def test__create_document_index_no_index_no_mapping(self, mocked_document_store):
@@ -522,6 +520,7 @@ def test__create_document_index_no_index_no_mapping(self, mocked_document_store)
             },
             "settings": {"analysis": {"analyzer": {"default": {"type": "standard"}}}, "index": {"knn": True}},
         }
+        assert mocked_document_store.embeddings_field_supports_similarity is True
 
     @pytest.mark.unit
     def test__create_document_index_no_index_no_mapping_with_synonyms(self, mocked_document_store):
@@ -563,6 +562,7 @@ def test__create_document_index_no_index_no_mapping_with_synonyms(self, mocked_d
                 "index": {"knn": True},
             },
         }
+        assert mocked_document_store.embeddings_field_supports_similarity is True
 
     @pytest.mark.unit
     def test__create_document_index_no_index_no_mapping_with_embedding_field(self, mocked_document_store):
@@ -597,6 +597,7 @@ def test__create_document_index_no_index_no_mapping_with_embedding_field(self, m
                 "index": {"knn": True, "knn.algo_param.ef_search": 20},
             },
         }
+        assert mocked_document_store.embeddings_field_supports_similarity is True
 
     @pytest.mark.unit
     def test__create_document_index_client_failure(self, mocked_document_store):