Search by meaning (semantic query)
Send plain text; Lucenia understands what it means and finds the closest matches — no embeddings, no vector math on your side. The semantic query embeds your text at search time and runs a k-NN search for you, so a caller never has to produce a vector or even know one is involved.
The query
Wrap your text in a semantic query, naming the knn_vector field to search:
GET /knowledge-base/_search
{
"query": {
"semantic": {
"chunks.embedding": {
"query_text": "airfield near a river",
"model_id": "hashing-v1",
"k": 10,
"filter": { "term": { "tenant": "acme" } }
}
}
}
}
The outer field name (chunks.embedding above) is the knn_vector field the query searches against — the same nesting the raw knn query uses.
Parameters
| Parameter | Type | Required/Optional | Description |
|---|---|---|---|
query_text | String | Required | The plain text to search with. Lucenia embeds it for you. |
model_id | String | Required | The embedding model (space) to embed the text with. It must match the model the index was embedded with at ingest time. |
provider | String | Optional | The embedding provider. Defaults to the built-in hashing provider. |
k | Integer | Optional | The number of nearest neighbors to retrieve. Default is 10. |
filter | Object | Optional | A query domain-specific language (DSL) object applied alongside the k-NN search (for example, to scope results by tenant or metadata). |
match vs. semantic
The two queries answer different questions, and reading them side by side is the fastest way to understand semantic:
matchfinds documents that contain your words.semanticfinds documents that mean the same thing, even when they use different words.
A match query for "airfield near a river" only finds documents with those tokens. A semantic query for the same text also surfaces a document that says "airstrip beside a stream" — no shared keywords, but the same meaning.
Hybrid: keyword and meaning together
You rarely have to choose. The hero pattern combines an exact-keyword match with a meaning-based semantic subquery inside a hybrid query, so precise term hits and semantic matches are ranked together:
GET /knowledge-base/_search?search_pipeline=hybrid-pipeline
{
"query": {
"hybrid": {
"queries": [
{
"match": {
"chunks.text": "airfield near a river"
}
},
{
"semantic": {
"chunks.embedding": {
"query_text": "airfield near a river",
"model_id": "hashing-v1",
"k": 10
}
}
}
]
}
}
}
Because the lexical and semantic subqueries score on different scales, pair the hybrid query with a normalization search pipeline that normalizes each subquery's scores and then combines them into a single ranking:
PUT _search/pipeline/hybrid-pipeline
{
"phase_results_processors": [
{
"normalization-processor": {
"normalization": { "technique": "min_max" },
"combination": {
"technique": "arithmetic_mean",
"parameters": { "weights": [0.3, 0.7] }
}
}
}
]
}
For the full set of normalization and combination options, see Combining the scores.
Under the hood
The semantic query is a thin, well-named front end over machinery Lucenia already ships. At query time it embeds your query_text with the configured provider and model, then rewrites itself into a k-NN search against the named knn_vector field. No vector ever appears in the DSL — that's the whole point.
Correctness note. The provider and model_id in the query must match the provider and model that embedded the documents at ingest time. Vectors produced by different providers or models don't live in the same space and aren't comparable, so a mismatch quietly returns meaningless nearest-neighbor results — no error is raised. If you embedded your documents with the content processing embed processor, use the same provider and model here.
Security
The semantic query is an ordinary query, so it inherits the caller's index-read authorization — there is no special scope to grant. Restrict who can search an index the same way you would for any other query, and use the filter clause to scope results (for example, by tenant).