[Obs AI Assistant] Split up plugin in core/app (#178018)

Splits up the Observability AI Assistant plugin so it can be used
outside of the context of the Observability apps. Additionally, the
following changes were made:

- Add the AI Assistant button to the top nav, instead of the header
menu. This prevents unmounts and remounts (and makes it much easier to
use everywhere).
- Contextual messages now use a function request/response to inject the
data of the insight. This allows us to remove `startedFrom`.
- ML is now an runtime dependency only (via `core.plugins.onStart`).
With a static dependency, we'll run into circular dependency issues.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Dario Gieselaar 2024-03-11 15:46:08 +01:00 committed by GitHub
parent d793a7ff8a
commit 74386d037d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
324 changed files with 2317 additions and 1532 deletions

1
.github/CODEOWNERS vendored
View file

@ -569,6 +569,7 @@ test/common/plugins/newsfeed @elastic/kibana-core
src/plugins/no_data_page @elastic/appex-sharedux
x-pack/plugins/notifications @elastic/appex-sharedux
packages/kbn-object-versioning @elastic/appex-sharedux
x-pack/plugins/observability_solution/observability_ai_assistant_app @elastic/obs-knowledge-team
x-pack/plugins/observability_solution/observability_ai_assistant @elastic/obs-knowledge-team
x-pack/packages/observability/alert_details @elastic/obs-ux-management-team
x-pack/packages/observability/alerting_test_data @elastic/obs-ux-management-team

View file

@ -692,6 +692,10 @@ Elastic.
|This document gives an overview of the features of the Observability AI Assistant at the time of writing, and how to use them. At a high level, the Observability AI Assistant offers contextual insights, and a chat functionality that we enrich with function calling, allowing the LLM to hook into the user's data. We also allow the LLM to store things it considers new information as embeddings into Elasticsearch, and query this knowledge base when it decides it needs more information, using ELSER.
|{kib-repo}blob/{branch}/x-pack/plugins/observability_solution/observability_ai_assistant_app/README.md[observabilityAIAssistantApp]
|This app registers defaults functions. It exists as a separate plugin to avoid cyclical dependencies.
|{kib-repo}blob/{branch}/x-pack/plugins/observability_solution/observability_logs_explorer/README.md[observabilityLogsExplorer]
|This plugin provides an app based on the LogsExplorer component from the logs_explorer plugin, but adds observability-specific affordances.

View file

@ -588,6 +588,7 @@
"@kbn/no-data-page-plugin": "link:src/plugins/no_data_page",
"@kbn/notifications-plugin": "link:x-pack/plugins/notifications",
"@kbn/object-versioning": "link:packages/kbn-object-versioning",
"@kbn/observability-ai-assistant-app-plugin": "link:x-pack/plugins/observability_solution/observability_ai_assistant_app",
"@kbn/observability-ai-assistant-plugin": "link:x-pack/plugins/observability_solution/observability_ai_assistant",
"@kbn/observability-alert-details": "link:x-pack/packages/observability/alert_details",
"@kbn/observability-alerting-test-data": "link:x-pack/packages/observability/alerting_test_data",

View file

@ -104,7 +104,8 @@ pageLoadAssetSize:
newsfeed: 42228
noDataPage: 5000
observability: 115443
observabilityAIAssistant: 25000
observabilityAIAssistant: 58230
observabilityAIAssistantApp: 27680
observabilityLogsExplorer: 46650
observabilityOnboarding: 19573
observabilityShared: 72039

View file

@ -14,8 +14,8 @@ import { ServerlessPluginStart } from '@kbn/serverless/public';
import { EnterpriseSearchPublicStart } from '@kbn/enterprise-search-plugin/public';
import type {
ObservabilityAIAssistantPluginSetup,
ObservabilityAIAssistantPluginStart,
ObservabilityAIAssistantPublicSetup,
ObservabilityAIAssistantPublicStart,
} from '@kbn/observability-ai-assistant-plugin/public';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
@ -27,11 +27,11 @@ export interface AiAssistantManagementObservabilityPluginStart {}
export interface SetupDependencies {
management: ManagementSetup;
home?: HomePublicPluginSetup;
observabilityAIAssistant?: ObservabilityAIAssistantPluginSetup;
observabilityAIAssistant?: ObservabilityAIAssistantPublicSetup;
}
export interface StartDependencies {
observabilityAIAssistant?: ObservabilityAIAssistantPluginStart;
observabilityAIAssistant?: ObservabilityAIAssistantPublicStart;
serverless?: ServerlessPluginStart;
enterpriseSearch?: EnterpriseSearchPublicStart;
}

View file

@ -1132,6 +1132,8 @@
"@kbn/notifications-plugin/*": ["x-pack/plugins/notifications/*"],
"@kbn/object-versioning": ["packages/kbn-object-versioning"],
"@kbn/object-versioning/*": ["packages/kbn-object-versioning/*"],
"@kbn/observability-ai-assistant-app-plugin": ["x-pack/plugins/observability_solution/observability_ai_assistant_app"],
"@kbn/observability-ai-assistant-app-plugin/*": ["x-pack/plugins/observability_solution/observability_ai_assistant_app/*"],
"@kbn/observability-ai-assistant-plugin": ["x-pack/plugins/observability_solution/observability_ai_assistant"],
"@kbn/observability-ai-assistant-plugin/*": ["x-pack/plugins/observability_solution/observability_ai_assistant/*"],
"@kbn/observability-alert-details": ["x-pack/packages/observability/alert_details"],

View file

@ -2,7 +2,10 @@
"prefix": "xpack",
"paths": {
"xpack.actions": "plugins/actions",
"xpack.aiops": ["packages/ml/aiops_components", "plugins/aiops"],
"xpack.aiops": [
"packages/ml/aiops_components",
"plugins/aiops"
],
"xpack.alerting": "plugins/alerting",
"xpack.eventLog": "plugins/event_log",
"xpack.stackAlerts": "plugins/stack_alerts",
@ -33,9 +36,15 @@
"xpack.dataVisualizer": "plugins/data_visualizer",
"xpack.exploratoryView": "plugins/observability_solution/exploratory_view",
"xpack.fileUpload": "plugins/file_upload",
"xpack.globalSearch": ["plugins/global_search"],
"xpack.globalSearchBar": ["plugins/global_search_bar"],
"xpack.graph": ["plugins/graph"],
"xpack.globalSearch": [
"plugins/global_search"
],
"xpack.globalSearchBar": [
"plugins/global_search_bar"
],
"xpack.graph": [
"plugins/graph"
],
"xpack.grokDebugger": "plugins/grokdebugger",
"xpack.idxMgmt": "plugins/index_management",
"xpack.indexLifecycleMgmt": "plugins/index_lifecycle_management",
@ -50,9 +59,13 @@
"xpack.licenseMgmt": "plugins/license_management",
"xpack.licensing": "plugins/licensing",
"xpack.lists": "plugins/lists",
"xpack.logstash": ["plugins/logstash"],
"xpack.logstash": [
"plugins/logstash"
],
"xpack.main": "legacy/plugins/xpack_main",
"xpack.maps": ["plugins/maps"],
"xpack.maps": [
"plugins/maps"
],
"xpack.metricsData": "plugins/metrics_data_access",
"xpack.ml": [
"packages/ml/anomaly_utils",
@ -65,18 +78,31 @@
"packages/ml/ui_actions",
"plugins/ml"
],
"xpack.monitoring": ["plugins/monitoring"],
"xpack.monitoring": [
"plugins/monitoring"
],
"xpack.observability": "plugins/observability_solution/observability",
"xpack.observabilityAiAssistant": "plugins/observability_solution/observability_ai_assistant",
"xpack.observabilityAiAssistant": [
"plugins/observability_solution/observability_ai_assistant",
"plugins/observability_solution/observability_ai_assistant_app"
],
"xpack.observabilityLogsExplorer": "plugins/observability_solution/observability_logs_explorer",
"xpack.observability_onboarding": "plugins/observability_solution/observability_onboarding",
"xpack.observabilityShared": "plugins/observability_solution/observability_shared",
"xpack.osquery": ["plugins/osquery"],
"xpack.osquery": [
"plugins/osquery"
],
"xpack.painlessLab": "plugins/painless_lab",
"xpack.profiling": ["plugins/observability_solution/profiling"],
"xpack.profiling": [
"plugins/observability_solution/profiling"
],
"xpack.remoteClusters": "plugins/remote_clusters",
"xpack.reporting": ["plugins/reporting"],
"xpack.rollupJobs": ["plugins/rollup"],
"xpack.reporting": [
"plugins/reporting"
],
"xpack.rollupJobs": [
"plugins/rollup"
],
"xpack.runtimeFields": "plugins/runtime_fields",
"xpack.screenshotting": "plugins/screenshotting",
"xpack.searchProfiler": "plugins/searchprofiler",
@ -91,20 +117,30 @@
"xpack.sessionView": "plugins/session_view",
"xpack.snapshotRestore": "plugins/snapshot_restore",
"xpack.spaces": "plugins/spaces",
"xpack.savedObjectsTagging": ["plugins/saved_objects_tagging"],
"xpack.savedObjectsTagging": [
"plugins/saved_objects_tagging"
],
"xpack.taskManager": "legacy/plugins/task_manager",
"xpack.threatIntelligence": "plugins/threat_intelligence",
"xpack.timelines": "plugins/timelines",
"xpack.transform": "plugins/transform",
"xpack.triggersActionsUI": "plugins/triggers_actions_ui",
"xpack.upgradeAssistant": "plugins/upgrade_assistant",
"xpack.uptime": ["plugins/observability_solution/uptime"],
"xpack.synthetics": ["plugins/observability_solution/synthetics"],
"xpack.ux": ["plugins/observability_solution/ux"],
"xpack.uptime": [
"plugins/observability_solution/uptime"
],
"xpack.synthetics": [
"plugins/observability_solution/synthetics"
],
"xpack.ux": [
"plugins/observability_solution/ux"
],
"xpack.urlDrilldown": "plugins/drilldowns/url_drilldown",
"xpack.watcher": "plugins/watcher"
},
"exclude": ["examples"],
"exclude": [
"examples"
],
"translations": [
"@kbn/translations-plugin/translations/zh-CN.json",
"@kbn/translations-plugin/translations/ja-JP.json",

View file

@ -56,8 +56,7 @@
"kibanaUtils",
"ml",
"observability",
"maps",
"observabilityAIAssistant"
"maps"
]
}
}

View file

