From 979ab73ff21d9b53072e2798e7d03764834bf2c2 Mon Sep 17 00:00:00 2001 From: Viduni Wickramarachchi Date: Tue, 14 Jan 2025 14:11:47 -0500 Subject: [PATCH] [Obs AI Assistant] Fix editing prompt from contextual insights (#201642) --- .../public/components/insight/insight.tsx | 83 +++++++++++++++---- .../alert_details_contextual_insights.tsx | 6 +- 2 files changed, 74 insertions(+), 15 deletions(-) diff --git a/x-pack/platform/plugins/shared/observability_solution/observability_ai_assistant/public/components/insight/insight.tsx b/x-pack/platform/plugins/shared/observability_solution/observability_ai_assistant/public/components/insight/insight.tsx index 680a92f559f0a..5be7a75449970 100644 --- a/x-pack/platform/plugins/shared/observability_solution/observability_ai_assistant/public/components/insight/insight.tsx +++ b/x-pack/platform/plugins/shared/observability_solution/observability_ai_assistant/public/components/insight/insight.tsx @@ -14,11 +14,13 @@ import { EuiText, EuiTextArea, EuiCallOut, + EuiLoadingSpinner, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { cloneDeep, isArray, isEmpty, last, once } from 'lodash'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import useObservable from 'react-use/lib/useObservable'; +import dedent from 'dedent'; import { ILicense } from '@kbn/licensing-plugin/public'; import { MessageRole, type Message } from '../../../common/types'; import { ObservabilityAIAssistantChatServiceContext } from '../../context/observability_ai_assistant_chat_service_context'; @@ -41,6 +43,10 @@ import { InsightBase } from './insight_base'; import { ActionsMenu } from './actions_menu'; import { ObservabilityAIAssistantTelemetryEventType } from '../../analytics/telemetry_event_type'; +// defined and used in x-pack/solutions/observability/plugins/observability/public/pages/alert_details/alert_details_contextual_insights.tsx +const CONTEXT_INSTRUCTION = + 'The following contextual information is available to help you understand the alert'; + function getLastMessageOfType(messages: Message[], role: MessageRole) { return last(messages.filter((msg) => msg.message.role === role)); } @@ -288,16 +294,61 @@ export function Insight({ [service] ); + const getPromptToEdit = () => { + const clonedMessages = cloneDeep(messages.messages); + const lastUserPrompt = getLastMessageOfType(clonedMessages, MessageRole.User)?.message.content; + + if (!lastUserPrompt) { + return ''; + } + + try { + const { instructions = '' } = JSON.parse(lastUserPrompt); + + // instructions is a large multiline string with contextual information appended to it. + // We'll split at CONTEXT_INSTRUCTION so that we can extract the instructions the user needs to edit + const [beforeContextInstruction] = instructions.split(CONTEXT_INSTRUCTION); + return beforeContextInstruction.trim(); + } catch (e) { + return ''; + } + }; + const onEditPrompt = (newPrompt: string) => { const clonedMessages = cloneDeep(messages.messages); const userMessage = getLastMessageOfType(clonedMessages, MessageRole.User); if (!userMessage) return false; - userMessage.message.content = newPrompt; - setIsPromptUpdated(true); - setMessages({ messages: clonedMessages, status: FETCH_STATUS.SUCCESS }); - setEditingPrompt(false); - return true; + try { + const parsedContent = JSON.parse(userMessage.message.content || ''); + + if (!parsedContent.instructions) { + return false; + } + + const [_beforeContextInstruction, afterContextInstruction] = + parsedContent.instructions.split(CONTEXT_INSTRUCTION); + + // Rebuild instructions with the new prompt, preserving everything after the delimiter + const updatedInstructions = dedent( + `${newPrompt} + ${CONTEXT_INSTRUCTION} + ${afterContextInstruction}` + ); + + // Assign the updated instructions + parsedContent.instructions = updatedInstructions; + userMessage.message.content = JSON.stringify(parsedContent); + + setIsPromptUpdated(true); + setMessages({ messages: clonedMessages, status: FETCH_STATUS.SUCCESS }); + setEditingPrompt(false); + return true; + } catch (e) { + // eslint-disable-next-line no-console + console.error('Failed to edit prompt:', e); + return false; + } }; const handleCancel = () => { @@ -372,15 +423,19 @@ export function Insight({ ); } else if (isEditingPrompt) { - children = ( - - ); + const instructionToEdit = getPromptToEdit(); + + if (messages.status === FETCH_STATUS.SUCCESS && instructionToEdit) { + children = ( + + ); + } else { + children = ; + } } else if (!connectors.loading && !connectors.connectors?.length) { children = ( diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/alert_details_contextual_insights.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/alert_details_contextual_insights.tsx index 17f7d335624d1..d63fd5d06eccd 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/alert_details_contextual_insights.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/alert_details_contextual_insights.tsx @@ -13,6 +13,10 @@ import { type AlertDetailsContextualInsight } from '../../../server/services'; import { useKibana } from '../../utils/kibana_react'; import { AlertData } from '../../hooks/use_fetch_alert_detail'; +// used in x-pack/platform/plugins/shared/observability_solution/observability_ai_assistant/public/components/insight/insight.tsx +const CONTEXT_INSTRUCTION = + 'The following contextual information is available to help you understand the alert'; + export function AlertDetailContextualInsights({ alert }: { alert: AlertData | null }) { const { services: { observabilityAIAssistant, http }, @@ -62,7 +66,7 @@ export function AlertDetailContextualInsights({ alert }: { alert: AlertData | nu instructions: dedent( `I'm an SRE. I am looking at an alert that was triggered. I want to understand why it was triggered, what it means, and what I should do next. - The following contextual information is available to help you understand the alert: + ${CONTEXT_INSTRUCTION}: ${obsAlertContext} The user already know the alert reason so do not repeat this: ${alert.formatted.reason}