Skip to content

Commit 7335699

Browse files
authored
feat: Add Semantic query (#216)
Semantic Query is available in Elasticsearch v9.0+.
1 parent 8030449 commit 7335699

File tree

6 files changed

+202
-1
lines changed

6 files changed

+202
-1
lines changed

src/index.d.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3858,6 +3858,44 @@ declare namespace esb {
38583858
field? : string
38593859
) : SparseVectorQuery;
38603860

3861+
/**
3862+
* The `semantic` query enables semantic search on a `semantic_text` field.
3863+
*
3864+
* NOTE: Only available in Elasticsearch v9.0+
3865+
*
3866+
* @param {string=} field The semantic_text field to query.
3867+
* @param {string=} query The semantic query text.
3868+
* @extends Query
3869+
*/
3870+
export class SemanticQuery extends Query {
3871+
constructor(field?: string, query?: string);
3872+
3873+
/**
3874+
* Sets the semantic field to query.
3875+
*
3876+
* @param {string} field The `semantic_text` field name.
3877+
*/
3878+
field(field: string): this;
3879+
3880+
/**
3881+
* Sets the semantic query text.
3882+
*
3883+
* @param {string} query The query text.
3884+
*/
3885+
query(query: string): this;
3886+
}
3887+
3888+
/**
3889+
* Creates a `semantic` query.
3890+
*
3891+
* @param {string=} field The semantic_text field to query.
3892+
* @param {string=} query The semantic query text.
3893+
*/
3894+
export function semanticQuery(
3895+
field?: string,
3896+
query?: string
3897+
): SemanticQuery;
3898+
38613899
/**
38623900
* Knn performs k-nearest neighbor (KNN) searches.
38633901
* This class allows configuring the KNN search with various parameters such as field, query vector,

src/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ const {
9090
SpanWithinQuery,
9191
SpanFieldMaskingQuery
9292
},
93-
vectorQueries: { SparseVectorQuery }
93+
vectorQueries: { SparseVectorQuery, SemanticQuery }
9494
} = require('./queries');
9595

9696
const {
@@ -349,6 +349,9 @@ exports.spanFieldMaskingQuery = constructorWrapper(SpanFieldMaskingQuery);
349349
exports.SparseVectorQuery = SparseVectorQuery;
350350
exports.sparseVectorQuery = constructorWrapper(SparseVectorQuery);
351351

352+
exports.SemanticQuery = SemanticQuery;
353+
exports.semanticQuery = constructorWrapper(SemanticQuery);
354+
352355
/* ============ ============ ============ */
353356
/* ======== KNN ======== */
354357
/* ============ ============ ============ */
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
'use strict';
22

33
exports.SparseVectorQuery = require('./sparse-vector-query');
4+
exports.SemanticQuery = require('./semantic-query');
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
'use strict';
2+
3+
const { Query } = require('../../core');
4+
const { isNil } = require('lodash');
5+
6+
/**
7+
* The semantic query enables you to perform semantic search on data stored in a semantic_text field.
8+
* Semantic search uses dense vector representations to capture the meaning and context of search terms,
9+
* providing more relevant results compared to traditional keyword-based search methods.
10+
*
11+
* Requires Elasticsearch v9.0+ (Stack 9 / Serverless) where the `semantic` query is available.
12+
*
13+
* [Elasticsearch reference](https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-semantic-query)
14+
*
15+
* @example
16+
* const qry = esb.semanticQuery('title_semantic', 'mountain lake').boost(2);
17+
*
18+
* @extends Query
19+
*/
20+
class SemanticQuery extends Query {
21+
// eslint-disable-next-line require-jsdoc
22+
constructor(field, query) {
23+
super('semantic');
24+
if (!isNil(field)) this._queryOpts.field = field;
25+
if (!isNil(query)) this._queryOpts.query = query;
26+
}
27+
28+
/**
29+
* Sets the semantic field to query.
30+
* @param {string} field The `semantic_text` field name.
31+
* @returns {SemanticQuery}
32+
*/
33+
field(field) {
34+
this._queryOpts.field = field;
35+
return this;
36+
}
37+
38+
/**
39+
* Sets the semantic query text.
40+
* @param {string} query The query text.
41+
* @returns {SemanticQuery}
42+
*/
43+
query(query) {
44+
this._queryOpts.query = query;
45+
return this;
46+
}
47+
}
48+
49+
module.exports = SemanticQuery;

test/index.test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ test('queries are exported', t => {
175175

176176
t.truthy(esb.sparseVectorQuery());
177177
t.truthy(esb.SparseVectorQuery);
178+
179+
t.truthy(esb.semanticQuery());
180+
t.truthy(esb.SemanticQuery);
178181
});
179182

180183
test('aggregations are exported', t => {
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import test from 'ava';
2+
import esb, { SemanticQuery } from '../../src';
3+
4+
test('constructor sets field and query correctly', t => {
5+
const q = new SemanticQuery('inference_field', 'Best surfing places');
6+
7+
const expected = {
8+
semantic: {
9+
field: 'inference_field',
10+
query: 'Best surfing places'
11+
}
12+
};
13+
t.deepEqual(q.toJSON(), expected);
14+
});
15+
16+
test('empty constructor allows method chaining', t => {
17+
const q = new SemanticQuery();
18+
q.field('inference_field').query('Best surfing places');
19+
20+
const expected = {
21+
semantic: {
22+
field: 'inference_field',
23+
query: 'Best surfing places'
24+
}
25+
};
26+
t.deepEqual(q.toJSON(), expected);
27+
});
28+
29+
test('field method sets field correctly', t => {
30+
const q = new SemanticQuery();
31+
q.field('title_semantic');
32+
33+
const expected = {
34+
semantic: {
35+
field: 'title_semantic'
36+
}
37+
};
38+
t.deepEqual(q.toJSON(), expected);
39+
});
40+
41+
test('query method sets query text correctly', t => {
42+
const q = new SemanticQuery();
43+
q.query('mountain lake');
44+
45+
const expected = {
46+
semantic: {
47+
query: 'mountain lake'
48+
}
49+
};
50+
t.deepEqual(q.toJSON(), expected);
51+
});
52+
53+
test('supports boost parameter', t => {
54+
const q = new SemanticQuery('title_semantic', 'mountain lake');
55+
q.boost(2);
56+
57+
const expected = {
58+
semantic: {
59+
field: 'title_semantic',
60+
query: 'mountain lake',
61+
boost: 2
62+
}
63+
};
64+
t.deepEqual(q.toJSON(), expected);
65+
});
66+
67+
test('call semantic query via esb factory function', t => {
68+
const q = esb.semanticQuery('inference_field', 'Best surfing places');
69+
70+
const expected = {
71+
semantic: {
72+
field: 'inference_field',
73+
query: 'Best surfing places'
74+
}
75+
};
76+
t.deepEqual(q.toJSON(), expected);
77+
});
78+
79+
test('call semantic query via esb factory function with chaining', t => {
80+
const q = esb
81+
.semanticQuery()
82+
.field('semantic_field')
83+
.query('shoes')
84+
.boost(1.5);
85+
86+
const expected = {
87+
semantic: {
88+
field: 'semantic_field',
89+
query: 'shoes',
90+
boost: 1.5
91+
}
92+
};
93+
t.deepEqual(q.toJSON(), expected);
94+
});
95+
96+
test('overwriting field and query works correctly', t => {
97+
const q = new SemanticQuery('old_field', 'old query');
98+
q.field('new_field').query('new query');
99+
100+
const expected = {
101+
semantic: {
102+
field: 'new_field',
103+
query: 'new query'
104+
}
105+
};
106+
t.deepEqual(q.toJSON(), expected);
107+
});

0 commit comments

Comments
 (0)