diff --git a/oas_docs/bundle.json b/oas_docs/bundle.json index a862d1cecf043..39892916a2c29 100644 --- a/oas_docs/bundle.json +++ b/oas_docs/bundle.json @@ -83646,6 +83646,14 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "sampleDocsSize", + "required": false, + "schema": { + "type": "number" + } } ], "requestBody": { diff --git a/oas_docs/bundle.serverless.json b/oas_docs/bundle.serverless.json index 58f7965695670..6079d33c59ecd 100644 --- a/oas_docs/bundle.serverless.json +++ b/oas_docs/bundle.serverless.json @@ -82725,6 +82725,14 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "sampleDocsSize", + "required": false, + "schema": { + "type": "number" + } } ], "requestBody": { diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index 615d3c240da01..5850b87b3636a 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -71602,6 +71602,11 @@ paths: required: true schema: type: string + - in: query + name: sampleDocsSize + required: false + schema: + type: number requestBody: content: application/json: diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index ae9a5862f1b5d..420cc78ac7507 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -75976,6 +75976,11 @@ paths: required: true schema: type: string + - in: query + name: sampleDocsSize + required: false + schema: + type: number requestBody: content: application/json: diff --git a/packages/kbn-check-saved-objects-cli/current_fields.json b/packages/kbn-check-saved-objects-cli/current_fields.json index fd34bc7369f72..3fab6dd2c52a6 100644 --- a/packages/kbn-check-saved-objects-cli/current_fields.json +++ b/packages/kbn-check-saved-objects-cli/current_fields.json @@ -1167,6 +1167,9 @@ "updated", "updatedBy" ], + "significant-events-prompts": [ + "type" + ], "slo": [ "budgetingMethod", "description", diff --git a/packages/kbn-check-saved-objects-cli/current_mappings.json b/packages/kbn-check-saved-objects-cli/current_mappings.json index 4501675a5745b..d1cac2e970dc5 100644 --- a/packages/kbn-check-saved-objects-cli/current_mappings.json +++ b/packages/kbn-check-saved-objects-cli/current_mappings.json @@ -3791,6 +3791,14 @@ } } }, + "significant-events-prompts": { + "dynamic": false, + "properties": { + "type": { + "type": "keyword" + } + } + }, "slo": { "dynamic": false, "properties": { diff --git a/src/core/packages/saved-objects/server-internal/src/object_types/index.ts b/src/core/packages/saved-objects/server-internal/src/object_types/index.ts index 1fca5f9436b59..7eb3862850ae4 100644 --- a/src/core/packages/saved-objects/server-internal/src/object_types/index.ts +++ b/src/core/packages/saved-objects/server-internal/src/object_types/index.ts @@ -11,4 +11,4 @@ export { registerCoreObjectTypes } from './registration'; // set minimum number of registered saved objects to ensure no object types are removed after 8.8 // declared in internal implementation explicitly to prevent unintended changes. -export const SAVED_OBJECT_TYPES_COUNT = 138 as const; +export const SAVED_OBJECT_TYPES_COUNT = 139 as const; diff --git a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts index 66b120f5bc9d6..d6b973eae0be9 100644 --- a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts +++ b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts @@ -175,6 +175,7 @@ describe('checking migration metadata changes on all registered SO types', () => "siem-ui-timeline": "439d5deaa90cc74d10a13804db1b40b9c66ccadfb9c6b34b2bdfcedab8d80e41", "siem-ui-timeline-note": "c81b789cea05ba59973639194825838d48c571b851cd8c359c1e4aacf2c8d2c5", "siem-ui-timeline-pinned-event": "abd9cf88c47bd4662a898ba8f8fb736b4d3da14975f0532830b17896983bf83d", + "significant-events-prompts": "5a04b7dcff90a8855a6eca97460abb0a6244127f9d9b0651bfb65ef7de9c07b8", "slo": "682c6d9e3ba7a489def2b824da547e26f67a16343747425a1efdf618c7fdb3e7", "slo-settings": "eaee24c76b1c02ba4ae1bf3742c1f5eca942a1662978f3420ec1b7f951746a32", "space": "c87c68da91a86291dfc4d8b406849979c97790bdc12187d9c9b7b119c393bcd7", @@ -1099,6 +1100,10 @@ describe('checking migration metadata changes on all registered SO types', () => "siem-ui-timeline-pinned-event|schemas: da39a3ee5e6b4b0d3255bfef95601890afd80709", "siem-ui-timeline-pinned-event|7.16.0: 91da406ec7758e5787f971ac10d62d7006d5cde5", "==============================================================================", + "significant-events-prompts|global: 0895baced3e1359416eb0d5173817d0a2bf08728", + "significant-events-prompts|mappings: 67889ff480917c7fef440d3a55d48b730ab74dd2", + "significant-events-prompts|schemas: da39a3ee5e6b4b0d3255bfef95601890afd80709", + "============================================================================", "slo|global: 3bb1282c625b0cbdaf1317157a973f0eb263b13d", "slo|mappings: 98c4bcb86ae664a5d21cd03fd7df79068b378caa", "slo|schemas: da39a3ee5e6b4b0d3255bfef95601890afd80709", @@ -1372,6 +1377,7 @@ describe('checking migration metadata changes on all registered SO types', () => "siem-ui-timeline": "10.1.0", "siem-ui-timeline-note": "10.0.0", "siem-ui-timeline-pinned-event": "10.0.0", + "significant-events-prompts": "10.0.0", "slo": "10.1.0", "slo-settings": "10.0.0", "space": "10.2.0", @@ -1520,6 +1526,7 @@ describe('checking migration metadata changes on all registered SO types', () => "siem-ui-timeline": "10.1.0", "siem-ui-timeline-note": "7.16.0", "siem-ui-timeline-pinned-event": "7.16.0", + "significant-events-prompts": "10.0.0", "slo": "10.1.0", "slo-settings": "10.0.0", "space": "10.2.0", diff --git a/x-pack/platform/packages/shared/kbn-ai-tools/src/tools/describe_dataset/index.ts b/x-pack/platform/packages/shared/kbn-ai-tools/src/tools/describe_dataset/index.ts index 0d134311e1e5e..ba3bc66e44d57 100644 --- a/x-pack/platform/packages/shared/kbn-ai-tools/src/tools/describe_dataset/index.ts +++ b/x-pack/platform/packages/shared/kbn-ai-tools/src/tools/describe_dataset/index.ts @@ -18,6 +18,7 @@ export async function describeDataset({ index, kql, filter, + sampleDocsSize, }: { esClient: ElasticsearchClient; start: number; @@ -25,6 +26,7 @@ export async function describeDataset({ index: string | string[]; kql?: string; filter?: QueryDslQueryContainer | QueryDslQueryContainer[]; + sampleDocsSize?: number; }) { const [fieldCaps, hits] = await Promise.all([ esClient.fieldCaps({ @@ -43,6 +45,7 @@ export async function describeDataset({ end, kql, filter, + size: sampleDocsSize, }), ]); diff --git a/x-pack/platform/packages/shared/kbn-streams-ai/src/features/identify_features.ts b/x-pack/platform/packages/shared/kbn-streams-ai/src/features/identify_features.ts index 028c5c30fd1a8..9aef956e390f8 100644 --- a/x-pack/platform/packages/shared/kbn-streams-ai/src/features/identify_features.ts +++ b/x-pack/platform/packages/shared/kbn-streams-ai/src/features/identify_features.ts @@ -17,7 +17,7 @@ import { } from '@kbn/streams-schema'; import type { Condition } from '@kbn/streamlang'; import { withSpan } from '@kbn/apm-utils'; -import { IdentifySystemsPrompt } from './prompt'; +import { createIdentifySystemsPrompt } from './prompt'; import { clusterLogs } from '../cluster_logs/cluster_logs'; import conditionSchemaText from '../shared/condition_schema.text'; import { generateStreamDescription } from '../description/generate_description'; @@ -33,6 +33,7 @@ export interface IdentifyFeaturesOptions { logger: Logger; signal: AbortSignal; analysis: DocumentAnalysis; + systemPromptOverride?: string; } /** @@ -54,6 +55,7 @@ export async function identifySystemFeatures({ analysis, dropUnmapped = false, maxSteps: initialMaxSteps, + systemPromptOverride, }: IdentifyFeaturesOptions & { dropUnmapped?: boolean; maxSteps?: number; @@ -94,7 +96,9 @@ export async function identifySystemFeatures({ initial_clustering: JSON.stringify(initialClustering), condition_schema: conditionSchemaText, }, - prompt: IdentifySystemsPrompt, + prompt: createIdentifySystemsPrompt({ + systemPromptOverride, + }), inferenceClient, finalToolChoice: { function: 'finalize_systems', diff --git a/x-pack/platform/packages/shared/kbn-streams-ai/src/features/prompt.ts b/x-pack/platform/packages/shared/kbn-streams-ai/src/features/prompt.ts index aaa87936dd582..8e51f5d626ed1 100644 --- a/x-pack/platform/packages/shared/kbn-streams-ai/src/features/prompt.ts +++ b/x-pack/platform/packages/shared/kbn-streams-ai/src/features/prompt.ts @@ -7,6 +7,7 @@ import { createPrompt } from '@kbn/inference-common'; import { z } from '@kbn/zod'; import { merge } from 'lodash'; +import systemPromptDefault from '../significant_events/system_prompt.text'; import systemPromptTemplate from './system_prompt.text'; import userPromptTemplate from './user_prompt.text'; @@ -59,38 +60,48 @@ export interface FinalizeSystemsResponse { }>; } -export const IdentifySystemsPrompt = createPrompt({ - name: 'identify_systems', - input: z.object({ - stream: z.object({ - name: z.string(), - description: z.string(), +export function createIdentifySystemsPrompt({ + systemPromptOverride, +}: { + systemPromptOverride?: string; +} = {}) { + const systemPrompt = systemPromptOverride ?? systemPromptDefault; + + return createPrompt({ + name: 'identify_systems', + input: z.object({ + stream: z.object({ + name: z.string(), + description: z.string(), + }), + dataset_analysis: z.string(), + initial_clustering: z.string(), + condition_schema: z.string(), }), - dataset_analysis: z.string(), - initial_clustering: z.string(), - condition_schema: z.string(), - }), -}) - .version({ - system: { - mustache: { - template: systemPromptTemplate, - }, - }, - template: { - mustache: { - template: userPromptTemplate, + }) + .version({ + system: { + mustache: { + template: systemPrompt, + }, }, - }, - tools: { - validate_systems: { - description: `Validate systems before finalizing`, - schema: systemsSchema, + template: { + mustache: { + template: userPromptTemplate, + }, }, - finalize_systems: { - description: 'Finalize system identification', - schema: finalSystemsSchema, + tools: { + validate_systems: { + description: `Validate systems before finalizing`, + schema: systemsSchema, + }, + finalize_systems: { + description: 'Finalize system identification', + schema: finalSystemsSchema, + }, }, - }, - }) - .get(); + }) + .get(); +} + +export { systemPromptTemplate as featuresSystemPromptTemplate }; diff --git a/x-pack/platform/packages/shared/kbn-streams-ai/src/significant_events/generate_significant_events.ts b/x-pack/platform/packages/shared/kbn-streams-ai/src/significant_events/generate_significant_events.ts index f91e6204a6ee0..667e119aa9ccb 100644 --- a/x-pack/platform/packages/shared/kbn-streams-ai/src/significant_events/generate_significant_events.ts +++ b/x-pack/platform/packages/shared/kbn-streams-ai/src/significant_events/generate_significant_events.ts @@ -14,7 +14,7 @@ import { conditionToQueryDsl } from '@kbn/streamlang'; import { executeAsReasoningAgent } from '@kbn/inference-prompt-utils'; import { fromKueryExpression } from '@kbn/es-query'; import { withSpan } from '@kbn/apm-utils'; -import { GenerateSignificantEventsPrompt } from './prompt'; +import { createGenerateSignificantEventsPrompt } from './prompt'; import type { SignificantEventType } from './types'; import { sumTokens } from '../helpers/sum_tokens'; @@ -39,6 +39,9 @@ export async function generateSignificantEvents({ esClient, inferenceClient, signal, + sampleDocsSize, + // optional overrides for templates + systemPromptOverride, logger, }: { stream: Streams.all.Definition; @@ -49,6 +52,8 @@ export async function generateSignificantEvents({ inferenceClient: BoundInferenceClient; signal: AbortSignal; logger: Logger; + sampleDocsSize?: number; + systemPromptOverride?: string; }): Promise<{ queries: Query[]; tokensUsed: ChatCompletionTokenCount; @@ -58,6 +63,7 @@ export async function generateSignificantEvents({ logger.trace('Describing dataset for significant event generation'); const analysis = await withSpan('describe_dataset_for_significant_event_generation', () => describeDataset({ + sampleDocsSize, start, end, esClient, @@ -66,6 +72,11 @@ export async function generateSignificantEvents({ }) ); + // create the prompt instance using provided overrides (if any) + const prompt = createGenerateSignificantEventsPrompt({ + systemPromptOverride, + }); + logger.trace('Generating significant events via reasoning agent'); const response = await withSpan('generate_significant_events', () => executeAsReasoningAgent({ @@ -75,7 +86,7 @@ export async function generateSignificantEvents({ description: feature?.description || stream.description, }, maxSteps: 4, - prompt: GenerateSignificantEventsPrompt, + prompt, inferenceClient, toolCallbacks: { add_queries: async (toolCall) => { diff --git a/x-pack/platform/packages/shared/kbn-streams-ai/src/significant_events/prompt.ts b/x-pack/platform/packages/shared/kbn-streams-ai/src/significant_events/prompt.ts index b63c3861b826d..d6c364089754e 100644 --- a/x-pack/platform/packages/shared/kbn-streams-ai/src/significant_events/prompt.ts +++ b/x-pack/platform/packages/shared/kbn-streams-ai/src/significant_events/prompt.ts @@ -7,8 +7,8 @@ import { z } from '@kbn/zod'; import { createPrompt } from '@kbn/inference-common'; -import systemPromptTemplate from './system_prompt.text'; -import userPromptTemplate from './user_prompt.text'; +import systemPromptDefault from './system_prompt.text'; +import userPromptDefault from './user_prompt.text'; import { SIGNIFICANT_EVENT_TYPE_CONFIGURATION, SIGNIFICANT_EVENT_TYPE_ERROR, @@ -17,65 +17,75 @@ import { SIGNIFICANT_EVENT_TYPE_SECURITY, } from './types'; -export const GenerateSignificantEventsPrompt = createPrompt({ - name: 'generate_significant_events', - input: z.object({ - name: z.string(), - description: z.string(), - dataset_analysis: z.string(), - }), -}) - .version({ - system: { - mustache: { - template: systemPromptTemplate, +export function createGenerateSignificantEventsPrompt({ + systemPromptOverride, +}: { + systemPromptOverride?: string; +} = {}) { + const systemPrompt = systemPromptOverride ?? systemPromptDefault; + + return createPrompt({ + name: 'generate_significant_events', + input: z.object({ + name: z.string(), + description: z.string(), + dataset_analysis: z.string(), + }), + }) + .version({ + system: { + mustache: { + template: systemPrompt, + }, }, - }, - template: { - mustache: { - template: userPromptTemplate, + template: { + mustache: { + template: userPromptDefault, + }, }, - }, - tools: { - add_queries: { - description: `Add queries to suggest to the user`, - schema: { - type: 'object', - properties: { - queries: { - type: 'array', - items: { - type: 'object', - properties: { - kql: { - type: 'string', - }, - title: { - type: 'string', - }, - category: { - type: 'string', - enum: [ - SIGNIFICANT_EVENT_TYPE_OPERATIONAL, - SIGNIFICANT_EVENT_TYPE_CONFIGURATION, - SIGNIFICANT_EVENT_TYPE_ERROR, - SIGNIFICANT_EVENT_TYPE_RESOURCE_HEALTH, - SIGNIFICANT_EVENT_TYPE_SECURITY, - ], - }, - severity_score: { - type: 'number', - minimum: 0, - maximum: 100, + tools: { + add_queries: { + description: `Add queries to suggest to the user`, + schema: { + type: 'object', + properties: { + queries: { + type: 'array', + items: { + type: 'object', + properties: { + kql: { + type: 'string', + }, + title: { + type: 'string', + }, + category: { + type: 'string', + enum: [ + SIGNIFICANT_EVENT_TYPE_OPERATIONAL, + SIGNIFICANT_EVENT_TYPE_CONFIGURATION, + SIGNIFICANT_EVENT_TYPE_ERROR, + SIGNIFICANT_EVENT_TYPE_RESOURCE_HEALTH, + SIGNIFICANT_EVENT_TYPE_SECURITY, + ], + }, + severity_score: { + type: 'number', + minimum: 0, + maximum: 100, + }, }, + required: ['kql', 'title', 'category', 'severity_score'], }, - required: ['kql', 'title', 'category', 'severity_score'], }, }, + required: ['queries'], }, - required: ['queries'], }, - }, - } as const, - }) - .get(); + } as const, + }) + .get(); +} + +export { systemPromptDefault as significantEventsSystemPromptTemplate }; diff --git a/x-pack/platform/plugins/shared/streams/server/lib/saved_objects/register_saved_objects.ts b/x-pack/platform/plugins/shared/streams/server/lib/saved_objects/register_saved_objects.ts new file mode 100644 index 0000000000000..7c928eb2fdc54 --- /dev/null +++ b/x-pack/platform/plugins/shared/streams/server/lib/saved_objects/register_saved_objects.ts @@ -0,0 +1,14 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObjectsServiceSetup } from '@kbn/core/server'; + +import { getSignificantEventPrompts } from './significant_events/prompts_config'; + +export const readStreamSavedObjects = (savedObjectsService: SavedObjectsServiceSetup) => { + savedObjectsService.registerType(getSignificantEventPrompts()); +}; diff --git a/x-pack/platform/plugins/shared/streams/server/lib/saved_objects/significant_events/promps_config_service.ts b/x-pack/platform/plugins/shared/streams/server/lib/saved_objects/significant_events/promps_config_service.ts new file mode 100644 index 0000000000000..e3be13fd20d6c --- /dev/null +++ b/x-pack/platform/plugins/shared/streams/server/lib/saved_objects/significant_events/promps_config_service.ts @@ -0,0 +1,96 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + Logger, + SavedObjectsClientContract, + SavedObjectsCreateOptions, +} from '@kbn/core/server'; +import { significantEventsSystemPromptTemplate } from '@kbn/streams-ai/src/significant_events/prompt'; +import { featuresSystemPromptTemplate } from '@kbn/streams-ai/src/features/prompt'; +import { significantEventsPromptsType } from './prompts_config'; + +const defaultsPrompts = { + featurePromptOverride: featuresSystemPromptTemplate, + significantEventsPromptOverride: significantEventsSystemPromptTemplate, +}; + +const SINGLETON_PROMPTS_ID = 'significant-events-prompts-config-id'; + +export interface PromptsConfigAttributes { + featurePromptOverride?: string; + significantEventsPromptOverride?: string; +} + +export class PromptsConfigService { + private readonly soClient: SavedObjectsClientContract; + private readonly logger: Logger; + + constructor({ soClient, logger }: { soClient: SavedObjectsClientContract; logger: Logger }) { + this.soClient = soClient; + this.logger = logger; + } + + /** + * Create a new prompt saved object. + * attributes is a plain object (e.g. { name, systemPromptTemplate, userPromptTemplate, inputExample }) + * Note: no forced singleton id/overwrite — allow multiple prompt objects (user-created). + */ + async createPrompt(attributes: PromptsConfigAttributes, options?: SavedObjectsCreateOptions) { + // fetch existing and merge it in to avoid overwriting other fields + const existing = await this.getPrompt(); + + this.logger.debug('Creating significant events prompt'); + // pass options through so callers can set id if they want; don't force overwrite + const data = await this.soClient.create( + significantEventsPromptsType, + { + ...defaultsPrompts, + ...existing, + ...attributes, + }, + { + ...(options ?? {}), + id: SINGLETON_PROMPTS_ID, + overwrite: true, + } + ); + return data.attributes; + } + + async getPrompt() { + try { + const data = await this.soClient.get( + significantEventsPromptsType, + SINGLETON_PROMPTS_ID + ); + this.logger.debug(`Retrieved significant events prompt ${SINGLETON_PROMPTS_ID}`); + return { + featurePromptOverride: + data.attributes.featurePromptOverride || defaultsPrompts.featurePromptOverride, + significantEventsPromptOverride: + data.attributes.significantEventsPromptOverride || + defaultsPrompts.significantEventsPromptOverride, + }; + } catch (err: any) { + // saved objects client throws with statusCode 404 for not found + if (err?.output?.statusCode === 404 || err?.statusCode === 404) { + // return the packaged default prompt on 404 as well + return defaultsPrompts; + } + throw err; + } + } + + /** + * Delete the prompts saved object (reset to defaults). + */ + async resetPrompts(): Promise { + this.logger.debug(`Deleting significant events prompt ${SINGLETON_PROMPTS_ID}`); + await this.soClient.delete(significantEventsPromptsType, SINGLETON_PROMPTS_ID); + } +} diff --git a/x-pack/platform/plugins/shared/streams/server/lib/saved_objects/significant_events/prompts_config.ts b/x-pack/platform/plugins/shared/streams/server/lib/saved_objects/significant_events/prompts_config.ts new file mode 100644 index 0000000000000..42778982137ec --- /dev/null +++ b/x-pack/platform/plugins/shared/streams/server/lib/saved_objects/significant_events/prompts_config.ts @@ -0,0 +1,29 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { SavedObjectsType } from '@kbn/core/server'; + +export const significantEventsPromptsType = 'significant-events-prompts'; + +export const getSignificantEventPrompts = (): SavedObjectsType => { + return { + name: significantEventsPromptsType, + hidden: false, + namespaceType: 'multiple', + mappings: { + dynamic: false, + properties: { + type: { + type: 'keyword', + }, + }, + }, + management: { + importableAndExportable: false, + }, + modelVersions: {}, + }; +}; diff --git a/x-pack/platform/plugins/shared/streams/server/lib/significant_events/generate_significant_events.ts b/x-pack/platform/plugins/shared/streams/server/lib/significant_events/generate_significant_events.ts index db59031011f88..a96f93801dc94 100644 --- a/x-pack/platform/plugins/shared/streams/server/lib/significant_events/generate_significant_events.ts +++ b/x-pack/platform/plugins/shared/streams/server/lib/significant_events/generate_significant_events.ts @@ -16,6 +16,9 @@ interface Params { start: number; end: number; feature?: Feature; + sampleDocsSize?: number; + // optional overrides for templates + systemPromptOverride?: string; } interface Dependencies { @@ -29,7 +32,8 @@ export async function generateSignificantEventDefinitions( params: Params, dependencies: Dependencies ): Promise<{ queries: GeneratedSignificantEventQuery[]; tokensUsed: ChatCompletionTokenCount }> { - const { definition, connectorId, start, end, feature } = params; + const { definition, connectorId, start, end, feature, sampleDocsSize, systemPromptOverride } = + params; const { inferenceClient, esClient, logger, signal } = dependencies; const boundInferenceClient = inferenceClient.bindTo({ @@ -45,6 +49,8 @@ export async function generateSignificantEventDefinitions( logger, feature, signal, + sampleDocsSize, + systemPromptOverride, }); return { diff --git a/x-pack/platform/plugins/shared/streams/server/plugin.ts b/x-pack/platform/plugins/shared/streams/server/plugin.ts index 653d65367b789..9facc9f1f4974 100644 --- a/x-pack/platform/plugins/shared/streams/server/plugin.ts +++ b/x-pack/platform/plugins/shared/streams/server/plugin.ts @@ -46,6 +46,7 @@ import { createStreamsGlobalSearchResultProvider } from './lib/streams/create_st import { FeatureService } from './lib/streams/feature/feature_service'; import { ProcessorSuggestionsService } from './lib/streams/ingest_pipelines/processor_suggestions_service'; import { getDefaultFeatureRegistry } from './lib/streams/feature/feature_type_registry'; +import { readStreamSavedObjects } from './lib/saved_objects/register_saved_objects'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface StreamsPluginSetup {} @@ -103,6 +104,7 @@ export class StreamsPlugin })); registerRules({ plugins, logger: this.logger.get('rules') }); + readStreamSavedObjects(core.savedObjects); const assetService = new AssetService(core, this.logger); const attachmentService = new AttachmentService(core, this.logger); diff --git a/x-pack/platform/plugins/shared/streams/server/routes/index.ts b/x-pack/platform/plugins/shared/streams/server/routes/index.ts index 4c4b6a4663c2c..fa6af4d410d35 100644 --- a/x-pack/platform/plugins/shared/streams/server/routes/index.ts +++ b/x-pack/platform/plugins/shared/streams/server/routes/index.ts @@ -17,6 +17,7 @@ import { contentRoutes } from './content/route'; import { internalCrudRoutes } from './internal/streams/crud/route'; import { internalManagementRoutes } from './internal/streams/management/route'; import { featureRoutes as internalFeaturesRoutes } from './internal/streams/features/route'; +import { internalPromptsRoutes } from './internal/streams/prompts/route'; import { significantEventsRoutes } from './streams/significant_events/route'; import { queryRoutes } from './queries/route'; import { failureStoreRoutes } from './internal/streams/failure_store/route'; @@ -34,6 +35,7 @@ export const streamsRouteRepository = { ...internalProcessingRoutes, ...failureStoreRoutes, ...internalFeaturesRoutes, + ...internalPromptsRoutes, ...internalIngestRoutes, ...connectorRoutes, ...internalAttachmentRoutes, diff --git a/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/features/route.ts b/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/features/route.ts index 0ecb95699705f..f0a63e7d1525a 100644 --- a/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/features/route.ts +++ b/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/features/route.ts @@ -21,6 +21,7 @@ import { generateStreamDescription } from '@kbn/streams-ai'; import type { Observable } from 'rxjs'; import { from, map } from 'rxjs'; import { sumTokens } from '@kbn/streams-ai/src/helpers/sum_tokens'; +import { PromptsConfigService } from '../../../../lib/saved_objects/significant_events/promps_config_service'; import { StatusError } from '../../../../lib/streams/errors/status_error'; import { getDefaultFeatureRegistry } from '../../../../lib/streams/feature/feature_type_registry'; import { createServerRoute } from '../../../create_server_route'; @@ -316,6 +317,7 @@ export const identifyFeaturesRoute = createServerRoute({ uiSettingsClient, streamsClient, inferenceClient, + soClient, } = await getScopedClients({ request, }); @@ -343,6 +345,12 @@ export const identifyFeaturesRoute = createServerRoute({ const boundInferenceClient = inferenceClient.bindTo({ connectorId }); const signal = getRequestAbortSignal(request); const featureRegistry = getDefaultFeatureRegistry(); + const promptsConfigService = new PromptsConfigService({ + soClient, + logger, + }); + + const { featurePromptOverride } = await promptsConfigService.getPrompt(); return from( featureRegistry.identifyFeatures({ @@ -354,6 +362,7 @@ export const identifyFeaturesRoute = createServerRoute({ stream, features: hits, signal, + systemPromptOverride: featurePromptOverride, }) ).pipe( map(({ features, tokensUsed }) => { diff --git a/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/prompts/route.ts b/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/prompts/route.ts new file mode 100644 index 0000000000000..639d52265a4eb --- /dev/null +++ b/x-pack/platform/plugins/shared/streams/server/routes/internal/streams/prompts/route.ts @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from '@kbn/zod'; +import type { PromptsConfigAttributes } from '../../../../lib/saved_objects/significant_events/promps_config_service'; +import { PromptsConfigService } from '../../../../lib/saved_objects/significant_events/promps_config_service'; +import { StatusError } from '../../../../lib/streams/errors/status_error'; +import { createServerRoute } from '../../../create_server_route'; +import { STREAMS_API_PRIVILEGES } from '../../../../../common/constants'; + +export const setFeaturesPromptRoute = createServerRoute({ + endpoint: 'PUT /internal/streams/_prompts', + options: { + access: 'internal', + summary: 'Set prompts for streams', + description: 'Set custom prompts for streams features and significant events generation', + }, + security: { + authz: { + requiredPrivileges: [STREAMS_API_PRIVILEGES.manage], + }, + }, + params: z.object({ + body: z.object({ + featurePromptOverride: z.string().optional(), + significantEventsPromptOverride: z.string().optional(), + }), + }), + handler: async ({ + params, + request, + getScopedClients, + logger, + }): Promise<{ results: PromptsConfigAttributes }> => { + const { soClient } = await getScopedClients({ + request, + }); + const promptsConfigService = new PromptsConfigService({ + soClient, + logger, + }); + const { featurePromptOverride, significantEventsPromptOverride } = params.body; + + if (!featurePromptOverride && !significantEventsPromptOverride) { + throw new StatusError('At least one prompt template must be provided', 400); + } + const results = await promptsConfigService.createPrompt({ + featurePromptOverride, + significantEventsPromptOverride, + }); + return { results }; + }, +}); + +export const resetFeaturesPromptRoute = createServerRoute({ + endpoint: 'DELETE /internal/streams/_prompts', + options: { + access: 'internal', + summary: 'Reset prompts for streams', + }, + security: { + authz: { + requiredPrivileges: [STREAMS_API_PRIVILEGES.manage], + }, + }, + params: z.object({}), + handler: async ({ request, getScopedClients, logger }): Promise<{ success: boolean }> => { + const { soClient } = await getScopedClients({ + request, + }); + const promptsConfigService = new PromptsConfigService({ + soClient, + logger, + }); + + await promptsConfigService.resetPrompts(); + return { success: true }; + }, +}); + +export const getFeaturesPromptRoute = createServerRoute({ + endpoint: 'GET /internal/streams/_prompts', + options: { + access: 'internal', + summary: 'Get prompts for streams', + }, + security: { + authz: { + requiredPrivileges: [STREAMS_API_PRIVILEGES.manage], + }, + }, + params: z.object({}), + handler: async ({ request, getScopedClients, logger }): Promise => { + const { soClient } = await getScopedClients({ + request, + }); + const promptsConfigService = new PromptsConfigService({ + soClient, + logger, + }); + return await promptsConfigService.getPrompt(); + }, +}); + +export const internalPromptsRoutes = { + ...setFeaturesPromptRoute, + ...getFeaturesPromptRoute, + ...resetFeaturesPromptRoute, +}; diff --git a/x-pack/platform/plugins/shared/streams/server/routes/streams/significant_events/route.ts b/x-pack/platform/plugins/shared/streams/server/routes/streams/significant_events/route.ts index 1ef9ac712f4b5..0436182e471b7 100644 --- a/x-pack/platform/plugins/shared/streams/server/routes/streams/significant_events/route.ts +++ b/x-pack/platform/plugins/shared/streams/server/routes/streams/significant_events/route.ts @@ -13,6 +13,7 @@ import { import { z } from '@kbn/zod'; import { conditionSchema } from '@kbn/streamlang'; import { from as fromRxjs, map } from 'rxjs'; +import { PromptsConfigService } from '../../../lib/saved_objects/significant_events/promps_config_service'; import { STREAMS_API_PRIVILEGES } from '../../../../common/constants'; import { generateSignificantEventDefinitions } from '../../../lib/significant_events/generate_significant_events'; import { previewSignificantEvents } from '../../../lib/significant_events/preview_significant_events'; @@ -155,6 +156,7 @@ const generateSignificantEventsRoute = createServerRoute({ currentDate: dateFromString.optional(), from: dateFromString, to: dateFromString, + sampleDocsSize: z.number().optional(), }), body: z.object({ feature: featureSchema.optional(), @@ -181,14 +183,27 @@ const generateSignificantEventsRoute = createServerRoute({ server, logger, }): Promise => { - const { streamsClient, scopedClusterClient, licensing, inferenceClient, uiSettingsClient } = - await getScopedClients({ request }); + const { + streamsClient, + scopedClusterClient, + licensing, + inferenceClient, + uiSettingsClient, + soClient, + } = await getScopedClients({ request }); await assertSignificantEventsAccess({ server, licensing, uiSettingsClient }); await streamsClient.ensureStream(params.path.name); + const promptsConfigService = new PromptsConfigService({ + soClient, + logger, + }); + const definition = await streamsClient.getStream(params.path.name); + const { significantEventsPromptOverride } = await promptsConfigService.getPrompt(); + return fromRxjs( generateSignificantEventDefinitions( { @@ -197,6 +212,8 @@ const generateSignificantEventsRoute = createServerRoute({ connectorId: params.query.connectorId, start: params.query.from.valueOf(), end: params.query.to.valueOf(), + sampleDocsSize: params.query.sampleDocsSize, + systemPromptOverride: significantEventsPromptOverride, }, { inferenceClient,