[Index Management/ML] Fix broken retries on inference id creation (#186961)

## Summary

This fixes the inference endpoint creation API being called multiple
times on error. The call will often time out because
downloading/deploying the model takes longer than the Kibana request
timeout limit. Setting the timeout limit higher would still be fragile,
so ignoring the timeout error makes more sense.

This PR also contains a few small language fixes and variable renames
for clarity.
This commit is contained in:
Sander Philipse 2024-07-02 14:21:52 +02:00 committed by GitHub
parent 5020721fdd
commit 69c0858731
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 44 additions and 25 deletions

View file

@ -40,7 +40,7 @@ import { TrainedModelConfigResponse } from '@kbn/ml-plugin/common/types/trained_
import { getFieldConfig } from '../../../lib';
import { useAppContext } from '../../../../../app_context';
import { Form, UseField, useForm } from '../../../shared_imports';
import { useLoadInferenceModels } from '../../../../../services/api';
import { useLoadInferenceEndpoints } from '../../../../../services/api';
import { getTrainedModelStats } from '../../../../../../hooks/use_details_page_mappings_model_management';
import { InferenceToModelIdMap } from '../fields';
import { useMLModelNotificationToasts } from '../../../../../../hooks/use_ml_model_status_toasts';
@ -134,7 +134,7 @@ export const SelectInferenceId = ({
];
}, []);
const { isLoading, data: models } = useLoadInferenceModels();
const { isLoading, data: models } = useLoadInferenceEndpoints();
const [options, setOptions] = useState<EuiSelectableOption[]>([...defaultInferenceIds]);
const inferenceIdOptionsFromModels = useMemo(() => {

View file

@ -104,7 +104,7 @@ jest.mock('../../../../../../component_templates/component_templates_context', (
}));
jest.mock('../../../../../../../services/api', () => ({
getInferenceModels: jest.fn().mockResolvedValue({
getInferenceEndpoints: jest.fn().mockResolvedValue({
data: [
{
model_id: 'e5',

View file

@ -17,7 +17,7 @@ import { FormHook } from '../../../../../shared_imports';
import { CustomInferenceEndpointConfig, DefaultInferenceModels, Field } from '../../../../../types';
import { useMLModelNotificationToasts } from '../../../../../../../../hooks/use_ml_model_status_toasts';
import { getInferenceModels } from '../../../../../../../services/api';
import { getInferenceEndpoints } from '../../../../../../../services/api';
interface UseSemanticTextProps {
form: FormHook<Field, Field>;
ml?: MlPluginStart;
@ -83,7 +83,7 @@ export function useSemanticText(props: UseSemanticTextProps) {
if (data.inferenceId === undefined) {
throw new Error(
i18n.translate('xpack.idxMgmt.mappingsEditor.createField.undefinedInferenceIdError', {
defaultMessage: 'InferenceId is undefined while creating the inference endpoint.',
defaultMessage: 'Inference ID is undefined',
})
);
}
@ -138,18 +138,17 @@ export function useSemanticText(props: UseSemanticTextProps) {
dispatch({ type: 'field.addSemanticText', value: data });
try {
// if model exists already, do not create inference endpoint
const inferenceModels = await getInferenceModels();
// if inference endpoint exists already, do not create inference endpoint
const inferenceModels = await getInferenceEndpoints();
const inferenceModel: InferenceAPIConfigResponse[] = inferenceModels.data.some(
(e: InferenceAPIConfigResponse) => e.model_id === inferenceValue
);
if (inferenceModel) {
return;
}
if (trainedModelId) {
// show toasts only if it's elastic models
showSuccessToasts();
// Only show toast if it's an internal Elastic model that hasn't been deployed yet
if (trainedModelId && inferenceData.isDeployable && !inferenceData.isDeployed) {
showSuccessToasts(trainedModelId);
}
await createInferenceEndpoint(trainedModelId, data, customInferenceEndpointConfig);

View file

@ -227,7 +227,7 @@ export const DetailsPageMappingsContent: FunctionComponent<{
if (!error) {
notificationService.showSuccessToast(
i18n.translate('xpack.idxMgmt.indexDetails.mappings.successfullyUpdatedIndexMappings', {
defaultMessage: 'Index Mapping was successfully updated',
defaultMessage: 'Updated index mapping',
})
);
refetchMapping();

View file

@ -442,14 +442,14 @@ export function updateIndexMappings(indexName: string, newFields: Fields) {
});
}
export function getInferenceModels() {
export function getInferenceEndpoints() {
return sendRequest({
path: `${API_BASE_PATH}/inference/all`,
method: 'get',
});
}
export function useLoadInferenceModels() {
export function useLoadInferenceEndpoints() {
return useRequest<InferenceAPIConfigResponse[]>({
path: `${API_BASE_PATH}/inference/all`,
method: 'get',

View file

@ -28,7 +28,7 @@ export {
loadIndexStatistics,
useLoadIndexSettings,
createIndex,
useLoadInferenceModels,
useLoadInferenceEndpoints,
} from './api';
export { sortTable } from './sort_table';

View file

@ -36,7 +36,7 @@ jest.mock('../application/app_context', () => ({
}));
jest.mock('../application/services/api', () => ({
getInferenceModels: jest.fn().mockResolvedValue({
getInferenceEndpoints: jest.fn().mockResolvedValue({
data: [
{
model_id: 'e5',

View file

@ -18,7 +18,7 @@ import {
DeploymentState,
NormalizedFields,
} from '../application/components/mappings_editor/types';
import { getInferenceModels } from '../application/services/api';
import { getInferenceEndpoints } from '../application/services/api';
interface InferenceModel {
data: InferenceAPIConfigResponse[];
@ -91,7 +91,7 @@ export const useDetailsPageMappingsModelManagement = (
const dispatch = useDispatch();
const fetchInferenceModelsAndTrainedModelStats = useCallback(async () => {
const inferenceModels = await getInferenceModels();
const inferenceModels = await getInferenceEndpoints();
const trainedModelStats = await ml?.mlApi?.trainedModels.getTrainedModelStats();

View file

@ -11,7 +11,7 @@ import { useComponentTemplatesContext } from '../application/components/componen
export function useMLModelNotificationToasts() {
const { toasts } = useComponentTemplatesContext();
const showSuccessToasts = () => {
const showSuccessToasts = (modelName: string) => {
return toasts.addSuccess({
title: i18n.translate(
'xpack.idxMgmt.mappingsEditor.createField.modelDeploymentStartedNotification',
@ -20,7 +20,10 @@ export function useMLModelNotificationToasts() {
}
),
text: i18n.translate('xpack.idxMgmt.mappingsEditor.createField.modelDeploymentNotification', {
defaultMessage: '1 model is being deployed on your ml_node.',
defaultMessage: 'Model {modelName} is being deployed on your machine learning node.',
values: {
modelName,
},
}),
});
};

View file

@ -597,11 +597,28 @@ export class ModelsProvider {
taskType: InferenceTaskType,
modelConfig: InferenceModelConfig
) {
return await this._client.asCurrentUser.inference.putModel({
inference_id: inferenceId,
task_type: taskType,
model_config: modelConfig,
});
try {
const result = await this._client.asCurrentUser.inference.putModel(
{
inference_id: inferenceId,
task_type: taskType,
model_config: modelConfig,
},
{ maxRetries: 0 }
);
return result;
} catch (error) {
// Request timeouts will usually occur when the model is being downloaded/deployed
// Erroring out is misleading in these cases, so we return the model_id and task_type
if (error.name === 'TimeoutError') {
return {
model_id: modelConfig.service,
task_type: taskType,
};
} else {
throw error;
}
}
}
async getModelsDownloadStatus() {