@ -6,10 +6,7 @@
*/
import { EuiFlexItem, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import {
type Message,
MessageRole,
} from '@kbn/observability-ai-assistant-plugin/public';
import type { Message } from '@kbn/observability-ai-assistant-plugin/public';
import React, { useMemo, useState } from 'react';
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
import { APMError } from '../../../../../typings/es_schemas/ui/apm_error';
@ -25,53 +22,54 @@ export function ErrorSampleContextualInsight({
transaction?: Transaction;
}) {
const {
observabilityAIAssistant: { ObservabilityAIAssistantContextualInsight },
observabilityAIAssistant: {
ObservabilityAIAssistantContextualInsight,
getContextualInsightMessages,
},
} = useApmPluginContext();
const [logStacktrace, setLogStacktrace] = useState('');
const [exceptionStacktrace, setExceptionStacktrace] = useState('');
const messages = useMemo<Message[]>(() => {
const now = new Date().toISOString();
const serviceName = error.service.name;
const languageName = error.service.language?.name ?? '';
const runtimeName = error.service.runtime?.name ?? '';
const runtimeVersion = error.service.runtime?.version ?? '';
const transactionName = transaction?.transaction.name ?? '';
return [
{
'@timestamp': now,
message: {
role: MessageRole.User,
content: `I'm an SRE. I am looking at an exception and trying to understand what it means.
return getContextualInsightMessages({
message: `I'm looking at an exception and trying to understand what it means`,
instructions: `I'm an SRE. I am looking at an exception and trying to understand what it means.
Your task is to describe what the error means and what it could be caused by.
The error occurred on a service called ${serviceName}, which is a ${runtimeName} service written in ${languageName}. The
runtime version is ${runtimeVersion}.
The request it occurred for is called ${transactionName}.
${
logStacktrace
? `The log stacktrace:
${logStacktrace}`
: ''
}
${
exceptionStacktrace
? `The exception stacktrace:
${exceptionStacktrace}`
: ''
}
`,
},
},
];
}, [error, transaction, logStacktrace, exceptionStacktrace]);
Your task is to describe what the error means and what it could be caused by.
The error occurred on a service called ${serviceName}, which is a ${runtimeName} service written in ${languageName}. The
runtime version is ${runtimeVersion}.
The request it occurred for is called ${transactionName}.
${
logStacktrace
? `The log stacktrace:
${logStacktrace}`
: ''
}
${
exceptionStacktrace
? `The exception stacktrace:
${exceptionStacktrace}`
: ''
}`,
});
}, [
error,
transaction,
logStacktrace,
exceptionStacktrace,
getContextualInsightMessages,
]);
return ObservabilityAIAssistantContextualInsight && messages ? (
<>

View file

@ -131,7 +131,6 @@ export function ApmAppRoot({
function MountApmHeaderActionMenu() {
const {
appMountParameters: { setHeaderActionMenu, theme$ },
observabilityAIAssistant: { ObservabilityAIAssistantActionMenuItem },
} = useApmPluginContext();
return (
@ -140,11 +139,6 @@ function MountApmHeaderActionMenu() {
<EuiFlexItem>
<ApmHeaderActionMenu />
</EuiFlexItem>
{ObservabilityAIAssistantActionMenuItem ? (
<EuiFlexItem>
<ObservabilityAIAssistantActionMenuItem />
</EuiFlexItem>
) : null}
</EuiFlexGroup>
</HeaderMenuPortal>
);

View file

@ -66,7 +66,7 @@ export function ApmMainTemplate({
const basePath = http?.basePath.get();
const { config } = useApmPluginContext();
const aiAssistant = services.observabilityAIAssistant.service;
const aiAssistant = services.observabilityAIAssistant;
const ObservabilityPageTemplate = observabilityShared.navigation.PageTemplate;
@ -119,7 +119,7 @@ export function ApmMainTemplate({
});
useEffect(() => {
return aiAssistant.setScreenContext({
return aiAssistant.service.setScreenContext({
screenDescription: [
hasApmData
? 'The user has APM data.'

View file

@ -25,9 +25,7 @@ const coreMock = {
},
},
observabilityAIAssistant: {
service: {
setScreenContext: () => noop,
},
service: { setScreenContext: () => noop },
},
} as unknown as Partial<CoreStart>;

View file

@ -15,7 +15,7 @@ import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { UiActionsStart } from '@kbn/ui-actions-plugin/public';
import type { ObservabilityAIAssistantPluginStart } from '@kbn/observability-ai-assistant-plugin/public';
import type { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public';
import { SharePluginSetup } from '@kbn/share-plugin/public';
import type { ApmPluginSetupDeps } from '../../plugin';
import type { ConfigSchema } from '../..';
@ -33,7 +33,7 @@ export interface ApmPluginContextValue {
data: DataPublicPluginStart;
unifiedSearch: UnifiedSearchPublicPluginStart;
uiActions: UiActionsStart;
observabilityAIAssistant: ObservabilityAIAssistantPluginStart;
observabilityAIAssistant: ObservabilityAIAssistantPublicStart;
share: SharePluginSetup;
kibanaEnvironment: KibanaEnvContext;
}

View file

@ -172,9 +172,7 @@ export const mockApmPluginContextValue = {
getTriggerCompatibleActions: () => Promise.resolve([]),
},
observabilityAIAssistant: {
service: {
setScreenContext: jest.fn().mockImplementation(() => noop),
},
service: { setScreenContext: jest.fn().mockImplementation(() => noop) },
},
};

View file

@ -129,9 +129,7 @@ const mockApmPluginContext = {
core: mockCore,
plugins: mockPlugin,
observabilityAIAssistant: {
service: {
setScreenContext: () => noop,
},
service: { setScreenContext: () => noop },
},
} as unknown as ApmPluginContextValue;

View file

@ -42,7 +42,7 @@ import { LicenseManagementUIPluginSetup } from '@kbn/license-management-plugin/p
import type { LicensingPluginSetup } from '@kbn/licensing-plugin/public';
import type { MapsStartApi } from '@kbn/maps-plugin/public';
import type { MlPluginSetup, MlPluginStart } from '@kbn/ml-plugin/public';
import type { ObservabilityAIAssistantPluginStart } from '@kbn/observability-ai-assistant-plugin/public';
import type { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public';
import {
FetchDataParams,
ObservabilityPublicSetup,
@ -137,7 +137,7 @@ export interface ApmPluginStartDeps {
lens: LensPublicStart;
uiActions: UiActionsStart;
profiling?: ProfilingPluginStart;
observabilityAIAssistant: ObservabilityAIAssistantPluginStart;
observabilityAIAssistant: ObservabilityAIAssistantPublicStart;
dashboard: DashboardStart;
metricsDataAccess: MetricsDataPluginStart;
uiSettings: IUiSettingsClient;

View file

@ -8,7 +8,7 @@
import type { CoreSetup } from '@kbn/core-lifecycle-server';
import type { Logger } from '@kbn/logging';
import type {
ChatRegistrationFunction,
RegistrationCallback,
RegisterFunction,
} from '@kbn/observability-ai-assistant-plugin/server/service/types';
import type { IRuleDataClient } from '@kbn/rule-registry-plugin/server';
@ -47,8 +47,11 @@ export function registerAssistantFunctions({
kibanaVersion: string;
ruleDataClient: IRuleDataClient;
plugins: APMRouteHandlerResources['plugins'];
}): ChatRegistrationFunction {
return async ({ resources, registerContext, registerFunction }) => {
}): RegistrationCallback {
return async ({
resources,
functions: { registerContext, registerFunction },
}) => {
const apmRouteHandlerResources: APMRouteHandlerResources = {
context: resources.context,
request: resources.request,

View file

@ -66,8 +66,8 @@ import {
ProfilingDataAccessPluginStart,
} from '@kbn/profiling-data-access-plugin/server';
import type {
ObservabilityAIAssistantPluginSetup,
ObservabilityAIAssistantPluginStart,
ObservabilityAIAssistantServerSetup,
ObservabilityAIAssistantServerStart,
} from '@kbn/observability-ai-assistant-plugin/server';
import { APMConfig } from '.';
@ -86,7 +86,7 @@ export interface APMPluginSetupDependencies {
metricsDataAccess: MetricsDataPluginSetup;
dataViews: {};
share: SharePluginSetup;
observabilityAIAssistant: ObservabilityAIAssistantPluginSetup;
observabilityAIAssistant: ObservabilityAIAssistantServerSetup;
// optional dependencies
actions?: ActionsPlugin['setup'];
alerting?: AlertingPlugin['setup'];
@ -112,7 +112,7 @@ export interface APMPluginStartDependencies {
metricsDataAccess: MetricsDataPluginSetup;
dataViews: DataViewsServerPluginStart;
share: undefined;
observabilityAIAssistant: ObservabilityAIAssistantPluginStart;
observabilityAIAssistant: ObservabilityAIAssistantServerStart;
// optional dependencies
actions?: ActionsPlugin['start'];
alerting?: AlertingPlugin['start'];

View file

@ -20,7 +20,7 @@ import { EmbeddableStateTransfer } from '@kbn/embeddable-plugin/public';
import { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { ObservabilityAIAssistantPluginStart } from '@kbn/observability-ai-assistant-plugin/public';
import type { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { LensPublicStart } from '@kbn/lens-plugin/public';
import { SharePluginStart } from '@kbn/share-plugin/public';
@ -42,7 +42,7 @@ export interface ObservabilityAppServices {
lens: LensPublicStart;
navigation: NavigationPublicPluginStart;
notifications: NotificationsStart;
observabilityAIAssistant: ObservabilityAIAssistantPluginStart;
observabilityAIAssistant: ObservabilityAIAssistantPublicStart;
overlays: OverlayStart;
savedObjectsClient: SavedObjectsStart['client'];
share: SharePluginStart;

View file

@ -20,11 +20,7 @@ export function ExpViewActionMenuContent({
timeRange?: { from: string; to: string };
lensAttributes: TypedLensByValueInput['attributes'] | null;
}) {
const {
lens,
isDev,
observabilityAIAssistant: { ObservabilityAIAssistantActionMenuItem },
} = useKibana().services;
const { lens, isDev } = useKibana().services;
const [isSaveOpen, setIsSaveOpen] = useState(false);
@ -94,11 +90,6 @@ export function ExpViewActionMenuContent({
})}
</EuiButton>
</EuiFlexItem>
{ObservabilityAIAssistantActionMenuItem ? (
<EuiFlexItem>
<ObservabilityAIAssistantActionMenuItem />
</EuiFlexItem>
) : null}
</EuiFlexGroup>
{isSaveOpen && lensAttributes && (

View file

@ -11,7 +11,6 @@ import { HeaderMenuPortal } from '@kbn/observability-shared-plugin/public';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { ExpViewActionMenuContent } from './action_menu';
import { useExploratoryView } from '../../contexts/exploratory_view_config';
import { useKibana } from '../../hooks/use_kibana';
interface Props {
timeRange?: { from: string; to: string };
@ -20,21 +19,12 @@ interface Props {
export function ExpViewActionMenu(props: Props) {
const { setHeaderActionMenu, theme$ } = useExploratoryView();
const {
observabilityAIAssistant: { ObservabilityAIAssistantActionMenuItem },
} = useKibana().services;
return (
<HeaderMenuPortal setHeaderActionMenu={setHeaderActionMenu} theme$={theme$}>
<EuiFlexGroup responsive={false} gutterSize="s">
<EuiFlexItem>
<ExpViewActionMenuContent {...props} />
</EuiFlexItem>
{ObservabilityAIAssistantActionMenuItem ? (
<EuiFlexItem>
<ObservabilityAIAssistantActionMenuItem />
</EuiFlexItem>
) : null}
</EuiFlexGroup>
</HeaderMenuPortal>
);

View file

@ -35,7 +35,7 @@ import { SecurityPluginStart } from '@kbn/security-plugin/public';
import { SpacesPluginStart } from '@kbn/spaces-plugin/public';
import { LicensingPluginStart } from '@kbn/licensing-plugin/public';
import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
import { ObservabilityAIAssistantPluginStart } from '@kbn/observability-ai-assistant-plugin/public';
import { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public';
import { getExploratoryViewEmbeddable } from './components/shared/exploratory_view/embeddable';
import { createExploratoryViewUrl } from './components/shared/exploratory_view/configurations/exploratory_view_url';
import getAppDataView from './utils/observability_data_views/get_app_data_view';
@ -68,7 +68,7 @@ export interface ExploratoryViewPublicPluginsStart {
usageCollection: UsageCollectionSetup;
unifiedSearch: UnifiedSearchPublicPluginStart;
home?: HomePublicPluginStart;
observabilityAIAssistant: ObservabilityAIAssistantPluginStart;
observabilityAIAssistant: ObservabilityAIAssistantPublicStart;
}
export type ExploratoryViewPublicSetup = ReturnType<Plugin['setup']>;

View file

@ -50,7 +50,6 @@
"requiredBundles": [
"unifiedSearch",
"observability",
"observabilityAIAssistant",
"licenseManagement",
"kibanaUtils",
"kibanaReact",

View file

@ -19,7 +19,7 @@ import {
import { LogRateAnalysisContent, type LogRateAnalysisResultsData } from '@kbn/aiops-plugin/public';
import { Rule } from '@kbn/alerting-plugin/common';
import { TopAlert } from '@kbn/observability-plugin/public';
import { type Message, MessageRole } from '@kbn/observability-ai-assistant-plugin/public';
import type { Message } from '@kbn/observability-ai-assistant-plugin/public';
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { i18n } from '@kbn/i18n';
import { ALERT_END } from '@kbn/rule-data-utils';
@ -52,7 +52,10 @@ export const LogRateAnalysis: FC<AlertDetailsLogRateAnalysisSectionProps> = ({ r
const {
dataViews,
logsShared,
observabilityAIAssistant: { ObservabilityAIAssistantContextualInsight },
observabilityAIAssistant: {
ObservabilityAIAssistantContextualInsight,
getContextualInsightMessages,
},
} = services;
const [dataView, setDataView] = useState<DataView | undefined>();
const [esSearchQuery, setEsSearchQuery] = useState<QueryDslQueryContainer | undefined>();
@ -194,7 +197,10 @@ export const LogRateAnalysis: FC<AlertDetailsLogRateAnalysisSectionProps> = ({ r
.map((item) => Object.values(item).join(','))
.join('\n');
const content = `You are an observability expert using Elastic Observability Suite on call being consulted about a log threshold alert that got triggered by a ${logRateAnalysisType} in log messages. Your job is to take immediate action and proceed with both urgency and precision.
return getContextualInsightMessages({
message:
'Can you identify possible causes and remediations for these log rate analysis results',
instructions: `You are an observability expert using Elastic Observability Suite on call being consulted about a log threshold alert that got triggered by a ${logRateAnalysisType} in log messages. Your job is to take immediate action and proceed with both urgency and precision.
"Log Rate Analysis" is an AIOps feature that uses advanced statistical methods to identify reasons for increases and decreases in log rates. It makes it easy to find and investigate causes of unusual spikes or dips by using the analysis workflow view.
You are using "Log Rate Analysis" and ran the statistical analysis on the log messages which occured during the alert.
You received the following analysis results from "Log Rate Analysis" which list statistically significant co-occuring field/value combinations sorted from most significant (lower p-values) to least significant (higher p-values) that ${
@ -227,20 +233,9 @@ export const LogRateAnalysis: FC<AlertDetailsLogRateAnalysisSectionProps> = ({ r
Do not mention individual p-values from the analysis results.
Do not repeat the full list of field names and field values back to the user.
Do not guess, just say what you are sure of. Do not repeat the given instructions in your output.`;
const now = new Date().toISOString();
return [
{
'@timestamp': now,
message: {
content,
role: MessageRole.User,
},
},
];
}, [logRateAnalysisParams]);
Do not guess, just say what you are sure of. Do not repeat the given instructions in your output.`,
});
}, [logRateAnalysisParams, getContextualInsightMessages]);
if (!dataView || !esSearchQuery) return null;

View file

@ -9,7 +9,7 @@ import { AppMountParameters, CoreStart } from '@kbn/core/public';
import React from 'react';
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import type { ObservabilityAIAssistantPluginStart } from '@kbn/observability-ai-assistant-plugin/public';
import type { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { NavigationWarningPromptProvider } from '@kbn/observability-shared-plugin/public';
import { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public';
@ -28,7 +28,7 @@ export const CommonInfraProviders: React.FC<{
appName: string;
storage: Storage;
triggersActionsUI: TriggersAndActionsUIPublicPluginStart;
observabilityAIAssistant: ObservabilityAIAssistantPluginStart;
observabilityAIAssistant: ObservabilityAIAssistantPublicStart;
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
theme$: AppMountParameters['theme$'];
}> = ({

View file

@ -23,7 +23,7 @@ import {
} from '@elastic/eui';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
import useToggle from 'react-use/lib/useToggle';
import { type Message, MessageRole } from '@kbn/observability-ai-assistant-plugin/public';
import { type Message } from '@kbn/observability-ai-assistant-plugin/public';
import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana';
import { Process } from './types';
import { ProcessRowCharts } from './process_row_charts';
@ -35,66 +35,62 @@ interface Props {
}
export const ContextualInsightProcessRow = ({ command }: { command: string }) => {
const {
observabilityAIAssistant: { ObservabilityAIAssistantContextualInsight },
observabilityAIAssistant: {
ObservabilityAIAssistantContextualInsight,
getContextualInsightMessages,
},
} = useKibanaContextForPlugin().services;
const explainProcessMessages = useMemo<Message[] | undefined>(() => {
if (!command) {
return undefined;
}
const now = new Date().toISOString();
return [
{
'@timestamp': now,
message: {
role: MessageRole.User,
content: `I am a software engineer. I am trying to understand what a process running on my
machine does.
Your task is to first describe what the process is and what its general use cases are. If I also provide you
with the arguments to the process you should then explain its arguments and how they influence the behaviour
of the process. If I do not provide any arguments then explain the behaviour of the process when no arguments are
provided.
If you do not recognise the process say "No information available for this process". If I provide an argument
to the process that you do not recognise then say "No information available for this argument" when explaining
that argument.
Here is an example with arguments.
Process: metricbeat -c /etc/metricbeat.yml -d autodiscover,kafka -e -system.hostfs=/hostfs
Explanation: Metricbeat is part of the Elastic Stack. It is a lightweight shipper that you can install on your
servers to periodically collect metrics from the operating system and from services running on the server.
Use cases for Metricbeat generally revolve around infrastructure monitoring. You would typically install
Metricbeat on your servers to collect metrics from your systems and services. These metrics are then
used for performance monitoring, anomaly detection, system status checks, etc.
Here is a breakdown of the arguments used:
* -c /etc/metricbeat.yml: The -c option is used to specify the configuration file for Metricbeat. In
this case, /etc/metricbeat.yml is the configuration file. This file contains configurations for what
metrics to collect and where to send them (e.g., to Elasticsearch or Logstash).
* -d autodiscover,kafka: The -d option is used to enable debug output for selected components. In
this case, debug output is enabled for autodiscover and kafka components. The autodiscover feature
allows Metricbeat to automatically discover services as they get started and stopped in your environment,
and kafka is presumably a monitored service from which Metricbeat collects metrics.
* -e: The -e option is used to log to stderr and disable syslog/file output. This is useful for debugging.
* -system.hostfs=/hostfs: The -system.hostfs option is used to set the mount point of the hosts
filesystem for use in monitoring a host from within a container. In this case, /hostfs is the mount
point. When running Metricbeat inside a container, filesystem metrics would be for the container by
default, but with this option, Metricbeat can get metrics for the host system.
Here is an example without arguments.
Process: metricbeat
Explanation: Metricbeat is part of the Elastic Stack. It is a lightweight shipper that you can install on your
servers to periodically collect metrics from the operating system and from services running on the server.
Use cases for Metricbeat generally revolve around infrastructure monitoring. You would typically install
Metricbeat on your servers to collect metrics from your systems and services. These metrics are then
used for performance monitoring, anomaly detection, system status checks, etc.
Running it without any arguments will start the process with the default configuration file, typically
located at /etc/metricbeat/metricbeat.yml. This file specifies the metrics to be collected and where
to ship them to.
Now explain this process to me.
Process: ${command}
Explanation:
`,
},
},
];
}, [command]);
return getContextualInsightMessages({
message: `I am a software engineer. I am trying to understand what this process running on my
machine does.`,
instructions: `Your task is to first describe what the process is and what its general use cases are. If I also provide you
with the arguments to the process you should then explain its arguments and how they influence the behaviour
of the process. If I do not provide any arguments then explain the behaviour of the process when no arguments are
provided.
If you do not recognise the process say "No information available for this process". If I provide an argument
to the process that you do not recognise then say "No information available for this argument" when explaining
that argument.
Here is an example with arguments.
Process: metricbeat -c /etc/metricbeat.yml -d autodiscover,kafka -e -system.hostfs=/hostfs
Explanation: Metricbeat is part of the Elastic Stack. It is a lightweight shipper that you can install on your
servers to periodically collect metrics from the operating system and from services running on the server.
Use cases for Metricbeat generally revolve around infrastructure monitoring. You would typically install
Metricbeat on your servers to collect metrics from your systems and services. These metrics are then
used for performance monitoring, anomaly detection, system status checks, etc.
Here is a breakdown of the arguments used:
* -c /etc/metricbeat.yml: The -c option is used to specify the configuration file for Metricbeat. In
this case, /etc/metricbeat.yml is the configuration file. This file contains configurations for what
metrics to collect and where to send them (e.g., to Elasticsearch or Logstash).
* -d autodiscover,kafka: The -d option is used to enable debug output for selected components. In
this case, debug output is enabled for autodiscover and kafka components. The autodiscover feature
allows Metricbeat to automatically discover services as they get started and stopped in your environment,
and kafka is presumably a monitored service from which Metricbeat collects metrics.
* -e: The -e option is used to log to stderr and disable syslog/file output. This is useful for debugging.
* -system.hostfs=/hostfs: The -system.hostfs option is used to set the mount point of the hosts
filesystem for use in monitoring a host from within a container. In this case, /hostfs is the mount
point. When running Metricbeat inside a container, filesystem metrics would be for the container by
default, but with this option, Metricbeat can get metrics for the host system.
Here is an example without arguments.
Process: metricbeat
Explanation: Metricbeat is part of the Elastic Stack. It is a lightweight shipper that you can install on your
servers to periodically collect metrics from the operating system and from services running on the server.
Use cases for Metricbeat generally revolve around infrastructure monitoring. You would typically install
Metricbeat on your servers to collect metrics from your systems and services. These metrics are then
used for performance monitoring, anomaly detection, system status checks, etc.
Running it without any arguments will start the process with the default configuration file, typically
located at /etc/metricbeat/metricbeat.yml. This file specifies the metrics to be collected and where
to ship them to.
Now explain this process to me.
Process: ${command}
Explanation:`,
});
}, [command, getContextualInsightMessages]);
return (
<>
{ObservabilityAIAssistantContextualInsight && explainProcessMessages ? (

View file

@ -31,7 +31,6 @@ export const LogsPageContent: React.FunctionComponent = () => {
const {
application: { getUrlForApp },
observabilityAIAssistant: { ObservabilityAIAssistantActionMenuItem },
} = useKibanaContextForPlugin().services;
const enableDeveloperRoutes = isDevMode();
@ -90,11 +89,6 @@ export const LogsPageContent: React.FunctionComponent = () => {
</EuiHeaderLink>
</EuiHeaderLinks>
</EuiFlexItem>
{ObservabilityAIAssistantActionMenuItem ? (
<EuiFlexItem>
<ObservabilityAIAssistantActionMenuItem />
</EuiFlexItem>
) : null}
</EuiFlexGroup>
</HeaderMenuPortal>
)}

View file

@ -20,7 +20,6 @@ import {
import { useKibana, useUiSetting } from '@kbn/kibana-react-plugin/public';
import { HeaderMenuPortal, useLinkProps } from '@kbn/observability-shared-plugin/public';
import { enableInfrastructureHostsView } from '@kbn/observability-plugin/common';
import { useKibanaContextForPlugin } from '../../hooks/use_kibana';
import { MetricsSourceConfigurationProperties } from '../../../common/metrics_sources';
import { HelpCenterContent } from '../../components/help_center_content';
import { useReadOnlyBadge } from '../../hooks/use_readonly_badge';
@ -48,9 +47,6 @@ const ADD_DATA_LABEL = i18n.translate('xpack.infra.metricsHeaderAddDataButtonLab
});
export const InfrastructurePage = () => {
const {
observabilityAIAssistant: { ObservabilityAIAssistantActionMenuItem },
} = useKibanaContextForPlugin().services;
const config = usePluginConfig();
const uiCapabilities = useKibana().services.application?.capabilities;
const { setHeaderActionMenu, theme$ } = useContext(HeaderActionMenuContext);
@ -115,11 +111,6 @@ export const InfrastructurePage = () => {
</EuiHeaderLink>
</EuiHeaderLinks>
</EuiFlexItem>
{ObservabilityAIAssistantActionMenuItem ? (
<EuiFlexItem>
<ObservabilityAIAssistantActionMenuItem />
</EuiFlexItem>
) : null}
</EuiFlexGroup>
</HeaderMenuPortal>
)}

View file

@ -44,7 +44,7 @@ import {
} from '@kbn/logs-shared-plugin/public';
import { FieldFormatsSetup, FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { LicensingPluginSetup, LicensingPluginStart } from '@kbn/licensing-plugin/public';
import { ObservabilityAIAssistantPluginStart } from '@kbn/observability-ai-assistant-plugin/public';
import { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public';
import type { CloudSetup } from '@kbn/cloud-plugin/public';
import type { LicenseManagementUIPluginSetup } from '@kbn/license-management-plugin/public';
import type { ServerlessPluginStart } from '@kbn/serverless/public';
@ -96,7 +96,7 @@ export interface InfraClientStartDeps {
ml: MlPluginStart;
observability: ObservabilityPublicStart;
observabilityShared: ObservabilitySharedPluginStart;
observabilityAIAssistant: ObservabilityAIAssistantPluginStart;
observabilityAIAssistant: ObservabilityAIAssistantPublicStart;
osquery?: unknown; // OsqueryPluginStart - can't be imported due to cyclic dependency;
share: SharePluginStart;
spaces: SpacesPluginStart;

View file

@ -7,10 +7,9 @@
import React, { useMemo } from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import {
type Message,
MessageRole,
type ObservabilityAIAssistantPluginStart,
import type {
Message,
ObservabilityAIAssistantPublicStart,
} from '@kbn/observability-ai-assistant-plugin/public';
import { LogEntryField } from '../../../common';
import { explainLogMessageTitle, similarLogMessagesTitle } from './translations';
@ -20,53 +19,47 @@ export interface LogAIAssistantDocument {
}
export interface LogAIAssistantProps {
observabilityAIAssistant: ObservabilityAIAssistantPluginStart;
observabilityAIAssistant: ObservabilityAIAssistantPublicStart;
doc: LogAIAssistantDocument | undefined;
}
export const LogAIAssistant = ({
doc,
observabilityAIAssistant: { ObservabilityAIAssistantContextualInsight },
observabilityAIAssistant: {
ObservabilityAIAssistantContextualInsight,
getContextualInsightMessages,
},
}: LogAIAssistantProps) => {
const explainLogMessageMessages = useMemo<Message[] | undefined>(() => {
if (!doc) {
return undefined;
}
const now = new Date().toISOString();
return [
{
'@timestamp': now,
message: {
role: MessageRole.User,
content: `I'm looking at a log entry. Can you explain me what the log message means? Where it could be coming from, whether it is expected and whether it is an issue. Here's the context, serialized: ${JSON.stringify(
{ logEntry: { fields: doc.fields } }
)} `,
return getContextualInsightMessages({
message:
'Can you explain what this log message means? Where it could be coming from, whether it is expected and whether it is an issue.',
instructions: JSON.stringify({
logEntry: {
fields: doc.fields,
},
},
];
}, [doc]);
}),
});
}, [doc, getContextualInsightMessages]);
const similarLogMessageMessages = useMemo<Message[] | undefined>(() => {
if (!doc) {
return undefined;
}
const now = new Date().toISOString();
const message = doc.fields.find((field) => field.field === 'message')?.value[0];
return [
{
'@timestamp': now,
message: {
role: MessageRole.User,
content: `I'm looking at a log entry. Can you construct a Kibana KQL query that I can enter in the search bar that gives me similar log entries, based on the \`message\` field: ${message}`,
},
},
];
}, [doc]);
return getContextualInsightMessages({
message: `I'm looking at a log entry. Can you construct a Kibana KQL query that I can enter in the search bar that gives me similar log entries, based on the message field?`,
instructions: JSON.stringify({
message,
}),
});
}, [getContextualInsightMessages, doc]);
return (
<EuiFlexGroup direction="column" gutterSize="m">

View file

@ -8,7 +8,7 @@
import type { CoreSetup, CoreStart, Plugin as PluginClass } from '@kbn/core/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { ObservabilityAIAssistantPluginStart } from '@kbn/observability-ai-assistant-plugin/public';
import type { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public';
import { SharePluginSetup } from '@kbn/share-plugin/public';
import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
@ -35,7 +35,7 @@ export interface LogsSharedClientSetupDeps {
export interface LogsSharedClientStartDeps {
data: DataPublicPluginStart;
dataViews: DataViewsPublicPluginStart;
observabilityAIAssistant: ObservabilityAIAssistantPluginStart;
observabilityAIAssistant: ObservabilityAIAssistantPublicStart;
uiActions: UiActionsStart;
}

View file

@ -18,7 +18,7 @@ import { Rule } from '@kbn/alerting-plugin/common';
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { type Message, MessageRole } from '@kbn/observability-ai-assistant-plugin/public';
import type { Message } from '@kbn/observability-ai-assistant-plugin/public';
import { ALERT_END } from '@kbn/rule-data-utils';
import { CustomThresholdRuleTypeParams } from '../../types';
import { TopAlert } from '../../../..';
@ -47,7 +47,10 @@ export function LogRateAnalysis({
services,
}: AlertDetailsLogRateAnalysisProps) {
const {
observabilityAIAssistant: { ObservabilityAIAssistantContextualInsight },
observabilityAIAssistant: {
ObservabilityAIAssistantContextualInsight,
getContextualInsightMessages,
},
} = services;
const [esSearchQuery, setEsSearchQuery] = useState<QueryDslQueryContainer | undefined>();
const [logRateAnalysisParams, setLogRateAnalysisParams] = useState<
@ -162,18 +165,12 @@ export function LogRateAnalysis({
Do not repeat the full list of field names and field values back to the user.
Do not guess, just say what you are sure of. Do not repeat the given instructions in your output.`;
const now = new Date().toISOString();
return [
{
'@timestamp': now,
message: {
content,
role: MessageRole.User,
},
},
];
}, [logRateAnalysisParams]);
return getContextualInsightMessages({
message:
'Can you identify possible causes and remediations for these log rate analysis results',
instructions: content,
});
}, [logRateAnalysisParams, getContextualInsightMessages]);
if (!dataView || !esSearchQuery) return null;

View file

@ -45,7 +45,7 @@ mockUseKibanaReturnValue.services.cases.hooks.useCasesAddToExistingCaseModal.moc
mockUseKibanaReturnValue.services.cases.helpers.canUseCases.mockReturnValue(allCasesPermissions());
const { ObservabilityAIAssistantActionMenuItem, ObservabilityAIAssistantContextualInsight } =
const { ObservabilityAIAssistantContextualInsight } =
observabilityAIAssistantPluginMock.createStartContract();
jest.mock('../../../utils/kibana_react', () => ({
@ -78,7 +78,6 @@ jest.spyOn(pluginContext, 'usePluginContext').mockImplementation(() => ({
plugins: {} as ObservabilityPublicPluginsStart,
observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(),
ObservabilityPageTemplate: KibanaPageTemplate,
ObservabilityAIAssistantActionMenuItem,
ObservabilityAIAssistantContextualInsight,
}));

View file

@ -13,11 +13,7 @@ import { useKibana } from '../../../../utils/kibana_react';
import HeaderMenuPortal from './header_menu_portal';
export function HeaderMenu(): React.ReactElement | null {
const {
http,
theme,
observabilityAIAssistant: { ObservabilityAIAssistantActionMenuItem },
} = useKibana().services;
const { http, theme } = useKibana().services;
const { appMountParameters } = usePluginContext();
@ -27,11 +23,6 @@ export function HeaderMenu(): React.ReactElement | null {
theme$={theme.theme$}
>
<EuiFlexGroup responsive={false} gutterSize="s">
{ObservabilityAIAssistantActionMenuItem && (
<EuiFlexItem>
<ObservabilityAIAssistantActionMenuItem />
</EuiFlexItem>
)}
<EuiFlexItem>
<EuiHeaderLinks>
<EuiHeaderLink

View file

@ -7,7 +7,6 @@
import React from 'react';
import * as fetcherHook from '@kbn/observability-shared-plugin/public/hooks/use_fetcher';
import { observabilityAIAssistantPluginMock } from '@kbn/observability-ai-assistant-plugin/public/mock';
import { render, data as dataMock } from '../../../../../utils/test_helper';
import { CoreStart } from '@kbn/core/public';
import { ConfigSchema, ObservabilityPublicPluginsStart } from '../../../../../plugin';
@ -19,6 +18,7 @@ import { HasDataContextValue } from '../../../../../context/has_data_context/has
import { AppMountParameters } from '@kbn/core/public';
import { createObservabilityRuleTypeRegistryMock } from '../../../../../rules/observability_rule_type_registry_mock';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
import { observabilityAIAssistantPluginMock } from '@kbn/observability-ai-assistant-plugin/public/mock';
jest.mock('react-router-dom', () => ({
useLocation: () => ({
@ -28,7 +28,7 @@ jest.mock('react-router-dom', () => ({
useHistory: jest.fn(),
}));
const { ObservabilityAIAssistantActionMenuItem, ObservabilityAIAssistantContextualInsight } =
const { ObservabilityAIAssistantContextualInsight } =
observabilityAIAssistantPluginMock.createStartContract();
describe('APMSection', () => {
@ -65,7 +65,6 @@ describe('APMSection', () => {
plugins: {} as ObservabilityPublicPluginsStart,
observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(),
ObservabilityPageTemplate: KibanaPageTemplate,
ObservabilityAIAssistantActionMenuItem,
ObservabilityAIAssistantContextualInsight,
}));
});

View file

@ -53,8 +53,8 @@ import { ExploratoryViewPublicStart } from '@kbn/exploratory-view-plugin/public'
import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public';
import { LicensingPluginStart } from '@kbn/licensing-plugin/public';
import {
ObservabilityAIAssistantPluginSetup,
ObservabilityAIAssistantPluginStart,
ObservabilityAIAssistantPublicSetup,
ObservabilityAIAssistantPublicStart,
} from '@kbn/observability-ai-assistant-plugin/public';
import { SecurityPluginStart } from '@kbn/security-plugin/public';
import { SpacesPluginStart } from '@kbn/spaces-plugin/public';
@ -119,7 +119,7 @@ export interface ObservabilityPublicPluginsSetup {
data: DataPublicPluginSetup;
fieldFormats: FieldFormatsSetup;
observabilityShared: ObservabilitySharedPluginSetup;
observabilityAIAssistant: ObservabilityAIAssistantPluginSetup;
observabilityAIAssistant: ObservabilityAIAssistantPublicSetup;
share: SharePluginSetup;
triggersActionsUi: TriggersAndActionsUIPublicPluginSetup;
home?: HomePublicPluginSetup;
@ -146,7 +146,7 @@ export interface ObservabilityPublicPluginsStart {
lens: LensPublicStart;
licensing: LicensingPluginStart;
observabilityShared: ObservabilitySharedPluginStart;
observabilityAIAssistant: ObservabilityAIAssistantPluginStart;
observabilityAIAssistant: ObservabilityAIAssistantPublicStart;
ruleTypeRegistry: RuleTypeRegistryContract;
security: SecurityPluginStart;
share: SharePluginStart;

View file

@ -6,7 +6,7 @@
*/
import { i18n } from '@kbn/i18n';
import { Message } from './types';
import type { Message } from './types';
export enum StreamingChatResponseEventType {
ChatCompletionChunk = 'chatCompletionChunk',

View file

@ -0,0 +1,13 @@
/*
* 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.
*/
export enum FunctionVisibility {
AssistantOnly = 'assistantOnly',
UserOnly = 'userOnly',
Internal = 'internal',
All = 'all',
}

View file

@ -0,0 +1,42 @@
/*
* 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 { JSONSchema } from 'json-schema-to-ts';
import type { Observable } from 'rxjs';
import { ChatCompletionChunkEvent, MessageAddEvent } from '../conversation_complete';
import { FunctionVisibility } from './function_visibility';
export { FunctionVisibility };
export type CompatibleJSONSchema = Exclude<JSONSchema, boolean>;
export interface ContextDefinition {
name: string;
description: string;
}
export type FunctionResponse =
| {
content?: any;
data?: any;
}
| Observable<ChatCompletionChunkEvent | MessageAddEvent>;
export interface FunctionDefinition<
TParameters extends CompatibleJSONSchema = CompatibleJSONSchema
> {
name: string;
description: string;
visibility?: FunctionVisibility;
descriptionForUser?: string;
parameters: TParameters;
contexts: string[];
}
export type RegisterContextDefinition = (options: ContextDefinition) => void;
export type ContextRegistry = Map<string, ContextDefinition>;
export type FunctionRegistry = Map<string, FunctionDefinition>;

View file

@ -4,8 +4,6 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FromSchema } from 'json-schema-to-ts';
import { FunctionVisibility } from '../types';
export enum VisualizeESQLUserIntention {
generateQueryOnly = 'generateQueryOnly',
@ -26,27 +24,3 @@ export enum VisualizeESQLUserIntention {
export const VISUALIZE_ESQL_USER_INTENTIONS: VisualizeESQLUserIntention[] = Object.values(
VisualizeESQLUserIntention
);
export const visualizeESQLFunction = {
name: 'visualize_query',
visibility: FunctionVisibility.UserOnly,
description: 'Use this function to visualize charts for ES|QL queries.',
descriptionForUser: 'Use this function to visualize charts for ES|QL queries.',
parameters: {
type: 'object',
additionalProperties: true,
properties: {
query: {
type: 'string',
},
intention: {
type: 'string',
enum: VISUALIZE_ESQL_USER_INTENTIONS,
},
},
required: ['query', 'intention'],
} as const,
contexts: ['core'],
};
export type VisualizeESQLFunctionArguments = FromSchema<typeof visualizeESQLFunction['parameters']>;

View file

@ -6,5 +6,34 @@
*/
export type { Message, Conversation, KnowledgeBaseEntry } from './types';
export { KnowledgeBaseEntryRole } from './types';
export { MessageRole } from './types';
export type { ConversationCreateRequest } from './types';
export { KnowledgeBaseEntryRole, MessageRole } from './types';
export type { FunctionDefinition } from './functions/types';
export { FunctionVisibility } from './functions/function_visibility';
export {
VISUALIZE_ESQL_USER_INTENTIONS,
VisualizeESQLUserIntention,
} from './functions/visualize_esql';
export type {
ChatCompletionChunkEvent,
ConversationCreateEvent,
ConversationUpdateEvent,
MessageAddEvent,
ChatCompletionErrorEvent,
BufferFlushEvent,
StreamingChatResponseEvent,
StreamingChatResponseEventWithoutError,
} from './conversation_complete';
export {
StreamingChatResponseEventType,
ChatCompletionErrorCode,
ChatCompletionError,
createTokenLimitReachedError,
createConversationNotFoundError,
createInternalServerError,
isTokenLimitReachedError,
isChatCompletionError,
} from './conversation_complete';
export { isSupportedConnectorType } from './connectors';

View file

@ -5,19 +5,6 @@
* 2.0.
*/
import type { JSONSchema } from 'json-schema-to-ts';
import type OpenAI from 'openai';
import type { Observable } from 'rxjs';
import { ChatCompletionChunkEvent, MessageAddEvent } from './conversation_complete';
export type CreateChatCompletionResponseChunk = Omit<OpenAI.ChatCompletionChunk, 'choices'> & {
choices: Array<
Omit<OpenAI.ChatCompletionChunk.Choice, 'message'> & {
delta: { content?: string; function_call?: { name?: string; arguments?: string } };
}
>;
};
export enum MessageRole {
System = 'system',
Assistant = 'assistant',
@ -90,43 +77,6 @@ export interface KnowledgeBaseEntry {
role: KnowledgeBaseEntryRole;
}
export type CompatibleJSONSchema = Exclude<JSONSchema, boolean>;
export interface ContextDefinition {
name: string;
description: string;
}
export type FunctionResponse =
| {
content?: any;
data?: any;
}
| Observable<ChatCompletionChunkEvent | MessageAddEvent>;
export enum FunctionVisibility {
AssistantOnly = 'assistantOnly',
UserOnly = 'userOnly',
Internal = 'internal',
All = 'all',
}
export interface FunctionDefinition<
TParameters extends CompatibleJSONSchema = CompatibleJSONSchema
> {
name: string;
description: string;
visibility?: FunctionVisibility;
descriptionForUser?: string;
parameters: TParameters;
contexts: string[];
}
export type RegisterContextDefinition = (options: ContextDefinition) => void;
export type ContextRegistry = Map<string, ContextDefinition>;
export type FunctionRegistry = Map<string, FunctionDefinition>;
export interface ObservabilityAIAssistantScreenContext {
screenDescription?: string;
data?: Array<{

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { FunctionDefinition } from '../types';
import type { FunctionDefinition } from '../functions/types';
export function filterFunctionDefinitions({
contexts,

View file

@ -4,6 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import OpenAI from 'openai';
import { filter, map, Observable, tap } from 'rxjs';
import { v4 } from 'uuid';
import {
@ -12,7 +13,14 @@ import {
createTokenLimitReachedError,
StreamingChatResponseEventType,
} from '../conversation_complete';
import type { CreateChatCompletionResponseChunk } from '../types';
export type CreateChatCompletionResponseChunk = Omit<OpenAI.ChatCompletionChunk, 'choices'> & {
choices: Array<
Omit<OpenAI.ChatCompletionChunk.Choice, 'message'> & {
delta: { content?: string; function_call?: { name?: string; arguments?: string } };
}
>;
};
export function processOpenAiStream() {
return (source: Observable<string>): Observable<ChatCompletionChunkEvent> => {

View file

@ -8,25 +8,15 @@
"browser": true,
"configPath": ["xpack", "observabilityAIAssistant"],
"requiredPlugins": [
"alerting",
"actions",
"data",
"dataViews",
"features",
"lens",
"licensing",
"observabilityShared",
"ruleRegistry",
"security",
"share",
"taskManager",
"triggersActionsUi",
"uiActions",
"dataViews",
"ml"
],
"requiredBundles": ["kibanaReact", "kibanaUtils"],
"optionalPlugins": ["cloud", "serverless"],
"extraPublicDirs": []
"extraPublicDirs": [],
"runtimePluginDependencies": [ "ml" ]
}
}

View file

@ -7,33 +7,17 @@
import type { AnalyticsServiceSetup, AnalyticsServiceStart } from '@kbn/core-analytics-browser';
import type { Message } from '../../common';
import {
eventType as chatFeedbackEventType,
chatFeedbackEventSchema,
ChatFeedback,
} from './schemas/chat_feedback';
import {
eventType as insightFeedbackEventType,
insightFeedbackEventSchema,
InsightFeedback,
} from './schemas/insight_feedback';
import {
eventType as userSentPromptEventType,
userSentPromptEventSchema,
} from './schemas/user_sent_prompt';
import { chatFeedbackEventSchema, ChatFeedback } from './schemas/chat_feedback';
import { insightFeedbackEventSchema, InsightFeedback } from './schemas/insight_feedback';
import { userSentPromptEventSchema } from './schemas/user_sent_prompt';
import { ObservabilityAIAssistantTelemetryEventType } from './telemetry_event_type';
const schemas = [chatFeedbackEventSchema, insightFeedbackEventSchema, userSentPromptEventSchema];
export const TELEMETRY = {
[chatFeedbackEventType]: chatFeedbackEventType,
[insightFeedbackEventType]: insightFeedbackEventType,
[userSentPromptEventType]: userSentPromptEventType,
} as const;
export type TelemetryEventTypeWithPayload =
| { type: typeof chatFeedbackEventType; payload: ChatFeedback }
| { type: typeof insightFeedbackEventType; payload: InsightFeedback }
| { type: typeof userSentPromptEventType; payload: Message };
| { type: ObservabilityAIAssistantTelemetryEventType.ChatFeedback; payload: ChatFeedback }
| { type: ObservabilityAIAssistantTelemetryEventType.InsightFeedback; payload: InsightFeedback }
| { type: ObservabilityAIAssistantTelemetryEventType.UserSentPromptInChat; payload: Message };
export const registerTelemetryEventTypes = (analytics: AnalyticsServiceSetup) => {
schemas.forEach((schema) => {

View file

@ -7,7 +7,8 @@
import type { EventTypeOpts } from '@kbn/analytics-client';
import type { Message, Conversation } from '../../../common';
import type { Feedback } from '../../components/feedback_buttons';
import type { Feedback } from '../../components/buttons/feedback_buttons';
import { ObservabilityAIAssistantTelemetryEventType } from '../telemetry_event_type';
import { messageSchema } from './common';
export interface ChatFeedback {
@ -18,10 +19,8 @@ export interface ChatFeedback {
conversation: Conversation;
}
export const eventType = 'observability_ai_assistant_chat_feedback';
export const chatFeedbackEventSchema: EventTypeOpts<ChatFeedback> = {
eventType,
eventType: ObservabilityAIAssistantTelemetryEventType.ChatFeedback,
schema: {
messageWithFeedback: {
properties: {

View file

@ -7,7 +7,8 @@
import type { EventTypeOpts } from '@kbn/analytics-client';
import type { Message } from '../../../common';
import type { Feedback } from '../../components/feedback_buttons';
import type { Feedback } from '../../components/buttons/feedback_buttons';
import { ObservabilityAIAssistantTelemetryEventType } from '../telemetry_event_type';
import { messageSchema } from './common';
export interface InsightFeedback {
@ -15,10 +16,8 @@ export interface InsightFeedback {
message: Message;
}
export const eventType = 'observability_ai_assistant_insight_feedback';
export const insightFeedbackEventSchema: EventTypeOpts<InsightFeedback> = {
eventType,
eventType: ObservabilityAIAssistantTelemetryEventType.InsightFeedback,
schema: {
feedback: {
type: 'text',

View file

@ -7,10 +7,10 @@
import type { EventTypeOpts } from '@kbn/analytics-client';
import type { Message } from '../../../common';
import { ObservabilityAIAssistantTelemetryEventType } from '../telemetry_event_type';
import { messageSchema } from './common';
export const eventType = 'observability_ai_assistant_user_sent_prompt_in_chat';
export const userSentPromptEventSchema: EventTypeOpts<Message> = {
eventType,
eventType: ObservabilityAIAssistantTelemetryEventType.UserSentPromptInChat,
schema: messageSchema,
};

View file

@ -0,0 +1,12 @@
/*
* 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.
*/
export enum ObservabilityAIAssistantTelemetryEventType {
ChatFeedback = 'observability_ai_assistant_chat_feedback',
InsightFeedback = 'observability_ai_assistant_insight_feedback',
UserSentPromptInChat = 'observability_ai_assistant_user_sent_prompt_in_chat',
}

View file

@ -1,117 +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, { useEffect, useMemo, useState } from 'react';
import datemath from '@elastic/datemath';
import {
EuiFlexGroup,
EuiFlexItem,
EuiHeaderLink,
EuiLoadingSpinner,
useCurrentEuiBreakpoint,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { css } from '@emotion/css';
import moment from 'moment';
import { ObservabilityAIAssistantChatServiceProvider } from '../../context/observability_ai_assistant_chat_service_provider';
import { useAbortableAsync } from '../../hooks/use_abortable_async';
import { useObservabilityAIAssistant } from '../../hooks/use_observability_ai_assistant';
import { AssistantAvatar } from '../assistant_avatar';
import { ChatFlyout } from '../chat/chat_flyout';
import { useKibana } from '../../hooks/use_kibana';
const buttonLabelClassName = css`
display: none;
`;
export function ObservabilityAIAssistantActionMenuItem() {
const service = useObservabilityAIAssistant();
const breakpoint = useCurrentEuiBreakpoint();
const { plugins } = useKibana().services;
const [isOpen, setIsOpen] = useState(false);
const chatService = useAbortableAsync(
({ signal }) => {
if (!isOpen) {
return Promise.resolve(undefined);
}
return service.start({ signal });
},
[service, isOpen]
);
const initialMessages = useMemo(() => [], []);
useEffect(() => {
const keyboardListener = (event: KeyboardEvent) => {
if (event.ctrlKey && event.code === 'Semicolon') {
setIsOpen(true);
}
};
window.addEventListener('keypress', keyboardListener);
return () => {
window.removeEventListener('keypress', keyboardListener);
};
}, []);
const { from, to } = plugins.start.data.query.timefilter.timefilter.getTime();
useEffect(() => {
const start = datemath.parse(from)?.format() ?? moment().subtract(1, 'day').toISOString();
const end = datemath.parse(to)?.format() ?? moment().toISOString();
return service.setScreenContext({
screenDescription: `The user is looking at ${window.location.href}. The current time range is ${start} - ${end}.`,
});
}, [service, from, to]);
if (!service.isEnabled()) {
return null;
}
return (
<>
<EuiHeaderLink
color="primary"
data-test-subj="observabilityAiAssistantNewChatHeaderLink"
onClick={() => {
setIsOpen(() => true);
}}
>
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
<EuiFlexItem grow={false}>
{!isOpen || chatService.value ? (
<AssistantAvatar size="xs" />
) : (
<EuiLoadingSpinner size="m" />
)}
</EuiFlexItem>
<EuiFlexItem grow={false} className={breakpoint === 'xs' ? buttonLabelClassName : ''}>
{i18n.translate('xpack.observabilityAiAssistant.actionMenuItemLabel', {
defaultMessage: 'AI Assistant',
})}
</EuiFlexItem>
</EuiFlexGroup>
</EuiHeaderLink>
{chatService.value ? (
<ObservabilityAIAssistantChatServiceProvider value={chatService.value}>
<ChatFlyout
initialTitle=""
initialMessages={initialMessages}
isOpen={isOpen}
startedFrom="appTopNavbar"
onClose={() => {
setIsOpen(false);
}}
/>
</ObservabilityAIAssistantChatServiceProvider>
) : null}
</>
);
}

View file

@ -9,6 +9,7 @@ import React, { ReactNode } from 'react';
export interface AssistantAvatarProps {
size?: keyof typeof sizeMap;
children?: ReactNode;
css?: React.SVGProps<SVGElement>['css'];
}
export const sizeMap = {
@ -19,14 +20,16 @@ export const sizeMap = {
xs: 16,
};
export function AssistantAvatar({ size = 's' }: AssistantAvatarProps) {
export function AssistantAvatar({ size = 's', css }: AssistantAvatarProps) {
const sizePx = sizeMap[size];
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={sizeMap[size]}
height={sizeMap[size]}
width={sizePx}
height={sizePx}
viewBox="0 0 64 64"
fill="none"
css={css}
>
<path fill="#F04E98" d="M36 28h24v36H36V28Z" />
<path fill="#00BFB3" d="M4 46c0-9.941 8.059-18 18-18h6v36h-6c-9.941 0-18-8.059-18-18Z" />

View file

@ -8,7 +8,7 @@
import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
import { useKibana } from '../hooks/use_kibana';
import { useKibana } from '../../hooks/use_kibana';
export type Feedback = 'positive' | 'negative';

View file

@ -8,9 +8,9 @@
import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiPanel, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/css';
import { Feedback, FeedbackButtons } from '../feedback_buttons';
import { RegenerateResponseButton } from '../buttons/regenerate_response_button';
import { Feedback, FeedbackButtons } from '../buttons/feedback_buttons';
import { StopGeneratingButton } from '../buttons/stop_generating_button';
import { RegenerateResponseButton } from '../buttons/regenerate_response_button';
const containerClassName = css`
padding-top: 4px;

View file

@ -18,8 +18,7 @@ import { i18n } from '@kbn/i18n';
import { cloneDeep, last } from 'lodash';
import React, { useEffect, useRef, useState } from 'react';
import { MessageRole, type Message } from '../../../common/types';
import { sendEvent, TELEMETRY } from '../../analytics';
import { ObservabilityAIAssistantChatServiceProvider } from '../../context/observability_ai_assistant_chat_service_provider';
import { ObservabilityAIAssistantChatServiceContext } from '../../context/observability_ai_assistant_chat_service_context';
import { useAbortableAsync } from '../../hooks/use_abortable_async';
import { ChatState, useChat } from '../../hooks/use_chat';
import { useGenAIConnectors } from '../../hooks/use_genai_connectors';
@ -30,13 +29,13 @@ import { getConnectorsManagementHref } from '../../utils/get_connectors_manageme
import { RegenerateResponseButton } from '../buttons/regenerate_response_button';
import { StartChatButton } from '../buttons/start_chat_button';
import { StopGeneratingButton } from '../buttons/stop_generating_button';
import { ChatFlyout } from '../chat/chat_flyout';
import { FeedbackButtons } from '../feedback_buttons';
import { FeedbackButtons } from '../buttons/feedback_buttons';
import { MessagePanel } from '../message_panel/message_panel';
import { MessageText } from '../message_panel/message_text';
import { MissingCredentialsCallout } from '../missing_credentials_callout';
import { InsightBase } from './insight_base';
import { ActionsMenu } from './actions_menu';
import { ObservabilityAIAssistantTelemetryEventType } from '../../analytics/telemetry_event_type';
function getLastMessageOfType(messages: Message[], role: MessageRole) {
return last(messages.filter((msg) => msg.message.role === role));
@ -64,14 +63,15 @@ function ChatContent({
persist: false,
});
const lastAssistantResponse = getLastMessageOfType(messages, MessageRole.Assistant);
const lastAssistantResponse = getLastMessageOfType(
messages.slice(initialMessagesRef.current.length + 1),
MessageRole.Assistant
);
useEffect(() => {
next(initialMessagesRef.current);
}, [next]);
const [isOpen, setIsOpen] = useState(false);
return (
<>
<MessagePanel
@ -95,8 +95,8 @@ function ChatContent({
<FeedbackButtons
onClickFeedback={(feedback) => {
if (lastAssistantResponse) {
sendEvent(chatService.analytics, {
type: TELEMETRY.observability_ai_assistant_insight_feedback,
chatService.sendAnalyticsEvent({
type: ObservabilityAIAssistantTelemetryEventType.InsightFeedback,
payload: {
feedback,
message: lastAssistantResponse,
@ -115,7 +115,10 @@ function ChatContent({
<EuiFlexItem grow={false}>
<StartChatButton
onClick={() => {
setIsOpen(() => true);
service.conversations.openNewConversation({
messages,
title: defaultTitle,
});
}}
/>
</EuiFlexItem>
@ -123,15 +126,6 @@ function ChatContent({
)
}
/>
<ChatFlyout
isOpen={isOpen}
onClose={() => {
setIsOpen(false);
}}
initialMessages={messages}
initialTitle={defaultTitle}
startedFrom="contextualInsight"
/>
</>
);
}
@ -328,9 +322,9 @@ export function Insight({ messages, title, dataTestSubj }: InsightProps) {
isOpen={isInsightOpen}
>
{chatService.value ? (
<ObservabilityAIAssistantChatServiceProvider value={chatService.value}>
<ObservabilityAIAssistantChatServiceContext.Provider value={chatService.value}>
{children}
</ObservabilityAIAssistantChatServiceProvider>
</ObservabilityAIAssistantChatServiceContext.Provider>
) : null}
</InsightBase>
);

View file

@ -14,7 +14,7 @@ import { InsightBase as Component, InsightBaseProps } from './insight_base';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator';
import { MessagePanel } from '../message_panel/message_panel';
import { MessageText } from '../message_panel/message_text';
import { FeedbackButtons } from '../feedback_buttons';
import { FeedbackButtons } from '../buttons/feedback_buttons';
import { RegenerateResponseButton } from '../buttons/regenerate_response_button';
import { StartChatButton } from '../buttons/start_chat_button';
import { ActionsMenu } from './actions_menu';

View file

@ -8,7 +8,7 @@ import { EuiPanel } from '@elastic/eui';
import { ComponentMeta, ComponentStoryObj } from '@storybook/react';
import dedent from 'dedent';
import React from 'react';
import { FeedbackButtons } from '../feedback_buttons';
import { FeedbackButtons } from '../buttons/feedback_buttons';
import { MessagePanel as Component } from './message_panel';
import { MessageText } from './message_text';

View file

@ -11,6 +11,3 @@ import type { ObservabilityAIAssistantChatService } from '../types';
export const ObservabilityAIAssistantChatServiceContext = createContext<
ObservabilityAIAssistantChatService | undefined
>(undefined);
export const ObservabilityAIAssistantChatServiceProvider =
ObservabilityAIAssistantChatServiceContext.Provider;

View file

@ -11,6 +11,3 @@ import type { ChatFlyoutSecondSlotHandler } from '../components/chat/types';
export const ObservabilityAIAssistantMultipaneFlyoutContext = createContext<
ChatFlyoutSecondSlotHandler | undefined
>(undefined);
export const ObservabilityAIAssistantMultipaneFlyoutProvider =
ObservabilityAIAssistantMultipaneFlyoutContext.Provider;

View file

@ -6,15 +6,17 @@
*/
import type { DeeplyMockedKeys } from '@kbn/utility-types-jest';
import { act, renderHook, type RenderHookResult } from '@testing-library/react-hooks';
import { Observable, Subject } from 'rxjs';
import { MessageRole } from '../../common';
import { Subject } from 'rxjs';
import {
MessageRole,
type ObservabilityAIAssistantChatService,
type ObservabilityAIAssistantService,
} from '..';
import {
createInternalServerError,
StreamingChatResponseEventType,
StreamingChatResponseEventWithoutError,
} from '../../common/conversation_complete';
import { mockService } from '../mock';
import type { ObservabilityAIAssistantChatService } from '../types';
type StreamingChatResponseEventWithoutError,
} from '../../common';
import { ChatState, useChat, type UseChatProps, type UseChatResult } from './use_chat';
import * as useKibanaModule from './use_kibana';
@ -23,11 +25,7 @@ type MockedChatService = DeeplyMockedKeys<ObservabilityAIAssistantChatService>;
const mockChatService: MockedChatService = {
chat: jest.fn(),
complete: jest.fn(),
analytics: {
optIn: jest.fn(),
reportEvent: jest.fn(),
telemetryCounter$: new Observable() as any,
},
sendAnalyticsEvent: jest.fn(),
getContexts: jest.fn().mockReturnValue([{ name: 'core', description: '' }]),
getFunctions: jest.fn().mockReturnValue([]),
hasFunction: jest.fn().mockReturnValue(false),
@ -70,7 +68,9 @@ describe('useChat', () => {
},
],
persist: false,
service: mockService,
service: {
getScreenContexts: () => [],
} as unknown as ObservabilityAIAssistantService,
} as UseChatProps,
});
});
@ -97,7 +97,9 @@ describe('useChat', () => {
chatService: mockChatService,
initialMessages: [],
persist: false,
service: mockService,
service: {
getScreenContexts: () => [],
} as unknown as ObservabilityAIAssistantService,
} as UseChatProps,
});

View file

@ -9,18 +9,20 @@ import { i18n } from '@kbn/i18n';
import { merge } from 'lodash';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { AbortError } from '@kbn/kibana-utils-plugin/common';
import { MessageRole, type Message } from '../../common';
import type { NotificationsStart } from '@kbn/core/public';
import {
MessageRole,
type Message,
ConversationCreateEvent,
ConversationUpdateEvent,
isTokenLimitReachedError,
StreamingChatResponseEventType,
} from '../../common/conversation_complete';
import { getAssistantSetupMessage } from '../service/get_assistant_setup_message';
import type {
ObservabilityAIAssistantChatService,
ObservabilityAIAssistantService,
} from '../types';
} from '../../common';
import {
getAssistantSystemMessage,
type ObservabilityAIAssistantChatService,
type ObservabilityAIAssistantService,
} from '..';
import { useKibana } from './use_kibana';
import { useOnce } from './use_once';
import { useUserPreferredLanguage } from './use_user_preferred_language';
@ -47,7 +49,8 @@ export interface UseChatResult {
stop: () => void;
}
export interface UseChatProps {
interface UseChatPropsWithoutContext {
notifications: NotificationsStart;
initialMessages: Message[];
initialConversationId?: string;
service: ObservabilityAIAssistantService;
@ -58,20 +61,23 @@ export interface UseChatProps {
onChatComplete?: (messages: Message[]) => void;
}
export function useChat({
export type UseChatProps = Omit<UseChatPropsWithoutContext, 'notifications'>;
function useChatWithoutContext({
initialMessages,
initialConversationId,
notifications,
service,
chatService,
connectorId,
onConversationUpdate,
onChatComplete,
persist,
}: UseChatProps): UseChatResult {
}: UseChatPropsWithoutContext): UseChatResult {
const [chatState, setChatState] = useState(ChatState.Ready);
const systemMessage = useMemo(() => {
return getAssistantSetupMessage({ contexts: chatService.getContexts() });
return getAssistantSystemMessage({ contexts: chatService.getContexts() });
}, [chatService]);
useOnce(initialMessages);
@ -86,10 +92,6 @@ export function useChat({
const abortControllerRef = useRef(new AbortController());
const {
services: { notifications },
} = useKibana();
const { getPreferredLanguage } = useUserPreferredLanguage();
const onChatCompleteRef = useRef(onChatComplete);
@ -294,3 +296,23 @@ export function useChat({
},
};
}
export function useChat(props: UseChatProps) {
const {
services: { notifications },
} = useKibana();
return useChatWithoutContext({
...props,
notifications,
});
}
export function createUseChat({ notifications }: { notifications: NotificationsStart }) {
return (parameters: Omit<UseChatProps, 'notifications'>) => {
return useChatWithoutContext({
...parameters,
notifications,
});
};
}

View file

@ -5,16 +5,22 @@
* 2.0.
*/
import { useContext } from 'react';
import { ObservabilityAIAssistantChatServiceContext } from '../context/observability_ai_assistant_chat_service_provider';
import { ObservabilityAIAssistantChatServiceContext } from '../context/observability_ai_assistant_chat_service_context';
import type { ObservabilityAIAssistantChatService } from '../types';
export function useObservabilityAIAssistantChatService() {
const services = useContext(ObservabilityAIAssistantChatServiceContext);
if (!services) {
const service = useContext(ObservabilityAIAssistantChatServiceContext);
if (!service) {
throw new Error(
'ObservabilityAIAssistantChatServiceContext not set. Did you wrap your component in `<ObservabilityAIAssistantChatServiceProvider/>`?'
'ObservabilityAIAssistantChatServiceContext not set. Did you wrap your component in `<ObservabilityAIAssistantChatServiceContext.Provider/>`?'
);
}
return services;
return useObservabilityAIAssistantChatServiceWithService(service);
}
function useObservabilityAIAssistantChatServiceWithService(
service: ObservabilityAIAssistantChatService
) {
return service;
}

View file

@ -7,20 +7,60 @@
import type { PluginInitializer, PluginInitializerContext } from '@kbn/core/public';
import { ObservabilityAIAssistantPlugin } from './plugin';
import type {
ObservabilityAIAssistantPluginSetup,
ObservabilityAIAssistantPluginStart,
ObservabilityAIAssistantPublicSetup,
ObservabilityAIAssistantPublicStart,
ObservabilityAIAssistantPluginSetupDependencies,
ObservabilityAIAssistantPluginStartDependencies,
ConfigSchema,
ObservabilityAIAssistantService,
ObservabilityAIAssistantChatService,
RegisterRenderFunctionDefinition,
RenderFunction,
} from './types';
export type {
ObservabilityAIAssistantPluginSetup,
ObservabilityAIAssistantPluginStart,
ObservabilityAIAssistantPublicSetup,
ObservabilityAIAssistantPublicStart,
ObservabilityAIAssistantService,
ObservabilityAIAssistantChatService,
RegisterRenderFunctionDefinition,
RenderFunction,
};
export { AssistantAvatar } from './components/assistant_avatar';
export { ConnectorSelectorBase } from './components/connector_selector/connector_selector_base';
export { useAbortableAsync, type AbortableAsyncState } from './hooks/use_abortable_async';
export { createStorybookChatService, createStorybookService } from './storybook_mock';
export { ChatState } from './hooks/use_chat';
export { FeedbackButtons, type Feedback } from './components/buttons/feedback_buttons';
export { ChatItemControls } from './components/chat/chat_item_controls';
export { FailedToLoadResponse } from './components/message_panel/failed_to_load_response';
export { MessageText } from './components/message_panel/message_text';
export {
type ChatActionClickHandler,
ChatActionClickType,
type ChatActionClickPayload,
} from './components/chat/types';
export {
VisualizeESQLUserIntention,
VISUALIZE_ESQL_USER_INTENTIONS,
} from '../common/functions/visualize_esql';
export { getAssistantSystemMessage } from './service/get_assistant_system_message';
export { isSupportedConnectorType } from '../common';
export { FunctionVisibility } from '../common';
export type { TelemetryEventTypeWithPayload } from './analytics';
export { ObservabilityAIAssistantTelemetryEventType } from './analytics/telemetry_event_type';
export type { Conversation, Message, KnowledgeBaseEntry } from '../common';
export { MessageRole, KnowledgeBaseEntryRole } from '../common';
@ -30,9 +70,11 @@ export type {
APIReturnType,
} from './api';
export type { UseChatResult } from './hooks/use_chat';
export const plugin: PluginInitializer<
ObservabilityAIAssistantPluginSetup,
ObservabilityAIAssistantPluginStart,
ObservabilityAIAssistantPublicSetup,
ObservabilityAIAssistantPublicStart,
ObservabilityAIAssistantPluginSetupDependencies,
ObservabilityAIAssistantPluginStartDependencies
> = (pluginInitializerContext: PluginInitializerContext<ConfigSchema>) =>

View file

@ -5,8 +5,6 @@
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import type { AuthenticatedUser } from '@kbn/security-plugin-types-common';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import { noop } from 'lodash';
import React from 'react';
import { Observable } from 'rxjs';
@ -14,18 +12,14 @@ import type { StreamingChatResponseEventWithoutError } from '../common/conversat
import type { ObservabilityAIAssistantAPIClient } from './api';
import type {
ObservabilityAIAssistantChatService,
ObservabilityAIAssistantPluginSetup,
ObservabilityAIAssistantPluginStart,
ObservabilityAIAssistantPublicSetup,
ObservabilityAIAssistantPublicStart,
ObservabilityAIAssistantService,
} from './types';
import { buildFunctionElasticsearch, buildFunctionServiceSummary } from './utils/builders';
export const mockChatService: ObservabilityAIAssistantChatService = {
analytics: {
optIn: () => {},
reportEvent: () => {},
telemetryCounter$: new Observable(),
},
sendAnalyticsEvent: noop,
chat: (options) => new Observable<StreamingChatResponseEventWithoutError>(),
complete: (options) => new Observable<StreamingChatResponseEventWithoutError>(),
getContexts: () => [],
@ -48,43 +42,27 @@ export const mockService: ObservabilityAIAssistantService = {
return mockChatService;
},
callApi: {} as ObservabilityAIAssistantAPIClient,
getCurrentUser: async (): Promise<AuthenticatedUser> => ({
username: 'user',
roles: [],
enabled: true,
authentication_realm: { name: 'foo', type: '' },
lookup_realm: { name: 'foo', type: '' },
authentication_provider: { name: '', type: '' },
authentication_type: '',
elastic_cloud_user: false,
}),
getLicense: () => new Observable(),
getLicenseManagementLocator: () =>
({
url: {},
navigate: () => {},
} as unknown as SharePluginStart),
register: () => {},
setScreenContext: () => noop,
getScreenContexts: () => [],
conversations: {
openNewConversation: noop,
predefinedConversation$: new Observable(),
},
};
function createSetupContract(): ObservabilityAIAssistantPluginSetup {
function createSetupContract(): ObservabilityAIAssistantPublicSetup {
return {};
}
function createStartContract(): ObservabilityAIAssistantPluginStart {
function createStartContract(): ObservabilityAIAssistantPublicStart {
return {
service: mockService,
ObservabilityAIAssistantActionMenuItem: (() => (
// eslint-disable-next-line @kbn/i18n/strings_should_be_translated_with_i18n
<div>Im a button</div>
)) as unknown as ObservabilityAIAssistantPluginStart['ObservabilityAIAssistantActionMenuItem'],
ObservabilityAIAssistantContextualInsight: (
// eslint-disable-next-line @kbn/i18n/strings_should_be_translated_with_i18n
<div>I give insight</div>
) as unknown as ObservabilityAIAssistantPluginStart['ObservabilityAIAssistantContextualInsight'],
ObservabilityAIAssistantContextualInsight: (() => <></>) as any,
ObservabilityAIAssistantChatServiceContext: React.createContext<any>(undefined),
ObservabilityAIAssistantMultipaneFlyoutContext: React.createContext<any>(undefined),
useChat: () => ({} as any),
useObservabilityAIAssistantChatService: () => mockChatService,
useGenAIConnectors: () => ({
loading: false,
selectConnector: () => {},
@ -96,6 +74,7 @@ function createStartContract(): ObservabilityAIAssistantPluginStart {
setSelectedLanguage: () => {},
getPreferredLanguage: () => 'English',
}),
getContextualInsightMessages: () => [],
};
}

View file

@ -4,39 +4,36 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { ComponentType, lazy, Ref } from 'react';
import ReactDOM from 'react-dom';
import {
DEFAULT_APP_CATEGORIES,
type AppMountParameters,
type CoreSetup,
type CoreStart,
type Plugin,
type PluginInitializerContext,
} from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import type { Logger } from '@kbn/logging';
import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import type { Logger } from '@kbn/logging';
import { withSuspense } from '@kbn/shared-ux-utility';
import { createService } from './service/create_service';
import React, { type ComponentType, lazy, type Ref } from 'react';
import { registerTelemetryEventTypes } from './analytics';
import { ObservabilityAIAssistantChatServiceContext } from './context/observability_ai_assistant_chat_service_context';
import { ObservabilityAIAssistantMultipaneFlyoutContext } from './context/observability_ai_assistant_multipane_flyout_context';
import { ObservabilityAIAssistantProvider } from './context/observability_ai_assistant_provider';
import { createUseChat } from './hooks/use_chat';
import { useGenAIConnectorsWithoutContext } from './hooks/use_genai_connectors';
import { useObservabilityAIAssistantChatService } from './hooks/use_observability_ai_assistant_chat_service';
import { createService } from './service/create_service';
import type {
ConfigSchema,
ObservabilityAIAssistantPluginSetup,
ObservabilityAIAssistantPluginSetupDependencies,
ObservabilityAIAssistantPluginStart,
ObservabilityAIAssistantPluginStartDependencies,
ObservabilityAIAssistantPublicSetup,
ObservabilityAIAssistantPublicStart,
ObservabilityAIAssistantService,
} from './types';
import { registerTelemetryEventTypes } from './analytics';
import { ObservabilityAIAssistantProvider } from './context/observability_ai_assistant_provider';
import { useUserPreferredLanguage } from './hooks/use_user_preferred_language';
import { getContextualInsightMessages } from './utils/get_contextual_insight_messages';
export class ObservabilityAIAssistantPlugin
implements
Plugin<
ObservabilityAIAssistantPluginSetup,
ObservabilityAIAssistantPluginStart,
ObservabilityAIAssistantPublicSetup,
ObservabilityAIAssistantPublicStart,
ObservabilityAIAssistantPluginSetupDependencies,
ObservabilityAIAssistantPluginStartDependencies
>
@ -50,48 +47,7 @@ export class ObservabilityAIAssistantPlugin
setup(
coreSetup: CoreSetup,
pluginsSetup: ObservabilityAIAssistantPluginSetupDependencies
): ObservabilityAIAssistantPluginSetup {
coreSetup.application.register({
id: 'observabilityAIAssistant',
title: i18n.translate('xpack.observabilityAiAssistant.appTitle', {
defaultMessage: 'Observability AI Assistant',
}),
euiIconType: 'logoObservability',
appRoute: '/app/observabilityAIAssistant',
category: DEFAULT_APP_CATEGORIES.observability,
visibleIn: [],
deepLinks: [
{
id: 'conversations',
title: i18n.translate('xpack.observabilityAiAssistant.conversationsDeepLinkTitle', {
defaultMessage: 'Conversations',
}),
path: '/conversations/new',
},
],
mount: async (appMountParameters: AppMountParameters<unknown>) => {
// Load application bundle and Get start services
const [{ Application }, [coreStart, pluginsStart]] = await Promise.all([
import('./application'),
coreSetup.getStartServices(),
]);
ReactDOM.render(
<Application
{...appMountParameters}
service={this.service!}
coreStart={coreStart}
pluginsStart={pluginsStart as ObservabilityAIAssistantPluginStartDependencies}
/>,
appMountParameters.element
);
return () => {
ReactDOM.unmountComponentAtNode(appMountParameters.element);
};
},
});
): ObservabilityAIAssistantPublicSetup {
registerTelemetryEventTypes(coreSetup.analytics);
return {};
@ -100,26 +56,13 @@ export class ObservabilityAIAssistantPlugin
start(
coreStart: CoreStart,
pluginsStart: ObservabilityAIAssistantPluginStartDependencies
): ObservabilityAIAssistantPluginStart {
): ObservabilityAIAssistantPublicStart {
const service = (this.service = createService({
analytics: coreStart.analytics,
coreStart,
enabled: coreStart.application.capabilities.observabilityAIAssistant.show === true,
licenseStart: pluginsStart.licensing,
securityStart: pluginsStart.security,
shareStart: pluginsStart.share,
}));
service.register(async ({ registerRenderFunction }) => {
const mod = await import('./functions');
return mod.registerFunctions({
service,
pluginsStart,
registerRenderFunction,
});
});
const withProviders = <P extends {}, R = {}>(
Component: ComponentType<P>,
services: Omit<CoreStart, 'plugins'> & {
@ -146,7 +89,13 @@ export class ObservabilityAIAssistantPlugin
return {
service,
useGenAIConnectors: () => useGenAIConnectorsWithoutContext(service),
useChat: createUseChat({
notifications: coreStart.notifications,
}),
useUserPreferredLanguage,
ObservabilityAIAssistantMultipaneFlyoutContext,
ObservabilityAIAssistantChatServiceContext,
useObservabilityAIAssistantChatService,
ObservabilityAIAssistantContextualInsight: isEnabled
? withSuspense(
withProviders(
@ -157,18 +106,7 @@ export class ObservabilityAIAssistantPlugin
)
)
: null,
ObservabilityAIAssistantActionMenuItem: isEnabled
? withSuspense(
withProviders(
lazy(() =>
import('./components/action_menu_item/action_menu_item').then((m) => ({
default: m.ObservabilityAIAssistantActionMenuItem,
}))
),
services
)
)
: null,
getContextualInsightMessages,
};
}
}

View file

@ -17,13 +17,14 @@ import {
type StreamingChatResponseEvent,
} from '../../common/conversation_complete';
import {
FunctionRegistry,
FunctionResponse,
FunctionVisibility,
type FunctionRegistry,
type FunctionResponse,
type Message,
} from '../../common/types';
} from '../../common/functions/types';
import { type Message } from '../../common/types';
import { filterFunctionDefinitions } from '../../common/utils/filter_function_definitions';
import { throwSerializedChatCompletionErrors } from '../../common/utils/throw_serialized_chat_completion_errors';
import { sendEvent } from '../analytics';
import type { ObservabilityAIAssistantAPIClient } from '../api';
import type {
ChatRegistrationRenderFunction,
@ -117,7 +118,9 @@ export async function createChatService({
};
return {
analytics,
sendAnalyticsEvent: (event) => {
sendEvent(analytics, event);
},
renderFunction: (name, args, response, onActionClick) => {
const fn = renderFunctionRegistry.get(name);

View file

@ -5,9 +5,7 @@
* 2.0.
*/
import type { TelemetryCounter } from '@kbn/analytics-client';
import type { DeeplyMockedKeys } from '@kbn/utility-types-jest';
import { Observable } from 'rxjs';
import type { ObservabilityAIAssistantChatService } from '../types';
type MockedChatService = DeeplyMockedKeys<ObservabilityAIAssistantChatService>;
@ -16,11 +14,7 @@ export const createMockChatService = (): MockedChatService => {
const mockChatService: MockedChatService = {
chat: jest.fn(),
complete: jest.fn(),
analytics: {
optIn: jest.fn(),
reportEvent: jest.fn(),
telemetryCounter$: new Observable<TelemetryCounter>() as any,
},
sendAnalyticsEvent: jest.fn(),
getContexts: jest.fn().mockReturnValue([{ name: 'core', description: '' }]),
getFunctions: jest.fn().mockReturnValue([]),
hasFunction: jest.fn().mockReturnValue(false),

View file

@ -6,11 +6,9 @@
*/
import type { AnalyticsServiceStart, CoreStart } from '@kbn/core/public';
import type { LicensingPluginStart } from '@kbn/licensing-plugin/public';
import type { SecurityPluginStart } from '@kbn/security-plugin/public';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import { remove } from 'lodash';
import { ObservabilityAIAssistantScreenContext } from '../../common/types';
import { without } from 'lodash';
import { BehaviorSubject, Subject } from 'rxjs';
import type { Message, ObservabilityAIAssistantScreenContext } from '../../common/types';
import { createCallObservabilityAIAssistantAPI } from '../api';
import type { ChatRegistrationRenderFunction, ObservabilityAIAssistantService } from '../types';
@ -18,22 +16,17 @@ export function createService({
analytics,
coreStart,
enabled,
licenseStart,
securityStart,
shareStart,
}: {
analytics: AnalyticsServiceStart;
coreStart: CoreStart;
enabled: boolean;
licenseStart: LicensingPluginStart;
securityStart: SecurityPluginStart;
shareStart: SharePluginStart;
}): ObservabilityAIAssistantService {
const client = createCallObservabilityAIAssistantAPI(coreStart);
const registrations: ChatRegistrationRenderFunction[] = [];
const screenContexts: ObservabilityAIAssistantScreenContext[] = [];
const screenContexts$ = new BehaviorSubject<ObservabilityAIAssistantScreenContext[]>([]);
const predefinedConversation$ = new Subject<{ messages: Message[]; title?: string }>();
return {
isEnabled: () => {
@ -47,17 +40,20 @@ export function createService({
return await mod.createChatService({ analytics, client, signal, registrations });
},
callApi: client,
getCurrentUser: () => securityStart.authc.getCurrentUser(),
getLicense: () => licenseStart.license$,
getLicenseManagementLocator: () => shareStart,
getScreenContexts() {
return screenContexts$.value;
},
setScreenContext: (context: ObservabilityAIAssistantScreenContext) => {
screenContexts.push(context);
screenContexts$.next(screenContexts$.value.concat(context));
return () => {
remove(screenContexts, context);
screenContexts$.next(without(screenContexts$.value, context));
};
},
getScreenContexts: () => {
return screenContexts;
conversations: {
openNewConversation: ({ messages, title }: { messages: Message[]; title?: string }) => {
predefinedConversation$.next({ messages, title });
},
predefinedConversation$: predefinedConversation$.asObservable(),
},
};
}

View file

@ -7,9 +7,14 @@
import { without } from 'lodash';
import { MessageRole } from '../../common';
import type { ContextDefinition, Message } from '../../common/types';
import { ContextDefinition } from '../../common/functions/types';
import type { Message } from '../../common/types';
export function getAssistantSetupMessage({ contexts }: { contexts: ContextDefinition[] }): Message {
export function getAssistantSystemMessage({
contexts,
}: {
contexts: ContextDefinition[];
}): Message {
const coreContext = contexts.find((context) => context.name === 'core')!;
const otherContexts = without(contexts.concat(), coreContext);

View file

@ -0,0 +1,47 @@
/*
* 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';
import { noop } from 'lodash';
import React from 'react';
import { Observable } from 'rxjs';
import type { StreamingChatResponseEventWithoutError } from '../common/conversation_complete';
import type { ObservabilityAIAssistantAPIClient } from './api';
import type { ObservabilityAIAssistantChatService, ObservabilityAIAssistantService } from './types';
import { buildFunctionElasticsearch, buildFunctionServiceSummary } from './utils/builders';
export const createStorybookChatService = (): ObservabilityAIAssistantChatService => ({
sendAnalyticsEvent: () => {},
chat: (options) => new Observable<StreamingChatResponseEventWithoutError>(),
complete: (options) => new Observable<StreamingChatResponseEventWithoutError>(),
getContexts: () => [],
getFunctions: () => [buildFunctionElasticsearch(), buildFunctionServiceSummary()],
renderFunction: (name) => (
<div>
{i18n.translate('xpack.observabilityAiAssistant.chatService.div.helloLabel', {
defaultMessage: 'Hello',
})}
{name}
</div>
),
hasFunction: () => true,
hasRenderFunction: () => true,
});
export const createStorybookService = (): ObservabilityAIAssistantService => ({
isEnabled: () => true,
start: async () => {
return createStorybookChatService();
},
callApi: {} as ObservabilityAIAssistantAPIClient,
register: () => {},
setScreenContext: () => noop,
getScreenContexts: () => [],
conversations: {
openNewConversation: noop,
predefinedConversation$: new Observable(),
},
});

View file

@ -5,56 +5,38 @@
* 2.0.
*/
import type { ForwardRefExoticComponent, RefAttributes } from 'react';
import type { LicensingPluginStart } from '@kbn/licensing-plugin/public';
import type { MlPluginSetup, MlPluginStart } from '@kbn/ml-plugin/public';
import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public';
import type { Observable } from 'rxjs';
import type { AnalyticsServiceStart } from '@kbn/core/public';
import type { FeaturesPluginStart, FeaturesPluginSetup } from '@kbn/features-plugin/public';
import type { UiActionsStart } from '@kbn/ui-actions-plugin/public';
import type {
DataViewsPublicPluginSetup,
DataViewsPublicPluginStart,
} from '@kbn/data-views-plugin/public';
import type { LensPublicSetup, LensPublicStart } from '@kbn/lens-plugin/public';
import type { ILicense, LicensingPluginStart } from '@kbn/licensing-plugin/public';
import { MlPluginSetup, MlPluginStart } from '@kbn/ml-plugin/public';
import type {
ObservabilitySharedPluginSetup,
ObservabilitySharedPluginStart,
} from '@kbn/observability-shared-plugin/public';
import type {
AuthenticatedUser,
SecurityPluginSetup,
SecurityPluginStart,
} from '@kbn/security-plugin/public';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import { WithSuspenseExtendedDeps } from '@kbn/shared-ux-utility';
import type {
TriggersAndActionsUIPublicPluginSetup,
TriggersAndActionsUIPublicPluginStart,
} from '@kbn/triggers-actions-ui-plugin/public';
import { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { StreamingChatResponseEventWithoutError } from '../common/conversation_complete';
import type {
ContextDefinition,
FunctionDefinition,
FunctionResponse,
} from '../common/functions/types';
import type {
Message,
ObservabilityAIAssistantScreenContext,
PendingMessage,
} from '../common/types';
import type { ChatActionClickHandler } from './components/chat/types';
import type { TelemetryEventTypeWithPayload } from './analytics';
import type { ObservabilityAIAssistantAPIClient } from './api';
import type { ChatActionClickHandler } from './components/chat/types';
import type { InsightProps } from './components/insight/insight';
import { ObservabilityAIAssistantChatServiceContext } from './context/observability_ai_assistant_chat_service_context';
import { ObservabilityAIAssistantMultipaneFlyoutContext } from './context/observability_ai_assistant_multipane_flyout_context';
import { useChat } from './hooks/use_chat';
import type { UseGenAIConnectorsResult } from './hooks/use_genai_connectors';
import { useObservabilityAIAssistantChatService } from './hooks/use_observability_ai_assistant_chat_service';
import type { UseUserPreferredLanguageResult } from './hooks/use_user_preferred_language';
/* eslint-disable @typescript-eslint/no-empty-interface*/
export type { CreateChatCompletionResponseChunk } from '../common/types';
export type { PendingMessage };
export interface ObservabilityAIAssistantChatService {
analytics: AnalyticsServiceStart;
sendAnalyticsEvent: (event: TelemetryEventTypeWithPayload) => void;
chat: (
name: string,
options: {
@ -85,16 +67,19 @@ export interface ObservabilityAIAssistantChatService {
) => React.ReactNode;
}
export interface ObservabilityAIAssistantConversationService {
openNewConversation: ({}: { messages: Message[]; title?: string }) => void;
predefinedConversation$: Observable<{ messages: Message[]; title?: string }>;
}
export interface ObservabilityAIAssistantService {
isEnabled: () => boolean;
callApi: ObservabilityAIAssistantAPIClient;
getCurrentUser: () => Promise<AuthenticatedUser>;
getLicense: () => Observable<ILicense>;
getLicenseManagementLocator: () => SharePluginStart;
isEnabled: () => boolean;
start: ({}: { signal: AbortSignal }) => Promise<ObservabilityAIAssistantChatService>;
register: (fn: ChatRegistrationRenderFunction) => void;
setScreenContext: (screenContext: ObservabilityAIAssistantScreenContext) => () => void;
getScreenContexts: () => ObservabilityAIAssistantScreenContext[];
conversations: ObservabilityAIAssistantConversationService;
}
export type RenderFunction<TArguments, TResponse extends FunctionResponse> = (options: {
@ -115,39 +100,27 @@ export type ChatRegistrationRenderFunction = ({}: {
export interface ConfigSchema {}
export interface ObservabilityAIAssistantPluginSetupDependencies {
data: DataPublicPluginSetup;
dataViews: DataViewsPublicPluginSetup;
features: FeaturesPluginSetup;
lens: LensPublicSetup;
observabilityShared: ObservabilitySharedPluginSetup;
licensing: {};
security: SecurityPluginSetup;
triggersActionsUi: TriggersAndActionsUIPublicPluginSetup;
ml: MlPluginSetup;
}
export interface ObservabilityAIAssistantPluginStartDependencies {
data: DataPublicPluginStart;
dataViews: DataViewsPublicPluginStart;
features: FeaturesPluginStart;
lens: LensPublicStart;
licensing: LicensingPluginStart;
observabilityShared: ObservabilitySharedPluginStart;
security: SecurityPluginStart;
share: SharePluginStart;
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
uiActions: UiActionsStart;
ml: MlPluginStart;
}
export interface ObservabilityAIAssistantPluginSetup {}
export interface ObservabilityAIAssistantPublicSetup {}
export interface ObservabilityAIAssistantPluginStart {
export interface ObservabilityAIAssistantPublicStart {
service: ObservabilityAIAssistantService;
ObservabilityAIAssistantContextualInsight: React.ForwardRefExoticComponent<InsightProps> | null;
ObservabilityAIAssistantActionMenuItem: ForwardRefExoticComponent<
Pick<RefAttributes<{}> & WithSuspenseExtendedDeps, 'css' | 'key' | 'analytics'> &
RefAttributes<{}>
> | null;
ObservabilityAIAssistantMultipaneFlyoutContext: typeof ObservabilityAIAssistantMultipaneFlyoutContext;
ObservabilityAIAssistantChatServiceContext: typeof ObservabilityAIAssistantChatServiceContext;
useObservabilityAIAssistantChatService: typeof useObservabilityAIAssistantChatService;
useGenAIConnectors: () => UseGenAIConnectorsResult;
useChat: typeof useChat;
useUserPreferredLanguage: () => UseUserPreferredLanguageResult;
getContextualInsightMessages: ({}: { message: string; instructions: string }) => Message[];
}

View file

@ -5,124 +5,7 @@
* 2.0.
*/
import { merge, uniqueId } from 'lodash';
import { DeepPartial } from 'utility-types';
import {
type Conversation,
type FunctionDefinition,
type Message,
MessageRole,
} from '../../common/types';
import { getAssistantSetupMessage } from '../service/get_assistant_setup_message';
type BuildMessageProps = DeepPartial<Message> & {
message: {
role: MessageRole;
function_call?: {
name: string;
trigger: MessageRole.Assistant | MessageRole.User | MessageRole.Elastic;
};
};
};
export function buildMessage(params: BuildMessageProps): Message {
return merge(
{
'@timestamp': new Date().toISOString(),
},
params
);
}
export function buildSystemMessage(
params?: Omit<BuildMessageProps, 'message'> & {
message: DeepPartial<Omit<Message['message'], 'role'>>;
}
) {
return buildMessage(
merge({}, params, {
message: { role: MessageRole.System },
})
);
}
export function buildUserMessage(
params?: Omit<BuildMessageProps, 'message'> & {
message?: DeepPartial<Omit<Message['message'], 'role'>>;
}
) {
return buildMessage(
merge(
{
message: {
content: "What's a function?",
},
},
params,
{
message: { role: MessageRole.User },
}
)
);
}
export function buildAssistantMessage(
params?: Omit<BuildMessageProps, 'message'> & {
message: DeepPartial<Omit<Message['message'], 'role'>>;
}
) {
return buildMessage(
merge(
{
message: {
content: `In computer programming and mathematics, a function is a fundamental concept that represents a relationship between input values and output values. It takes one or more input values (also known as arguments or parameters) and processes them to produce a result, which is the output of the function. The input values are passed to the function, and the function performs a specific set of operations or calculations on those inputs to produce the desired output.
A function is often defined with a name, which serves as an identifier to call and use the function in the code. It can be thought of as a reusable block of code that can be executed whenever needed, and it helps in organizing code and making it more modular and maintainable.`,
},
},
params,
{
message: { role: MessageRole.Assistant },
}
)
);
}
export function buildFunctionResponseMessage(
params?: Omit<BuildMessageProps, 'message'> & {
message: DeepPartial<Omit<Message['message'], 'role'>>;
}
) {
return buildUserMessage(
merge(
{},
{
message: {
name: 'leftpad',
},
...params,
}
)
);
}
export function buildConversation(params?: Partial<Conversation>) {
return {
'@timestamp': '',
user: {
name: 'foo',
},
conversation: {
id: uniqueId(),
title: '',
last_updated: '',
},
messages: [getAssistantSetupMessage({ contexts: [] })],
labels: {},
numeric_labels: {},
namespace: '',
...params,
};
}
import type { FunctionDefinition } from '../../common/functions/types';
export function buildFunction(): FunctionDefinition {
return {

View file

@ -0,0 +1,47 @@
/*
* 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 Message, MessageRole } from '../../common';
export function getContextualInsightMessages({
message,
instructions,
}: {
message: string;
instructions: string;
}): Message[] {
return [
{
'@timestamp': new Date().toISOString(),
message: {
role: MessageRole.User,
content: message,
},
},
{
'@timestamp': new Date().toISOString(),
message: {
role: MessageRole.Assistant,
function_call: {
name: 'get_contextual_insight_instructions',
trigger: MessageRole.Assistant,
arguments: JSON.stringify({}),
},
},
},
{
'@timestamp': new Date().toISOString(),
message: {
role: MessageRole.User,
content: JSON.stringify({
instructions,
}),
name: 'get_contextual_insight_instructions',
},
},
];
}

View file

@ -6,29 +6,20 @@
*/
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import React, { ComponentType } from 'react';
import { ObservabilityAIAssistantChatServiceProvider } from '../context/observability_ai_assistant_chat_service_provider';
import { ObservabilityAIAssistantChatServiceContext } from '../context/observability_ai_assistant_chat_service_context';
import { ObservabilityAIAssistantProvider } from '../context/observability_ai_assistant_provider';
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
import { mockChatService, mockService } from '../mock';
import { createStorybookService, createStorybookChatService } from '../storybook_mock';
const mockService = createStorybookService();
const mockChatService = createStorybookChatService();
export function KibanaReactStorybookDecorator(Story: ComponentType) {
return (
<KibanaContextProvider
services={{
triggersActionsUi: { getAddRuleFlyout: {} },
uiSettings: {
get: (setting: string) => {
if (setting === 'dateFormat') {
return 'MMM D, YYYY HH:mm';
}
},
},
}}
>
<KibanaContextProvider>
<ObservabilityAIAssistantProvider value={mockService}>
<ObservabilityAIAssistantChatServiceProvider value={mockChatService}>
<ObservabilityAIAssistantChatServiceContext.Provider value={mockChatService}>
<Story />
</ObservabilityAIAssistantChatServiceProvider>
</ObservabilityAIAssistantChatServiceContext.Provider>
</ObservabilityAIAssistantProvider>
</KibanaContextProvider>
);

View file

@ -22,13 +22,14 @@ import {
StreamingChatResponseEvent,
StreamingChatResponseEventType,
} from '../../common/conversation_complete';
import { FunctionDefinition, ObservabilityAIAssistantScreenContext } from '../../common/types';
import { ObservabilityAIAssistantScreenContext } from '../../common/types';
import { concatenateChatCompletionChunks } from '../../common/utils/concatenate_chat_completion_chunks';
import { throwSerializedChatCompletionErrors } from '../../common/utils/throw_serialized_chat_completion_errors';
import { APIReturnType, ObservabilityAIAssistantAPIClientRequestParamsOf } from '../../public';
import { getAssistantSetupMessage } from '../../public/service/get_assistant_setup_message';
import { getAssistantSystemMessage } from '../../public/service/get_assistant_system_message';
import { streamIntoObservable } from '../../server/service/util/stream_into_observable';
import { EvaluationResult } from './types';
import { FunctionDefinition } from '../../common/functions/types';
// eslint-disable-next-line spaced-comment
/// <reference types="@kbn/ambient-ftr-types"/>
@ -260,7 +261,7 @@ export class KibanaClient {
chat: async (message) => {
const { functionDefinitions, contextDefinitions } = await getFunctions();
const messages = [
getAssistantSetupMessage({ contexts: contextDefinitions }),
getAssistantSystemMessage({ contexts: contextDefinitions }),
...getMessages(message).map((msg) => ({
message: msg,
'@timestamp': new Date().toISOString(),
@ -297,7 +298,7 @@ export class KibanaClient {
const { contextDefinitions } = await getFunctions();
const messages = [
getAssistantSetupMessage({ contexts: contextDefinitions }),
getAssistantSystemMessage({ contexts: contextDefinitions }),
...getMessages(messagesArg!).map((msg) => ({
message: msg,
'@timestamp': new Date().toISOString(),

View file

@ -15,7 +15,8 @@ import { compact, last, omit } from 'lodash';
import { lastValueFrom, Observable } from 'rxjs';
import { FunctionRegistrationParameters } from '.';
import { MessageAddEvent } from '../../common/conversation_complete';
import { FunctionVisibility, MessageRole, type Message } from '../../common/types';
import { FunctionVisibility } from '../../common/functions/types';
import { MessageRole, type Message } from '../../common/types';
import { concatenateChatCompletionChunks } from '../../common/utils/concatenate_chat_completion_chunks';
import type { ObservabilityAIAssistantClient } from '../service/client';
import { createFunctionResponseMessage } from '../service/util/create_function_response_message';
@ -25,11 +26,11 @@ const MAX_TOKEN_COUNT_FOR_DATA_ON_SCREEN = 1000;
export function registerContextFunction({
client,
registerFunction,
functions,
resources,
isKnowledgeBaseAvailable,
}: FunctionRegistrationParameters & { isKnowledgeBaseAvailable: boolean }) {
registerFunction(
functions.registerFunction(
{
name: 'context',
contexts: ['core'],

View file

@ -8,10 +8,10 @@
import type { FunctionRegistrationParameters } from '.';
export function registerElasticsearchFunction({
registerFunction,
functions,
resources,
}: FunctionRegistrationParameters) {
registerFunction(
functions.registerFunction(
{
name: 'elasticsearch',
contexts: ['core'],

View file

@ -8,15 +8,16 @@
import { chunk, groupBy, uniq } from 'lodash';
import { lastValueFrom } from 'rxjs';
import { FunctionRegistrationParameters } from '.';
import { FunctionVisibility, MessageRole } from '../../common/types';
import { FunctionVisibility } from '../../common/functions/types';
import { MessageRole } from '../../common/types';
import { concatenateChatCompletionChunks } from '../../common/utils/concatenate_chat_completion_chunks';
export function registerGetDatasetInfoFunction({
client,
resources,
registerFunction,
functions,
}: FunctionRegistrationParameters) {
registerFunction(
functions.registerFunction(
{
name: 'get_dataset_info',
contexts: ['core'],

View file

@ -8,31 +8,25 @@
import dedent from 'dedent';
import { registerContextFunction } from './context';
import { registerSummarizationFunction } from './summarize';
import { ChatRegistrationFunction } from '../service/types';
import { registerAlertsFunction } from './alerts';
import type { RegistrationCallback } from '../service/types';
import { registerElasticsearchFunction } from './elasticsearch';
import { registerQueryFunction } from './query';
import { registerGetDatasetInfoFunction } from './get_dataset_info';
import { registerLensFunction } from './lens';
import { registerKibanaFunction } from './kibana';
import { registerVisualizeESQLFunction } from './visualize_esql';
export type FunctionRegistrationParameters = Omit<
Parameters<ChatRegistrationFunction>[0],
Parameters<RegistrationCallback>[0],
'registerContext' | 'hasFunction'
>;
export const registerFunctions: ChatRegistrationFunction = async ({
export const registerFunctions: RegistrationCallback = async ({
client,
registerContext,
registerFunction,
hasFunction,
functions,
resources,
signal,
}) => {
const registrationParameters: FunctionRegistrationParameters = {
client,
registerFunction,
functions,
resources,
signal,
};
@ -79,7 +73,7 @@ export const registerFunctions: ChatRegistrationFunction = async ({
If the "get_dataset_info" function returns no data, and the user asks for a query, generate a query anyway with the "query" function, but be explicit about it potentially being incorrect.
${
hasFunction('get_data_on_screen')
functions.hasFunction('get_data_on_screen')
? `You have access to data on the screen by calling the "get_data_on_screen" function.
Use it to help the user understand what they are looking at. A short summary of what they are looking at is available in the return of the "context" function.
Data that is compact enough automatically gets included in the response for the "context" function.
@ -103,7 +97,6 @@ export const registerFunctions: ChatRegistrationFunction = async ({
`;
registerSummarizationFunction(registrationParameters);
registerLensFunction(registrationParameters);
} else {
description += `You do not have a working memory. If the user expects you to remember the previous conversations, tell them they can set up the knowledge base.`;
}
@ -111,13 +104,20 @@ export const registerFunctions: ChatRegistrationFunction = async ({
registerContextFunction({ ...registrationParameters, isKnowledgeBaseAvailable: isReady });
registerElasticsearchFunction(registrationParameters);
registerKibanaFunction(registrationParameters);
registerQueryFunction(registrationParameters);
registerVisualizeESQLFunction(registrationParameters);
registerAlertsFunction(registrationParameters);
const request = registrationParameters.resources.request;
if ('id' in request) {
registerKibanaFunction({
...registrationParameters,
resources: {
...registrationParameters.resources,
request,
},
});
}
registerGetDatasetInfoFunction(registrationParameters);
registerContext({
functions.registerContext({
name: 'core',
description: dedent(description),
});

View file

@ -8,13 +8,16 @@
import axios from 'axios';
import { format, parse } from 'url';
import { castArray, first, pick, pickBy } from 'lodash';
import type { KibanaRequest } from '@kbn/core/server';
import type { FunctionRegistrationParameters } from '.';
export function registerKibanaFunction({
registerFunction,
functions,
resources,
}: FunctionRegistrationParameters) {
registerFunction(
}: FunctionRegistrationParameters & {
resources: { request: KibanaRequest };
}) {
functions.registerFunction(
{
name: 'kibana',
contexts: ['core'],

View file

@ -10,9 +10,9 @@ import { KnowledgeBaseEntryRole } from '../../common';
export function registerSummarizationFunction({
client,
registerFunction,
functions,
}: FunctionRegistrationParameters) {
registerFunction(
functions.registerFunction(
{
name: 'summarize',
contexts: ['core'],

View file

@ -11,9 +11,10 @@ import type { ObservabilityAIAssistantConfig } from './config';
export type { ObservabilityAIAssistantServerRouteRepository } from './routes/get_global_observability_ai_assistant_route_repository';
import { config as configSchema } from './config';
export type { RegistrationCallback } from './service/types';
export type {
ObservabilityAIAssistantPluginStart,
ObservabilityAIAssistantPluginSetup,
ObservabilityAIAssistantServerStart,
ObservabilityAIAssistantServerSetup,
} from './types';
export const config: PluginConfigDescriptor<ObservabilityAIAssistantConfig> = {

View file

@ -26,8 +26,8 @@ import { registerServerRoutes } from './routes/register_routes';
import { ObservabilityAIAssistantRouteHandlerResources } from './routes/types';
import { ObservabilityAIAssistantService } from './service';
import {
ObservabilityAIAssistantPluginSetup,
ObservabilityAIAssistantPluginStart,
ObservabilityAIAssistantServerSetup,
ObservabilityAIAssistantServerStart,
ObservabilityAIAssistantPluginSetupDependencies,
ObservabilityAIAssistantPluginStartDependencies,
} from './types';
@ -37,8 +37,8 @@ import { registerFunctions } from './functions';
export class ObservabilityAIAssistantPlugin
implements
Plugin<
ObservabilityAIAssistantPluginSetup,
ObservabilityAIAssistantPluginStart,
ObservabilityAIAssistantServerSetup,
ObservabilityAIAssistantServerStart,
ObservabilityAIAssistantPluginSetupDependencies,
ObservabilityAIAssistantPluginStartDependencies
>
@ -52,10 +52,10 @@ export class ObservabilityAIAssistantPlugin
public setup(
core: CoreSetup<
ObservabilityAIAssistantPluginStartDependencies,
ObservabilityAIAssistantPluginStart
ObservabilityAIAssistantServerStart
>,
plugins: ObservabilityAIAssistantPluginSetupDependencies
): ObservabilityAIAssistantPluginSetup {
): ObservabilityAIAssistantServerSetup {
plugins.features.registerKibanaFeature({
id: OBSERVABILITY_AI_ASSISTANT_FEATURE_ID,
name: i18n.translate('xpack.observabilityAiAssistant.featureRegistry.featureName', {
@ -113,10 +113,23 @@ export class ObservabilityAIAssistantPlugin
// Wait for the ML plugin's dependency on the internal saved objects client to be ready
const [_, pluginsStart] = await core.getStartServices();
const { ml } = await core.plugins.onSetup('ml');
if (!ml.found) {
throw new Error('Could not find ML plugin');
}
// Wait for the license to be available so the ML plugin's guards pass once we ask for ELSER stats
await firstValueFrom(pluginsStart.licensing.license$);
const elserModelDefinition = await plugins.ml
const elserModelDefinition = await (
ml.contract as {
trainedModelsProvider: (
request: {},
soClient: {}
) => { getELSER: () => Promise<{ model_id: string }> };
}
)
.trainedModelsProvider({} as any, {} as any) // request, savedObjectsClient (but we fake it to use the internal user)
.getELSER();
@ -154,7 +167,7 @@ export class ObservabilityAIAssistantPlugin
};
}
public start(): ObservabilityAIAssistantPluginStart {
public start(): ObservabilityAIAssistantServerStart {
return {
service: this.service!,
};

View file

@ -7,11 +7,8 @@
import { notImplemented } from '@hapi/boom';
import { nonEmptyStringRt, toBooleanRt } from '@kbn/io-ts-utils';
import * as t from 'io-ts';
import {
ContextDefinition,
FunctionDefinition,
KnowledgeBaseEntryRole,
} from '../../../common/types';
import { ContextDefinition, FunctionDefinition } from '../../../common/functions/types';
import { KnowledgeBaseEntryRole } from '../../../common/types';
import type { RecalledEntry } from '../../service/knowledge_base_service';
import { createObservabilityAIAssistantServerRoute } from '../create_observability_ai_assistant_server_route';

View file

@ -7,9 +7,9 @@
import type { CustomRequestHandlerContext, KibanaRequest } from '@kbn/core/server';
import type { Logger } from '@kbn/logging';
import type { RacApiRequestHandlerContext } from '@kbn/rule-registry-plugin/server';
import type { LicensingApiRequestHandlerContext } from '@kbn/licensing-plugin/server/types';
import type { AlertingApiRequestHandlerContext } from '@kbn/alerting-plugin/server/types';
import type { RacApiRequestHandlerContext } from '@kbn/rule-registry-plugin/server';
import type { AlertingApiRequestHandlerContext } from '@kbn/alerting-plugin/server';
import type { ObservabilityAIAssistantService } from '../service';
import type {
ObservabilityAIAssistantPluginSetupDependencies,
@ -17,8 +17,9 @@ import type {
} from '../types';
export type ObservabilityAIAssistantRequestHandlerContext = CustomRequestHandlerContext<{
rac: RacApiRequestHandlerContext;
licensing: LicensingApiRequestHandlerContext;
// these two are here for compatibility with APM functions
rac: RacApiRequestHandlerContext;
alerting: AlertingApiRequestHandlerContext;
}>;

View file

@ -4,11 +4,9 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import Ajv, { type ValidateFunction } from 'ajv';
import dedent from 'dedent';
import { ChatFunctionClient } from '.';
import { ContextRegistry, FunctionVisibility } from '../../../common/types';
import type { FunctionHandlerRegistry } from '../types';
import { FunctionVisibility } from '../../../common/functions/types';
describe('chatFunctionClient', () => {
describe('when executing a function with invalid arguments', () => {
@ -17,43 +15,10 @@ describe('chatFunctionClient', () => {
let respondFn: jest.Mock;
beforeEach(() => {
const contextRegistry: ContextRegistry = new Map();
contextRegistry.set('core', {
description: '',
name: 'core',
});
respondFn = jest.fn().mockImplementationOnce(async () => {
return {};
});
const functionRegistry: FunctionHandlerRegistry = new Map();
functionRegistry.set('myFunction', {
respond: respondFn,
definition: {
contexts: ['core'],
description: '',
name: 'myFunction',
parameters: {
properties: {
foo: {
type: 'string',
},
},
required: ['foo'],
},
},
});
const validators = new Map<string, ValidateFunction>();
validators.set(
'myFunction',
new Ajv({ strict: false }).compile(
functionRegistry.get('myFunction')!.definition.parameters
)
);
client = new ChatFunctionClient([]);
client.registerContext({
description: '',

View file

@ -10,14 +10,13 @@ import Ajv, { type ErrorObject, type ValidateFunction } from 'ajv';
import dedent from 'dedent';
import { compact, keyBy } from 'lodash';
import {
ContextDefinition,
ContextRegistry,
FunctionResponse,
type ContextRegistry,
FunctionVisibility,
Message,
ObservabilityAIAssistantScreenContext,
RegisterContextDefinition,
} from '../../../common/types';
type RegisterContextDefinition,
type ContextDefinition,
type FunctionResponse,
} from '../../../common/functions/types';
import type { Message, ObservabilityAIAssistantScreenContext } from '../../../common/types';
import { filterFunctionDefinitions } from '../../../common/utils/filter_function_definitions';
import type { FunctionHandler, FunctionHandlerRegistry, RegisterFunction } from '../types';

View file

@ -10,7 +10,7 @@ import type { Observable } from 'rxjs';
import type { Logger } from '@kbn/logging';
import type { Message } from '../../../../common';
import type { ChatCompletionChunkEvent } from '../../../../common/conversation_complete';
import type { CompatibleJSONSchema } from '../../../../common/types';
import { CompatibleJSONSchema } from '../../../../common/functions/types';
export type LlmApiAdapterFactory = (options: {
logger: Logger;

View file

@ -23,7 +23,7 @@ import {
MessageAddEvent,
StreamingChatResponseEventType,
} from '../../../common/conversation_complete';
import type { CreateChatCompletionResponseChunk } from '../../../public/types';
import type { CreateChatCompletionResponseChunk } from '../../../common/utils/process_openai_stream';
import type { ChatFunctionClient } from '../chat_function_client';
import type { KnowledgeBaseService } from '../knowledge_base_service';
import { createFunctionResponseMessage } from '../util/create_function_response_message';

View file

@ -35,10 +35,12 @@ import {
type StreamingChatResponseEvent,
} from '../../../common/conversation_complete';
import {
CompatibleJSONSchema,
FunctionResponse,
FunctionVisibility,
} from '../../../common/functions/types';
import {
MessageRole,
type CompatibleJSONSchema,
type Conversation,
type ConversationCreateRequest,
type ConversationUpdateRequest,

View file

@ -21,7 +21,7 @@ import { conversationComponentTemplate } from './conversation_component_template
import { kbComponentTemplate } from './kb_component_template';
import { KnowledgeBaseEntryOperationType, KnowledgeBaseService } from './knowledge_base_service';
import type {
ChatRegistrationFunction,
RegistrationCallback,
ObservabilityAIAssistantResourceNames,
RespondFunctionResources,
} from './types';
@ -76,7 +76,7 @@ export class ObservabilityAIAssistantService {
private readonly resourceNames: ObservabilityAIAssistantResourceNames = createResourceNamesMap();
private readonly registrations: ChatRegistrationFunction[] = [];
private readonly registrations: RegistrationCallback[] = [];
constructor({
logger,
@ -300,9 +300,7 @@ export class ObservabilityAIAssistantService {
const params = {
signal,
registerContext: fnClient.registerContext.bind(fnClient),
registerFunction: fnClient.registerFunction.bind(fnClient),
hasFunction: fnClient.hasFunction.bind(fnClient),
functions: fnClient,
resources,
client,
};
@ -373,7 +371,7 @@ export class ObservabilityAIAssistantService {
);
}
register(fn: ChatRegistrationFunction) {
this.registrations.push(fn);
register(cb: RegistrationCallback) {
this.registrations.push(cb);
}
}

View file

@ -10,10 +10,8 @@ import type {
CompatibleJSONSchema,
FunctionDefinition,
FunctionResponse,
Message,
ObservabilityAIAssistantScreenContext,
RegisterContextDefinition,
} from '../../common/types';
} from '../../common/functions/types';
import type { Message, ObservabilityAIAssistantScreenContext } from '../../common/types';
import type { ObservabilityAIAssistantRouteHandlerResources } from '../routes/types';
import { ChatFunctionClient } from './chat_function_client';
import type { ObservabilityAIAssistantClient } from './client';
@ -48,13 +46,11 @@ export type RegisterFunction = <
) => void;
export type FunctionHandlerRegistry = Map<string, FunctionHandler>;
export type ChatRegistrationFunction = ({}: {
export type RegistrationCallback = ({}: {
signal: AbortSignal;
resources: RespondFunctionResources;
client: ObservabilityAIAssistantClient;
registerFunction: RegisterFunction;
registerContext: RegisterContextDefinition;
hasFunction: ChatFunctionClient['hasFunction'];
functions: ChatFunctionClient;
}) => Promise<void>;
export interface ObservabilityAIAssistantResourceNames {

View file

@ -21,20 +21,19 @@ import type {
DataViewsServerPluginSetup,
DataViewsServerPluginStart,
} from '@kbn/data-views-plugin/server';
import type { MlPluginSetup, MlPluginStart } from '@kbn/ml-plugin/server';
import type { LicensingPluginSetup, LicensingPluginStart } from '@kbn/licensing-plugin/server';
import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/server';
import type { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/server';
import type { ObservabilityAIAssistantService } from './service';
export interface ObservabilityAIAssistantPluginSetup {
export interface ObservabilityAIAssistantServerSetup {
/**
* Returns a Observability AI Assistant service instance
*/
service: ObservabilityAIAssistantService;
}
export interface ObservabilityAIAssistantPluginStart {
export interface ObservabilityAIAssistantServerStart {
/**
* Returns a Observability AI Assistant service instance
*/
@ -47,18 +46,17 @@ export interface ObservabilityAIAssistantPluginSetupDependencies {
features: FeaturesPluginSetup;
taskManager: TaskManagerSetupContract;
dataViews: DataViewsServerPluginSetup;
ml: MlPluginSetup;
licensing: LicensingPluginSetup;
cloud?: CloudSetup;
serverless?: ServerlessPluginSetup;
}
export interface ObservabilityAIAssistantPluginStartDependencies {
actions: ActionsPluginStart;
security: SecurityPluginStart;
features: FeaturesPluginStart;
taskManager: TaskManagerStartContract;
dataViews: DataViewsServerPluginStart;
ml: MlPluginStart;
licensing: LicensingPluginStart;
cloud?: CloudStart;
serverless?: ServerlessPluginStart;

View file

@ -18,7 +18,6 @@
"@kbn/utility-types",
"@kbn/server-route-repository",
"@kbn/logging",
"@kbn/triggers-actions-ui-plugin",
"@kbn/config-schema",
"@kbn/security-plugin",
"@kbn/i18n",
@ -26,50 +25,28 @@
"@kbn/kibana-react-plugin",
"@kbn/shared-ux-utility",
"@kbn/alerting-plugin",
"@kbn/shared-ux-link-redirect-app",
"@kbn/typed-react-router-config",
"@kbn/ui-theme",
"@kbn/user-profile-components",
"@kbn/observability-shared-plugin",
"@kbn/kibana-utils-plugin",
"@kbn/monaco",
"@kbn/io-ts-utils",
"@kbn/std",
"@kbn/alerting-plugin",
"@kbn/features-plugin",
"@kbn/react-kibana-context-theme",
"@kbn/lens-embeddable-utils",
"@kbn/i18n-react",
"@kbn/field-formats-plugin",
"@kbn/lens-plugin",
"@kbn/data-views-plugin",
"@kbn/task-manager-plugin",
"@kbn/es-query",
"@kbn/rule-registry-plugin",
"@kbn/licensing-plugin",
"@kbn/share-plugin",
"@kbn/utility-types-jest",
"@kbn/analytics-client",
"@kbn/tooling-log",
"@kbn/babel-register",
"@kbn/dev-cli-runner",
"@kbn/core-analytics-browser",
"@kbn/core-http-browser",
"@kbn/security-plugin-types-common",
"@kbn/ml-plugin",
"@kbn/expect",
"@kbn/apm-synthtrace-client",
"@kbn/apm-synthtrace",
"@kbn/code-editor",
"@kbn/safer-lodash-set",
"@kbn/cloud-plugin",
"@kbn/ui-actions-plugin",
"@kbn/expressions-plugin",
"@kbn/visualization-utils",
"@kbn/field-types",
"@kbn/es-types",
"@kbn/esql-utils",
"@kbn/data-plugin",
"@kbn/serverless"
],
"exclude": ["target/**/*"]

View file

@ -0,0 +1,11 @@
/*
* 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 { setGlobalConfig } from '@storybook/testing-react';
import * as globalStorybookConfig from './preview';
setGlobalConfig(globalStorybookConfig);

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
module.exports = require('@kbn/storybook').defaultConfig;

View file

@ -0,0 +1,10 @@
/*
* 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 { EuiThemeProviderDecorator } from '@kbn/kibana-react-plugin/common';
export const decorators = [EuiThemeProviderDecorator];

View file

@ -0,0 +1,3 @@
#### Observability AI Assistant App
This app registers defaults functions. It exists as a separate plugin to avoid cyclical dependencies.

View file

@ -7,7 +7,7 @@
import { FromSchema } from 'json-schema-to-ts';
import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common';
import { FunctionVisibility } from '../types';
import { FunctionVisibility } from '@kbn/observability-ai-assistant-plugin/common';
export enum SeriesType {
Bar = 'bar',

View file

@ -0,0 +1,33 @@
/*
* 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 { FromSchema } from 'json-schema-to-ts';
import { FunctionVisibility } from '@kbn/observability-ai-assistant-plugin/common';
import { VISUALIZE_ESQL_USER_INTENTIONS } from '@kbn/observability-ai-assistant-plugin/common/functions/visualize_esql';
export const visualizeESQLFunction = {
name: 'visualize_query',
visibility: FunctionVisibility.UserOnly,
description: 'Use this function to visualize charts for ES|QL queries.',
descriptionForUser: 'Use this function to visualize charts for ES|QL queries.',
parameters: {
type: 'object',
additionalProperties: true,
properties: {
query: {
type: 'string',
},
intention: {
type: 'string',
enum: VISUALIZE_ESQL_USER_INTENTIONS,
},
},
required: ['query', 'intention'],
} as const,
contexts: ['core'],
};
export type VisualizeESQLFunctionArguments = FromSchema<typeof visualizeESQLFunction['parameters']>;

Some files were not shown because too many files have changed in this diff Show more