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