From e74e5e2bfccb03806871b66b83525096069270c4 Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Tue, 3 Oct 2023 17:53:46 -0600 Subject: [PATCH] [Security Solution][Elastic AI Assistant] Refactors Knowledge Base feature flag to UI feature toggle (#167935) ## Summary This PR refactors the `assistantLangChain` code feature flag introduced in https://github.com/elastic/kibana/pull/164908, to be a UI feature toggle that users can enable/disable via the `Knowledge Base` assistant advanced settings. Left image shows the feature disabled, and the right image shows the feature partly enabled. If ELSER is configured, the UI will attempt to install all resources automatically for a one-click UX, however if ELSER is not configured, or there are failures, the user can manually enable the Knowledge Base or ES|QL base documentation:

Also, since this code feature flag was shared with the model evaluator experimental feature, a `modelEvaluatorEnabled` flag has been plumbed to fully decouple the two settings. Now _only the_ model evaluator is enabled when setting security Solution Advanced setting: ``` xpack.securitySolution.enableExperimental: ['assistantModelEvaluation'] ``` and the previous `assistantLangChain` code feature flag is now enabled by simply toggling on the Knowledge Base in the settings shown above. > [!NOTE] > Even if ELSER isn't configured, and the knowledge base/docs aren't setup, if the Knowledge Base is enabled, the LangChain code path will still be enabled as intended, but we can change this behavior if testing shows this is not ideal. ### Checklist Delete any items that are not applicable to this PR. - [X] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)added to match the most common scenarios - [X] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) --- .../advanced_settings/advanced_settings.tsx | 193 ------------ .../advanced_settings/translations.ts | 79 ----- .../assistant/settings/assistant_settings.tsx | 39 ++- .../impl/assistant/settings/translations.ts | 6 +- .../use_settings_updater.tsx | 23 +- .../impl/assistant/types.ts | 4 + .../assistant/use_send_messages/index.tsx | 6 +- .../impl/assistant_context/constants.tsx | 7 + .../impl/assistant_context/index.test.tsx | 1 - .../impl/assistant_context/index.tsx | 28 +- .../knowledge_base_settings.tsx | 294 ++++++++++++++++++ .../knowledge_base_settings/translations.ts | 109 +++++++ .../mock/test_providers/test_providers.tsx | 1 - .../mock/test_providers/test_providers.tsx | 1 - .../public/assistant/provider.tsx | 4 +- .../common/mock/mock_assistant_provider.tsx | 1 - 16 files changed, 487 insertions(+), 309 deletions(-) delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/advanced_settings/advanced_settings.tsx delete mode 100644 x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/advanced_settings/translations.ts create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings/knowledge_base_settings.tsx create mode 100644 x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings/translations.ts diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/advanced_settings/advanced_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/advanced_settings/advanced_settings.tsx deleted file mode 100644 index 4474973b3ce62..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/advanced_settings/advanced_settings.tsx +++ /dev/null @@ -1,193 +0,0 @@ -/* - * 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, { useCallback, useMemo, useState } from 'react'; -import { - EuiFormRow, - EuiTitle, - EuiText, - EuiTextColor, - EuiHorizontalRule, - EuiLoadingSpinner, - EuiSpacer, - EuiSwitch, - EuiToolTip, - EuiSwitchEvent, - EuiLink, -} from '@elastic/eui'; - -import { FormattedMessage } from '@kbn/i18n-react'; -import * as i18n from './translations'; -import { useKnowledgeBaseStatus } from '../../../knowledge_base/use_knowledge_base_status/use_knowledge_base_status'; -import { useAssistantContext } from '../../../assistant_context'; -import { useSetupKnowledgeBase } from '../../../knowledge_base/use_setup_knowledge_base/use_setup_knowledge_base'; -import { useDeleteKnowledgeBase } from '../../../knowledge_base/use_delete_knowledge_base/use_delete_knowledge_base'; - -const ESQL_RESOURCE = 'esql'; -interface Props { - onAdvancedSettingsChange?: () => void; -} - -/** - * Advanced Settings -- enable and disable LangChain integration, Knowledge Base, and ESQL KB Documents - */ -export const AdvancedSettings: React.FC = React.memo(({ onAdvancedSettingsChange }) => { - const { http, assistantLangChain } = useAssistantContext(); - const { - data: kbStatus, - isLoading, - isFetching, - } = useKnowledgeBaseStatus({ http, resource: ESQL_RESOURCE }); - const { mutate: setupKB, isLoading: isSettingUpKB } = useSetupKnowledgeBase({ http }); - const { mutate: deleteKB, isLoading: isDeletingUpKB } = useDeleteKnowledgeBase({ http }); - - const [isLangChainEnabled, setIsLangChainEnabled] = useState(assistantLangChain); - const isKnowledgeBaseEnabled = - (kbStatus?.index_exists && kbStatus?.pipeline_exists && kbStatus?.elser_exists) ?? false; - const isESQLEnabled = kbStatus?.esql_exists ?? false; - - const isLoadingKb = isLoading || isFetching || isSettingUpKB || isDeletingUpKB; - const isKnowledgeBaseAvailable = isLangChainEnabled && kbStatus?.elser_exists; - const isESQLAvailable = isLangChainEnabled && isKnowledgeBaseAvailable && isKnowledgeBaseEnabled; - - const onEnableKnowledgeBaseChange = useCallback( - (event: EuiSwitchEvent) => { - if (event.target.checked) { - setupKB(); - } else { - deleteKB(); - } - }, - [deleteKB, setupKB] - ); - - const onEnableESQLChange = useCallback( - (event: EuiSwitchEvent) => { - if (event.target.checked) { - setupKB(ESQL_RESOURCE); - } else { - deleteKB(ESQL_RESOURCE); - } - }, - [deleteKB, setupKB] - ); - - const langchainSwitch = useMemo(() => { - return ( - setIsLangChainEnabled(!isLangChainEnabled)} - showLabel={false} - /> - ); - }, [isLangChainEnabled]); - - const knowledgeBaseSwitch = useMemo(() => { - return isLoadingKb ? ( - - ) : ( - - - - ); - }, [isLoadingKb, isKnowledgeBaseAvailable, isKnowledgeBaseEnabled, onEnableKnowledgeBaseChange]); - - const esqlSwitch = useMemo(() => { - return isLoadingKb ? ( - - ) : ( - - - - ); - }, [isLoadingKb, isESQLAvailable, isESQLEnabled, onEnableESQLChange]); - - return ( - <> - -

{i18n.SETTINGS_TITLE}

-
- - - {i18n.SETTINGS_DESCRIPTION} - - - - - {langchainSwitch} - - - {i18n.LANNGCHAIN_DESCRIPTION} - - - - {knowledgeBaseSwitch} - - - - - {i18n.KNOWLEDGE_BASE_DESCRIPTION_ELSER_LEARN_MORE} - - ), - }} - /> - - - - - {esqlSwitch} - - - {i18n.ESQL_DESCRIPTION} - - - ); -}); - -AdvancedSettings.displayName = 'AdvancedSettings'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/advanced_settings/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/advanced_settings/translations.ts deleted file mode 100644 index ca849f0a6f7c5..0000000000000 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/advanced_settings/translations.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -export const SETTINGS_TITLE = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.advancedSettings.settingsTitle', - { - defaultMessage: 'Advanced Settings', - } -); -export const SETTINGS_DESCRIPTION = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.advancedSettings.settingsDescription', - { - defaultMessage: 'Additional knobs and dials for the Elastic AI Assistant.', - } -); - -export const LANNGCHAIN_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.advancedSettings.langChainLabel', - { - defaultMessage: 'Experimental LangChain Integration', - } -); - -export const LANNGCHAIN_DESCRIPTION = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.advancedSettings.langChainDescription', - { - defaultMessage: - 'Enables advanced features and workflows like the Knowledge Base, Functions, Memories, and advanced agent and chain configurations. ', - } -); - -export const KNOWLEDGE_BASE_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.advancedSettings.knowledgeBaseLabel', - { - defaultMessage: 'Knowledge Base', - } -); - -export const KNOWLEDGE_BASE_LABEL_TOOLTIP = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.advancedSettings.knowledgeBaseLabelTooltip', - { - defaultMessage: 'Requires ELSER to be configured and started.', - } -); - -export const KNOWLEDGE_BASE_DESCRIPTION_ELSER_LEARN_MORE = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.advancedSettings.knowledgeBaseElserLearnMoreDescription', - { - defaultMessage: 'Learn more.', - } -); - -export const ESQL_LABEL = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.advancedSettings.esqlLabel', - { - defaultMessage: 'ES|QL Knowledge Base Documents', - } -); - -export const ESQL_LABEL_TOOLTIP = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.advancedSettings.esqlTooltip', - { - defaultMessage: 'Requires `Knowledge Base` to be enabled.', - } -); - -export const ESQL_DESCRIPTION = i18n.translate( - 'xpack.elasticAssistant.assistant.settings.advancedSettings.esqlDescription', - { - defaultMessage: - 'Loads ES|QL documentation and language files into the Knowledge Base for use in generating ES|QL queries.', - } -); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx index caf8fa77a9b61..5b357fb6594cc 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/settings/assistant_settings.tsx @@ -30,7 +30,7 @@ import { useAssistantContext } from '../../assistant_context'; import { AnonymizationSettings } from '../../data_anonymization/settings/anonymization_settings'; import { QuickPromptSettings } from '../quick_prompts/quick_prompt_settings/quick_prompt_settings'; import { SystemPromptSettings } from '../prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings'; -import { AdvancedSettings } from './advanced_settings/advanced_settings'; +import { KnowledgeBaseSettings } from '../../knowledge_base/knowledge_base_settings/knowledge_base_settings'; import { ConversationSettings } from '../conversations/conversation_settings/conversation_settings'; import { TEST_IDS } from '../constants'; import { useSettingsUpdater } from './use_settings_updater/use_settings_updater'; @@ -45,7 +45,7 @@ export const CONVERSATIONS_TAB = 'CONVERSATION_TAB' as const; export const QUICK_PROMPTS_TAB = 'QUICK_PROMPTS_TAB' as const; export const SYSTEM_PROMPTS_TAB = 'SYSTEM_PROMPTS_TAB' as const; export const ANONYMIZATION_TAB = 'ANONYMIZATION_TAB' as const; -export const ADVANCED_TAB = 'ADVANCED_TAB' as const; +export const KNOWLEDGE_BASE_TAB = 'KNOWLEDGE_BASE_TAB' as const; export const EVALUATION_TAB = 'EVALUATION_TAB' as const; export type SettingsTabs = @@ -53,7 +53,7 @@ export type SettingsTabs = | typeof QUICK_PROMPTS_TAB | typeof SYSTEM_PROMPTS_TAB | typeof ANONYMIZATION_TAB - | typeof ADVANCED_TAB + | typeof KNOWLEDGE_BASE_TAB | typeof EVALUATION_TAB; interface Props { defaultConnectorId?: string; @@ -68,7 +68,7 @@ interface Props { /** * Modal for overall Assistant Settings, including conversation settings, quick prompts, system prompts, - * anonymization, functions (coming soon!), and advanced settings. + * anonymization, knowledge base, and evaluation via the `isModelEvaluationEnabled` feature flag. */ export const AssistantSettings: React.FC = React.memo( ({ @@ -79,17 +79,19 @@ export const AssistantSettings: React.FC = React.memo( selectedConversation: defaultSelectedConversation, setSelectedConversationId, }) => { - const { assistantLangChain, http, selectedSettingsTab, setSelectedSettingsTab } = + const { modelEvaluatorEnabled, http, selectedSettingsTab, setSelectedSettingsTab } = useAssistantContext(); const { conversationSettings, defaultAllow, defaultAllowReplacement, + knowledgeBase, quickPromptSettings, systemPromptSettings, setUpdatedConversationSettings, setUpdatedDefaultAllow, setUpdatedDefaultAllowReplacement, + setUpdatedKnowledgeBaseSettings, setUpdatedQuickPromptSettings, setUpdatedSystemPromptSettings, saveSettings, @@ -236,17 +238,15 @@ export const AssistantSettings: React.FC = React.memo( > - {assistantLangChain && ( - setSelectedSettingsTab(ADVANCED_TAB)} - > - - - )} - {assistantLangChain && ( + setSelectedSettingsTab(KNOWLEDGE_BASE_TAB)} + > + + + {modelEvaluatorEnabled && ( = React.memo( setUpdatedDefaultAllowReplacement={setUpdatedDefaultAllowReplacement} /> )} - {selectedSettingsTab === ADVANCED_TAB && } + {selectedSettingsTab === KNOWLEDGE_BASE_TAB && ( + + )} {selectedSettingsTab === EVALUATION_TAB && } void; systemPromptSettings: Prompt[]; @@ -21,6 +23,7 @@ interface UseSettingsUpdater { setUpdatedConversationSettings: React.Dispatch< React.SetStateAction >; + setUpdatedKnowledgeBaseSettings: React.Dispatch>; setUpdatedQuickPromptSettings: React.Dispatch>; setUpdatedSystemPromptSettings: React.Dispatch>; saveSettings: () => void; @@ -34,11 +37,13 @@ export const useSettingsUpdater = (): UseSettingsUpdater => { conversations, defaultAllow, defaultAllowReplacement, + knowledgeBase, setAllQuickPrompts, setAllSystemPrompts, setConversations, setDefaultAllow, setDefaultAllowReplacement, + setKnowledgeBase, } = useAssistantContext(); /** @@ -57,6 +62,9 @@ export const useSettingsUpdater = (): UseSettingsUpdater => { const [updatedDefaultAllow, setUpdatedDefaultAllow] = useState(defaultAllow); const [updatedDefaultAllowReplacement, setUpdatedDefaultAllowReplacement] = useState(defaultAllowReplacement); + // Knowledge Base + const [updatedKnowledgeBaseSettings, setUpdatedKnowledgeBaseSettings] = + useState(knowledgeBase); /** * Reset all pending settings @@ -64,10 +72,18 @@ export const useSettingsUpdater = (): UseSettingsUpdater => { const resetSettings = useCallback((): void => { setUpdatedConversationSettings(conversations); setUpdatedQuickPromptSettings(allQuickPrompts); + setUpdatedKnowledgeBaseSettings(knowledgeBase); setUpdatedSystemPromptSettings(allSystemPrompts); setUpdatedDefaultAllow(defaultAllow); setUpdatedDefaultAllowReplacement(defaultAllowReplacement); - }, [allQuickPrompts, allSystemPrompts, conversations, defaultAllow, defaultAllowReplacement]); + }, [ + allQuickPrompts, + allSystemPrompts, + conversations, + defaultAllow, + defaultAllowReplacement, + knowledgeBase, + ]); /** * Save all pending settings @@ -76,6 +92,7 @@ export const useSettingsUpdater = (): UseSettingsUpdater => { setAllQuickPrompts(updatedQuickPromptSettings); setAllSystemPrompts(updatedSystemPromptSettings); setConversations(updatedConversationSettings); + setKnowledgeBase(updatedKnowledgeBaseSettings); setDefaultAllow(updatedDefaultAllow); setDefaultAllowReplacement(updatedDefaultAllowReplacement); }, [ @@ -84,9 +101,11 @@ export const useSettingsUpdater = (): UseSettingsUpdater => { setConversations, setDefaultAllow, setDefaultAllowReplacement, + setKnowledgeBase, updatedConversationSettings, updatedDefaultAllow, updatedDefaultAllowReplacement, + updatedKnowledgeBaseSettings, updatedQuickPromptSettings, updatedSystemPromptSettings, ]); @@ -95,6 +114,7 @@ export const useSettingsUpdater = (): UseSettingsUpdater => { conversationSettings: updatedConversationSettings, defaultAllow: updatedDefaultAllow, defaultAllowReplacement: updatedDefaultAllowReplacement, + knowledgeBase: updatedKnowledgeBaseSettings, quickPromptSettings: updatedQuickPromptSettings, resetSettings, systemPromptSettings: updatedSystemPromptSettings, @@ -102,6 +122,7 @@ export const useSettingsUpdater = (): UseSettingsUpdater => { setUpdatedDefaultAllow, setUpdatedDefaultAllowReplacement, setUpdatedConversationSettings, + setUpdatedKnowledgeBaseSettings, setUpdatedQuickPromptSettings, setUpdatedSystemPromptSettings, }; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/types.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/types.ts index dd5d184abfd20..e9ffd8f8014e8 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/types.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/types.ts @@ -15,3 +15,7 @@ export interface Prompt { isDefault?: boolean; // TODO: Should be renamed to isImmutable as this flag is used to prevent users from deleting prompts isNewConversationDefault?: boolean; } + +export interface KnowledgeBaseConfig { + assistantLangChain: boolean; +} diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_send_messages/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_send_messages/index.tsx index 38dc60c5fc9e7..f9f63aa8ef8ac 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_send_messages/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/use_send_messages/index.tsx @@ -29,7 +29,7 @@ interface UseSendMessages { } export const useSendMessages = (): UseSendMessages => { - const { assistantLangChain } = useAssistantContext(); + const { knowledgeBase } = useAssistantContext(); const [isLoading, setIsLoading] = useState(false); const sendMessages = useCallback( @@ -37,7 +37,7 @@ export const useSendMessages = (): UseSendMessages => { setIsLoading(true); try { return await fetchConnectorExecuteAction({ - assistantLangChain, + assistantLangChain: knowledgeBase.assistantLangChain, http, messages, apiConfig, @@ -46,7 +46,7 @@ export const useSendMessages = (): UseSendMessages => { setIsLoading(false); } }, - [assistantLangChain] + [knowledgeBase.assistantLangChain] ); return { isLoading, sendMessages }; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx index cad3783c4669b..fbf1f68e05e0d 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/constants.tsx @@ -5,7 +5,14 @@ * 2.0. */ +import { KnowledgeBaseConfig } from '../assistant/types'; + export const DEFAULT_ASSISTANT_NAMESPACE = 'elasticAssistantDefault'; export const QUICK_PROMPT_LOCAL_STORAGE_KEY = 'quickPrompts'; export const SYSTEM_PROMPT_LOCAL_STORAGE_KEY = 'systemPrompts'; export const LAST_CONVERSATION_ID_LOCAL_STORAGE_KEY = 'lastConversationId'; +export const KNOWLEDGE_BASE_LOCAL_STORAGE_KEY = 'knowledgeBase'; + +export const DEFAULT_KNOWLEDGE_BASE_SETTINGS: KnowledgeBaseConfig = { + assistantLangChain: false, +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.test.tsx index 89d3e4c6e8878..7e50643ae7595 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant_context/index.test.tsx @@ -28,7 +28,6 @@ const ContextWrapper: React.FC = ({ children }) => ( CodeBlockDetails[][]; baseAllow: string[]; @@ -73,6 +74,7 @@ export interface AssistantProviderProps { }) => EuiCommentProps[]; http: HttpSetup; getInitialConversations: () => Record; + modelEvaluatorEnabled?: boolean; nameSpace?: string; setConversations: React.Dispatch>>; setDefaultAllow: React.Dispatch>; @@ -87,7 +89,6 @@ export interface UseAssistantContext { augmentMessageCodeBlocks: (currentConversation: Conversation) => CodeBlockDetails[][]; allQuickPrompts: QuickPrompt[]; allSystemPrompts: Prompt[]; - assistantLangChain: boolean; baseAllow: string[]; baseAllowReplacement: string[]; docLinks: Omit; @@ -110,8 +111,10 @@ export interface UseAssistantContext { showAnonymizedValues: boolean; }) => EuiCommentProps[]; http: HttpSetup; + knowledgeBase: KnowledgeBaseConfig; localStorageLastConversationId: string | undefined; promptContexts: Record; + modelEvaluatorEnabled: boolean; nameSpace: string; registerPromptContext: RegisterPromptContext; selectedSettingsTab: SettingsTabs; @@ -120,6 +123,7 @@ export interface UseAssistantContext { setConversations: React.Dispatch>>; setDefaultAllow: React.Dispatch>; setDefaultAllowReplacement: React.Dispatch>; + setKnowledgeBase: React.Dispatch>; setLastConversationId: React.Dispatch>; setSelectedSettingsTab: React.Dispatch>; setShowAssistantOverlay: (showAssistantOverlay: ShowAssistantOverlay) => void; @@ -133,7 +137,6 @@ const AssistantContext = React.createContext(un export const AssistantProvider: React.FC = ({ actionTypeRegistry, assistantAvailability, - assistantLangChain, assistantTelemetry, augmentMessageCodeBlocks, baseAllow, @@ -149,6 +152,7 @@ export const AssistantProvider: React.FC = ({ getComments, http, getInitialConversations, + modelEvaluatorEnabled = false, nameSpace = DEFAULT_ASSISTANT_NAMESPACE, setConversations, setDefaultAllow, @@ -174,6 +178,14 @@ export const AssistantProvider: React.FC = ({ const [localStorageLastConversationId, setLocalStorageLastConversationId] = useLocalStorage(`${nameSpace}.${LAST_CONVERSATION_ID_LOCAL_STORAGE_KEY}`); + /** + * Local storage for knowledge base configuration, prefixed by assistant nameSpace + */ + const [localStorageKnowledgeBase, setLocalStorageKnowledgeBase] = useLocalStorage( + `${nameSpace}.${KNOWLEDGE_BASE_LOCAL_STORAGE_KEY}`, + DEFAULT_KNOWLEDGE_BASE_SETTINGS + ); + /** * Prompt contexts are used to provide components a way to register and make their data available to the assistant. */ @@ -254,7 +266,6 @@ export const AssistantProvider: React.FC = ({ () => ({ actionTypeRegistry, assistantAvailability, - assistantLangChain, assistantTelemetry, augmentMessageCodeBlocks, allQuickPrompts: localStorageQuickPrompts ?? [], @@ -272,6 +283,8 @@ export const AssistantProvider: React.FC = ({ docLinks, getComments, http, + knowledgeBase: localStorageKnowledgeBase ?? DEFAULT_KNOWLEDGE_BASE_SETTINGS, + modelEvaluatorEnabled, promptContexts, nameSpace, registerPromptContext, @@ -281,6 +294,7 @@ export const AssistantProvider: React.FC = ({ setConversations: onConversationsUpdated, setDefaultAllow, setDefaultAllowReplacement, + setKnowledgeBase: setLocalStorageKnowledgeBase, setSelectedSettingsTab, setShowAssistantOverlay, showAssistantOverlay, @@ -292,7 +306,6 @@ export const AssistantProvider: React.FC = ({ [ actionTypeRegistry, assistantAvailability, - assistantLangChain, assistantTelemetry, augmentMessageCodeBlocks, baseAllow, @@ -308,9 +321,11 @@ export const AssistantProvider: React.FC = ({ docLinks, getComments, http, + localStorageKnowledgeBase, localStorageLastConversationId, localStorageQuickPrompts, localStorageSystemPrompts, + modelEvaluatorEnabled, nameSpace, onConversationsUpdated, promptContexts, @@ -318,6 +333,7 @@ export const AssistantProvider: React.FC = ({ selectedSettingsTab, setDefaultAllow, setDefaultAllowReplacement, + setLocalStorageKnowledgeBase, setLocalStorageLastConversationId, setLocalStorageQuickPrompts, setLocalStorageSystemPrompts, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings/knowledge_base_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings/knowledge_base_settings.tsx new file mode 100644 index 0000000000000..2c667a5739ef6 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings/knowledge_base_settings.tsx @@ -0,0 +1,294 @@ +/* + * 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, { useCallback, useMemo } from 'react'; +import { + EuiFormRow, + EuiTitle, + EuiText, + EuiHorizontalRule, + EuiLoadingSpinner, + EuiSpacer, + EuiSwitchEvent, + EuiLink, + EuiBetaBadge, + EuiFlexGroup, + EuiFlexItem, + EuiHealth, + EuiButtonEmpty, + EuiSwitch, +} from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n-react'; +import { css } from '@emotion/react'; +import * as i18n from './translations'; +import { useAssistantContext } from '../../assistant_context'; +import { useDeleteKnowledgeBase } from '../use_delete_knowledge_base/use_delete_knowledge_base'; +import { useKnowledgeBaseStatus } from '../use_knowledge_base_status/use_knowledge_base_status'; +import { useSetupKnowledgeBase } from '../use_setup_knowledge_base/use_setup_knowledge_base'; + +import type { KnowledgeBaseConfig } from '../../assistant/types'; + +const ESQL_RESOURCE = 'esql'; +const KNOWLEDGE_BASE_INDEX_PATTERN = '.kibana-elastic-ai-assistant-kb'; + +interface Props { + knowledgeBase: KnowledgeBaseConfig; + setUpdatedKnowledgeBaseSettings: React.Dispatch>; +} + +/** + * Knowledge Base Settings -- enable and disable LangChain integration, Knowledge Base, and ESQL KB Documents + */ +export const KnowledgeBaseSettings: React.FC = React.memo( + ({ knowledgeBase, setUpdatedKnowledgeBaseSettings }) => { + const { http } = useAssistantContext(); + const { + data: kbStatus, + isLoading, + isFetching, + } = useKnowledgeBaseStatus({ http, resource: ESQL_RESOURCE }); + const { mutate: setupKB, isLoading: isSettingUpKB } = useSetupKnowledgeBase({ http }); + const { mutate: deleteKB, isLoading: isDeletingUpKB } = useDeleteKnowledgeBase({ http }); + + // Resource enabled state + const isKnowledgeBaseEnabled = + (kbStatus?.index_exists && kbStatus?.pipeline_exists && kbStatus?.elser_exists) ?? false; + const isESQLEnabled = kbStatus?.esql_exists ?? false; + + // Resource availability state + const isLoadingKb = isLoading || isFetching || isSettingUpKB || isDeletingUpKB; + const isKnowledgeBaseAvailable = knowledgeBase.assistantLangChain && kbStatus?.elser_exists; + const isESQLAvailable = + knowledgeBase.assistantLangChain && isKnowledgeBaseAvailable && isKnowledgeBaseEnabled; + + // Calculated health state for EuiHealth component + const elserHealth = kbStatus?.elser_exists ? 'success' : 'subdued'; + const knowledgeBaseHealth = isKnowledgeBaseEnabled ? 'success' : 'subdued'; + const esqlHealth = isESQLEnabled ? 'success' : 'subdued'; + + ////////////////////////////////////////////////////////////////////////////////////////// + // Main `Knowledge Base` switch, which toggles the `assistantLangChain` UI feature toggle + // setting that is saved to localstorage + const onEnableAssistantLangChainChange = useCallback( + (event: EuiSwitchEvent) => { + setUpdatedKnowledgeBaseSettings({ + ...knowledgeBase, + assistantLangChain: event.target.checked, + }); + + // If enabling and ELSER exists, try to set up automatically + if (event.target.checked && kbStatus?.elser_exists) { + setupKB(ESQL_RESOURCE); + } + }, + [kbStatus?.elser_exists, knowledgeBase, setUpdatedKnowledgeBaseSettings, setupKB] + ); + + const assistantLangChainSwitch = useMemo(() => { + return isLoadingKb ? ( + + ) : ( + + ); + }, [isLoadingKb, knowledgeBase.assistantLangChain, onEnableAssistantLangChainChange]); + + ////////////////////////////////////////////////////////////////////////////////////////// + // Knowledge Base Resource + const onEnableKB = useCallback( + (enabled: boolean) => { + if (enabled) { + setupKB(); + } else { + deleteKB(); + } + }, + [deleteKB, setupKB] + ); + + const knowledgeBaseActionButton = useMemo(() => { + return isLoadingKb || !isKnowledgeBaseAvailable ? ( + <> + ) : ( + onEnableKB(!isKnowledgeBaseEnabled)} + size="xs" + > + {isKnowledgeBaseEnabled + ? i18n.KNOWLEDGE_BASE_DELETE_BUTTON + : i18n.KNOWLEDGE_BASE_INIT_BUTTON} + + ); + }, [isKnowledgeBaseAvailable, isKnowledgeBaseEnabled, isLoadingKb, onEnableKB]); + + const knowledgeBaseDescription = useMemo(() => { + return isKnowledgeBaseEnabled ? ( + <> + {i18n.KNOWLEDGE_BASE_DESCRIPTION_INSTALLED(KNOWLEDGE_BASE_INDEX_PATTERN)}{' '} + {knowledgeBaseActionButton} + + ) : ( + <> + {i18n.KNOWLEDGE_BASE_DESCRIPTION} {knowledgeBaseActionButton} + + ); + }, [isKnowledgeBaseEnabled, knowledgeBaseActionButton]); + + ////////////////////////////////////////////////////////////////////////////////////////// + // ESQL Resource + const onEnableESQL = useCallback( + (enabled: boolean) => { + if (enabled) { + setupKB(ESQL_RESOURCE); + } else { + deleteKB(ESQL_RESOURCE); + } + }, + [deleteKB, setupKB] + ); + + const esqlActionButton = useMemo(() => { + return isLoadingKb || !isESQLAvailable ? ( + <> + ) : ( + onEnableESQL(!isESQLEnabled)} + size="xs" + > + {isESQLEnabled ? i18n.KNOWLEDGE_BASE_DELETE_BUTTON : i18n.KNOWLEDGE_BASE_INIT_BUTTON} + + ); + }, [isLoadingKb, isESQLAvailable, isESQLEnabled, onEnableESQL]); + + const esqlDescription = useMemo(() => { + return isESQLEnabled ? ( + <> + {i18n.ESQL_DESCRIPTION_INSTALLED} {esqlActionButton} + + ) : ( + <> + {i18n.ESQL_DESCRIPTION} {esqlActionButton} + + ); + }, [esqlActionButton, isESQLEnabled]); + + return ( + <> + +

+ {i18n.SETTINGS_TITLE}{' '} + +

+
+ + {i18n.SETTINGS_DESCRIPTION} + + + + {assistantLangChainSwitch} + + + + + +
+ {i18n.KNOWLEDGE_BASE_ELSER_LABEL} + + + {i18n.KNOWLEDGE_BASE_ELSER_MACHINE_LEARNING} + + ), + seeDocs: ( + + {i18n.KNOWLEDGE_BASE_ELSER_SEE_DOCS} + + ), + }} + /> + +
+
+ +
+ {i18n.KNOWLEDGE_BASE_LABEL} + + {knowledgeBaseDescription} + +
+
+ + + {i18n.ESQL_LABEL} + + {esqlDescription} + + + +
+ + ); + } +); + +KnowledgeBaseSettings.displayName = 'KnowledgeBaseSettings'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings/translations.ts new file mode 100644 index 0000000000000..95417ddf6a889 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings/translations.ts @@ -0,0 +1,109 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const SETTINGS_TITLE = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.settingsTitle', + { + defaultMessage: 'Knowledge Base', + } +); + +export const SETTINGS_BADGE = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.settingsBadgeTitle', + { + defaultMessage: 'Experimental', + } +); + +export const SETTINGS_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.settingsDescription', + { + defaultMessage: + 'Powered by ELSER, the Knowledge Base enables the ability to recall documents and other relevant context within your conversation.', + } +); + +export const KNOWLEDGE_BASE_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.knowledgeBaseLabel', + { + defaultMessage: 'Knowledge Base', + } +); + +export const KNOWLEDGE_BASE_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.knowledgeBaseDescription', + { + defaultMessage: 'Index where Knowledge Base docs are stored', + } +); + +export const KNOWLEDGE_BASE_DESCRIPTION_INSTALLED = (kbIndexPattern: string) => + i18n.translate( + 'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.knowledgeBaseInstalledDescription', + { + defaultMessage: 'Initialized to `{kbIndexPattern}`', + values: { kbIndexPattern }, + } + ); + +export const KNOWLEDGE_BASE_INIT_BUTTON = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.initializeKnowledgeBaseButton', + { + defaultMessage: 'Initialize', + } +); + +export const KNOWLEDGE_BASE_DELETE_BUTTON = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.deleteKnowledgeBaseButton', + { + defaultMessage: 'Delete', + } +); + +export const KNOWLEDGE_BASE_ELSER_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.elserLabel', + { + defaultMessage: 'ELSER Configured', + } +); + +export const KNOWLEDGE_BASE_ELSER_MACHINE_LEARNING = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.elserMachineLearningDescription', + { + defaultMessage: 'Machine Learning', + } +); + +export const KNOWLEDGE_BASE_ELSER_SEE_DOCS = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.elserSeeDocsDescription', + { + defaultMessage: 'See docs', + } +); + +export const ESQL_LABEL = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.esqlLabel', + { + defaultMessage: 'ES|QL Knowledge Base Documents', + } +); + +export const ESQL_DESCRIPTION = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.esqlDescription', + { + defaultMessage: 'Knowledge Base docs for generating ES|QL queries', + } +); + +export const ESQL_DESCRIPTION_INSTALLED = i18n.translate( + 'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettings.esqlInstalledDescription', + { + defaultMessage: 'ES|QL Knowledge Base docs loaded', + } +); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx b/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx index 53a73ec16b661..024e39ac46c3c 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx @@ -72,7 +72,6 @@ export const TestProvidersComponent: React.FC = ({ = ({ children, isILMAvailab { actionTypeRegistry={actionTypeRegistry} augmentMessageCodeBlocks={augmentMessageCodeBlocks} assistantAvailability={assistantAvailability} - // NOTE: `assistantLangChain` and `assistantModelEvaluation` experimental feature will be coupled until upcoming - // Knowledge Base UI updates, which will remove the `assistantLangChain` feature flag in favor of a UI feature toggle - assistantLangChain={isModelEvaluationEnabled} assistantTelemetry={assistantTelemetry} defaultAllow={defaultAllow} defaultAllowReplacement={defaultAllowReplacement} @@ -71,6 +68,7 @@ export const AssistantProvider: React.FC = ({ children }) => { getInitialConversations={getInitialConversation} getComments={getComments} http={http} + modelEvaluatorEnabled={isModelEvaluationEnabled} nameSpace={nameSpace} setConversations={setConversations} setDefaultAllow={setDefaultAllow} diff --git a/x-pack/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx b/x-pack/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx index 2954f47e0d1a8..4dc5f01b0ee7d 100644 --- a/x-pack/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx @@ -34,7 +34,6 @@ export const MockAssistantProviderComponent: React.FC = ({ children }) => [])} baseAllow={[]} baseAllowReplacement={[]}