Skip to content

[ES|QL] Build function arguments suggestions from hints#246495

Closed
sddonne wants to merge 7 commits intoelastic:mainfrom
sddonne:hints
Closed

[ES|QL] Build function arguments suggestions from hints#246495
sddonne wants to merge 7 commits intoelastic:mainfrom
sddonne:hints

Conversation

@sddonne
Copy link
Contributor

@sddonne sddonne commented Dec 16, 2025

Closes #237794

Summary

For some special function arguments ES is now providing information about how to resolve them. For instance, functions that accepts inference endpoints.

image image

This PR adds support for the inference_endpoint hint, and an easy way to extend it to other hints.

@sddonne sddonne added v9.4.0 Feature:ES|QL ES|QL related features in Kibana Team:ESQL ES|QL related features in Kibana t// release_note:skip Skip the PR/issue when compiling release notes backport:skip This PR does not require backporting labels Dec 16, 2025
@sddonne sddonne marked this pull request as ready for review December 16, 2025 08:20
@sddonne sddonne requested a review from a team as a code owner December 16, 2025 08:20
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-esql (Team:ESQL)

): Promise<ISuggestionItem[]> {
// Keep the hints that are unique by entityType + constraints
const hints: ParameterHint[] = uniqWith(
paramDefinitions.filter((param) => param.hint !== undefined).map((param) => param.hint!),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Null coaleshing should do the work

 paramDefinitions.flatMap(({ hint }) => hint ?? []),

Also no need to check the existence of hints

.....
  const results = await Promise.all(
    hints.map((hint) => parametersFromHintsMap[hint.entityType]?.(hint, ctx) ?? [])
  );

  return results.flat();
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! adopted -> 6249586

Copy link
Contributor

@stratoula stratoula left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Seb for working on it. I didnt test as we are aiming for 9.4 but I did a first code review!

hasMinimumLicenseRequired,
canCreateLookupIndex: callbacks?.canCreateLookupIndex,
isServerless: callbacks?.isServerless,
getInferenceEndpoints: callbacks?.getInferenceEndpoints,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer the inferences to come on the context

export interface ICommandContext {
  columns: Map<string, ESQLColumnData>;
  sources?: ESQLSourceResult[];
  joinSources?: IndexAutocompleteItem[];
  timeSeriesSources?: IndexAutocompleteItem[];
  inferenceEndpoints?: InferenceEndpointAutocompleteItem[];
  policies?: Map<string, ESQLPolicy>;
  editorExtensions?: EditorExtensions;
  variables?: ESQLControlVariable[];
  supportsControls?: boolean;
  histogramBarTarget?: number;
  activeProduct?: PricingProduct | undefined;
  isCursorInSubquery?: boolean;
}

We are having them already at the context, it is confusing to have them at the callbacks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My first idea was using the Command context yes, but did found some drawbacks:

These are suggestions for functions, not commands, in the command context we check if the command is COMPLETION or RERANK and fetch that data.

So the implementation is less straightforward: when creating each command context we would need to walk the command and check the signature of every function within the command to check if some argument has a hint, if it has one do the fetching of the data in case we need it later. This is in theory good for user perceived performance as we have the data before we need it, but adds extra parsing code early on in the code and fetches for data that may not be used, and IMO the difference in speed it's not really noticeable.

In contrast the current implementation makes use of the signature detection that is already been used for other suggestions, so at that point of the flow it's easy to detect if we have hints in it and if we need to fetch for data, and when we do it's because it's needed.

So, putting that on the table, there are some tradeoffs between solutions, I think the current one is simpler, adds less code to maintain and blends with the flow we use for suggesting within functions.
If we go for adding it into the context, it could have better perceived performance and unifies the "resources" available for the command in that object, but that comes with having to do deep analysis early on in the autocomplete routine for each command.

Summarising I think it's a decision about:
1 - Gathering all data we may need for autocomplete early on in the routine.
2 - Or fetching the data as we need it in the subroutines.

Let me know your thoughts with this new information, and if the directrice is to always go with option 1, which is totally fine to me btw :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is problematic to have this call in 2 different places. It shows that something goes wrong with the architecture. As long as we have this information on the context, adding this also to the callbacks is wrong.

We should stick with our current one I think (otherwise we need to re-architect this thing). So I think we should go with the more complex one as it aligns with our current architeture.

Can you create another PR with this to see how this will be? If it doesnt look ok then we should think more thoroughly and re-architect!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stratoula what do you think about this implementation path -> #246736
(it still need some love before being prod ready, but want to validate we want to go that path before polishing the deatils.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes I really like it!! 👏

export const parameterHintEntityTypes = ['inference_endpoint'] as const;
export type ParameterHintEntityType = (typeof parameterHintEntityTypes)[number];
export interface ParameterHint {
entityType: ParameterHintEntityType;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make it an enum instead

Copy link
Contributor Author

@sddonne sddonne Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a type that is used within the autogenerated files, we will have type errors in them if using enums.

ctx: ExpressionContext
): Promise<ISuggestionItem[]> {
// Keep the hints that are unique by entityType + constraints
const hints: ParameterHint[] = uniqWith(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need the uniq?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's to make it more robust, signatures can be redundant with parameters as ES creates permutations with the parameters, so I don't want to create duplicated suggestions if the 'inference_id' param appears in multiple signatures.
At the same time, if the 'inference_id' appears in multiple signatures but with different constraints (different task_types) I DO want to fetch the different endpoints.

We don't have cases now, but we probably will not be notified if they change this signature to these cases, so better be prepared as it's just a line code.

... signature 1...
params: [
        {
          name: 'inference_id',
          type: 'keyword',
          hint: {
            entityType: 'inference_endpoint',
            constraints: {
              task_type: 'text_embedding',
            },
          },
        },
      ],
... signature 2...
params: [
        {
          name: 'inference_id',
          type: 'keyword',
          hint: {
            entityType: 'inference_endpoint',
            constraints: {
              task_type: 'rerank',
            },
          },
        },
      ],

ParameterHintEntityType,
(hint: ParameterHint, ctx: ExpressionContext) => Promise<ISuggestionItem[]>
> = {
['inference_endpoint']: inferenceEndpointHandler,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need better types here otherwise everytime they add a hint we need to change this part.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh it was on purpose, it's a way of getting notified if they add a new hint, so we know we might want to add support to it, fixing the type error it's just adding ['new_hint']: () => [],, and we can create an issue to tackle it later.

I can add a Partial<> to it, if you prefer to avoid this.

export const parametersFromHintsMap: Partial<
  Record<
    ParameterHintEntityType,
    (hint: ParameterHint, ctx: ExpressionContext) => Promise<ISuggestionItem[]>
  >
>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it makes it too hardcoded. We need an architecture as we have for functions. So something that we dont need to bother every time they add something. So have that in mind

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean with hardcoded?
Each new hint type will need a dedicated handler, as each hint will be very different from each other right? 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (hint.constraints?.task_type) {
const inferenceEnpoints =
(
await ctx.callbacks?.getInferenceEndpoints?.(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should follow the other context logic and not having the callback here

)
)?.inferenceEndpoints || [];

return inferenceEnpoints.map(createInferenceEndpointToCompletionItem).map((item) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the 2 maps?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in new branch 67a221e

return inferenceEnpoints.map(createInferenceEndpointToCompletionItem).map((item) => {
return {
...item,
detail: i18n.translate('kbn-esql-ast.esql.definitions.inferenceEndpointInFunction', {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is it visible (I didn't test, can you provide a screenshot?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the documentation detail of the suggestion
image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok so this text is wrong but we can discuss after #246495 (comment)

});
});

describe('function parameter built from hint', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of hardcoding them every time they add a hint lets create these tests automatically from the info ES sent to us

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't see it 🤔 💭, hints could vary a lot one from each other, some may need to mock new endpoints, etc. They tell us, hey this param is special, here it's a hint of how to create the suggestions for it, but each hint needs a different implementation from each other, so I'm not seeing how to make a generic test for all u.u

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have the function from the hints, so I am hoping that we can create generic tests that we show suggestions for the hints without every time to have to hard code them (the less work we do for this the better, it means that our architecture can scale). In my mind it should be doable at least 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I get your idea now.
But it would leave the actual handler untested. So we will still need to create a test for the dedicated hint handler to have good coverage.

I liked this approach more because it test both the handler and the autocomplete routine integration all together, with less code.
I can go with your solution too if you want and create specific tests for the handlers, in the bright side of it, they will be closer. It's all a matter of trade offs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes I prefer a generic solution and then yes specific tests for the handlers would be ok 👌

@sddonne
Copy link
Contributor Author

sddonne commented Dec 19, 2025

Closed in favour of #246736

@sddonne sddonne closed this Dec 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:skip This PR does not require backporting Feature:ES|QL ES|QL related features in Kibana release_note:skip Skip the PR/issue when compiling release notes Team:ESQL ES|QL related features in Kibana t// v9.4.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ES|QL] Support hints on the editor

4 participants