Skip to content

Commit 7877ebd

Browse files
nseidandantuzi
authored andcommitted
DAGE-19: Add OpenSearch support
1 parent b4dbf0c commit 7877ebd

11 files changed

Lines changed: 731 additions & 1 deletion

File tree

rre-dataset-generator/README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ output_destination: "output/generated_dataset.json"
4545
4646
### 3. Running the Generator
4747
48+
Before running the command below, you need to have running search engine instance (`solr`/`opensearch`/`elasticsearch`/`vespa`).
49+
50+
4851
Execute the main script via the `argparse` CLI, pointing to your configuration file:
4952
```bash
5053
uv run dataset_generator.py --config_file config.yaml
@@ -98,4 +101,28 @@ docker compose up --build
98101

99102
This will start 2 services:
100103
- `solr`, available at http://localhost:8983/solr
101-
- `solr-init`, loads documents from solr/data/dataset.json only if Solr doesn't index any documents.
104+
- `solr-init`, loads documents from solr-init/data/dataset.json.
105+
106+
107+
##### Running OpenSearch (Single Node)
108+
109+
To run a local OpenSearch test environment using docker-compose:
110+
```bash
111+
cd tests/integration/
112+
```
113+
114+
Depending on your Docker version, you may need to use `docker compose` instead of `docker-compose`.
115+
If you have Docker Compose v1 installed, use:
116+
117+
```bash
118+
docker-compose -f docker-compose.opensearch.yml up --build
119+
```
120+
If you have Docker Compose v2 installed, use:
121+
```bash
122+
docker compose -f docker-compose.opensearch.yml up --build
123+
```
124+
125+
This will start 2 services:
126+
- `opensearch`, available at http://localhost:9200/
127+
- `opensearch-init`, loads documents (`bulk indexing`) from opensearch-init/data/dataset.jsonl.
128+

rre-dataset-generator/config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ query_template: "q=#$query##&df=description&rows=2&q.op=OR&wt=json"
99
search_engine_type: "solr"
1010

1111
# Endpoint URL of the search engine collection or index
12+
# opensearch: http://localhost:9200/testcore/
1213
search_engine_collection_endpoint: "http://localhost:8983/solr/testcore/"
1314

1415
# (Optional) Filter query to restrict the set of documents used to generate queries.
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import logging
2+
from typing import List, Dict, Any, Union
3+
4+
import requests
5+
from pydantic import HttpUrl
6+
from requests.exceptions import HTTPError, ConnectionError, Timeout, RequestException
7+
8+
from src.model.document import Document
9+
from src.search_engine.search_engine_base import BaseSearchEngine
10+
from src.utils import clean_text
11+
12+
log = logging.getLogger(__name__)
13+
14+
15+
class OpenSearchEngine(BaseSearchEngine):
16+
"""
17+
OpenSearch implementation to search in a given index.
18+
"""
19+
20+
def __init__(self, endpoint: HttpUrl):
21+
super().__init__(endpoint)
22+
self.HEADERS = {'Content-Type': 'application/json'}
23+
self.UNIQUE_KEY = "id"
24+
25+
def fetch_for_query_generation(self,
26+
documents_filter: Union[None, List[Dict[str, List[str]]]],
27+
doc_number: int,
28+
doc_fields: List[str]) -> List[Document]:
29+
filters = []
30+
if documents_filter:
31+
for field_values in documents_filter:
32+
for field, values in field_values.items():
33+
if not values:
34+
continue
35+
if len(values) == 1:
36+
filters.append({"term": {field: values[0]}})
37+
else:
38+
filters.append({"terms": {field: values}})
39+
40+
fields = doc_fields if self.UNIQUE_KEY in doc_fields else doc_fields + [self.UNIQUE_KEY]
41+
42+
if filters:
43+
query = {
44+
"bool": {
45+
"filter": filters
46+
}
47+
}
48+
else:
49+
query = {
50+
"match_all": {}
51+
}
52+
53+
payload = {
54+
"query": query,
55+
"_source": fields,
56+
"size": doc_number
57+
}
58+
59+
return self._search(payload)
60+
61+
def fetch_for_evaluation(self, query_template: str, doc_fields: List[str], keyword: str = "*") -> List[Document]:
62+
query = query_template.replace(self.PLACEHOLDER, keyword)
63+
fields = doc_fields if self.UNIQUE_KEY in doc_fields else doc_fields + [self.UNIQUE_KEY]
64+
65+
payload = {
66+
"query": {
67+
"query_string": {
68+
"query": query
69+
}
70+
},
71+
"_source": fields
72+
}
73+
return self._search(payload)
74+
75+
def _search(self, payload: Dict[str, Any]) -> List[Document]:
76+
search_url = f"{self.endpoint}/_search"
77+
try:
78+
response = requests.post(search_url, headers=self.HEADERS, json=payload)
79+
response.raise_for_status()
80+
except (ConnectionError, Timeout, RequestException, HTTPError) as e:
81+
log.error(f"OpenSearch query failed: {e}\nPayload: {payload}")
82+
raise
83+
84+
hits = response.json().get("hits", {}).get("hits", [])
85+
result = []
86+
87+
for hit in hits:
88+
source = hit.get("_source", {})
89+
doc_id = source.get("id", hit.get("_id"))
90+
91+
fields = {
92+
key: self._normalize(value)
93+
for key, value in source.items()
94+
if key != "id"
95+
}
96+
97+
result.append(Document(id=doc_id, fields=fields))
98+
99+
return result
100+
101+
@staticmethod
102+
def _normalize(value: Any) -> Any:
103+
"""
104+
Normalize a field value:
105+
- string → return [cleaned string]
106+
- list of strings → return [cleaned strings]
107+
- else → return as it is
108+
"""
109+
if isinstance(value, str):
110+
return [clean_text(value)]
111+
if isinstance(value, list) and all(isinstance(i, str) for i in value):
112+
return [clean_text(i) for i in value]
113+
return value

rre-dataset-generator/src/search_engine/search_engine_factory.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
from pydantic import HttpUrl
22

3+
from .opensearch_engine import OpenSearchEngine
34
from .search_engine_base import BaseSearchEngine
45
from .solr_search_engine import SolrSearchEngine
56
import logging
67

78
log = logging.getLogger(__name__)
89

10+
911
class SearchEngineFactory:
1012
SEARCH_ENGINE_REGISTRY = {
1113
"solr": SolrSearchEngine,
14+
"opensearch": OpenSearchEngine,
1215
}
16+
1317
@classmethod
1418
def build(cls, search_engine_type: str, endpoint: HttpUrl) -> BaseSearchEngine:
1519
if search_engine_type not in cls.SEARCH_ENGINE_REGISTRY:
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
services:
2+
opensearch:
3+
image: opensearchproject/opensearch:3.1.0
4+
container_name: opensearch
5+
environment:
6+
- discovery.type=single-node
7+
- node.name=opensearch
8+
- bootstrap.memory_lock=true
9+
- DISABLE_INSTALL_DEMO_CONFIG=true
10+
- DISABLE_SECURITY_PLUGIN=true
11+
ports:
12+
- 9200:9200
13+
- 9600:9600
14+
15+
opensearch-dashboards:
16+
image: opensearchproject/opensearch-dashboards:3.1.0
17+
container_name: opensearch-dashboards
18+
ports:
19+
- 5601:5601
20+
expose:
21+
- "5601"
22+
environment:
23+
- OPENSEARCH_HOSTS=http://opensearch:9200
24+
- DISABLE_SECURITY_DASHBOARDS_PLUGIN=true
25+
depends_on:
26+
- opensearch
27+
28+
opensearch-init:
29+
build:
30+
context: opensearch-init
31+
depends_on:
32+
- opensearch
33+
command: ["sh", "-c", "/opt/rre-dataset-generator/opensearch-init.sh"]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
FROM curlimages/curl:8.14.1
2+
3+
COPY data/dataset.jsonl /opt/rre-dataset-generator/data/dataset.jsonl
4+
COPY --chmod=777 opensearch-init.sh /opt/rre-dataset-generator/opensearch-init.sh

0 commit comments

Comments
 (0)