Elasticsearch Integration#
Dify ships Elasticsearch support as a VDB plugin (dify-vdb-elasticsearch) under api/providers/vdb/vdb-elasticsearch/. The plugin registers two backends via Python entry points :
VectorType enum value | Factory class | Use case |
|---|---|---|
elasticsearch | ElasticSearchVectorFactory | General-purpose |
elasticsearch-ja | ElasticSearchJaVectorFactory | Japanese text (custom analyzers) |
Both variants are declared in VectorType and share the same elasticsearch==8.14.0 client library . Elasticsearch 8.0.0 or later is required; the factory enforces this at init time .
Index Schema and Metadata Mappings#
When an index does not yet exist, create_collection creates it with a fixed mapping :
| Field | ES type | Notes |
|---|---|---|
page_content (Field.CONTENT_KEY) | text | Full-text search target |
vector (Field.VECTOR) | dense_vector | Cosine similarity, dimension inferred from first embedding batch |
metadata (Field.METADATA_KEY) | object | Nested object |
metadata.doc_id | keyword | Explicit keyword mapping β enables exact-match filtering |
metadata.document_id | keyword | Same β used for document_ids_filter in KNN queries |
Mapping doc_id and document_id as keyword (rather than letting ES auto-map them as text) is intentional: it avoids tokenization and makes term-level filters efficient .
The collection name is lowercased and derived from the dataset ID via Dataset.gen_collection_name_by_id. Index creation is guarded by a Redis lock to prevent race conditions during concurrent indexing .
Search Behavior#
Vector search (search_by_vector) uses the native ES KNN query. num_candidates is set to ceil(top_k * 1.5). When a document_ids_filter is present, it becomes a terms filter on metadata.document_id inside the KNN body β keeping scoring in ES rather than doing post-hoc filtering . Scores come directly from hit["_score"] (cosine similarity). A score_threshold kwarg can prune low-scoring results .
Full-text search (search_by_full_text) issues a standard match query against page_content. With a document_ids_filter it wraps the match in a bool + filter clause . No score threshold is applied on the full-text path.
See Vector Store Integration for cross-provider score behavior comparisons, and the Hybrid Search article for parallel vector/keyword retrieval and result fusion.
Japanese Variant (elasticsearch-ja)#
ElasticSearchJaVector extends ElasticSearchVector and only overrides create_collection . It adds an index-level settings block with a custom analyzer (ja_analyzer) that chains:
- Char filters:
icu_normalizer,kuromoji_iteration_mark - Tokenizer:
kuromoji_tokenizer - Token filters:
kuromoji_baseform,kuromoji_part_of_speech,ja_stop,kuromoji_number,kuromoji_stemmer
The page_content field is then mapped with "analyzer": "ja_analyzer" and "search_analyzer": "ja_analyzer" , ensuring both index-time and query-time tokenization use Japanese morphological analysis. All other methods (vector search, full-text search, CRUD) are inherited unchanged from ElasticSearchVector.
Note: The Japanese factory (
ElasticSearchJaVectorFactory) does not support the Elastic Cloud path β it reads onlyELASTICSEARCH_HOST/PORT/USERNAME/PASSWORD.
Configuration and Deployment#
Set VECTOR_STORE=elasticsearch (or elasticsearch-ja) in the environment. The standard factory reads from Flask config :
| Variable | Default | Notes |
|---|---|---|
ELASTICSEARCH_USE_CLOUD | false | Switch to Elastic Cloud mode |
ELASTICSEARCH_HOST | elasticsearch | Self-hosted host |
ELASTICSEARCH_PORT | 9200 | β |
ELASTICSEARCH_USERNAME | elastic | β |
ELASTICSEARCH_PASSWORD | elastic | β |
ELASTICSEARCH_CLOUD_URL | β | Cloud mode: full URL |
ELASTICSEARCH_API_KEY | β | Cloud mode: API key |
ELASTICSEARCH_CA_CERTS | β | Optional CA cert path |
ELASTICSEARCH_VERIFY_CERTS | false | SSL cert verification |
ELASTICSEARCH_REQUEST_TIMEOUT | 100000 | ms |
ELASTICSEARCH_RETRY_ON_TIMEOUT | true | β |
ELASTICSEARCH_MAX_RETRIES | 10000 | β |
A reference env file lives at docker/envs/vectorstores/elasticsearch.env.example. Docker Compose spins up docker.elastic.co/elasticsearch/elasticsearch:8.14.3 with xpack.security.enabled=true and single-node discovery .
Entry points (from pyproject.toml) wire the factories into Dify's plugin discovery system so no code changes are needed to swap backends .