Skip to content

Commit cae8e68

Browse files
sorenlouvqn895
authored andcommitted
[Obs AI Assistant] Improve flaky recall tests (elastic#220638)
1 parent ac31e3b commit cae8e68

File tree

11 files changed

+123
-46
lines changed

11 files changed

+123
-46
lines changed

x-pack/platform/plugins/shared/observability_ai_assistant/server/routes/knowledge_base/route.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ const getKnowledgeBaseStatus = createObservabilityAIAssistantServerRoute({
5050
const setupKnowledgeBase = createObservabilityAIAssistantServerRoute({
5151
endpoint: 'POST /internal/observability_ai_assistant/kb/setup',
5252
params: t.type({
53-
query: t.type({
54-
inference_id: t.string,
55-
}),
53+
query: t.intersection([
54+
t.type({ inference_id: t.string }),
55+
t.partial({ wait_until_complete: toBooleanRt }),
56+
]),
5657
}),
5758
security: {
5859
authz: {
@@ -67,8 +68,9 @@ const setupKnowledgeBase = createObservabilityAIAssistantServerRoute({
6768
nextInferenceId: string;
6869
}> => {
6970
const client = await resources.service.getClient({ request: resources.request });
70-
const { inference_id: inferenceId } = resources.params.query;
71-
return client.setupKnowledgeBase(inferenceId);
71+
const { inference_id: inferenceId, wait_until_complete: waitUntilComplete } =
72+
resources.params.query;
73+
return client.setupKnowledgeBase(inferenceId, waitUntilComplete);
7274
},
7375
});
7476

x-pack/platform/plugins/shared/observability_ai_assistant/server/service/client/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,8 @@ export class ObservabilityAIAssistantClient {
664664
};
665665

666666
setupKnowledgeBase = async (
667-
nextInferenceId: string
667+
nextInferenceId: string,
668+
waitUntilComplete: boolean = false
668669
): Promise<{
669670
reindex: boolean;
670671
currentInferenceId: string | undefined;
@@ -693,7 +694,7 @@ export class ObservabilityAIAssistantClient {
693694
inferenceId: nextInferenceId,
694695
});
695696

696-
waitForKbModel({
697+
const kbSetupPromise = waitForKbModel({
697698
core: this.dependencies.core,
698699
esClient,
699700
logger,
@@ -728,6 +729,10 @@ export class ObservabilityAIAssistantClient {
728729
}
729730
});
730731

732+
if (waitUntilComplete) {
733+
await kbSetupPromise;
734+
}
735+
731736
return { reindex: true, currentInferenceId, nextInferenceId };
732737
};
733738

x-pack/platform/plugins/shared/observability_ai_assistant/server/service/knowledge_base_service/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ export class KnowledgeBaseService {
416416
}
417417

418418
try {
419-
await this.dependencies.esClient.asInternalUser.index<
419+
const indexResult = await this.dependencies.esClient.asInternalUser.index<
420420
Omit<KnowledgeBaseEntry, 'id'> & { namespace: string }
421421
>({
422422
index: resourceNames.writeIndexAlias.kb,
@@ -432,7 +432,7 @@ export class KnowledgeBaseService {
432432
});
433433

434434
this.dependencies.logger.debug(
435-
`Entry added to knowledge base. title = "${doc.title}", user = "${user?.name}, namespace = "${namespace}"`
435+
`Entry added to knowledge base. title = "${doc.title}", user = "${user?.name}, namespace = "${namespace}", index = ${indexResult._index}, id = ${indexResult._id}`
436436
);
437437
} catch (error) {
438438
this.dependencies.logger.error(`Failed to add entry to knowledge base ${error}`);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { errors } from '@elastic/elasticsearch';
9+
import { ElasticsearchClient } from '@kbn/core/server';
10+
import { resourceNames } from '..';
11+
12+
export async function addIndexWriteBlock({
13+
esClient,
14+
index,
15+
}: {
16+
esClient: { asInternalUser: ElasticsearchClient };
17+
index: string;
18+
}) {
19+
await esClient.asInternalUser.indices.addBlock({ index, block: 'write' });
20+
}
21+
22+
export function removeIndexWriteBlock({
23+
esClient,
24+
index,
25+
}: {
26+
esClient: { asInternalUser: ElasticsearchClient };
27+
index: string;
28+
}) {
29+
return esClient.asInternalUser.indices.putSettings({
30+
index,
31+
body: { 'index.blocks.write': false },
32+
});
33+
}
34+
35+
export function isKnowledgeBaseIndexWriteBlocked(error: any) {
36+
return (
37+
error instanceof errors.ResponseError &&
38+
error.message.includes(`cluster_block_exception`) &&
39+
error.message.includes(resourceNames.writeIndexAlias.kb)
40+
);
41+
}

x-pack/platform/plugins/shared/observability_ai_assistant/server/service/knowledge_base_service/reindex_knowledge_base.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,6 @@ async function reIndexKnowledgeBase({
5959
`Re-indexing knowledge base from "${currentWriteIndexName}" to index "${nextWriteIndexName}"...`
6060
);
6161

62-
const reindexResponse = await esClient.asInternalUser.reindex({
63-
source: { index: currentWriteIndexName },
64-
dest: { index: nextWriteIndexName },
65-
refresh: true,
66-
wait_for_completion: false,
67-
});
68-
6962
// Point write index alias to the new index
7063
await updateKnowledgeBaseWriteIndexAlias({
7164
esClient,
@@ -74,6 +67,13 @@ async function reIndexKnowledgeBase({
7467
currentWriteIndexName,
7568
});
7669

70+
const reindexResponse = await esClient.asInternalUser.reindex({
71+
source: { index: currentWriteIndexName },
72+
dest: { index: nextWriteIndexName },
73+
refresh: true,
74+
wait_for_completion: false,
75+
});
76+
7777
const taskId = reindexResponse.task?.toString();
7878
if (taskId) {
7979
await waitForReIndexTaskToComplete({ esClient, taskId, logger });

x-pack/platform/plugins/shared/observability_ai_assistant/server/service/knowledge_base_service/update_knowledge_base_index_alias.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
99
import { Logger } from '@kbn/logging';
1010
import { resourceNames } from '..';
11+
import { addIndexWriteBlock, removeIndexWriteBlock } from './index_write_block_utils';
1112

1213
export async function updateKnowledgeBaseWriteIndexAlias({
1314
esClient,
@@ -25,14 +26,26 @@ export async function updateKnowledgeBaseWriteIndexAlias({
2526
);
2627
const alias = resourceNames.writeIndexAlias.kb;
2728
try {
29+
await addIndexWriteBlock({ esClient, index: currentWriteIndexName });
30+
logger.debug(
31+
`Added write block to "${currentWriteIndexName}". It is now read-only and writes are temporarily blocked.`
32+
);
33+
2834
await esClient.asInternalUser.indices.updateAliases({
2935
actions: [
3036
{ remove: { index: currentWriteIndexName, alias } },
3137
{ add: { index: nextWriteIndexName, alias, is_write_index: true } },
3238
],
3339
});
3440
} catch (error) {
35-
logger.error(`Failed to update write index alias: ${error.message}`);
41+
await removeIndexWriteBlock({ esClient, index: currentWriteIndexName });
42+
logger.error(
43+
`Failed to update write index alias: ${error.message}. Reverting back to ${currentWriteIndexName}`
44+
);
3645
throw error;
3746
}
47+
48+
logger.debug(
49+
`Successfully updated write index alias to "${nextWriteIndexName}". Writes are now enabled again.`
50+
);
3851
}

x-pack/test/api_integration/deployment_agnostic/apis/observability/ai_assistant/knowledge_base/knowledge_base_change_model_from_elser_to_e5.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon
5656
before(async () => {
5757
await importModel(getService, { modelId: TINY_ELSER_MODEL_ID });
5858
await createTinyElserInferenceEndpoint(getService, { inferenceId: TINY_ELSER_INFERENCE_ID });
59-
await setupKnowledgeBase(observabilityAIAssistantAPIClient, TINY_ELSER_INFERENCE_ID);
59+
await setupKnowledgeBase(getService, TINY_ELSER_INFERENCE_ID);
6060
await waitForKnowledgeBaseReady(getService);
6161

6262
// ingest documents
@@ -76,7 +76,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon
7676
await createTinyTextEmbeddingInferenceEndpoint(getService, {
7777
inferenceId: TINY_TEXT_EMBEDDING_INFERENCE_ID,
7878
});
79-
await setupKnowledgeBase(observabilityAIAssistantAPIClient, TINY_TEXT_EMBEDDING_INFERENCE_ID);
79+
await setupKnowledgeBase(getService, TINY_TEXT_EMBEDDING_INFERENCE_ID);
8080

8181
await waitForKnowledgeBaseIndex(getService, '.kibana-observability-ai-assistant-kb-000002');
8282
await waitForKnowledgeBaseReady(getService);

x-pack/test/api_integration/deployment_agnostic/apis/observability/ai_assistant/knowledge_base/knowledge_base_setup.spec.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon
6565
let body: Awaited<ReturnType<typeof setupKbAsAdmin>>['body'];
6666

6767
before(async () => {
68-
// setup KB initially
69-
7068
await deployTinyElserAndSetupKb(getService);
71-
72-
// setup KB with custom inference endpoint
7369
await createTinyElserInferenceEndpoint(getService, {
7470
inferenceId: CUSTOM_TINY_ELSER_INFERENCE_ID,
7571
});
@@ -120,7 +116,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon
120116
await createTinyElserInferenceEndpoint(getService, {
121117
inferenceId: customInferenceId,
122118
});
123-
await setupKnowledgeBase(observabilityAIAssistantAPIClient, customInferenceId);
119+
await setupKnowledgeBase(getService, customInferenceId);
124120
await waitForKnowledgeBaseReady(getService);
125121
});
126122

@@ -154,19 +150,14 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon
154150
}
155151

156152
function setupKbAsAdmin(inferenceId: string) {
157-
return observabilityAIAssistantAPIClient.admin({
158-
endpoint: 'POST /internal/observability_ai_assistant/kb/setup',
159-
params: {
160-
query: { inference_id: inferenceId },
161-
},
162-
});
153+
return setupKnowledgeBase(getService, inferenceId);
163154
}
164155

165156
function setupKbAsViewer(inferenceId: string) {
166157
return observabilityAIAssistantAPIClient.viewer({
167158
endpoint: 'POST /internal/observability_ai_assistant/kb/setup',
168159
params: {
169-
query: { inference_id: inferenceId },
160+
query: { inference_id: inferenceId, wait_until_complete: true },
170161
},
171162
});
172163
}

x-pack/test/api_integration/deployment_agnostic/apis/observability/ai_assistant/utils/index_assets.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
1515
import type { ObservabilityAIAssistantApiClient } from '../../../../services/observability_ai_assistant_api';
1616
import { TINY_ELSER_INFERENCE_ID } from './model_and_inference';
17+
import { getConcreteWriteIndexFromAlias } from './knowledge_base';
1718

1819
export async function runStartupMigrations(
1920
observabilityAIAssistantAPIClient: ObservabilityAIAssistantApiClient
@@ -67,9 +68,16 @@ export async function restoreIndexAssets(
6768
getService: DeploymentAgnosticFtrProviderContext['getService']
6869
) {
6970
const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantApi');
71+
const retry = getService('retry');
72+
const es = getService('es');
73+
const log = getService('log');
7074

71-
await deleteIndexAssets(getService);
72-
await createOrUpdateIndexAssets(observabilityAIAssistantAPIClient);
75+
await retry.try(async () => {
76+
log.debug('Restoring index assets');
77+
await deleteIndexAssets(getService);
78+
await createOrUpdateIndexAssets(observabilityAIAssistantAPIClient);
79+
expect(await getConcreteWriteIndexFromAlias(es)).to.be(resourceNames.concreteWriteIndexName.kb);
80+
});
7381
}
7482

7583
export async function getComponentTemplate(es: Client) {

x-pack/test/api_integration/deployment_agnostic/apis/observability/ai_assistant/utils/knowledge_base.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ export async function waitForKnowledgeBaseIndex(
4040
});
4141
}
4242

43+
export async function getKnowledgeBaseStatus(
44+
observabilityAIAssistantAPIClient: ObservabilityAIAssistantApiClient
45+
) {
46+
return observabilityAIAssistantAPIClient.editor({
47+
endpoint: 'GET /internal/observability_ai_assistant/kb/status',
48+
});
49+
}
50+
4351
export async function waitForKnowledgeBaseReady(
4452
getService: DeploymentAgnosticFtrProviderContext['getService']
4553
) {
@@ -49,9 +57,7 @@ export async function waitForKnowledgeBaseReady(
4957

5058
await retry.tryForTime(5 * 60 * 1000, async () => {
5159
log.debug(`Waiting for knowledge base to be ready...`);
52-
const res = await observabilityAIAssistantAPIClient.editor({
53-
endpoint: 'GET /internal/observability_ai_assistant/kb/status',
54-
});
60+
const res = await getKnowledgeBaseStatus(observabilityAIAssistantAPIClient);
5561
expect(res.status).to.be(200);
5662
expect(res.body.kbState).to.be(KnowledgeBaseState.READY);
5763
expect(res.body.isReIndexing).to.be(false);
@@ -60,13 +66,21 @@ export async function waitForKnowledgeBaseReady(
6066
}
6167

6268
export async function setupKnowledgeBase(
63-
observabilityAIAssistantAPIClient: ObservabilityAIAssistantApiClient,
69+
getService: DeploymentAgnosticFtrProviderContext['getService'],
6470
inferenceId: string
6571
) {
72+
const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantApi');
73+
const log = getService('log');
74+
75+
const statusResult = await getKnowledgeBaseStatus(observabilityAIAssistantAPIClient);
76+
77+
log.debug(
78+
`Setting up knowledge base with inference endpoint = "${TINY_ELSER_INFERENCE_ID}", concreteWriteIndex = ${statusResult.body.concreteWriteIndex}, currentInferenceId = ${statusResult.body.currentInferenceId}, isReIndexing = ${statusResult.body.isReIndexing}`
79+
);
6680
return observabilityAIAssistantAPIClient.admin({
6781
endpoint: 'POST /internal/observability_ai_assistant/kb/setup',
6882
params: {
69-
query: { inference_id: inferenceId },
83+
query: { inference_id: inferenceId, wait_until_complete: true },
7084
},
7185
});
7286
}
@@ -77,6 +91,8 @@ export async function addSampleDocsToInternalKb(
7791
) {
7892
const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantApi');
7993
const es = getService('es');
94+
const log = getService('log');
95+
const retry = getService('retry');
8096

8197
await observabilityAIAssistantAPIClient.editor({
8298
endpoint: 'POST /internal/observability_ai_assistant/kb/entries/import',
@@ -88,6 +104,14 @@ export async function addSampleDocsToInternalKb(
88104
});
89105

90106
await refreshKbIndex(es);
107+
108+
await retry.try(async () => {
109+
const itemsInKb = await getKnowledgeBaseEntriesFromEs(es);
110+
log.debug(
111+
`Waiting for at least ${sampleDocs.length} docs to be available for search in KB. Currently ${itemsInKb.length} docs available.`
112+
);
113+
expect(itemsInKb.length >= sampleDocs.length).to.be(true);
114+
});
91115
}
92116

93117
// refresh the index to make sure the documents are searchable

0 commit comments

Comments
 (0)