Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ title: OpenSearch Serverless Vector Store

- [Overview](#overview)
- [Install dependencies](#install-dependencies)
- [Creating an OpenSearch Serverless vector store](#creating-an-opensearch-serverless-vector-store)
- [Creating an Amazon OpenSearch Serverless vector store](#creating-an-opensearch-serverless-vector-store)
- [Amazon OpenSearch Serverless and custom document IDs](#amazon-opensearch-serverless-and-custom-document-ids)
- [Verify and repair an Amazon OpenSearch Serverless vector store](#verify-and-repair-an-amazon-opensearch-serverless-vector-store)
- [Connecting to an OpenSearch vector store](#connecting-to-an-opensearch-vector-store)

### Overview

Expand Down Expand Up @@ -130,3 +131,38 @@ Field descriptions:
| `num_docs` | Number of documents in a specific tenant vector index |
| `num_deleted` | Number of documents deleted from a specific tenant vector index (indicative number only if `dry_run` is `true`) |
| `num_unindexed` | Number of nodes that have not been indexed in a specific tenant vector index |

### Connecting to an OpenSearch vector store

You can use [OpenSearch](https://opensearch.org) directly as a vector store — in a Docker or Finch container, on an EC2 instance, or on your own infrastructure. graphrag-toolkit connects to it with the [opensearch-py](https://pypi.org/project/opensearch-py/) client. Supply a connection string that begins with `opensearch://`, followed by the endpoint:

```python
from graphrag_toolkit.lexical_graph.storage import VectorStoreFactory

opensearch_connection_info = 'opensearch://localhost:9200'

with VectorStoreFactory.for_vector_store(
opensearch_connection_info,
index_names=['chunk']
) as vector_store:
...
```

The endpoint doesn't need a scheme: `opensearch://localhost:9200` connects over SSL, the toolkit's default (matching OpenSearch, which ships with SSL enabled). Use an explicit `http://` scheme for a plain-HTTP instance: `opensearch://http://localhost:9200`. An endpoint without a port defaults to 9200; for an endpoint on port 443, include the port or an `https://` scheme.

The client authenticates with HTTP basic auth from the `OPENSEARCH_USERNAME` and `OPENSEARCH_PASSWORD` environment variables (or `GraphRAGConfig.opensearch_username` / `GraphRAGConfig.opensearch_password`), or with no auth if neither is set.

Any `client_kwargs` you pass to `VectorStoreFactory.for_vector_store` are forwarded to the opensearch-py client, so all of its [connection options](https://opensearch-project.github.io/opensearch-py/api-ref/clients/opensearch_client.html) — certificate verification, CA bundles, timeouts — are available. For example, for an endpoint with a self-signed TLS certificate:

```python
with VectorStoreFactory.for_vector_store(
'opensearch://https://localhost:9200',
index_names=['chunk'],
client_kwargs={'verify_certs': False}
) as vector_store:
...
```

#### Limitations

Authentication methods other than basic auth, including SigV4 for IAM-authenticated domains, are not supported yet ([#407](https://github.com/awslabs/graphrag-toolkit/issues/407)).
7 changes: 5 additions & 2 deletions lexical-graph/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ If you're running on AWS, you must run your application in an AWS region contain

You will need to install additional dependencies for specific graph and vector store backends:

#### Amazon OpenSearch Serverless
#### OpenSearch and Amazon OpenSearch Serverless (AOSS)

```bash
$ pip install opensearch-py llama-index-vector-stores-opensearch
Expand All @@ -68,12 +68,15 @@ Pass a connection string to `GraphStoreFactory.for_graph_store()` or `VectorStor
| Neptune Analytics (graph) | `neptune-graph://<graph-id>` |
| Neptune Database (graph) | `neptune-db://<hostname>` or any hostname ending `.neptune.amazonaws.com` |
| Neo4j (graph) | `bolt://`, `bolt+ssc://`, `bolt+s://`, `neo4j://`, `neo4j+ssc://`, or `neo4j+s://` URLs |
| OpenSearch Serverless (vector) | `aoss://<url>` |
| Amazon OpenSearch Serverless / AOSS (vector) | `aoss://<url>` |
| OpenSearch (vector) | `opensearch://<url>` |
| Neptune Analytics (vector) | `neptune-graph://<graph-id>` |
| pgvector (vector) | constructed via `PGVectorIndexFactory` |
| S3 Vectors (vector) | constructed via `S3VectorIndexFactory` |
| Dummy / no-op | `None` or any unrecognised string — falls back to `DummyGraphStore` / `DummyVectorIndex` |

An `opensearch://<url>` endpoint connects with HTTP basic auth from the `OPENSEARCH_USERNAME` / `OPENSEARCH_PASSWORD` environment variables (no auth if unset), over SSL unless the endpoint has an explicit `http://` scheme. `client_kwargs` passed to `VectorStoreFactory.for_vector_store` are forwarded to the [opensearch-py](https://pypi.org/project/opensearch-py/) client. See [the vector store documentation](https://awslabs.github.io/graphrag-toolkit/lexical-graph/vector-store-opensearch-serverless/) for details and limitations.

## Example of use

### Indexing
Expand Down
27 changes: 27 additions & 0 deletions lexical-graph/src/graphrag_toolkit/lexical_graph/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,8 @@ class _GraphRAGConfig:
_enable_cache: Optional[bool] = None
_metadata_datetime_suffixes: Optional[List[str]] = None
_opensearch_engine: Optional[str] = None
_opensearch_username: Optional[str] = None
_opensearch_password: Optional[str] = None
_opensearch_serverless_generation: Optional['OpenSearchServerlessGeneration'] = None
_opensearch_serverless_nextgen_compression: Optional[str] = None
_enable_versioning = None
Expand Down Expand Up @@ -1203,6 +1205,31 @@ def opensearch_engine(self) -> str:
def opensearch_engine(self, opensearch_engine: str) -> None:
self._opensearch_engine = opensearch_engine

@property
def opensearch_username(self) -> Optional[str]:
"""Basic-auth username for an OpenSearch endpoint that isn't Amazon OpenSearch
Serverless (AOSS). Unset (None) means no basic auth is applied -- used with an
OpenSearch instance that has no security plugin configured."""
if self._opensearch_username is None:
self._opensearch_username = os.environ.get('OPENSEARCH_USERNAME')
return self._opensearch_username

@opensearch_username.setter
def opensearch_username(self, opensearch_username: Optional[str]) -> None:
self._opensearch_username = opensearch_username

@property
def opensearch_password(self) -> Optional[str]:
"""Basic-auth password for an OpenSearch endpoint that isn't Amazon OpenSearch
Serverless (AOSS). See opensearch_username."""
if self._opensearch_password is None:
self._opensearch_password = os.environ.get('OPENSEARCH_PASSWORD')
return self._opensearch_password

@opensearch_password.setter
def opensearch_password(self, opensearch_password: Optional[str]) -> None:
self._opensearch_password = opensearch_password

@property
def opensearch_serverless_generation(self) -> Optional['OpenSearchServerlessGeneration']:
"""Unset (None) auto-detects Classic vs NextGen: index_exists() tries the Classic
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

OPENSEARCH_SERVERLESS = 'aoss://'
OPENSEARCH_SERVERLESS_DNS = 'aoss.amazonaws.com'
OPENSEARCH = 'opensearch://'

class OpenSearchVectorIndexFactory(VectorIndexFactoryMethod):
"""Factory class for creating OpenSearch vector indexes.
Expand Down Expand Up @@ -43,19 +44,25 @@ def try_create(self, index_names:List[str], vector_index_info:str, **kwargs) ->
names and endpoint configuration, or None if no suitable endpoint is found.
"""
endpoint = None
is_sigv4_auth = True
if vector_index_info.startswith(OPENSEARCH_SERVERLESS):
endpoint = vector_index_info[len(OPENSEARCH_SERVERLESS):]
if not endpoint.startswith('https://') and not endpoint.startswith('http://'):
endpoint = f'https://{endpoint}'
elif vector_index_info.startswith(OPENSEARCH):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider remove this constraint on the endpoint. If it starts with aoss:// - it's an amazon opensearch serverless endpoint. Otherwise, it's for opensearch endpoint.

endpoint = vector_index_info[len(OPENSEARCH):]
if not endpoint:
raise ValueError(f'Empty endpoint in OpenSearch vector store connection info: {vector_index_info}')
is_sigv4_auth = False
elif vector_index_info.startswith('https://') and vector_index_info.endswith(OPENSEARCH_SERVERLESS_DNS):
endpoint = vector_index_info
if endpoint:
try:
from graphrag_toolkit.lexical_graph.storage.vector.opensearch_vector_indexes import OpenSearchIndex
logger.debug(f'Opening OpenSearch vector indexes [index_names: {index_names}, endpoint: {endpoint}]')
return [OpenSearchIndex.for_index(index_name, endpoint, **kwargs) for index_name in index_names]
logger.debug(f'Opening OpenSearch vector indexes [index_names: {index_names}, endpoint: {endpoint}, is_sigv4_auth: {is_sigv4_auth}]')
return [OpenSearchIndex.for_index(index_name, endpoint, is_sigv4_auth=is_sigv4_auth, **kwargs) for index_name in index_names]
except ImportError as e:
raise e

else:
return None
return None
Loading