Skip to content

Commit

Permalink
[Obs AI Assistant] Fix editing prompt from contextual insights (elast…
Browse files Browse the repository at this point in the history
  • Loading branch information
viduni94 committed Jan 15, 2025
1 parent 364abf7 commit 979ab73
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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));
}
Expand Down Expand Up @@ -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 = () => {
Expand Down Expand Up @@ -372,15 +423,19 @@ export function Insight({
</>
);
} else if (isEditingPrompt) {
children = (
<PromptEdit
initialPrompt={
getLastMessageOfType(messages.messages, MessageRole.User)?.message.content || ''
}
onSend={onEditPrompt}
onCancel={handleCancel}
/>
);
const instructionToEdit = getPromptToEdit();

if (messages.status === FETCH_STATUS.SUCCESS && instructionToEdit) {
children = (
<PromptEdit
initialPrompt={instructionToEdit}
onSend={onEditPrompt}
onCancel={handleCancel}
/>
);
} else {
children = <EuiLoadingSpinner size="m" />;
}
} else if (!connectors.loading && !connectors.connectors?.length) {
children = (
<MissingCredentialsCallout connectorsManagementHref={getConnectorsManagementHref(http!)} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down Expand Up @@ -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}
Expand Down

0 comments on commit 979ab73

Please sign in to comment.