@@ -134,6 +134,49 @@ def change_point(self, value: FieldType) -> "ChangePoint":
134
134
"""
135
135
return ChangePoint (self , value )
136
136
137
+ def completion (
138
+ self , * prompt : ExpressionType , ** named_prompt : ExpressionType
139
+ ) -> "Completion" :
140
+ """The `COMPLETION` command allows you to send prompts and context to a Large
141
+ Language Model (LLM) directly within your ES|QL queries, to perform text
142
+ generation tasks.
143
+
144
+ :param prompt: The input text or expression used to prompt the LLM. This can
145
+ be a string literal or a reference to a column containing text.
146
+ :param named_prompt: The input text or expresion, given as a keyword argument.
147
+ The argument name is used for the column name. If not
148
+ specified, the results will be stored in a column named
149
+ `completion`. If the specified column already exists, it
150
+ will be overwritten with the new results.
151
+
152
+ Examples::
153
+
154
+ query1 = (
155
+ ESQL.row(question="What is Elasticsearch?")
156
+ .completion("question").with_("test_completion_model")
157
+ .keep("question", "completion")
158
+ )
159
+ query2 = (
160
+ ESQL.row(question="What is Elasticsearch?")
161
+ .completion(answer="question").with_("test_completion_model")
162
+ .keep("question", "answer")
163
+ )
164
+ query3 = (
165
+ ESQL.from_("movies")
166
+ .sort("rating DESC")
167
+ .limit(10)
168
+ .eval(prompt=\" \" \" CONCAT(
169
+ "Summarize this movie using the following information: \\ n",
170
+ "Title: ", title, "\\ n",
171
+ "Synopsis: ", synopsis, "\\ n",
172
+ "Actors: ", MV_CONCAT(actors, ", "), "\\ n",
173
+ )\" \" \" )
174
+ .completion(summary="prompt").with_("test_completion_model")
175
+ .keep("title", "summary", "rating")
176
+ )
177
+ """
178
+ return Completion (self , * prompt , ** named_prompt )
179
+
137
180
def dissect (self , input : FieldType , pattern : str ) -> "Dissect" :
138
181
"""``DISSECT`` enables you to extract structured data out of a string.
139
182
@@ -306,43 +349,39 @@ def limit(self, max_number_of_rows: int) -> "Limit":
306
349
"""
307
350
return Limit (self , max_number_of_rows )
308
351
309
- def lookup_join (self , lookup_index : IndexType , field : FieldType ) -> "LookupJoin" :
352
+ def lookup_join (self , lookup_index : IndexType ) -> "LookupJoin" :
310
353
"""`LOOKUP JOIN` enables you to add data from another index, AKA a 'lookup' index,
311
354
to your ES|QL query results, simplifying data enrichment and analysis workflows.
312
355
313
356
:param lookup_index: The name of the lookup index. This must be a specific index
314
357
name - wildcards, aliases, and remote cluster references are
315
358
not supported. Indices used for lookups must be configured
316
359
with the lookup index mode.
317
- :param field: The field to join on. This field must exist in both your current query
318
- results and in the lookup index. If the field contains multi-valued
319
- entries, those entries will not match anything (the added fields will
320
- contain null for those rows).
321
360
322
361
Examples::
323
362
324
363
query1 = (
325
364
ESQL.from_("firewall_logs")
326
- .lookup_join("threat_list", "source.IP")
365
+ .lookup_join("threat_list").on( "source.IP")
327
366
.where("threat_level IS NOT NULL")
328
367
)
329
368
query2 = (
330
369
ESQL.from_("system_metrics")
331
- .lookup_join("host_inventory", "host.name")
332
- .lookup_join("ownerships", "host.name")
370
+ .lookup_join("host_inventory").on( "host.name")
371
+ .lookup_join("ownerships").on( "host.name")
333
372
)
334
373
query3 = (
335
374
ESQL.from_("app_logs")
336
- .lookup_join("service_owners", "service_id")
375
+ .lookup_join("service_owners").on( "service_id")
337
376
)
338
377
query4 = (
339
378
ESQL.from_("employees")
340
379
.eval(language_code="languages")
341
380
.where("emp_no >= 10091 AND emp_no < 10094")
342
- .lookup_join("languages_lookup", "language_code")
381
+ .lookup_join("languages_lookup").on( "language_code")
343
382
)
344
383
"""
345
- return LookupJoin (self , lookup_index , field )
384
+ return LookupJoin (self , lookup_index )
346
385
347
386
def mv_expand (self , column : FieldType ) -> "MvExpand" :
348
387
"""The `MV_EXPAND` processing command expands multivalued columns into one row per
@@ -635,6 +674,47 @@ def _render_internal(self) -> str:
635
674
return f"CHANGE_POINT { self ._value } { key } { names } "
636
675
637
676
677
+ class Completion (ESQLBase ):
678
+ """Implementation of the ``COMPLETION`` processing command.
679
+
680
+ This class inherits from :class:`ESQLBase <elasticsearch.esql.esql.ESQLBase>`,
681
+ to make it possible to chain all the commands that belong to an ES|QL query
682
+ in a single expression.
683
+ """
684
+
685
+ def __init__ (
686
+ self , parent : ESQLBase , * prompt : ExpressionType , ** named_prompt : ExpressionType
687
+ ):
688
+ if len (prompt ) + len (named_prompt ) > 1 :
689
+ raise ValueError (
690
+ "this method requires either one positional or one keyword argument only"
691
+ )
692
+ super ().__init__ (parent )
693
+ self ._prompt = prompt
694
+ self ._named_prompt = named_prompt
695
+ self ._inference_id : Optional [str ] = None
696
+
697
+ def with_ (self , inference_id : str ) -> "Completion" :
698
+ """Continuation of the `COMPLETION` command.
699
+
700
+ :param inference_id: The ID of the inference endpoint to use for the task. The
701
+ inference endpoint must be configured with the completion
702
+ task type.
703
+ """
704
+ self ._inference_id = inference_id
705
+ return self
706
+
707
+ def _render_internal (self ) -> str :
708
+ if self ._inference_id is None :
709
+ raise ValueError ("The completion command requires an inference ID" )
710
+ if self ._named_prompt :
711
+ column = list (self ._named_prompt .keys ())[0 ]
712
+ prompt = list (self ._named_prompt .values ())[0 ]
713
+ return f"COMPLETION { column } = { prompt } WITH { self ._inference_id } "
714
+ else :
715
+ return f"COMPLETION { self ._prompt [0 ]} WITH { self ._inference_id } "
716
+
717
+
638
718
class Dissect (ESQLBase ):
639
719
"""Implementation of the ``DISSECT`` processing command.
640
720
@@ -861,12 +941,25 @@ class LookupJoin(ESQLBase):
861
941
in a single expression.
862
942
"""
863
943
864
- def __init__ (self , parent : ESQLBase , lookup_index : IndexType , field : FieldType ):
944
+ def __init__ (self , parent : ESQLBase , lookup_index : IndexType ):
865
945
super ().__init__ (parent )
866
946
self ._lookup_index = lookup_index
947
+ self ._field = None
948
+
949
+ def on (self , field : FieldType ) -> "LookupJoin" :
950
+ """Continuation of the `LOOKUP_JOIN` command.
951
+
952
+ :param field: The field to join on. This field must exist in both your current query
953
+ results and in the lookup index. If the field contains multi-valued
954
+ entries, those entries will not match anything (the added fields will
955
+ contain null for those rows).
956
+ """
867
957
self ._field = field
958
+ return self
868
959
869
960
def _render_internal (self ) -> str :
961
+ if self ._field is None :
962
+ raise ValueError ("Joins require a field to join on." )
870
963
index = (
871
964
self ._lookup_index
872
965
if isinstance (self ._lookup_index , str )
0 commit comments