From 8114028bb69f6e18a4c7e454d0167f3c5e84148e Mon Sep 17 00:00:00 2001 From: ppisljar Date: Thu, 30 Apr 2026 10:28:40 +0200 Subject: [PATCH 1/2] sml schema updates --- .../server/services/sml/sml_indexer.ts | 34 ++++++++++++------- .../server/services/sml/sml_service.test.ts | 31 ++++++++++++++--- .../server/services/sml/sml_service.ts | 14 +++++++- .../server/services/sml/sml_storage.ts | 5 ++- .../server/services/sml/types.ts | 18 ++++++++-- 5 files changed, 80 insertions(+), 22 deletions(-) diff --git a/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/sml_indexer.ts b/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/sml_indexer.ts index c991bcceaf811..cb27545baf513 100644 --- a/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/sml_indexer.ts +++ b/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/sml_indexer.ts @@ -13,7 +13,7 @@ import type { } from '@kbn/core-saved-objects-api-server'; import type { Logger } from '@kbn/logging'; import type { SmlTypeRegistry } from './sml_type_registry'; -import type { SmlIndexAction, SmlContext } from './types'; +import type { SmlIndexAction, SmlContext, SmlDocument } from './types'; import { createSmlStorage, smlIndexName } from './sml_storage'; import { isNotFoundError } from './sml_service'; @@ -124,20 +124,30 @@ class SmlIndexerImpl implements SmlIndexer { const now = new Date().toISOString(); const bulkOps = smlData.chunks.map((chunk) => { const chunkId = `${attachmentType}:${originId}:${uuidv4()}`; + const document: SmlDocument = { + id: chunkId, + type: chunk.type, + title: chunk.title, + origin_id: originId, + content: chunk.content, + created_at: now, + updated_at: now, + spaces, + permissions: chunk.permissions ?? [], + }; + if (chunk.description !== undefined) { + document.description = chunk.description; + } + if (chunk.user_id !== undefined) { + document.user_id = chunk.user_id; + } + if (chunk.references !== undefined) { + document.references = chunk.references; + } return { index: { _id: chunkId, - document: { - id: chunkId, - type: chunk.type, - title: chunk.title, - origin_id: originId, - content: chunk.content, - created_at: now, - updated_at: now, - spaces, - permissions: chunk.permissions ?? [], - }, + document, }, }; }); diff --git a/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/sml_service.test.ts b/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/sml_service.test.ts index 48a1cace46a3c..868cb08b6fb7b 100644 --- a/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/sml_service.test.ts +++ b/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/sml_service.test.ts @@ -208,10 +208,19 @@ describe('SmlService', () => { bool: { must: [ { - multi_match: { - query: 'foo bar', - type: 'bool_prefix', - fields: saytBoolPrefixFields, + bool: { + should: [ + { + multi_match: { + query: 'foo bar', + type: 'bool_prefix', + fields: saytBoolPrefixFields, + }, + }, + { match: { content: 'foo bar' } }, + { match: { description: 'foo bar' } }, + ], + minimum_should_match: 1, }, }, ], @@ -247,7 +256,7 @@ describe('SmlService', () => { }); const call = esClient.search.mock.calls[0]![0]!; - expect(call._source).toEqual({ excludes: ['content'] }); + expect(call._source).toEqual({ excludes: ['content', 'description'] }); }); it('uses match_all for query "*"', async () => { @@ -312,6 +321,9 @@ describe('SmlService', () => { title: 'My Viz', origin_id: 'ref-1', content: 'content text', + description: 'A lens viz', + user_id: 'user-1', + references: ['lens:other:uuid'], created_at: '2024-01-01', updated_at: '2024-01-02', spaces: ['default'], @@ -338,6 +350,9 @@ describe('SmlService', () => { title: 'My Viz', origin_id: 'ref-1', content: 'content text', + description: 'A lens viz', + user_id: 'user-1', + references: ['lens:other:uuid'], created_at: '2024-01-01', updated_at: '2024-01-02', spaces: ['default'], @@ -803,6 +818,9 @@ describe('SmlService', () => { title: 'Doc 2', origin_id: 'ref-2', content: 'content 2', + description: 'dash desc', + user_id: 'u2', + references: ['lens:x:y'], created_at: '2024-01-01', updated_at: '2024-01-02', spaces: ['default'], @@ -837,6 +855,9 @@ describe('SmlService', () => { title: 'Doc 2', origin_id: 'ref-2', content: 'content 2', + description: 'dash desc', + user_id: 'u2', + references: ['lens:x:y'], created_at: '2024-01-01', updated_at: '2024-01-02', spaces: ['default'], diff --git a/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/sml_service.ts b/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/sml_service.ts index b4bf2aefe3a9f..ae21ee15342b7 100644 --- a/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/sml_service.ts +++ b/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/sml_service.ts @@ -379,7 +379,7 @@ const searchSml = async ({ ], }, }, - _source: skipContent ? { excludes: ['content'] } : true, + _source: skipContent ? { excludes: ['content', 'description'] } : true, }); const total = @@ -397,10 +397,13 @@ const searchSml = async ({ title: source.title ?? '', origin_id: source.origin_id ?? '', content: source.content, + description: source.description, + references: source.references, created_at: source.created_at ?? '', updated_at: source.updated_at ?? '', spaces: source.spaces ?? [], permissions: source.permissions ?? [], + user_id: source.user_id, score: hit._score ?? 0, }; }); @@ -470,6 +473,15 @@ const getDocumentsByIds = async ({ spaces: source.spaces ?? [], permissions: source.permissions ?? [], }; + if (source.description !== undefined) { + doc.description = source.description; + } + if (source.user_id !== undefined) { + doc.user_id = source.user_id; + } + if (source.references !== undefined) { + doc.references = source.references; + } docMap.set(doc.id, doc); } } catch (error) { diff --git a/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/sml_storage.ts b/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/sml_storage.ts index ed7e90467be1d..239b5acf6ec4e 100644 --- a/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/sml_storage.ts +++ b/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/sml_storage.ts @@ -25,7 +25,10 @@ const smlStorageSchemaProperties = { }), title: types.search_as_you_type({}), origin_id: types.keyword({}), - content: types.text({}), + content: types.semantic_text({}), + description: types.semantic_text({}), + user_id: types.keyword({}), + references: types.keyword({}), created_at: types.date({}), updated_at: types.date({}), spaces: types.keyword({}), diff --git a/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/types.ts b/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/types.ts index 9b9669eca356e..564fde6345d61 100644 --- a/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/types.ts +++ b/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/types.ts @@ -20,10 +20,16 @@ import type { AttachmentInput } from '@kbn/agent-builder-common/attachments'; export interface SmlChunk { /** Type of the chunk (e.g., 'dashboard', 'lens', 'esql') */ type: string; - /** Searchable content text */ + /** Searchable content (indexed as `semantic_text`) */ content: string; /** Display title */ title: string; + /** Longer summary for semantic search (indexed as `semantic_text`); omit or empty if none */ + description?: string; + /** Owner or last-modifier user id when known */ + user_id?: string; + /** Other SML chunk ids this item references */ + references?: string[]; /** Permissions required to access the underlying element (e.g., 'saved_object:lens/get') */ permissions?: string[]; } @@ -116,8 +122,14 @@ export interface SmlDocument { title: string; /** Origin ID (e.g., saved object ID) */ origin_id: string; - /** Searchable content */ + /** Searchable content (`semantic_text` in the index) */ content: string; + /** Semantic summary (`semantic_text` in the index) */ + description?: string; + /** Owner or last-modifier user id */ + user_id?: string; + /** Referenced SML chunk ids */ + references?: string[]; /** Timestamp when first created */ created_at: string; /** Timestamp when last updated */ @@ -130,7 +142,7 @@ export interface SmlDocument { /** * An SML search result — same fields as {@link SmlDocument} plus relevance score. - * `content` is optional when the query excluded it from `_source` (e.g. `skipContent`). + * `content` and `description` are optional when excluded from `_source` (e.g. `skipContent`). */ export type SmlSearchResult = Omit & { content?: string; From 09dd19d4c8868e4df909fd1ab71fb24edbba1918 Mon Sep 17 00:00:00 2001 From: ppisljar Date: Wed, 6 May 2026 14:31:02 +0200 Subject: [PATCH 2/2] ... --- .../server/services/sml/sml_service.ts | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/sml_service.ts b/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/sml_service.ts index ae21ee15342b7..7ec4b7ab31035 100644 --- a/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/sml_service.ts +++ b/x-pack/platform/plugins/shared/agent_context_layer/server/services/sml/sml_service.ts @@ -316,8 +316,11 @@ const SML_SEARCH_AS_YOU_TYPE_FIELDS = [ ] as const; /** - * Build the search query from a single string. Only `type` and `title` (search_as_you_type + bool_prefix) are searched. - * After trim: empty string or `*` → `match_all` (return everything) + * Build the search query from a single string. `type` and `title` use search_as_you_type + bool_prefix + * for autocomplete-style matching, while `content` and `description` (semantic_text fields) are + * matched with standard `match` queries so longer-form text is also retrievable. + * + * After trim: empty string or `*` → `match_all` (return everything). */ const buildSmlSearchQuery = (query: string): Record => { const trimmed = query.trim(); @@ -325,10 +328,19 @@ const buildSmlSearchQuery = (query: string): Record => { return { match_all: {} }; } return { - multi_match: { - query: trimmed, - type: 'bool_prefix', - fields: [...SML_SEARCH_AS_YOU_TYPE_FIELDS], + bool: { + should: [ + { + multi_match: { + query: trimmed, + type: 'bool_prefix', + fields: [...SML_SEARCH_AS_YOU_TYPE_FIELDS], + }, + }, + { match: { content: trimmed } }, + { match: { description: trimmed } }, + ], + minimum_should_match: 1, }, }; };