[Security Assistant] Starter prompts (#224981)

This commit is contained in:
Steph Milovic 2025-06-25 05:52:31 -06:00 committed by GitHub
parent e140d226bd
commit b82ab8acb8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 580 additions and 9 deletions

View file

@ -19,7 +19,7 @@ export interface UseFindPromptsParams {
context: {
isAssistantEnabled: boolean;
httpFetch: HttpHandler;
toasts: IToasts;
toasts?: IToasts;
};
signal?: AbortSignal | undefined;
params: FindSecurityAIPromptsRequestQuery;
@ -82,7 +82,7 @@ const getPrompts = async ({
query,
}: {
httpFetch: HttpHandler;
toasts: IToasts;
toasts?: IToasts;
signal?: AbortSignal | undefined;
query: FindSecurityAIPromptsRequestQuery;
}) => {

View file

@ -10,27 +10,42 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui';
import { css } from '@emotion/react';
import { PromptResponse } from '@kbn/elastic-assistant-common';
import { AssistantBeacon } from '@kbn/ai-assistant-icon';
import { useAssistantContext } from '../../..';
import { StarterPrompts } from './starter_prompts';
import { SystemPrompt } from '../prompt_editor/system_prompt';
import { SetupKnowledgeBaseButton } from '../../knowledge_base/setup_knowledge_base_button';
import * as i18n from '../translations';
interface Props {
connectorId?: string;
currentSystemPromptId: string | undefined;
isSettingsModalVisible: boolean;
setIsSettingsModalVisible: Dispatch<SetStateAction<boolean>>;
setCurrentSystemPromptId: (promptId: string | undefined) => void;
allSystemPrompts: PromptResponse[];
setUserPrompt: React.Dispatch<React.SetStateAction<string | null>>;
}
const starterPromptWrapperClassName = css`
max-width: 95%;
`;
export const EmptyConvo: React.FC<Props> = ({
allSystemPrompts,
connectorId,
currentSystemPromptId,
isSettingsModalVisible,
setCurrentSystemPromptId,
setIsSettingsModalVisible,
setUserPrompt,
}) => {
const { assistantAvailability } = useAssistantContext();
return (
<EuiFlexGroup alignItems="center" justifyContent="center" data-test-subj="emptyConvo">
<EuiFlexGroup
alignItems="center"
justifyContent="spaceBetween"
data-test-subj="emptyConvo"
direction="column"
>
<EuiFlexItem grow={false}>
<EuiPanel
hasShadow={false}
@ -64,6 +79,11 @@ export const EmptyConvo: React.FC<Props> = ({
</EuiFlexGroup>
</EuiPanel>
</EuiFlexItem>
{assistantAvailability.isStarterPromptsEnabled && (
<EuiFlexItem grow={false} css={starterPromptWrapperClassName}>
<StarterPrompts connectorId={connectorId} setUserPrompt={setUserPrompt} />
</EuiFlexItem>
)}
</EuiFlexGroup>
);
};

View file

@ -43,6 +43,7 @@ interface Props {
http: HttpSetup;
setCurrentSystemPromptId: (promptId: string | undefined) => void;
setIsSettingsModalVisible: Dispatch<SetStateAction<boolean>>;
setUserPrompt: React.Dispatch<React.SetStateAction<string | null>>;
}
export const AssistantBody: FunctionComponent<Props> = ({
@ -58,6 +59,7 @@ export const AssistantBody: FunctionComponent<Props> = ({
isSettingsModalVisible,
isWelcomeSetup,
setIsSettingsModalVisible,
setUserPrompt,
}) => {
const { euiTheme } = useEuiTheme();
@ -109,7 +111,7 @@ export const AssistantBody: FunctionComponent<Props> = ({
return (
<EuiFlexGroup direction="column" justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiFlexItem>
{isLoading ? (
<EuiEmptyPrompt
data-test-subj="animatedLogo"
@ -127,6 +129,8 @@ export const AssistantBody: FunctionComponent<Props> = ({
isSettingsModalVisible={isSettingsModalVisible}
setCurrentSystemPromptId={setCurrentSystemPromptId}
setIsSettingsModalVisible={setIsSettingsModalVisible}
setUserPrompt={setUserPrompt}
connectorId={currentConversation?.apiConfig?.connectorId}
/>
) : (
<EuiPanel

View file

@ -0,0 +1,161 @@
/*
* 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 {
formatPromptGroups,
getAllPromptIds,
promptGroups,
StarterPrompts,
} from './starter_prompts';
import { fireEvent, render } from '@testing-library/react';
import { TestProviders } from '../../mock/test_providers/test_providers';
import React from 'react';
import { useFindPrompts } from '../../..';
const mockResponse = [
{
promptId: 'starterPromptTitle1',
prompt: 'starterPromptTitle1 from API yall',
},
{
promptId: 'starterPromptDescription1',
prompt: 'starterPromptDescription1 from API yall',
},
{
promptId: 'starterPromptIcon1',
prompt: 'starterPromptIcon1 from API yall',
},
{
promptId: 'starterPromptPrompt1',
prompt: 'starterPromptPrompt1 from API yall',
},
{
promptId: 'starterPromptDescription2',
prompt: 'starterPromptDescription2 from API yall',
},
{
promptId: 'starterPromptTitle2',
prompt: 'starterPromptTitle2 from API yall',
},
{
promptId: 'starterPromptIcon2',
prompt: 'starterPromptIcon2 from API yall',
},
{
promptId: 'starterPromptPrompt2',
prompt: 'starterPromptPrompt2 from API yall',
},
{
promptId: 'starterPromptDescription3',
prompt: 'starterPromptDescription3 from API yall',
},
{
promptId: 'starterPromptTitle3',
prompt: 'starterPromptTitle3 from API yall',
},
{
promptId: 'starterPromptIcon3',
prompt: 'starterPromptIcon3 from API yall',
},
{
promptId: 'starterPromptPrompt3',
prompt: 'starterPromptPrompt3 from API yall',
},
{
promptId: 'starterPromptDescription4',
prompt: 'starterPromptDescription4 from API yall',
},
{
promptId: 'starterPromptTitle4',
prompt: 'starterPromptTitle4 from API yall',
},
{
promptId: 'starterPromptPrompt4',
prompt: 'starterPromptPrompt4 from API yall',
},
];
const testProps = {
setUserPrompt: jest.fn(),
};
jest.mock('../../..', () => {
return {
useFindPrompts: jest.fn(),
useAssistantContext: () => ({
assistantAvailability: {
isAssistantEnabled: true,
},
http: { fetch: {} },
}),
};
});
describe('StarterPrompts', () => {
it('should return an empty array if no prompts are provided', () => {
expect(getAllPromptIds(promptGroups)).toEqual([
'starterPromptTitle1',
'starterPromptDescription1',
'starterPromptIcon1',
'starterPromptPrompt1',
'starterPromptDescription2',
'starterPromptTitle2',
'starterPromptIcon2',
'starterPromptPrompt2',
'starterPromptDescription3',
'starterPromptTitle3',
'starterPromptIcon3',
'starterPromptPrompt3',
'starterPromptDescription4',
'starterPromptTitle4',
'starterPromptIcon4',
'starterPromptPrompt4',
]);
});
it('should return the correct prompt groups with fetched prompts', () => {
const response = formatPromptGroups(mockResponse);
expect(response).toEqual([
{
description: 'starterPromptDescription1 from API yall',
icon: 'starterPromptIcon1 from API yall',
prompt: 'starterPromptPrompt1 from API yall',
title: 'starterPromptTitle1 from API yall',
},
{
description: 'starterPromptDescription2 from API yall',
icon: 'starterPromptIcon2 from API yall',
prompt: 'starterPromptPrompt2 from API yall',
title: 'starterPromptTitle2 from API yall',
},
{
description: 'starterPromptDescription3 from API yall',
icon: 'starterPromptIcon3 from API yall',
prompt: 'starterPromptPrompt3 from API yall',
title: 'starterPromptTitle3 from API yall',
},
// starterPrompt Group4 should not exist because starterPromptIcon4 is not in the mockResponse
]);
});
it('the component renders correctly with valid props', () => {
(useFindPrompts as jest.Mock).mockReturnValue({ data: { prompts: mockResponse } });
const { getByTestId } = render(
<TestProviders>
<StarterPrompts {...testProps} />
</TestProviders>
);
expect(getByTestId('starterPromptPrompt2 from API yall')).toBeInTheDocument();
});
it('calls setUserPrompt when a prompt is selected', () => {
(useFindPrompts as jest.Mock).mockReturnValue({ data: { prompts: mockResponse } });
const { getByTestId } = render(
<TestProviders>
<StarterPrompts {...testProps} />
</TestProviders>
);
fireEvent.click(getByTestId('starterPromptPrompt2 from API yall'));
expect(testProps.setUserPrompt).toHaveBeenCalledWith('starterPromptPrompt2 from API yall');
});
});

View file

@ -0,0 +1,147 @@
/*
* 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 React, { useMemo, useCallback } from 'react';
import {
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiPanel,
EuiSpacer,
EuiText,
EuiTitle,
} from '@elastic/eui';
import { css } from '@emotion/css';
import { PromptItemArray } from '@kbn/elastic-assistant-common/impl/schemas/security_ai_prompts/common_attributes.gen';
import { useAssistantContext, useFindPrompts } from '../../..';
interface Props {
connectorId?: string;
setUserPrompt: React.Dispatch<React.SetStateAction<string | null>>;
}
const starterPromptClassName = css`
max-width: 50%;
min-width: calc(50% - 8px);
`;
const starterPromptInnerClassName = css`
text-align: center !important;
`;
interface PromptGroup {
description: string;
title: string;
icon: string;
prompt: string;
}
// these are the promptIds (Security AI Prompts integration) for each of the starter prompts fields
export const promptGroups = [
{
title: 'starterPromptTitle1',
description: 'starterPromptDescription1',
icon: 'starterPromptIcon1',
prompt: 'starterPromptPrompt1',
},
{
description: 'starterPromptDescription2',
title: 'starterPromptTitle2',
icon: 'starterPromptIcon2',
prompt: 'starterPromptPrompt2',
},
{
description: 'starterPromptDescription3',
title: 'starterPromptTitle3',
icon: 'starterPromptIcon3',
prompt: 'starterPromptPrompt3',
},
{
description: 'starterPromptDescription4',
title: 'starterPromptTitle4',
icon: 'starterPromptIcon4',
prompt: 'starterPromptPrompt4',
},
];
export const StarterPrompts: React.FC<Props> = ({ connectorId, setUserPrompt }) => {
const {
assistantAvailability: { isAssistantEnabled },
http,
toasts,
} = useAssistantContext();
const {
data: { prompts: actualPrompts },
} = useFindPrompts({
context: {
isAssistantEnabled,
httpFetch: http.fetch,
toasts,
},
params: {
connector_id: connectorId,
prompt_group_id: 'aiAssistant',
prompt_ids: getAllPromptIds(promptGroups),
},
});
const fetchedPromptGroups = useMemo(() => {
if (!actualPrompts.length) {
return [];
}
return formatPromptGroups(actualPrompts);
}, [actualPrompts]);
const onSelectPrompt = useCallback(
(prompt: string) => {
setUserPrompt(prompt);
},
[setUserPrompt]
);
return (
<EuiFlexGroup direction="row" gutterSize="m" wrap>
{fetchedPromptGroups.map(({ description, title, icon, prompt }) => (
<EuiFlexItem key={prompt} className={starterPromptClassName}>
<EuiPanel
paddingSize="m"
hasShadow={false}
hasBorder
data-test-subj={prompt}
onClick={() => onSelectPrompt(prompt)}
className={starterPromptInnerClassName}
>
<EuiSpacer size="s" />
<EuiIcon type={icon} size="xl" />
<EuiSpacer size="s" />
<EuiTitle size="xs">
<h2>{title}</h2>
</EuiTitle>
<EuiText size="s">{description}</EuiText>
</EuiPanel>
</EuiFlexItem>
))}
</EuiFlexGroup>
);
};
export const getAllPromptIds = (pGroups: PromptGroup[]) => {
return pGroups.map((promptGroup: PromptGroup) => [...Object.values(promptGroup)]).flat();
};
export const formatPromptGroups = (actualPrompts: PromptItemArray): PromptGroup[] =>
promptGroups.reduce<PromptGroup[]>((acc, promptGroup) => {
const foundPrompt = (field: keyof PromptGroup) =>
actualPrompts.find((p) => p.promptId === promptGroup[field])?.prompt;
const toBePrompt = {
prompt: foundPrompt('prompt'),
icon: foundPrompt('icon'),
title: foundPrompt('title'),
description: foundPrompt('description'),
};
if (toBePrompt.prompt && toBePrompt.icon && toBePrompt.title && toBePrompt.description) {
acc.push(toBePrompt as PromptGroup);
}
return acc;
}, []);

View file

@ -541,6 +541,7 @@ const AssistantComponent: React.FC<Props> = ({
isLoading={isInitialLoad}
isSettingsModalVisible={isSettingsModalVisible}
isWelcomeSetup={isWelcomeSetup}
setUserPrompt={setUserPrompt}
setCurrentSystemPromptId={setCurrentSystemPromptId}
setIsSettingsModalVisible={setIsSettingsModalVisible}
/>

View file

@ -74,6 +74,7 @@ export interface AssistantAvailability {
hasUpdateAIAssistantAnonymization: boolean;
// When true, user has `Edit` privilege for `Global Knowledge Base`
hasManageGlobalKnowledgeBase: boolean;
isStarterPromptsEnabled: boolean;
}
export type GetAssistantMessages = (commentArgs: {

View file

@ -42,6 +42,7 @@ export const mockAssistantAvailability: AssistantAvailability = {
hasUpdateAIAssistantAnonymization: true,
hasManageGlobalKnowledgeBase: true,
isAssistantEnabled: true,
isStarterPromptsEnabled: true,
};
/** A utility for wrapping children in the providers required to run tests */

View file

@ -201,4 +201,6 @@ export interface UseAssistantAvailability {
hasUpdateAIAssistantAnonymization: boolean;
// When true, user has `Edit` privilege for `Global Knowledge Base`
hasManageGlobalKnowledgeBase: boolean;
// remove once product has signed off on prompt text
isStarterPromptsEnabled: boolean;
}

View file

@ -59,6 +59,7 @@ const TestExternalProvidersComponent: React.FC<TestExternalProvidersProps> = ({
hasUpdateAIAssistantAnonymization: true,
hasManageGlobalKnowledgeBase: true,
isAssistantEnabled: true,
isStarterPromptsEnabled: true,
};
const queryClient = new QueryClient({
defaultOptions: {

View file

@ -40,6 +40,9 @@ describe('AssistantProvider', () => {
http: mockHttp,
notifications,
elasticAssistantSharedState,
featureFlags: {
getBooleanValue: jest.fn().mockReturnValue(false),
},
}}
>
{children}
@ -63,6 +66,7 @@ describe('AssistantProvider', () => {
hasSearchAILakeConfigurations: expect.any(Boolean),
hasUpdateAIAssistantAnonymization: expect.any(Boolean),
isAssistantEnabled: expect.any(Boolean),
isStarterPromptsEnabled: expect.any(Boolean),
}),
assistantFeatures: expect.objectContaining({
advancedEsqlGeneration: expect.any(Boolean),

View file

@ -47,6 +47,9 @@ describe('useAssistantAvailability', () => {
},
},
},
featureFlags: {
getBooleanValue: jest.fn().mockReturnValue(true),
},
},
} as unknown as ReturnType<typeof useKibana>);
@ -58,6 +61,7 @@ describe('useAssistantAvailability', () => {
hasConnectorsAllPrivilege: true,
hasConnectorsReadPrivilege: true,
isAssistantEnabled: true,
isStarterPromptsEnabled: true,
hasUpdateAIAssistantAnonymization: true,
hasManageGlobalKnowledgeBase: true,
});
@ -88,6 +92,9 @@ describe('useAssistantAvailability', () => {
},
},
},
featureFlags: {
getBooleanValue: jest.fn().mockReturnValue(false),
},
},
} as unknown as ReturnType<typeof useKibana>);
@ -99,6 +106,7 @@ describe('useAssistantAvailability', () => {
hasConnectorsAllPrivilege: false,
hasConnectorsReadPrivilege: false,
isAssistantEnabled: false,
isStarterPromptsEnabled: false,
hasUpdateAIAssistantAnonymization: false,
hasManageGlobalKnowledgeBase: false,
});
@ -129,6 +137,9 @@ describe('useAssistantAvailability', () => {
},
},
},
featureFlags: {
getBooleanValue: jest.fn().mockReturnValue(true),
},
},
} as unknown as ReturnType<typeof useKibana>);
@ -140,6 +151,7 @@ describe('useAssistantAvailability', () => {
hasConnectorsAllPrivilege: false,
hasConnectorsReadPrivilege: true,
isAssistantEnabled: true,
isStarterPromptsEnabled: true,
hasUpdateAIAssistantAnonymization: false,
hasManageGlobalKnowledgeBase: false,
});
@ -155,6 +167,9 @@ describe('useAssistantAvailability', () => {
application: {
capabilities: {},
},
featureFlags: {
getBooleanValue: jest.fn().mockReturnValue(true),
},
},
} as unknown as ReturnType<typeof useKibana>);
@ -166,6 +181,7 @@ describe('useAssistantAvailability', () => {
hasConnectorsAllPrivilege: false,
hasConnectorsReadPrivilege: false,
isAssistantEnabled: true,
isStarterPromptsEnabled: true,
hasUpdateAIAssistantAnonymization: false,
hasManageGlobalKnowledgeBase: false,
});

View file

@ -11,9 +11,13 @@ import { useKibana } from '../../context/typed_kibana_context/typed_kibana_conte
import { useLicense } from '../licence/use_licence';
export const STARTER_PROMPTS_FEATURE_FLAG = 'elasticAssistant.starterPromptsEnabled' as const;
export const useAssistantAvailability = (): UseAssistantAvailability => {
const isEnterprise = useLicense().isEnterprise();
const capabilities = useKibana().services.application.capabilities;
const {
application: { capabilities },
featureFlags,
} = useKibana().services;
const hasAssistantPrivilege = capabilities[ASSISTANT_FEATURE_ID]?.['ai-assistant'] === true;
const hasUpdateAIAssistantAnonymization =
@ -32,11 +36,14 @@ export const useAssistantAvailability = (): UseAssistantAvailability => {
capabilities.actions?.delete === true &&
capabilities.actions?.save === true;
const isStarterPromptsEnabled = featureFlags.getBooleanValue(STARTER_PROMPTS_FEATURE_FLAG, false);
return {
hasSearchAILakeConfigurations,
hasAssistantPrivilege,
hasConnectorsAllPrivilege,
hasConnectorsReadPrivilege,
isStarterPromptsEnabled,
isAssistantEnabled: isEnterprise,
hasUpdateAIAssistantAnonymization,
hasManageGlobalKnowledgeBase,

View file

@ -29,6 +29,22 @@ import {
RULE_ANALYSIS,
DATA_QUALITY_ANALYSIS,
ALERT_EVALUATION,
starterPromptTitle1,
starterPromptDescription1,
starterPromptIcon1,
starterPromptPrompt1,
starterPromptDescription2,
starterPromptTitle2,
starterPromptIcon2,
starterPromptPrompt2,
starterPromptDescription3,
starterPromptTitle3,
starterPromptIcon3,
starterPromptPrompt3,
starterPromptDescription4,
starterPromptTitle4,
starterPromptIcon4,
starterPromptPrompt4,
} from './prompts';
export const promptGroupId = {
@ -41,9 +57,6 @@ export const promptGroupId = {
};
export const promptDictionary = {
alertEvaluation: `alertEvaluation`,
dataQualityAnalysis: 'dataQualityAnalysis',
ruleAnalysis: 'ruleAnalysis',
alertSummary: `alertSummary`,
alertSummarySystemPrompt: `alertSummarySystemPrompt`,
systemPrompt: `systemPrompt`,
@ -68,6 +81,26 @@ export const promptDictionary = {
'defendInsights-incompatibleAntivirusEventsEndpointId',
defendInsightsIncompatibleAntivirusEventsValue: 'defendInsights-incompatibleAntivirusEventsValue',
// context prompts
alertEvaluation: `alertEvaluation`,
dataQualityAnalysis: 'dataQualityAnalysis',
ruleAnalysis: 'ruleAnalysis',
// starter prompts
starterPromptDescription1: 'starterPromptDescription1',
starterPromptTitle1: 'starterPromptTitle1',
starterPromptIcon1: 'starterPromptIcon1',
starterPromptPrompt1: 'starterPromptPrompt1',
starterPromptDescription2: 'starterPromptDescription2',
starterPromptTitle2: 'starterPromptTitle2',
starterPromptIcon2: 'starterPromptIcon2',
starterPromptPrompt2: 'starterPromptPrompt2',
starterPromptDescription3: 'starterPromptDescription3',
starterPromptTitle3: 'starterPromptTitle3',
starterPromptIcon3: 'starterPromptIcon3',
starterPromptPrompt3: 'starterPromptPrompt3',
starterPromptDescription4: 'starterPromptDescription4',
starterPromptTitle4: 'starterPromptTitle4',
starterPromptIcon4: 'starterPromptIcon4',
starterPromptPrompt4: 'starterPromptPrompt4',
};
export const localPrompts: Prompt[] = [
@ -287,4 +320,84 @@ export const localPrompts: Prompt[] = [
default: RULE_ANALYSIS,
},
},
{
promptId: promptDictionary.starterPromptDescription1,
promptGroupId: promptGroupId.aiAssistant,
prompt: { default: starterPromptDescription1 },
},
{
promptId: promptDictionary.starterPromptTitle1,
promptGroupId: promptGroupId.aiAssistant,
prompt: { default: starterPromptTitle1 },
},
{
promptId: promptDictionary.starterPromptIcon1,
promptGroupId: promptGroupId.aiAssistant,
prompt: { default: starterPromptIcon1 },
},
{
promptId: promptDictionary.starterPromptPrompt1,
promptGroupId: promptGroupId.aiAssistant,
prompt: { default: starterPromptPrompt1 },
},
{
promptId: promptDictionary.starterPromptDescription2,
promptGroupId: promptGroupId.aiAssistant,
prompt: { default: starterPromptDescription2 },
},
{
promptId: promptDictionary.starterPromptTitle2,
promptGroupId: promptGroupId.aiAssistant,
prompt: { default: starterPromptTitle2 },
},
{
promptId: promptDictionary.starterPromptIcon2,
promptGroupId: promptGroupId.aiAssistant,
prompt: { default: starterPromptIcon2 },
},
{
promptId: promptDictionary.starterPromptPrompt2,
promptGroupId: promptGroupId.aiAssistant,
prompt: { default: starterPromptPrompt2 },
},
{
promptId: promptDictionary.starterPromptDescription3,
promptGroupId: promptGroupId.aiAssistant,
prompt: { default: starterPromptDescription3 },
},
{
promptId: promptDictionary.starterPromptTitle3,
promptGroupId: promptGroupId.aiAssistant,
prompt: { default: starterPromptTitle3 },
},
{
promptId: promptDictionary.starterPromptIcon3,
promptGroupId: promptGroupId.aiAssistant,
prompt: { default: starterPromptIcon3 },
},
{
promptId: promptDictionary.starterPromptPrompt3,
promptGroupId: promptGroupId.aiAssistant,
prompt: { default: starterPromptPrompt3 },
},
{
promptId: promptDictionary.starterPromptDescription4,
promptGroupId: promptGroupId.aiAssistant,
prompt: { default: starterPromptDescription4 },
},
{
promptId: promptDictionary.starterPromptTitle4,
promptGroupId: promptGroupId.aiAssistant,
prompt: { default: starterPromptTitle4 },
},
{
promptId: promptDictionary.starterPromptIcon4,
promptGroupId: promptGroupId.aiAssistant,
prompt: { default: starterPromptIcon4 },
},
{
promptId: promptDictionary.starterPromptPrompt4,
promptGroupId: promptGroupId.aiAssistant,
prompt: { default: starterPromptPrompt4 },
},
];

View file

@ -255,3 +255,67 @@ Formatting Requirements:
- Use concise, actionable language.
- Include relevant emojis in section headers for visual clarity (e.g., 📝, 🛡, 🔍, 📚).
`;
export const starterPromptTitle1 = 'Alerts';
export const starterPromptDescription1 = 'Most important alerts from the last 24 hrs';
export const starterPromptIcon1 = 'bell';
export const starterPromptPrompt1 = `🔍 Identify and Prioritize Today's Most Critical Alerts
Provide a structured summary of today's most significant alerts, including:
🛡 Critical Alerts Overview
Highlight the most impactful alerts based on risk scores, severity, and affected entities.
Summarize key details such as alert name, risk score, severity, and associated users or hosts.
📊 Risk Context
Include user and host risk scores for each alert to provide additional context.
Reference relevant MITRE ATT&CK techniques, with hyperlinks to the official MITRE pages.
🚨 Why These Alerts Matter
Explain why these alerts are critical, focusing on potential business impact, lateral movement risks, or sensitive data exposure.
🔧 Recommended Next Steps
Provide actionable triage steps for each alert, such as:
Investigating the alert in Elastic Security.
Reviewing related events in Timelines.
Analyzing user and host behavior using Entity Analytics.
Suggest Elastic Defend endpoint response actions (e.g., isolate host, kill process, retrieve/delete file), with links to Elastic documentation.
📚 Documentation and References
Include direct links to Elastic Security documentation and relevant MITRE ATT&CK pages for further guidance.
Use markdown headers, tables, and code blocks for clarity. Include relevant emojis for visual distinction and ensure the response is concise, actionable, and tailored to Elastic Security workflows.`;
export const starterPromptDescription2 = 'Latest Elastic Security Labs research';
export const starterPromptTitle2 = 'Research';
export const starterPromptIcon2 = 'launch';
export const starterPromptPrompt2 = `Retrieve and summarize the latest Elastic Security Labs articles one by one sorted by latest at the top. Ensure the response includes:
Article Summaries
Title and Link: Provide the title of each article with a hyperlink to the original content.
Publication Date: Include the date the article was published.
Key Insights: Summarize the main points or findings of each article in concise bullet points.
Relevant Threats or Techniques: Highlight any specific malware, attack techniques, or adversary behaviors discussed, with references to MITRE ATT&CK techniques (include hyperlinks to the official MITRE pages).
Practical Applications
Detection and Response Guidance: Provide actionable steps or recommendations based on the article's content, tailored for Elastic Security workflows.
Elastic Security Features: Highlight any Elastic Security features, detection rules, or tools mentioned in the articles, with links to relevant documentation.
Example Queries: If applicable, include example ES|QL or OSQuery Manager queries inspired by the article's findings, formatted as code blocks.
Documentation and Resources
Elastic Security Labs: Include a link to the Elastic Security Labs homepage.
Additional References: Provide links to any related Elastic documentation or external resources mentioned in the articles.
Formatting Requirements
Use markdown headers, tables, and code blocks for clarity.
Organize the response into visually distinct sections.
Use concise, actionable language.`;
export const starterPromptDescription3 = 'Generate ES|QL Queries';
export const starterPromptTitle3 = 'Query';
export const starterPromptIcon3 = 'esqlVis';
export const starterPromptPrompt3 =
'I need an Elastic ES|QL query to achieve the following goal:\n' +
'Goal/Requirement:\n' +
'<Insert your specific requirement or goal here, e.g., "Identify all failed login attempts from a specific IP address within the last 24 hours.">\n' +
'Please:\n' +
'Generate the ES|QL Query: Provide a complete ES|QL query tailored to the stated goal.\n' +
'Explain the Query: Offer a brief explanation of each part of the query, including filters, fields, and logic used.\n' +
'Optimize for Elastic Security: Suggest additional filters, aggregations, or enhancements to make the query more efficient and actionable within Elastic Security workflows.\n' +
'Provide Documentation Links: Include links to relevant Elastic Security documentation for deeper understanding.\n' +
'Formatting Requirements:\n' +
'Use code blocks for the ES|QL query.\n' +
'Include concise explanations in bullet points for clarity.\n' +
'Highlight any advanced ES|QL features used in the query.\n';
export const starterPromptDescription4 = 'Discover the types of questions you can ask';
export const starterPromptTitle4 = 'Suggest';
export const starterPromptIcon4 = 'sparkles';
export const starterPromptPrompt4 =
'Can you provide examples of questions I can ask about Elastic Security, such as investigating alerts, running ES|QL queries, incident response, or threat intelligence?';

View file

@ -40,6 +40,7 @@ export const MockAssistantProviderComponent: React.FC<Props> = ({
hasUpdateAIAssistantAnonymization: true,
hasManageGlobalKnowledgeBase: true,
isAssistantEnabled: true,
isStarterPromptsEnabled: true,
};
const chrome = chromeServiceMock.createStartContract();
chrome.getChromeStyle$.mockReturnValue(of('classic'));

View file

@ -15,7 +15,8 @@ import type {
import { newContentReferencesStoreMock } from '@kbn/elastic-assistant-common/impl/content_references/content_references_store/__mocks__/content_references_store.mock';
import type { AssistantToolParams } from '@kbn/elastic-assistant-plugin/server';
import { Document } from 'langchain/document';
import { getIsKnowledgeBaseInstalled } from '@kbn/elastic-assistant-plugin/server/routes/helpers';
jest.mock('@kbn/elastic-assistant-plugin/server/routes/helpers');
describe('SecurityLabsTool', () => {
const contentReferencesStore = newContentReferencesStoreMock();
const getKnowledgeBaseDocumentEntries = jest.fn();
@ -100,5 +101,23 @@ In previous publications,`,
expect(result).toContain('Citation: {reference(exampleContentReferenceId)}');
});
it('Responds with The "AI Assistant knowledge base" needs to be installed... when no docs and no kb install', async () => {
getKnowledgeBaseDocumentEntries.mockResolvedValue([]);
(getIsKnowledgeBaseInstalled as jest.Mock).mockResolvedValue(false);
const tool = SECURITY_LABS_KNOWLEDGE_BASE_TOOL.getTool(defaultArgs) as DynamicStructuredTool;
const result = await tool.func({ query: 'What is Kibana Security?', product: 'kibana' });
expect(result).toContain('The "AI Assistant knowledge base" needs to be installed');
});
it('Responds with empty response when no docs and kb is installed', async () => {
getKnowledgeBaseDocumentEntries.mockResolvedValue([]);
(getIsKnowledgeBaseInstalled as jest.Mock).mockResolvedValue(true);
const tool = SECURITY_LABS_KNOWLEDGE_BASE_TOOL.getTool(defaultArgs) as DynamicStructuredTool;
const result = await tool.func({ query: 'What is Kibana Security?', product: 'kibana' });
expect(result).toEqual('[]');
});
});
});

View file

@ -18,6 +18,7 @@ import {
knowledgeBaseReference,
} from '@kbn/elastic-assistant-common/impl/content_references/references';
import { Document } from 'langchain/document';
import { getIsKnowledgeBaseInstalled } from '@kbn/elastic-assistant-plugin/server/routes/helpers';
import { APP_UI_ID } from '../../../../common';
const toolDetails = {
@ -52,6 +53,14 @@ export const SECURITY_LABS_KNOWLEDGE_BASE_TOOL: AssistantTool = {
query: input.question,
});
if (docs.length === 0) {
const isKnowledgeBaseInstalled = await getIsKnowledgeBaseInstalled(kbDataClient);
if (!isKnowledgeBaseInstalled) {
// prompt to help user install knowledge base
return 'The "AI Assistant knowledge base" needs to be installed, containing the Security Labs content. Navigate to the Knowledge Base page in the AI Assistant Settings to install it.';
}
}
const citedDocs = docs.map((doc) => {
let reference: ContentReference | undefined;
try {