-
Notifications
You must be signed in to change notification settings - Fork 8.5k
[ES|QL] Build function arguments suggestions from hints #246736
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
7d982a6
fix rerank not fetching inference endpoints
sddonne 2e0ce65
suggest inference endpoint in TEXT_EMBEDDING function
sddonne f84d936
add test
sddonne 78e02b4
clean
sddonne 8a71197
make hints unique
sddonne 4da15db
clean
sddonne 6249586
simplify hint resolving
sddonne ae07e86
early population of context for paramenters suggestions
sddonne aed7447
smarter merge of context
sddonne 68cbf07
clean
sddonne 6824170
clean
sddonne 67a221e
clean map
sddonne 7ece930
add tests
sddonne f8d363e
merge with main
sddonne 4909a34
add generic test
sddonne 786df0a
throw if not found
sddonne 9301d7f
clean async functions
sddonne 449ab38
better doc
sddonne 3e6bcf2
add switch breaks
sddonne 8cc768d
merge with main
sddonne 143db7a
Merge branch 'main' into hints-2
stratoula 0714374
solve comments
sddonne ba9e60d
Merge branch 'hints-2' of https://github.com/sddonne/kibana into hints-2
sddonne 31e9142
Merge branch 'main' into hints-2
sddonne b208d47
Merge branch 'main' into hints-2
sddonne File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 113 additions & 0 deletions
113
...n-esql-language/src/commands/definitions/utils/autocomplete/parameters_from_hints.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the "Elastic License | ||
| * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side | ||
| * Public License v 1"; you may not use this file except in compliance with, at | ||
| * your election, the "Elastic License 2.0", the "GNU Affero General Public | ||
| * License v3.0 only", or the "Server Side Public License, v 1". | ||
| */ | ||
|
|
||
| import type { ICommandContext } from '../../../registry/types'; | ||
| import type { ParameterHint } from '../../types'; | ||
| import { parametersFromHintsResolvers } from './parameters_from_hints'; | ||
| import type { ESQLCallbacks, InferenceEndpointAutocompleteItem } from '@kbn/esql-types'; | ||
|
|
||
| describe('Parameters from hints handlers', () => { | ||
| describe('inference_endpoint hint', () => { | ||
| const inferenceEndpoints: InferenceEndpointAutocompleteItem[] = [ | ||
| { | ||
| inference_id: 'text_embedding_endpoint', | ||
| task_type: 'text_embedding', | ||
| }, | ||
| ]; | ||
|
|
||
| const mockCallbacks: ESQLCallbacks = { | ||
| getInferenceEndpoints: jest.fn(async () => ({ inferenceEndpoints })), | ||
| }; | ||
|
|
||
| const hint: ParameterHint = { | ||
| entityType: 'inference_endpoint' as const, | ||
| constraints: { | ||
| task_type: 'text_embedding', | ||
| }, | ||
| }; | ||
|
|
||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| }); | ||
|
|
||
| it('should return inference endpoint suggestions filtered by task_type constraint', async () => { | ||
| const suggestions = await getSuggestionsForHint(hint, undefined, mockCallbacks); | ||
| expect(suggestions).toEqual(['text_embedding_endpoint']); | ||
| }); | ||
|
|
||
| it('should not refetch inference endpoints if the context already has endpoints for the task type', async () => { | ||
| const suggestions = await getSuggestionsForHint( | ||
| hint, | ||
| { | ||
| columns: new Map(), | ||
| inferenceEndpoints, | ||
| }, | ||
| mockCallbacks | ||
| ); | ||
|
|
||
| expect(suggestions).toEqual(['text_embedding_endpoint']); | ||
| expect(mockCallbacks.getInferenceEndpoints).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('should fetch inference endpoints if the context already has endpoints, but not of the requested task type, also, it should preserve both', async () => { | ||
| const otherInferenceEndpoints: InferenceEndpointAutocompleteItem[] = [ | ||
| { | ||
| inference_id: 'completion_endpoint', | ||
| task_type: 'completion', | ||
| }, | ||
| ]; | ||
|
|
||
| const suggestions = await getSuggestionsForHint( | ||
| hint, | ||
| { | ||
| columns: new Map(), | ||
| inferenceEndpoints: otherInferenceEndpoints, | ||
| }, | ||
| mockCallbacks | ||
| ); | ||
|
|
||
| expect(suggestions).toEqual(['text_embedding_endpoint']); | ||
| expect(mockCallbacks.getInferenceEndpoints).toHaveBeenCalledWith('text_embedding'); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| /** | ||
| * Calculates which would be the suggestions for a given parameter hint | ||
| * given certain callbacks and former context. | ||
| */ | ||
| export async function getSuggestionsForHint( | ||
| hint: ParameterHint, | ||
| formerContext?: ICommandContext, | ||
| callbacks: ESQLCallbacks = {} | ||
| ) { | ||
| const resolversEntry = parametersFromHintsResolvers[hint.entityType]; | ||
|
|
||
| if (!resolversEntry) { | ||
| throw new Error(`No resolvers found for hint type: ${hint.entityType}`); | ||
| } | ||
|
|
||
| const { suggestionResolver, contextResolver } = resolversEntry; | ||
| if (!suggestionResolver) { | ||
| throw new Error(`No suggestionResolver found for hint type: ${hint.entityType}`); | ||
| } | ||
|
|
||
| // Build the context using the context resolver if available | ||
| let context: ICommandContext = formerContext ?? { columns: new Map() }; | ||
| if (contextResolver) { | ||
| context = { | ||
| ...context, | ||
| ...((await contextResolver?.(hint, context, callbacks)) ?? {}), | ||
| }; | ||
| } | ||
|
|
||
| const suggestions = suggestionResolver(hint, context).map((s) => s.label); | ||
|
|
||
| return suggestions; | ||
| } |
104 changes: 104 additions & 0 deletions
104
...ed/kbn-esql-language/src/commands/definitions/utils/autocomplete/parameters_from_hints.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the "Elastic License | ||
| * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side | ||
| * Public License v 1"; you may not use this file except in compliance with, at | ||
| * your election, the "Elastic License 2.0", the "GNU Affero General Public | ||
| * License v3.0 only", or the "Server Side Public License, v 1". | ||
| */ | ||
|
|
||
| import type { InferenceTaskType } from '@elastic/elasticsearch/lib/api/types'; | ||
| import type { ESQLCallbacks } from '@kbn/esql-types'; | ||
| import { uniqBy } from 'lodash'; | ||
| import type { ParameterHint, ParameterHintEntityType } from '../../..'; | ||
| import type { ICommandContext, ISuggestionItem } from '../../../registry/types'; | ||
| import { createInferenceEndpointToCompletionItem } from './helpers'; | ||
|
|
||
| type SuggestionResolver = (hint: ParameterHint, ctx?: ICommandContext) => ISuggestionItem[]; | ||
|
|
||
| type ContextResolver = ( | ||
| hint: ParameterHint, | ||
| ctx: Partial<ICommandContext>, | ||
| callbacks: ESQLCallbacks | ||
| ) => Promise<Record<string, unknown>>; | ||
|
|
||
| /** | ||
| * For some parameters, ES gives us hints about the nature of it, that we use to provide | ||
| * custom autocompletion handlers. | ||
| * | ||
| * For each hint we need to provide: | ||
| * - a suggestionResolver to generate the autocompletion items for this param. | ||
| * - optionally, a contextResolver that populates the context with the data needed by the suggestionResolver. | ||
| * | ||
| * Important! | ||
| * Be mindful while implementing context resolvers, context is shared by the command and all functions used within it. | ||
| * If the data you need is already present, don't overwrite it, prefer merging it. | ||
| */ | ||
| export const parametersFromHintsResolvers: Partial< | ||
| Record< | ||
| ParameterHintEntityType, | ||
| { | ||
| suggestionResolver: SuggestionResolver; | ||
| contextResolver?: ContextResolver; | ||
| } | ||
| > | ||
| > = { | ||
| ['inference_endpoint']: { | ||
| suggestionResolver: inferenceEndpointSuggestionResolver, | ||
| contextResolver: inferenceEndpointContextResolver, | ||
| }, | ||
| }; | ||
|
|
||
| // -------- INFERENCE ENDPOINT HINT -------- // | ||
| function inferenceEndpointSuggestionResolver( | ||
| hint: ParameterHint, | ||
| ctx?: ICommandContext | ||
| ): ISuggestionItem[] { | ||
| if (hint.constraints?.task_type) { | ||
| const inferenceEnpoints = | ||
| ctx?.inferenceEndpoints?.filter((endpoint) => { | ||
| return endpoint.task_type === hint.constraints?.task_type; | ||
| }) ?? []; | ||
|
|
||
| return inferenceEnpoints.map((inferenceEndpoint) => { | ||
| const item = createInferenceEndpointToCompletionItem(inferenceEndpoint); | ||
| return { | ||
| ...item, | ||
| detail: '', | ||
| text: `"${item.text}"`, | ||
| }; | ||
| }); | ||
| } | ||
| return []; | ||
| } | ||
|
|
||
| async function inferenceEndpointContextResolver( | ||
| hint: ParameterHint, | ||
| ctx: Partial<ICommandContext>, | ||
| callbacks: ESQLCallbacks | ||
| ): Promise<Record<string, unknown>> { | ||
| if (hint.constraints?.task_type) { | ||
| const inferenceEndpointsFromContext = ctx.inferenceEndpoints ?? []; | ||
|
|
||
| // If the context already has an endpoint for the task type, we don't need to fetch them again | ||
| if ( | ||
| inferenceEndpointsFromContext.find( | ||
| (endpoint) => endpoint.task_type === hint.constraints?.task_type | ||
| ) | ||
| ) { | ||
| return {}; | ||
| } | ||
|
|
||
| const inferenceEnpoints = | ||
| (await callbacks?.getInferenceEndpoints?.(hint.constraints?.task_type as InferenceTaskType)) | ||
| ?.inferenceEndpoints || []; | ||
|
|
||
| return { | ||
| inferenceEndpoints: uniqBy( | ||
| [...inferenceEndpointsFromContext, ...inferenceEnpoints], | ||
| 'inference_id' | ||
| ), | ||
| }; | ||
| } | ||
| return {}; | ||
| } |
67 changes: 67 additions & 0 deletions
67
...l-language/src/language/autocomplete/__tests__/autocomplete.parameters_with_hints.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the "Elastic License | ||
| * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side | ||
| * Public License v 1"; you may not use this file except in compliance with, at | ||
| * your election, the "Elastic License 2.0", the "GNU Affero General Public | ||
| * License v3.0 only", or the "Server Side Public License, v 1". | ||
| */ | ||
|
|
||
| import { getSuggestionsForHint } from '../../../commands/definitions/utils/autocomplete/parameters_from_hints.test'; | ||
| import type { ESQLCallbacks } from '@kbn/esql-types'; | ||
| import { setup } from './helpers'; | ||
| import { getAllFunctions } from '../../../commands/definitions/utils/functions'; | ||
| import { uniqBy } from 'lodash'; | ||
| import { setTestFunctions } from '../../../commands/definitions/utils/test_functions'; | ||
| import { FunctionDefinitionTypes } from '../../../commands'; | ||
| import { Location } from '../../../commands/registry/types'; | ||
|
|
||
| const allUniqueParameterHints = uniqBy( | ||
| getAllFunctions() | ||
| .flatMap((fn) => fn.signatures) | ||
| .flatMap((signature) => signature.params) | ||
| .flatMap((param) => (param.hint ? [param.hint] : [])), | ||
| 'entityType' | ||
| ); | ||
|
|
||
| describe('function parameters autocomplete from hints', () => { | ||
| const callbacks: ESQLCallbacks = { | ||
| getInferenceEndpoints: async () => { | ||
| return { | ||
| inferenceEndpoints: [{ inference_id: 'inference_endpoint_1', task_type: 'text_embedding' }], | ||
| }; | ||
| }, | ||
| }; | ||
|
|
||
| it.each(allUniqueParameterHints)('should resolve suggestions for $entityType', async (hint) => { | ||
| const functionName = `test_hint_${hint.entityType}`; | ||
|
|
||
| // Define a fake function to test the parameter hint | ||
| // (real functions can have the hinted param in different positions, making it difficult to generalize) | ||
| setTestFunctions([ | ||
| { | ||
| type: FunctionDefinitionTypes.SCALAR, | ||
| name: functionName, | ||
| description: '', | ||
| signatures: [ | ||
| { | ||
| params: [{ name: 'field', type: 'keyword', hint }], | ||
| returnType: 'double', | ||
| }, | ||
| ], | ||
| locationsAvailable: [Location.EVAL], | ||
| }, | ||
| ]); | ||
|
|
||
| const { suggest } = await setup(); | ||
| const suggestions = ( | ||
| await suggest(`FROM index | EVAL result = ${functionName}(/`, { | ||
| callbacks, | ||
| }) | ||
| ).map((s) => s.label); | ||
|
|
||
| const suggestionsForHint = await getSuggestionsForHint(hint, undefined, callbacks); | ||
|
|
||
| expect(suggestions).toEqual(suggestionsForHint); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like this test