mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [[Search Assistant] Use scopes to modify behavior contextually (#195785)](https://github.com/elastic/kibana/pull/195785) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Sander Philipse","email":"94373878+sphilipse@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-10-11T23:09:06Z","message":"[Search Assistant] Use scopes to modify behavior contextually (#195785)\n\n## Summary\r\n\r\nThis actually uses the Search Assistant scope to modify the assistant's\r\nbehavior depending on the context they're in. The assistant now:\r\n- Defaults to Observability mode\r\n- Is a Search assistant in the Search pages\r\n- Switches dynamically, changing available functions, prompts and\r\ninstructions based on context\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"ee341d5f801ca42ed26acf0544b0bc59948d0214","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Search","Team:Obs AI Assistant","ci:project-deploy-observability","v8.16.0","backport:version"],"title":"[Search Assistant] Use scopes to modify behavior contextually","number":195785,"url":"https://github.com/elastic/kibana/pull/195785","mergeCommit":{"message":"[Search Assistant] Use scopes to modify behavior contextually (#195785)\n\n## Summary\r\n\r\nThis actually uses the Search Assistant scope to modify the assistant's\r\nbehavior depending on the context they're in. The assistant now:\r\n- Defaults to Observability mode\r\n- Is a Search assistant in the Search pages\r\n- Switches dynamically, changing available functions, prompts and\r\ninstructions based on context\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"ee341d5f801ca42ed26acf0544b0bc59948d0214"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/195785","number":195785,"mergeCommit":{"message":"[Search Assistant] Use scopes to modify behavior contextually (#195785)\n\n## Summary\r\n\r\nThis actually uses the Search Assistant scope to modify the assistant's\r\nbehavior depending on the context they're in. The assistant now:\r\n- Defaults to Observability mode\r\n- Is a Search assistant in the Search pages\r\n- Switches dynamically, changing available functions, prompts and\r\ninstructions based on context\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"ee341d5f801ca42ed26acf0544b0bc59948d0214"}},{"branch":"8.x","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":false,"url":"https://github.com/elastic/kibana/pull/196013","number":196013,"state":"OPEN"}]}] BACKPORT--> Co-authored-by: Sander Philipse <94373878+sphilipse@users.noreply.github.com>
This commit is contained in:
parent
d301b8f7b0
commit
4951ab959c
69 changed files with 628 additions and 223 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -11,6 +11,7 @@ x-pack/test/alerting_api_integration/common/plugins/actions_simulators @elastic/
|
|||
packages/kbn-actions-types @elastic/response-ops
|
||||
src/plugins/advanced_settings @elastic/appex-sharedux @elastic/kibana-management
|
||||
x-pack/packages/kbn-ai-assistant @elastic/search-kibana
|
||||
x-pack/packages/kbn-ai-assistant-common @elastic/search-kibana
|
||||
src/plugins/ai_assistant_management/selection @elastic/obs-knowledge-team
|
||||
x-pack/packages/ml/aiops_change_point_detection @elastic/ml-ui
|
||||
x-pack/packages/ml/aiops_common @elastic/ml-ui
|
||||
|
|
|
@ -159,6 +159,7 @@
|
|||
"@kbn/actions-types": "link:packages/kbn-actions-types",
|
||||
"@kbn/advanced-settings-plugin": "link:src/plugins/advanced_settings",
|
||||
"@kbn/ai-assistant": "link:x-pack/packages/kbn-ai-assistant",
|
||||
"@kbn/ai-assistant-common": "link:x-pack/packages/kbn-ai-assistant-common",
|
||||
"@kbn/ai-assistant-management-plugin": "link:src/plugins/ai_assistant_management/selection",
|
||||
"@kbn/aiops-change-point-detection": "link:x-pack/packages/ml/aiops_change_point_detection",
|
||||
"@kbn/aiops-common": "link:x-pack/packages/ml/aiops_common",
|
||||
|
|
|
@ -348,6 +348,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
|
|||
'xpack.observability.unsafe.thresholdRule.enabled (boolean?)',
|
||||
'xpack.observability_onboarding.ui.enabled (boolean?)',
|
||||
'xpack.observabilityLogsExplorer.navigation.showAppLink (boolean?|never)',
|
||||
'xpack.observabilityAIAssistant.scope (observability?|search?)',
|
||||
'share.new_version.enabled (boolean?)',
|
||||
'aiAssistantManagementSelection.preferredAIAssistantType (default?|never?|observability?)',
|
||||
/**
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
"@kbn/advanced-settings-plugin/*": ["src/plugins/advanced_settings/*"],
|
||||
"@kbn/ai-assistant": ["x-pack/packages/kbn-ai-assistant"],
|
||||
"@kbn/ai-assistant/*": ["x-pack/packages/kbn-ai-assistant/*"],
|
||||
"@kbn/ai-assistant-common": ["x-pack/packages/kbn-ai-assistant-common"],
|
||||
"@kbn/ai-assistant-common/*": ["x-pack/packages/kbn-ai-assistant-common/*"],
|
||||
"@kbn/ai-assistant-management-plugin": ["src/plugins/ai_assistant_management/selection"],
|
||||
"@kbn/ai-assistant-management-plugin/*": ["src/plugins/ai_assistant_management/selection/*"],
|
||||
"@kbn/aiops-change-point-detection": ["x-pack/packages/ml/aiops_change_point_detection"],
|
||||
|
|
3
x-pack/packages/kbn-ai-assistant-common/README.md
Normal file
3
x-pack/packages/kbn-ai-assistant-common/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# @kbn/ai-assistant-common
|
||||
|
||||
Provides types and utils to render the AI Assistant in plugins.
|
7
x-pack/packages/kbn-ai-assistant-common/index.ts
Normal file
7
x-pack/packages/kbn-ai-assistant-common/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* 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 * from './src';
|
19
x-pack/packages/kbn-ai-assistant-common/jest.config.js
Normal file
19
x-pack/packages/kbn-ai-assistant-common/jest.config.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 = {
|
||||
coverageDirectory:
|
||||
'<rootDir>/target/kibana-coverage/jest/x-pack/packages/kbn_ai_assistant_common_src',
|
||||
coverageReporters: ['text', 'html'],
|
||||
collectCoverageFrom: [
|
||||
'<rootDir>/x-pack/packages/kbn-ai-assistant-common/src/**/*.{ts,tsx}',
|
||||
'!<rootDir>/x-pack/packages/kbn-ai-assistant-common/src/*.test.{ts,tsx}',
|
||||
],
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../..',
|
||||
roots: ['<rootDir>/x-pack/packages/kbn-ai-assistant-common'],
|
||||
};
|
5
x-pack/packages/kbn-ai-assistant-common/kibana.jsonc
Normal file
5
x-pack/packages/kbn-ai-assistant-common/kibana.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"id": "@kbn/ai-assistant-common",
|
||||
"owner": "@elastic/search-kibana",
|
||||
"type": "shared-common"
|
||||
}
|
7
x-pack/packages/kbn-ai-assistant-common/package.json
Normal file
7
x-pack/packages/kbn-ai-assistant-common/package.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "@kbn/ai-assistant-common",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "Elastic License 2.0",
|
||||
"sideEffects": false
|
||||
}
|
9
x-pack/packages/kbn-ai-assistant-common/setup_tests.ts
Normal file
9
x-pack/packages/kbn-ai-assistant-common/setup_tests.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import '@testing-library/jest-dom';
|
9
x-pack/packages/kbn-ai-assistant-common/src/index.ts
Normal file
9
x-pack/packages/kbn-ai-assistant-common/src/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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 * from './types';
|
||||
export * from './utils';
|
|
@ -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.
|
||||
*/
|
||||
|
||||
export type AssistantScope = 'observability' | 'search' | 'all';
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 { AssistantScope } from '../types';
|
||||
|
||||
export function filterScopes<T extends { scopes?: AssistantScope[] }>(scope?: AssistantScope) {
|
||||
return function (value: T): boolean {
|
||||
if (!scope || !value) {
|
||||
return true;
|
||||
}
|
||||
return value?.scopes ? value.scopes.includes(scope) || value.scopes.includes('all') : true;
|
||||
};
|
||||
}
|
|
@ -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.
|
||||
*/
|
||||
|
||||
export * from './filter_scopes';
|
19
x-pack/packages/kbn-ai-assistant-common/tsconfig.json
Normal file
19
x-pack/packages/kbn-ai-assistant-common/tsconfig.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
]
|
||||
}
|
|
@ -22,7 +22,7 @@ import type { EuiSelectableOptionCheckedType } from '@elastic/eui/src/components
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { FunctionVisibility } from '@kbn/observability-ai-assistant-plugin/public';
|
||||
import type { FunctionDefinition } from '@kbn/observability-ai-assistant-plugin/common';
|
||||
import { useAIAssistantChatService } from '../hooks/use_ai_assistant_chat_service';
|
||||
import { useFunctions } from '../hooks/use_functions';
|
||||
|
||||
interface FunctionListOption {
|
||||
label: string;
|
||||
|
@ -40,8 +40,7 @@ export function FunctionListPopover({
|
|||
onSelectFunction: (func: string | undefined) => void;
|
||||
disabled: boolean;
|
||||
}) {
|
||||
const { getFunctions } = useAIAssistantChatService();
|
||||
const functions = getFunctions();
|
||||
const functions = useFunctions();
|
||||
|
||||
const [functionOptions, setFunctionOptions] = useState<
|
||||
Array<EuiSelectableOption<FunctionListOption>>
|
||||
|
|
|
@ -31,7 +31,6 @@ const starterPromptInnerClassName = css`
|
|||
|
||||
export function StarterPrompts({ onSelectPrompt }: { onSelectPrompt: (prompt: string) => void }) {
|
||||
const service = useAIAssistantAppService();
|
||||
|
||||
const { connectors } = useGenAIConnectors();
|
||||
|
||||
if (!connectors || connectors.length === 0) {
|
||||
|
|
|
@ -9,6 +9,7 @@ import { css } from '@emotion/css';
|
|||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import type { AssistantScope } from '@kbn/ai-assistant-common';
|
||||
import { useKibana } from '../hooks/use_kibana';
|
||||
import { ConversationList, ChatBody, ChatInlineEditingContent } from '../chat';
|
||||
import { useConversationKey } from '../hooks/use_conversation_key';
|
||||
|
@ -26,6 +27,7 @@ interface ConversationViewProps {
|
|||
navigateToConversation: (nextConversationId?: string) => void;
|
||||
getConversationHref?: (conversationId: string) => string;
|
||||
newConversationHref?: string;
|
||||
scope?: AssistantScope;
|
||||
}
|
||||
|
||||
export const ConversationView: React.FC<ConversationViewProps> = ({
|
||||
|
@ -33,6 +35,7 @@ export const ConversationView: React.FC<ConversationViewProps> = ({
|
|||
navigateToConversation,
|
||||
getConversationHref,
|
||||
newConversationHref,
|
||||
scope,
|
||||
}) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
|
@ -57,6 +60,12 @@ export const ConversationView: React.FC<ConversationViewProps> = ({
|
|||
[service]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (scope) {
|
||||
service.setScope(scope);
|
||||
}
|
||||
}, [scope, service]);
|
||||
|
||||
const { key: bodyKey, updateConversationIdInPlace } = useConversationKey(conversationId);
|
||||
|
||||
const [secondSlotContainer, setSecondSlotContainer] = useState<HTMLDivElement | null>(null);
|
||||
|
|
|
@ -8,3 +8,4 @@
|
|||
export * from './use_ai_assistant_app_service';
|
||||
export * from './use_ai_assistant_chat_service';
|
||||
export * from './use_knowledge_base';
|
||||
export * from './use_scope';
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
} from '@testing-library/react-hooks';
|
||||
import { merge } from 'lodash';
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
import { Observable, of, Subject } from 'rxjs';
|
||||
import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
|
||||
import {
|
||||
MessageRole,
|
||||
StreamingChatResponseEventType,
|
||||
|
@ -31,6 +31,7 @@ import { createMockChatService } from '../utils/create_mock_chat_service';
|
|||
import { createUseChat } from '@kbn/observability-ai-assistant-plugin/public/hooks/use_chat';
|
||||
import type { NotificationsStart } from '@kbn/core/public';
|
||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import { AssistantScope } from '@kbn/ai-assistant-common';
|
||||
|
||||
let hookResult: RenderHookResult<UseConversationProps, UseConversationResult>;
|
||||
|
||||
|
@ -54,7 +55,9 @@ const mockService: MockedService = {
|
|||
predefinedConversation$: new Observable(),
|
||||
},
|
||||
navigate: jest.fn().mockReturnValue(of()),
|
||||
scope: 'all',
|
||||
scope$: new BehaviorSubject<AssistantScope>('all') as MockedService['scope$'],
|
||||
setScope: jest.fn(),
|
||||
getScope: jest.fn(),
|
||||
};
|
||||
|
||||
const mockChatService = createMockChatService();
|
||||
|
|
|
@ -20,6 +20,7 @@ import { useAIAssistantAppService } from './use_ai_assistant_app_service';
|
|||
import { useKibana } from './use_kibana';
|
||||
import { useOnce } from './use_once';
|
||||
import { useAbortableAsync } from './use_abortable_async';
|
||||
import { useScope } from './use_scope';
|
||||
|
||||
function createNewConversation({
|
||||
title = EMPTY_CONVERSATION_TITLE,
|
||||
|
@ -61,7 +62,7 @@ export function useConversation({
|
|||
onConversationUpdate,
|
||||
}: UseConversationProps): UseConversationResult {
|
||||
const service = useAIAssistantAppService();
|
||||
const { scope } = service;
|
||||
const scope = useScope();
|
||||
|
||||
const {
|
||||
services: {
|
||||
|
|
15
x-pack/packages/kbn-ai-assistant/src/hooks/use_functions.ts
Normal file
15
x-pack/packages/kbn-ai-assistant/src/hooks/use_functions.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 { useObservable } from 'react-use/lib';
|
||||
import { useAIAssistantChatService } from './use_ai_assistant_chat_service';
|
||||
|
||||
export const useFunctions = () => {
|
||||
const service = useAIAssistantChatService();
|
||||
const functions = useObservable(service.functions$);
|
||||
return functions || [];
|
||||
};
|
|
@ -7,8 +7,8 @@
|
|||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { monaco } from '@kbn/monaco';
|
||||
import { createInitializedObject } from '../utils/create_initialized_object';
|
||||
import { useAIAssistantChatService } from './use_ai_assistant_chat_service';
|
||||
import { safeJsonParse } from '../utils/safe_json_parse';
|
||||
import { useFunctions } from './use_functions';
|
||||
|
||||
const { editor, languages, Uri } = monaco;
|
||||
|
||||
|
@ -19,9 +19,9 @@ export const useJsonEditorModel = ({
|
|||
functionName: string | undefined;
|
||||
initialJson?: string | undefined;
|
||||
}) => {
|
||||
const chatService = useAIAssistantChatService();
|
||||
const functions = useFunctions();
|
||||
|
||||
const functionDefinition = chatService.getFunctions().find((func) => func.name === functionName);
|
||||
const functionDefinition = functions.find((func) => func.name === functionName);
|
||||
|
||||
const [initialJsonValue, setInitialJsonValue] = useState<string | undefined>(initialJson);
|
||||
|
||||
|
|
15
x-pack/packages/kbn-ai-assistant/src/hooks/use_scope.ts
Normal file
15
x-pack/packages/kbn-ai-assistant/src/hooks/use_scope.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 { useObservable } from 'react-use/lib';
|
||||
import { useAIAssistantAppService } from './use_ai_assistant_app_service';
|
||||
|
||||
export const useScope = () => {
|
||||
const service = useAIAssistantAppService();
|
||||
const scope = useObservable(service.scope$);
|
||||
return scope || 'all';
|
||||
};
|
|
@ -7,9 +7,11 @@
|
|||
|
||||
import type { DeeplyMockedKeys } from '@kbn/utility-types-jest';
|
||||
import {
|
||||
FunctionDefinition,
|
||||
MessageRole,
|
||||
ObservabilityAIAssistantChatService,
|
||||
} from '@kbn/observability-ai-assistant-plugin/public';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
type MockedChatService = DeeplyMockedKeys<ObservabilityAIAssistantChatService>;
|
||||
|
||||
|
@ -18,6 +20,7 @@ export const createMockChatService = (): MockedChatService => {
|
|||
chat: jest.fn(),
|
||||
complete: jest.fn(),
|
||||
sendAnalyticsEvent: jest.fn(),
|
||||
functions$: new BehaviorSubject<FunctionDefinition[]>([]) as MockedChatService['functions$'],
|
||||
getFunctions: jest.fn().mockReturnValue([]),
|
||||
hasFunction: jest.fn().mockReturnValue(false),
|
||||
hasRenderFunction: jest.fn().mockReturnValue(true),
|
||||
|
@ -29,6 +32,7 @@ export const createMockChatService = (): MockedChatService => {
|
|||
content: '',
|
||||
},
|
||||
}),
|
||||
getScope: jest.fn(),
|
||||
};
|
||||
return mockChatService;
|
||||
};
|
||||
|
|
|
@ -35,5 +35,6 @@
|
|||
"@kbn/code-editor",
|
||||
"@kbn/ml-plugin",
|
||||
"@kbn/share-plugin",
|
||||
"@kbn/ai-assistant-common",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
*/
|
||||
import type { JSONSchema7TypeName } from 'json-schema';
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { AssistantScope } from '@kbn/ai-assistant-common';
|
||||
import { ChatCompletionChunkEvent, MessageAddEvent } from '../conversation_complete';
|
||||
import { FunctionVisibility } from './function_visibility';
|
||||
import { AssistantScope } from '../types';
|
||||
export { FunctionVisibility };
|
||||
|
||||
type JSONSchemaOrPrimitive = CompatibleJSONSchema | string | number | boolean;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
import { IconType } from '@elastic/eui';
|
||||
import type { ToolSchema } from '@kbn/inference-plugin/common';
|
||||
import type { AssistantScope } from '@kbn/ai-assistant-common';
|
||||
import type { ObservabilityAIAssistantChatService } from '../public';
|
||||
import type { FunctionResponse } from './functions/types';
|
||||
|
||||
|
@ -145,6 +146,7 @@ export interface StarterPrompt {
|
|||
title: string;
|
||||
prompt: string;
|
||||
icon: IconType;
|
||||
scopes?: AssistantScope[];
|
||||
}
|
||||
|
||||
export interface ObservabilityAIAssistantScreenContext {
|
||||
|
@ -157,5 +159,3 @@ export interface ObservabilityAIAssistantScreenContext {
|
|||
actions?: Array<ScreenContextActionDefinition<any>>;
|
||||
starterPrompts?: StarterPrompt[];
|
||||
}
|
||||
|
||||
export type AssistantScope = 'observability' | 'search' | 'all';
|
||||
|
|
|
@ -13,7 +13,7 @@ export function filterFunctionDefinitions({
|
|||
}: {
|
||||
filter?: string;
|
||||
definitions: FunctionDefinition[];
|
||||
}) {
|
||||
}): FunctionDefinition[] {
|
||||
return filter
|
||||
? definitions.filter((fn) => {
|
||||
const matchesFilter =
|
||||
|
|
|
@ -56,7 +56,7 @@ function ChatContent({
|
|||
}) {
|
||||
const service = useObservabilityAIAssistant();
|
||||
const chatService = useObservabilityAIAssistantChatService();
|
||||
const { scope } = service;
|
||||
const scope = chatService.getScope();
|
||||
|
||||
const initialMessagesRef = useRef(initialMessages);
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
import type { DeeplyMockedKeys } from '@kbn/utility-types-jest';
|
||||
import { act, renderHook, type RenderHookResult } from '@testing-library/react-hooks';
|
||||
import { Subject } from 'rxjs';
|
||||
import { BehaviorSubject, Subject } from 'rxjs';
|
||||
import {
|
||||
MessageRole,
|
||||
type ObservabilityAIAssistantChatService,
|
||||
|
@ -14,6 +14,7 @@ import {
|
|||
} from '..';
|
||||
import {
|
||||
createInternalServerError,
|
||||
FunctionDefinition,
|
||||
StreamingChatResponseEventType,
|
||||
type StreamingChatResponseEventWithoutError,
|
||||
} from '../../common';
|
||||
|
@ -26,6 +27,7 @@ const mockChatService: MockedChatService = {
|
|||
chat: jest.fn(),
|
||||
complete: jest.fn(),
|
||||
sendAnalyticsEvent: jest.fn(),
|
||||
functions$: new BehaviorSubject<FunctionDefinition[]>([]) as MockedChatService['functions$'],
|
||||
getFunctions: jest.fn().mockReturnValue([]),
|
||||
hasFunction: jest.fn().mockReturnValue(false),
|
||||
hasRenderFunction: jest.fn().mockReturnValue(true),
|
||||
|
@ -37,6 +39,7 @@ const mockChatService: MockedChatService = {
|
|||
role: MessageRole.System,
|
||||
},
|
||||
}),
|
||||
getScope: jest.fn(),
|
||||
};
|
||||
|
||||
const addErrorMock = jest.fn();
|
||||
|
|
|
@ -10,7 +10,7 @@ import { merge } from 'lodash';
|
|||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { AbortError } from '@kbn/kibana-utils-plugin/common';
|
||||
import type { NotificationsStart } from '@kbn/core/public';
|
||||
import { AssistantScope } from '../../common/types';
|
||||
import type { AssistantScope } from '@kbn/ai-assistant-common';
|
||||
import {
|
||||
MessageRole,
|
||||
type Message,
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { noop } from 'lodash';
|
||||
import React from 'react';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { BehaviorSubject, Observable, of } from 'rxjs';
|
||||
import { AssistantScope } from '@kbn/ai-assistant-common';
|
||||
import type {
|
||||
ChatCompletionChunkEvent,
|
||||
StreamingChatResponseEventWithoutError,
|
||||
|
@ -21,12 +22,14 @@ import type {
|
|||
ObservabilityAIAssistantService,
|
||||
} from './types';
|
||||
import { buildFunctionElasticsearch, buildFunctionServiceSummary } from './utils/builders';
|
||||
import { FunctionDefinition } from '../common';
|
||||
|
||||
export const mockChatService: ObservabilityAIAssistantChatService = {
|
||||
sendAnalyticsEvent: noop,
|
||||
chat: (options) => new Observable<ChatCompletionChunkEvent>(),
|
||||
complete: (options) => new Observable<StreamingChatResponseEventWithoutError>(),
|
||||
getFunctions: () => [buildFunctionElasticsearch(), buildFunctionServiceSummary()],
|
||||
functions$: new BehaviorSubject<FunctionDefinition[]>([] as FunctionDefinition[]),
|
||||
renderFunction: (name) => (
|
||||
<div>
|
||||
{i18n.translate('xpack.observabilityAiAssistant.chatService.div.helloLabel', {
|
||||
|
@ -44,6 +47,7 @@ export const mockChatService: ObservabilityAIAssistantChatService = {
|
|||
content: 'System',
|
||||
},
|
||||
}),
|
||||
getScope: jest.fn(),
|
||||
};
|
||||
|
||||
export const mockService: ObservabilityAIAssistantService = {
|
||||
|
@ -60,7 +64,9 @@ export const mockService: ObservabilityAIAssistantService = {
|
|||
predefinedConversation$: new Observable(),
|
||||
},
|
||||
navigate: async () => of(),
|
||||
scope: 'all',
|
||||
setScope: jest.fn(),
|
||||
getScope: jest.fn(),
|
||||
scope$: new BehaviorSubject<AssistantScope>('all'),
|
||||
};
|
||||
|
||||
function createSetupContract(): ObservabilityAIAssistantPublicSetup {
|
||||
|
|
|
@ -10,6 +10,7 @@ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
|||
import type { Logger } from '@kbn/logging';
|
||||
import { withSuspense } from '@kbn/shared-ux-utility';
|
||||
import React, { type ComponentType, lazy, type Ref } from 'react';
|
||||
import { AssistantScope } from '@kbn/ai-assistant-common';
|
||||
import { registerTelemetryEventTypes } from './analytics';
|
||||
import { ObservabilityAIAssistantChatServiceContext } from './context/observability_ai_assistant_chat_service_context';
|
||||
import { ObservabilityAIAssistantMultipaneFlyoutContext } from './context/observability_ai_assistant_multipane_flyout_context';
|
||||
|
@ -41,16 +42,17 @@ export class ObservabilityAIAssistantPlugin
|
|||
{
|
||||
logger: Logger;
|
||||
service?: ObservabilityAIAssistantService;
|
||||
scopeFromConfig?: AssistantScope;
|
||||
|
||||
constructor(context: PluginInitializerContext<ConfigSchema>) {
|
||||
this.logger = context.logger.get();
|
||||
this.scopeFromConfig = context.config.get().scope;
|
||||
}
|
||||
setup(
|
||||
coreSetup: CoreSetup,
|
||||
pluginsSetup: ObservabilityAIAssistantPluginSetupDependencies
|
||||
): ObservabilityAIAssistantPublicSetup {
|
||||
registerTelemetryEventTypes(coreSetup.analytics);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -65,7 +67,8 @@ export class ObservabilityAIAssistantPlugin
|
|||
coreStart.application.capabilities.observabilityAIAssistant[
|
||||
aiAssistantCapabilities.show
|
||||
] === true,
|
||||
scope: 'observability',
|
||||
scope: this.scopeFromConfig || 'observability',
|
||||
scopeIsMutable: !!this.scopeFromConfig,
|
||||
}));
|
||||
|
||||
const withProviders = <P extends {}, R = {}>(
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import type { HttpFetchOptions } from '@kbn/core/public';
|
||||
import { filter, lastValueFrom, Observable } from 'rxjs';
|
||||
import { BehaviorSubject, filter, lastValueFrom, Observable } from 'rxjs';
|
||||
import { ReadableStream } from 'stream/web';
|
||||
import { AbortError } from '@kbn/kibana-utils-plugin/common';
|
||||
import {
|
||||
|
@ -17,6 +17,7 @@ import {
|
|||
import { concatenateChatCompletionChunks } from '../../common/utils/concatenate_chat_completion_chunks';
|
||||
import type { ObservabilityAIAssistantChatService } from '../types';
|
||||
import { createChatService } from './create_chat_service';
|
||||
import { AssistantScope } from '@kbn/ai-assistant-common';
|
||||
|
||||
async function getConcatenatedMessage(
|
||||
response$: Observable<StreamingChatResponseEventWithoutError>
|
||||
|
@ -70,7 +71,7 @@ describe('createChatService', () => {
|
|||
apiClient: clientSpy,
|
||||
registrations: [],
|
||||
signal: new AbortController().signal,
|
||||
scope: 'observability',
|
||||
scope$: new BehaviorSubject<AssistantScope>('observability'),
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -23,7 +23,8 @@ import {
|
|||
throwError,
|
||||
timestamp,
|
||||
} from 'rxjs';
|
||||
import { AssistantScope } from '../../common/types';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import type { AssistantScope } from '@kbn/ai-assistant-common';
|
||||
import { ChatCompletionChunkEvent, Message, MessageRole } from '../../common';
|
||||
import {
|
||||
StreamingChatResponseEventType,
|
||||
|
@ -31,11 +32,15 @@ import {
|
|||
type StreamingChatResponseEvent,
|
||||
type StreamingChatResponseEventWithoutError,
|
||||
} from '../../common/conversation_complete';
|
||||
import { FunctionRegistry, FunctionResponse } from '../../common/functions/types';
|
||||
import {
|
||||
FunctionDefinition,
|
||||
FunctionRegistry,
|
||||
FunctionResponse,
|
||||
} from '../../common/functions/types';
|
||||
import { filterFunctionDefinitions } from '../../common/utils/filter_function_definitions';
|
||||
import { throwSerializedChatCompletionErrors } from '../../common/utils/throw_serialized_chat_completion_errors';
|
||||
import { untilAborted } from '../../common/utils/until_aborted';
|
||||
import { sendEvent } from '../analytics';
|
||||
import { TelemetryEventTypeWithPayload, sendEvent } from '../analytics';
|
||||
import type {
|
||||
ObservabilityAIAssistantAPIClient,
|
||||
ObservabilityAIAssistantAPIClientRequestParamsOf,
|
||||
|
@ -48,6 +53,7 @@ import type {
|
|||
} from '../types';
|
||||
import { readableStreamReaderIntoObservable } from '../utils/readable_stream_reader_into_observable';
|
||||
import { complete } from './complete';
|
||||
import { ChatActionClickHandler } from '../components/chat/types';
|
||||
|
||||
const MIN_DELAY = 10;
|
||||
|
||||
|
@ -133,60 +139,131 @@ function serialize(
|
|||
);
|
||||
}
|
||||
|
||||
export async function createChatService({
|
||||
analytics,
|
||||
signal: setupAbortSignal,
|
||||
registrations,
|
||||
apiClient,
|
||||
scope,
|
||||
}: {
|
||||
analytics: AnalyticsServiceStart;
|
||||
signal: AbortSignal;
|
||||
registrations: ChatRegistrationRenderFunction[];
|
||||
apiClient: ObservabilityAIAssistantAPIClient;
|
||||
scope: AssistantScope;
|
||||
}): Promise<ObservabilityAIAssistantChatService> {
|
||||
const functionRegistry: FunctionRegistry = new Map();
|
||||
class ChatService {
|
||||
private functionRegistry: FunctionRegistry;
|
||||
private renderFunctionRegistry: Map<string, RenderFunction<unknown, FunctionResponse>>;
|
||||
private abortSignal: AbortSignal;
|
||||
private apiClient: ObservabilityAIAssistantAPIClient;
|
||||
public scope$: BehaviorSubject<AssistantScope>;
|
||||
private analytics: AnalyticsServiceStart;
|
||||
private registrations: ChatRegistrationRenderFunction[];
|
||||
private systemMessage: string;
|
||||
public functions$: BehaviorSubject<FunctionDefinition[]>;
|
||||
|
||||
const renderFunctionRegistry: Map<string, RenderFunction<unknown, FunctionResponse>> = new Map();
|
||||
constructor({
|
||||
abortSignal,
|
||||
apiClient,
|
||||
scope$,
|
||||
analytics,
|
||||
registrations,
|
||||
}: {
|
||||
abortSignal: AbortSignal;
|
||||
apiClient: ObservabilityAIAssistantAPIClient;
|
||||
scope$: BehaviorSubject<AssistantScope>;
|
||||
analytics: AnalyticsServiceStart;
|
||||
registrations: ChatRegistrationRenderFunction[];
|
||||
}) {
|
||||
this.functionRegistry = new Map();
|
||||
this.renderFunctionRegistry = new Map();
|
||||
this.abortSignal = abortSignal;
|
||||
this.apiClient = apiClient;
|
||||
this.scope$ = scope$;
|
||||
this.analytics = analytics;
|
||||
this.registrations = registrations;
|
||||
this.systemMessage = '';
|
||||
this.functions$ = new BehaviorSubject([] as FunctionDefinition[]);
|
||||
scope$.subscribe(() => {
|
||||
this.initialize();
|
||||
});
|
||||
}
|
||||
|
||||
const [{ functionDefinitions, systemMessage }] = await Promise.all([
|
||||
apiClient('GET /internal/observability_ai_assistant/{scope}/functions', {
|
||||
signal: setupAbortSignal,
|
||||
params: {
|
||||
path: {
|
||||
scope,
|
||||
private getClient = () => {
|
||||
return {
|
||||
chat: this.chat,
|
||||
complete: this.complete,
|
||||
};
|
||||
};
|
||||
|
||||
async initialize() {
|
||||
this.functionRegistry = new Map();
|
||||
const [{ functionDefinitions, systemMessage }] = await Promise.all([
|
||||
this.apiClient('GET /internal/observability_ai_assistant/{scope}/functions', {
|
||||
signal: this.abortSignal,
|
||||
params: {
|
||||
path: {
|
||||
scope: this.getScope(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
...registrations.map((registration) => {
|
||||
return registration({
|
||||
registerRenderFunction: (name, renderFn) => {
|
||||
renderFunctionRegistry.set(name, renderFn);
|
||||
},
|
||||
});
|
||||
}),
|
||||
]);
|
||||
}),
|
||||
...this.registrations.map((registration) => {
|
||||
return registration({
|
||||
registerRenderFunction: (name, renderFn) => {
|
||||
this.renderFunctionRegistry.set(name, renderFn);
|
||||
},
|
||||
});
|
||||
}),
|
||||
]);
|
||||
|
||||
functionDefinitions.forEach((fn) => {
|
||||
functionRegistry.set(fn.name, fn);
|
||||
});
|
||||
functionDefinitions.forEach((fn) => {
|
||||
this.functionRegistry.set(fn.name, fn);
|
||||
});
|
||||
this.systemMessage = systemMessage;
|
||||
|
||||
const getFunctions = (options?: { contexts?: string[]; filter?: string }) => {
|
||||
return filterFunctionDefinitions({
|
||||
...options,
|
||||
definitions: functionDefinitions,
|
||||
this.functions$.next(this.getFunctions());
|
||||
}
|
||||
|
||||
public sendAnalyticsEvent = (event: TelemetryEventTypeWithPayload) => {
|
||||
sendEvent(this.analytics, event);
|
||||
};
|
||||
|
||||
public renderFunction = (
|
||||
name: string,
|
||||
args: string | undefined,
|
||||
response: { data?: string; content?: string },
|
||||
onActionClick: ChatActionClickHandler
|
||||
) => {
|
||||
const fn = this.renderFunctionRegistry.get(name);
|
||||
|
||||
if (!fn) {
|
||||
throw new Error(`Function ${name} not found`);
|
||||
}
|
||||
|
||||
const parsedArguments = args ? JSON.parse(args) : {};
|
||||
|
||||
const parsedResponse = {
|
||||
content: JSON.parse(response.content ?? '{}'),
|
||||
data: JSON.parse(response.data ?? '{}'),
|
||||
};
|
||||
|
||||
return fn?.({
|
||||
response: parsedResponse,
|
||||
arguments: parsedArguments,
|
||||
onActionClick,
|
||||
});
|
||||
};
|
||||
|
||||
function callStreamingApi<TEndpoint extends ObservabilityAIAssistantAPIEndpoint>(
|
||||
public getFunctions = (options?: {
|
||||
contexts?: string[];
|
||||
filter?: string;
|
||||
}): FunctionDefinition[] => {
|
||||
return filterFunctionDefinitions({
|
||||
...options,
|
||||
definitions: Array.from(this.functionRegistry.values()),
|
||||
}).filter((value) => {
|
||||
return value.scopes
|
||||
? value.scopes?.includes(this.getScope()) || value.scopes?.includes('all')
|
||||
: true;
|
||||
});
|
||||
};
|
||||
|
||||
public callStreamingApi<TEndpoint extends ObservabilityAIAssistantAPIEndpoint>(
|
||||
endpoint: TEndpoint,
|
||||
options: {
|
||||
signal: AbortSignal;
|
||||
} & ObservabilityAIAssistantAPIClientRequestParamsOf<TEndpoint>
|
||||
): Observable<StreamingChatResponseEventWithoutError> {
|
||||
return from(
|
||||
apiClient(endpoint, {
|
||||
this.apiClient(endpoint, {
|
||||
...options,
|
||||
asResponse: true,
|
||||
rawResponse: true,
|
||||
|
@ -194,101 +271,103 @@ export async function createChatService({
|
|||
).pipe(serialize(options.signal));
|
||||
}
|
||||
|
||||
const client: Pick<ObservabilityAIAssistantChatService, 'chat' | 'complete'> = {
|
||||
chat(name: string, { connectorId, messages, functionCall, functions, signal }) {
|
||||
return callStreamingApi('POST /internal/observability_ai_assistant/chat', {
|
||||
params: {
|
||||
body: {
|
||||
name,
|
||||
messages,
|
||||
connectorId,
|
||||
functionCall,
|
||||
functions: functions ?? [],
|
||||
scope,
|
||||
},
|
||||
},
|
||||
signal,
|
||||
}).pipe(
|
||||
filter(
|
||||
(line): line is ChatCompletionChunkEvent =>
|
||||
line.type === StreamingChatResponseEventType.ChatCompletionChunk
|
||||
)
|
||||
);
|
||||
},
|
||||
complete({
|
||||
getScreenContexts,
|
||||
connectorId,
|
||||
conversationId,
|
||||
messages,
|
||||
persist,
|
||||
disableFunctions,
|
||||
signal,
|
||||
public hasFunction = (name: string) => {
|
||||
return this.functionRegistry.has(name);
|
||||
};
|
||||
|
||||
instructions,
|
||||
}) {
|
||||
return complete(
|
||||
{
|
||||
getScreenContexts,
|
||||
connectorId,
|
||||
conversationId,
|
||||
public hasRenderFunction = (name: string) => {
|
||||
return this.renderFunctionRegistry.has(name);
|
||||
};
|
||||
|
||||
public getSystemMessage = (): Message => {
|
||||
return {
|
||||
'@timestamp': new Date().toISOString(),
|
||||
message: {
|
||||
role: MessageRole.System,
|
||||
content: this.systemMessage,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
public chat: ObservabilityAIAssistantChatService['chat'] = (
|
||||
name: string,
|
||||
{ connectorId, messages, functionCall, functions, signal }
|
||||
) => {
|
||||
return this.callStreamingApi('POST /internal/observability_ai_assistant/chat', {
|
||||
params: {
|
||||
body: {
|
||||
name,
|
||||
messages,
|
||||
persist,
|
||||
disableFunctions,
|
||||
connectorId,
|
||||
functionCall,
|
||||
functions: functions ?? [],
|
||||
scope: this.getScope(),
|
||||
},
|
||||
},
|
||||
signal,
|
||||
}).pipe(
|
||||
filter(
|
||||
(line): line is ChatCompletionChunkEvent =>
|
||||
line.type === StreamingChatResponseEventType.ChatCompletionChunk
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
public complete: ObservabilityAIAssistantChatService['complete'] = ({
|
||||
getScreenContexts,
|
||||
connectorId,
|
||||
conversationId,
|
||||
messages,
|
||||
persist,
|
||||
disableFunctions,
|
||||
signal,
|
||||
instructions,
|
||||
}) => {
|
||||
return complete(
|
||||
{
|
||||
getScreenContexts,
|
||||
connectorId,
|
||||
conversationId,
|
||||
messages,
|
||||
persist,
|
||||
disableFunctions,
|
||||
signal,
|
||||
client: this.getClient(),
|
||||
instructions,
|
||||
scope: this.getScope(),
|
||||
},
|
||||
({ params }) => {
|
||||
return this.callStreamingApi('POST /internal/observability_ai_assistant/chat/complete', {
|
||||
params,
|
||||
signal,
|
||||
client,
|
||||
instructions,
|
||||
scope,
|
||||
},
|
||||
({ params }) => {
|
||||
return callStreamingApi('POST /internal/observability_ai_assistant/chat/complete', {
|
||||
params,
|
||||
signal,
|
||||
});
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
sendAnalyticsEvent: (event) => {
|
||||
sendEvent(analytics, event);
|
||||
},
|
||||
renderFunction: (name, args, response, onActionClick) => {
|
||||
const fn = renderFunctionRegistry.get(name);
|
||||
|
||||
if (!fn) {
|
||||
throw new Error(`Function ${name} not found`);
|
||||
});
|
||||
}
|
||||
|
||||
const parsedArguments = args ? JSON.parse(args) : {};
|
||||
|
||||
const parsedResponse = {
|
||||
content: JSON.parse(response.content ?? '{}'),
|
||||
data: JSON.parse(response.data ?? '{}'),
|
||||
};
|
||||
|
||||
return fn?.({
|
||||
response: parsedResponse,
|
||||
arguments: parsedArguments,
|
||||
onActionClick,
|
||||
});
|
||||
},
|
||||
getFunctions,
|
||||
hasFunction: (name: string) => {
|
||||
return functionRegistry.has(name);
|
||||
},
|
||||
hasRenderFunction: (name: string) => {
|
||||
return renderFunctionRegistry.has(name);
|
||||
},
|
||||
getSystemMessage: (): Message => {
|
||||
return {
|
||||
'@timestamp': new Date().toISOString(),
|
||||
message: {
|
||||
role: MessageRole.System,
|
||||
content: systemMessage,
|
||||
},
|
||||
};
|
||||
},
|
||||
...client,
|
||||
);
|
||||
};
|
||||
|
||||
public getScope() {
|
||||
return this.scope$.value;
|
||||
}
|
||||
}
|
||||
|
||||
export async function createChatService({
|
||||
analytics,
|
||||
signal: setupAbortSignal,
|
||||
registrations,
|
||||
apiClient,
|
||||
scope$,
|
||||
}: {
|
||||
analytics: AnalyticsServiceStart;
|
||||
signal: AbortSignal;
|
||||
registrations: ChatRegistrationRenderFunction[];
|
||||
apiClient: ObservabilityAIAssistantAPIClient;
|
||||
scope$: BehaviorSubject<AssistantScope>;
|
||||
}): Promise<ObservabilityAIAssistantChatService> {
|
||||
return new ChatService({
|
||||
analytics,
|
||||
apiClient,
|
||||
scope$,
|
||||
registrations,
|
||||
abortSignal: setupAbortSignal,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
*/
|
||||
|
||||
import type { DeeplyMockedKeys } from '@kbn/utility-types-jest';
|
||||
import { MessageRole } from '../../common';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { FunctionDefinition, MessageRole } from '../../common';
|
||||
import type { ObservabilityAIAssistantChatService } from '../types';
|
||||
|
||||
type MockedChatService = DeeplyMockedKeys<ObservabilityAIAssistantChatService>;
|
||||
|
@ -16,6 +17,7 @@ export const createMockChatService = (): MockedChatService => {
|
|||
chat: jest.fn(),
|
||||
complete: jest.fn(),
|
||||
sendAnalyticsEvent: jest.fn(),
|
||||
functions$: new BehaviorSubject<FunctionDefinition[]>([]) as MockedChatService['functions$'],
|
||||
getFunctions: jest.fn().mockReturnValue([]),
|
||||
hasFunction: jest.fn().mockReturnValue(false),
|
||||
hasRenderFunction: jest.fn().mockReturnValue(true),
|
||||
|
@ -27,6 +29,7 @@ export const createMockChatService = (): MockedChatService => {
|
|||
content: 'system',
|
||||
},
|
||||
}),
|
||||
getScope: jest.fn(),
|
||||
};
|
||||
return mockChatService;
|
||||
};
|
||||
|
|
|
@ -8,11 +8,8 @@
|
|||
import type { AnalyticsServiceStart, CoreStart } from '@kbn/core/public';
|
||||
import { compact, without } from 'lodash';
|
||||
import { BehaviorSubject, debounceTime, filter, lastValueFrom, of, Subject, take } from 'rxjs';
|
||||
import type {
|
||||
AssistantScope,
|
||||
Message,
|
||||
ObservabilityAIAssistantScreenContext,
|
||||
} from '../../common/types';
|
||||
import { type AssistantScope, filterScopes } from '@kbn/ai-assistant-common';
|
||||
import type { Message, ObservabilityAIAssistantScreenContext } from '../../common/types';
|
||||
import { createFunctionRequestMessage } from '../../common/utils/create_function_request_message';
|
||||
import { createFunctionResponseMessage } from '../../common/utils/create_function_response_message';
|
||||
import { createCallObservabilityAIAssistantAPI } from '../api';
|
||||
|
@ -24,11 +21,13 @@ export function createService({
|
|||
coreStart,
|
||||
enabled,
|
||||
scope,
|
||||
scopeIsMutable,
|
||||
}: {
|
||||
analytics: AnalyticsServiceStart;
|
||||
coreStart: CoreStart;
|
||||
enabled: boolean;
|
||||
scope: AssistantScope;
|
||||
scopeIsMutable: boolean;
|
||||
}): ObservabilityAIAssistantService {
|
||||
const apiClient = createCallObservabilityAIAssistantAPI(coreStart);
|
||||
|
||||
|
@ -39,6 +38,17 @@ export function createService({
|
|||
]);
|
||||
const predefinedConversation$ = new Subject<{ messages: Message[]; title?: string }>();
|
||||
|
||||
const scope$ = new BehaviorSubject<AssistantScope>(scope);
|
||||
|
||||
const getScreenContexts = () => {
|
||||
const currentScope = scope$.value;
|
||||
const screenContexts = screenContexts$.value.map(({ starterPrompts, ...rest }) => ({
|
||||
...rest,
|
||||
starterPrompts: starterPrompts?.filter(filterScopes(currentScope)),
|
||||
}));
|
||||
return screenContexts;
|
||||
};
|
||||
|
||||
return {
|
||||
isEnabled: () => {
|
||||
return enabled;
|
||||
|
@ -48,12 +58,10 @@ export function createService({
|
|||
},
|
||||
start: async ({ signal }) => {
|
||||
const mod = await import('./create_chat_service');
|
||||
return await mod.createChatService({ analytics, apiClient, signal, registrations, scope });
|
||||
return await mod.createChatService({ analytics, apiClient, signal, registrations, scope$ });
|
||||
},
|
||||
callApi: apiClient,
|
||||
getScreenContexts() {
|
||||
return screenContexts$.value;
|
||||
},
|
||||
getScreenContexts,
|
||||
setScreenContext: (context: ObservabilityAIAssistantScreenContext) => {
|
||||
screenContexts$.next(screenContexts$.value.concat(context));
|
||||
|
||||
|
@ -83,7 +91,7 @@ export function createService({
|
|||
name: 'context',
|
||||
content: {
|
||||
screenDescription: compact(
|
||||
screenContexts$.value.map((context) => context.screenDescription)
|
||||
getScreenContexts().map((context) => context.screenDescription)
|
||||
).join('\n\n'),
|
||||
},
|
||||
})
|
||||
|
@ -95,6 +103,12 @@ export function createService({
|
|||
},
|
||||
predefinedConversation$: predefinedConversation$.asObservable(),
|
||||
},
|
||||
scope,
|
||||
setScope: (newScope: AssistantScope) => {
|
||||
if (!scopeIsMutable) {
|
||||
scope$.next(newScope);
|
||||
}
|
||||
},
|
||||
getScope: () => scope$.value,
|
||||
scope$,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,8 +6,9 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { StarterPrompt } from '../../common/types';
|
||||
|
||||
export const defaultStarterPrompts = [
|
||||
export const defaultStarterPrompts: StarterPrompt[] = [
|
||||
{
|
||||
title: i18n.translate(
|
||||
'xpack.observabilityAiAssistant.app.starterPrompts.exampleQuestions.title',
|
||||
|
@ -20,6 +21,7 @@ export const defaultStarterPrompts = [
|
|||
}
|
||||
),
|
||||
icon: 'sparkles',
|
||||
scopes: ['all'],
|
||||
},
|
||||
{
|
||||
title: i18n.translate(
|
||||
|
@ -33,6 +35,7 @@ export const defaultStarterPrompts = [
|
|||
}
|
||||
),
|
||||
icon: 'inspect',
|
||||
scopes: ['all'],
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.observabilityAiAssistant.app.starterPrompts.doIHaveAlerts.title', {
|
||||
|
@ -45,6 +48,7 @@ export const defaultStarterPrompts = [
|
|||
}
|
||||
),
|
||||
icon: 'bell',
|
||||
scopes: ['observability'],
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.observabilityAiAssistant.app.starterPrompts.whatAreSlos.title', {
|
||||
|
@ -54,5 +58,6 @@ export const defaultStarterPrompts = [
|
|||
defaultMessage: 'What are SLOs?',
|
||||
}),
|
||||
icon: 'bullseye',
|
||||
scopes: ['observability'],
|
||||
},
|
||||
];
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { noop } from 'lodash';
|
||||
import React from 'react';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { ChatCompletionChunkEvent, MessageRole } from '.';
|
||||
import { BehaviorSubject, Observable, of } from 'rxjs';
|
||||
import { AssistantScope } from '@kbn/ai-assistant-common';
|
||||
import { ChatCompletionChunkEvent, FunctionDefinition, MessageRole } from '.';
|
||||
import type { StreamingChatResponseEventWithoutError } from '../common/conversation_complete';
|
||||
import type { ObservabilityAIAssistantAPIClient } from './api';
|
||||
import type { ObservabilityAIAssistantChatService, ObservabilityAIAssistantService } from './types';
|
||||
|
@ -36,6 +37,10 @@ export const createStorybookChatService = (): ObservabilityAIAssistantChatServic
|
|||
content: 'System',
|
||||
},
|
||||
}),
|
||||
functions$: new BehaviorSubject<FunctionDefinition[]>(
|
||||
[]
|
||||
) as ObservabilityAIAssistantChatService['functions$'],
|
||||
getScope: () => 'all',
|
||||
});
|
||||
|
||||
export const createStorybookService = (): ObservabilityAIAssistantService => ({
|
||||
|
@ -52,5 +57,7 @@ export const createStorybookService = (): ObservabilityAIAssistantService => ({
|
|||
predefinedConversation$: new Observable(),
|
||||
},
|
||||
navigate: async () => of(),
|
||||
scope: 'observability',
|
||||
scope$: new BehaviorSubject<AssistantScope>('all') as ObservabilityAIAssistantService['scope$'],
|
||||
getScope: () => 'all',
|
||||
setScope: () => {},
|
||||
});
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
import type { LicensingPluginStart } from '@kbn/licensing-plugin/public';
|
||||
import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public';
|
||||
import type { Observable } from 'rxjs';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import type { AssistantScope } from '@kbn/ai-assistant-common';
|
||||
import type {
|
||||
ChatCompletionChunkEvent,
|
||||
MessageAddEvent,
|
||||
|
@ -19,7 +21,6 @@ import type {
|
|||
ObservabilityAIAssistantScreenContext,
|
||||
PendingMessage,
|
||||
AdHocInstruction,
|
||||
AssistantScope,
|
||||
} from '../common/types';
|
||||
import type { TelemetryEventTypeWithPayload } from './analytics';
|
||||
import type { ObservabilityAIAssistantAPIClient } from './api';
|
||||
|
@ -76,6 +77,7 @@ export interface ObservabilityAIAssistantChatService {
|
|||
filter?: string;
|
||||
scope: AssistantScope;
|
||||
}) => FunctionDefinition[];
|
||||
functions$: BehaviorSubject<FunctionDefinition[]>;
|
||||
hasFunction: (name: string) => boolean;
|
||||
getSystemMessage: () => Message;
|
||||
hasRenderFunction: (name: string) => boolean;
|
||||
|
@ -83,9 +85,9 @@ export interface ObservabilityAIAssistantChatService {
|
|||
name: string,
|
||||
args: string | undefined,
|
||||
response: { data?: string; content?: string },
|
||||
onActionClick: ChatActionClickHandler,
|
||||
scope?: AssistantScope
|
||||
onActionClick: ChatActionClickHandler
|
||||
) => React.ReactNode;
|
||||
getScope: () => AssistantScope;
|
||||
}
|
||||
|
||||
export interface ObservabilityAIAssistantConversationService {
|
||||
|
@ -102,7 +104,9 @@ export interface ObservabilityAIAssistantService {
|
|||
getScreenContexts: () => ObservabilityAIAssistantScreenContext[];
|
||||
conversations: ObservabilityAIAssistantConversationService;
|
||||
navigate: (callback: () => void) => Promise<Observable<MessageAddEvent>>;
|
||||
scope: AssistantScope;
|
||||
scope$: BehaviorSubject<AssistantScope>;
|
||||
setScope: (scope: AssistantScope) => void;
|
||||
getScope: () => AssistantScope;
|
||||
}
|
||||
|
||||
export type RenderFunction<TArguments, TResponse extends FunctionResponse> = (options: {
|
||||
|
@ -120,7 +124,9 @@ export type ChatRegistrationRenderFunction = ({}: {
|
|||
registerRenderFunction: RegisterRenderFunctionDefinition;
|
||||
}) => Promise<void>;
|
||||
|
||||
export interface ConfigSchema {}
|
||||
export interface ConfigSchema {
|
||||
scope?: AssistantScope;
|
||||
}
|
||||
|
||||
export interface ObservabilityAIAssistantPluginSetupDependencies {
|
||||
licensing: {};
|
||||
|
|
|
@ -10,6 +10,7 @@ import { schema, type TypeOf } from '@kbn/config-schema';
|
|||
export const config = schema.object({
|
||||
enabled: schema.boolean({ defaultValue: true }),
|
||||
modelId: schema.maybe(schema.string()),
|
||||
scope: schema.maybe(schema.oneOf([schema.literal('observability'), schema.literal('search')])),
|
||||
});
|
||||
|
||||
export type ObservabilityAIAssistantConfig = TypeOf<typeof config>;
|
||||
|
|
|
@ -47,7 +47,7 @@ export const config: PluginConfigDescriptor<ObservabilityAIAssistantConfig> = {
|
|||
level: 'warning',
|
||||
}),
|
||||
],
|
||||
exposeToBrowser: {},
|
||||
exposeToBrowser: { scope: true },
|
||||
schema: configSchema,
|
||||
};
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import { context as otelContext } from '@opentelemetry/api';
|
|||
import * as t from 'io-ts';
|
||||
import { from, map } from 'rxjs';
|
||||
import { Readable } from 'stream';
|
||||
import { AssistantScope } from '../../../common/types';
|
||||
import { AssistantScope } from '@kbn/ai-assistant-common';
|
||||
import { aiAssistantSimulatedFunctionCalling } from '../..';
|
||||
import { createFunctionResponseMessage } from '../../../common/utils/create_function_response_message';
|
||||
import { withoutTokenCountEvents } from '../../../common/utils/without_token_count_events';
|
||||
|
|
|
@ -61,7 +61,7 @@ const getFunctionsRoute = createObservabilityAIAssistantServerRoute({
|
|||
const availableFunctionNames = functionDefinitions.map((def) => def.name);
|
||||
|
||||
return {
|
||||
functionDefinitions: functionClient.getFunctions().map((fn) => fn.definition),
|
||||
functionDefinitions,
|
||||
systemMessage: getSystemMessageFromInstructions({
|
||||
applicationInstructions: functionClient.getInstructions(scope),
|
||||
userInstructions,
|
||||
|
|
|
@ -134,11 +134,14 @@ export const functionRt = t.intersection([
|
|||
}),
|
||||
]);
|
||||
|
||||
export const starterPromptRt: t.Type<StarterPrompt> = t.type({
|
||||
title: t.string,
|
||||
prompt: t.string,
|
||||
icon: t.any,
|
||||
});
|
||||
export const starterPromptRt: t.Type<StarterPrompt> = t.intersection([
|
||||
t.type({
|
||||
title: t.string,
|
||||
prompt: t.string,
|
||||
icon: t.any,
|
||||
}),
|
||||
t.partial({ scopes: t.array(assistantScopeType) }),
|
||||
]);
|
||||
|
||||
export const screenContextRt: t.Type<ObservabilityAIAssistantScreenContextRequest> = t.partial({
|
||||
description: t.string,
|
||||
|
|
|
@ -9,12 +9,9 @@
|
|||
import Ajv, { type ErrorObject, type ValidateFunction } from 'ajv';
|
||||
import dedent from 'dedent';
|
||||
import { compact, keyBy } from 'lodash';
|
||||
import { type AssistantScope, filterScopes } from '@kbn/ai-assistant-common';
|
||||
import { FunctionVisibility, type FunctionResponse } from '../../../common/functions/types';
|
||||
import type {
|
||||
AssistantScope,
|
||||
Message,
|
||||
ObservabilityAIAssistantScreenContextRequest,
|
||||
} from '../../../common/types';
|
||||
import type { Message, ObservabilityAIAssistantScreenContextRequest } from '../../../common/types';
|
||||
import { filterFunctionDefinitions } from '../../../common/utils/filter_function_definitions';
|
||||
import type {
|
||||
FunctionCallChatFunction,
|
||||
|
@ -114,11 +111,7 @@ export class ChatFunctionClient {
|
|||
}
|
||||
|
||||
getInstructions(scope: AssistantScope): InstructionOrCallback[] {
|
||||
return this.instructions
|
||||
.filter(
|
||||
(instruction) => instruction.scopes.includes(scope) || instruction.scopes.includes('all')
|
||||
)
|
||||
.map((i) => i.instruction);
|
||||
return this.instructions.filter(filterScopes(scope)).map((i) => i.instruction);
|
||||
}
|
||||
|
||||
hasAction(name: string) {
|
||||
|
@ -133,9 +126,7 @@ export class ChatFunctionClient {
|
|||
scope?: AssistantScope;
|
||||
} = {}): FunctionHandler[] {
|
||||
const allFunctions = Array.from(this.functionRegistry.values())
|
||||
.filter(({ handler, scopes }) =>
|
||||
scope ? scopes.includes(scope) || scopes.includes('all') : true
|
||||
)
|
||||
.filter(filterScopes(scope))
|
||||
.map(({ handler }) => handler);
|
||||
|
||||
const functionsByName = keyBy(allFunctions, (definition) => definition.definition.name);
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
} from 'rxjs';
|
||||
import { Readable } from 'stream';
|
||||
import { v4 } from 'uuid';
|
||||
import type { AssistantScope } from '@kbn/ai-assistant-common';
|
||||
import { resourceNames } from '..';
|
||||
import { ObservabilityAIAssistantConnectorType } from '../../../common/connectors';
|
||||
import {
|
||||
|
@ -52,7 +53,6 @@ import {
|
|||
type KnowledgeBaseEntry,
|
||||
type Message,
|
||||
type AdHocInstruction,
|
||||
AssistantScope,
|
||||
} from '../../../common/types';
|
||||
import { withoutTokenCountEvents } from '../../../common/utils/without_token_count_events';
|
||||
import { CONTEXT_FUNCTION_NAME } from '../../functions/context';
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
switchMap,
|
||||
throwError,
|
||||
} from 'rxjs';
|
||||
import type { AssistantScope } from '@kbn/ai-assistant-common';
|
||||
import { CONTEXT_FUNCTION_NAME } from '../../../functions/context';
|
||||
import { createFunctionNotFoundError, Message, MessageRole } from '../../../../common';
|
||||
import {
|
||||
|
@ -28,7 +29,7 @@ import {
|
|||
MessageOrChatEvent,
|
||||
} from '../../../../common/conversation_complete';
|
||||
import { FunctionVisibility } from '../../../../common/functions/types';
|
||||
import { AdHocInstruction, AssistantScope, Instruction } from '../../../../common/types';
|
||||
import { AdHocInstruction, Instruction } from '../../../../common/types';
|
||||
import { createFunctionResponseMessage } from '../../../../common/utils/create_function_response_message';
|
||||
import { emitWithConcatenatedMessage } from '../../../../common/utils/emit_with_concatenated_message';
|
||||
import { withoutTokenCountEvents } from '../../../../common/utils/without_token_count_events';
|
||||
|
@ -137,6 +138,7 @@ function getFunctionDefinitions({
|
|||
functionClient,
|
||||
functionLimitExceeded,
|
||||
disableFunctions,
|
||||
scope,
|
||||
}: {
|
||||
functionClient: ChatFunctionClient;
|
||||
functionLimitExceeded: boolean;
|
||||
|
@ -145,13 +147,14 @@ function getFunctionDefinitions({
|
|||
| {
|
||||
except: string[];
|
||||
};
|
||||
scope: AssistantScope;
|
||||
}) {
|
||||
if (functionLimitExceeded || disableFunctions === true) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let systemFunctions = functionClient
|
||||
.getFunctions()
|
||||
.getFunctions({ scope })
|
||||
.map((fn) => fn.definition)
|
||||
.filter(
|
||||
(def) =>
|
||||
|
@ -213,6 +216,7 @@ export function continueConversation({
|
|||
functionLimitExceeded,
|
||||
functionClient,
|
||||
disableFunctions,
|
||||
scope,
|
||||
});
|
||||
|
||||
const messagesWithUpdatedSystemMessage = replaceSystemMessage(
|
||||
|
|
|
@ -12,8 +12,8 @@ import type { SecurityPluginStart } from '@kbn/security-plugin/server';
|
|||
import { getSpaceIdFromPath } from '@kbn/spaces-plugin/common';
|
||||
import type { TaskManagerSetupContract } from '@kbn/task-manager-plugin/server';
|
||||
import { once } from 'lodash';
|
||||
import type { AssistantScope } from '@kbn/ai-assistant-common';
|
||||
import {
|
||||
AssistantScope,
|
||||
KnowledgeBaseEntryRole,
|
||||
ObservabilityAIAssistantScreenContextRequest,
|
||||
} from '../../common/types';
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import type { FromSchema } from 'json-schema-to-ts';
|
||||
import { Observable } from 'rxjs';
|
||||
import type { AssistantScope } from '@kbn/ai-assistant-common';
|
||||
import { ChatCompletionChunkEvent, ChatEvent } from '../../common/conversation_complete';
|
||||
import type {
|
||||
CompatibleJSONSchema,
|
||||
|
@ -17,7 +18,6 @@ import type {
|
|||
Message,
|
||||
ObservabilityAIAssistantScreenContextRequest,
|
||||
InstructionOrPlainText,
|
||||
AssistantScope,
|
||||
} from '../../common/types';
|
||||
import type { ObservabilityAIAssistantRouteHandlerResources } from '../routes/types';
|
||||
import { ChatFunctionClient } from './chat_function_client';
|
||||
|
|
|
@ -45,7 +45,8 @@
|
|||
"@kbn/core-elasticsearch-server",
|
||||
"@kbn/core-ui-settings-server",
|
||||
"@kbn/inference-plugin",
|
||||
"@kbn/management-settings-ids"
|
||||
"@kbn/management-settings-ids",
|
||||
"@kbn/ai-assistant-common"
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import { useTheme } from '../../hooks/use_theme';
|
|||
import { useNavControlScreenContext } from '../../hooks/use_nav_control_screen_context';
|
||||
import { SharedProviders } from '../../utils/shared_providers';
|
||||
import { ObservabilityAIAssistantAppPluginStartDependencies } from '../../types';
|
||||
import { useNavControlScope } from '../../hooks/use_nav_control_scope';
|
||||
|
||||
interface NavControlWithProviderDeps {
|
||||
appService: AIAssistantAppService;
|
||||
|
@ -61,6 +62,7 @@ export function NavControl() {
|
|||
const [hasBeenOpened, setHasBeenOpened] = useState(false);
|
||||
|
||||
useNavControlScreenContext();
|
||||
useNavControlScope();
|
||||
|
||||
const chatService = useAbortableAsync(
|
||||
({ signal }) => {
|
||||
|
|
|
@ -32,7 +32,10 @@ function getVisibility(
|
|||
return categoryId !== DEFAULT_APP_CATEGORIES.security.id;
|
||||
}
|
||||
|
||||
return categoryId === DEFAULT_APP_CATEGORIES.observability.id;
|
||||
return [
|
||||
DEFAULT_APP_CATEGORIES.observability.id,
|
||||
DEFAULT_APP_CATEGORIES.enterpriseSearch.id,
|
||||
].includes(categoryId);
|
||||
}
|
||||
|
||||
export function useIsNavControlVisible({ coreStart, pluginsStart }: UseIsNavControlVisibleProps) {
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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 { useEffect } from 'react';
|
||||
import { useAIAssistantAppService } from '@kbn/ai-assistant';
|
||||
import { AssistantScope } from '@kbn/ai-assistant-common';
|
||||
import { useObservable } from 'react-use/lib';
|
||||
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/public';
|
||||
import { useKibana } from './use_kibana';
|
||||
|
||||
const scopeUrlLookup: Record<string, AssistantScope> = {
|
||||
[DEFAULT_APP_CATEGORIES.observability.id]: 'observability',
|
||||
[DEFAULT_APP_CATEGORIES.enterpriseSearch.id]: 'search',
|
||||
};
|
||||
|
||||
export function useNavControlScope() {
|
||||
const service = useAIAssistantAppService();
|
||||
|
||||
const {
|
||||
services: { application },
|
||||
} = useKibana();
|
||||
|
||||
const currentApplication = useObservable(application.currentAppId$);
|
||||
const applications = useObservable(application.applications$);
|
||||
|
||||
useEffect(() => {
|
||||
const currentCategoryId =
|
||||
(currentApplication && applications?.get(currentApplication)?.category?.id) ||
|
||||
DEFAULT_APP_CATEGORIES.kibana.id;
|
||||
const newScope = Object.entries(scopeUrlLookup).find(
|
||||
([categoryId]) => categoryId === currentCategoryId
|
||||
)?.[1];
|
||||
if (newScope && newScope !== service.getScope()) {
|
||||
service.setScope(newScope);
|
||||
}
|
||||
}, [applications, currentApplication, service]);
|
||||
}
|
|
@ -18,10 +18,8 @@ import {
|
|||
StreamingChatResponseEvent,
|
||||
StreamingChatResponseEventType,
|
||||
} from '@kbn/observability-ai-assistant-plugin/common';
|
||||
import type {
|
||||
AssistantScope,
|
||||
ObservabilityAIAssistantScreenContext,
|
||||
} from '@kbn/observability-ai-assistant-plugin/common/types';
|
||||
import type { ObservabilityAIAssistantScreenContext } from '@kbn/observability-ai-assistant-plugin/common/types';
|
||||
import type { AssistantScope } from '@kbn/ai-assistant-common';
|
||||
import { throwSerializedChatCompletionErrors } from '@kbn/observability-ai-assistant-plugin/common/utils/throw_serialized_chat_completion_errors';
|
||||
import {
|
||||
isSupportedConnectorType,
|
||||
|
|
|
@ -68,6 +68,7 @@
|
|||
"@kbn/task-manager-plugin",
|
||||
"@kbn/cloud-plugin",
|
||||
"@kbn/logs-data-access-plugin",
|
||||
"@kbn/ai-assistant-common",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
],
|
||||
"optionalPlugins": [
|
||||
"cloud",
|
||||
"serverless",
|
||||
"usageCollection",
|
||||
],
|
||||
"requiredBundles": [
|
||||
|
|
|
@ -30,6 +30,7 @@ export function ConversationViewWithProps() {
|
|||
getConversationHref={(id: string) =>
|
||||
http?.basePath.prepend(`/app/searchAssistant/conversations/${id || ''}`) || ''
|
||||
}
|
||||
scope="search"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
36
x-pack/plugins/search_assistant/server/functions/index.ts
Normal file
36
x-pack/plugins/search_assistant/server/functions/index.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 { RegistrationCallback } from '@kbn/observability-ai-assistant-plugin/server';
|
||||
|
||||
export const registerFunctions: (isServerless: boolean) => RegistrationCallback =
|
||||
(isServerless: boolean) =>
|
||||
async ({ client, functions, resources, signal }) => {
|
||||
functions.registerInstruction({
|
||||
instruction: `You are a helpful assistant for Elasticsearch. Your goal is to help Elasticsearch users accomplish tasks using Kibana and Elasticsearch. You can help them construct queries, index data, search data, use Elasticsearch APIs, generate sample data, visualise and analyze data.
|
||||
|
||||
It's very important to not assume what the user means. Ask them for clarification if needed.
|
||||
|
||||
If you are unsure about which function should be used and with what arguments, ask the user for clarification or confirmation.
|
||||
|
||||
In KQL ("kqlFilter")) escaping happens with double quotes, not single quotes. Some characters that need escaping are: ':()\\\
|
||||
/\". Always put a field value in double quotes. Best: service.name:\"opbeans-go\". Wrong: service.name:opbeans-go. This is very important!
|
||||
|
||||
You can use Github-flavored Markdown in your responses. If a function returns an array, consider using a Markdown table to format the response.
|
||||
|
||||
Note that the Elasticsearch query DSL is the preferred language. Do not use ES|QL.
|
||||
|
||||
If you want to call a function or tool, only call it a single time per message. Wait until the function has been executed and its results
|
||||
returned to you, before executing the same tool or another tool again if needed.
|
||||
|
||||
The user is able to change the language which they want you to reply in on the settings page of the AI Assistant for Observability, which can be found in the ${
|
||||
isServerless ? `Project settings.` : `Stack Management app under the option AI Assistants`
|
||||
}.
|
||||
If the user asks how to change the language, reply in the same language the user asked in.`,
|
||||
scopes: ['search'],
|
||||
});
|
||||
};
|
|
@ -4,12 +4,12 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { PluginInitializerContext } from '@kbn/core/server';
|
||||
import { SearchAssistantPlugin } from './plugin';
|
||||
|
||||
export { config } from './config';
|
||||
|
||||
export function plugin() {
|
||||
return new SearchAssistantPlugin();
|
||||
}
|
||||
export const plugin = (initializerContext: PluginInitializerContext) =>
|
||||
new SearchAssistantPlugin(initializerContext);
|
||||
|
||||
export type { SearchAssistantPluginSetup, SearchAssistantPluginStart } from './types';
|
||||
|
|
|
@ -5,20 +5,37 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { Plugin } from '@kbn/core/server';
|
||||
import type { CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/server';
|
||||
|
||||
import type { SearchAssistantPluginSetup, SearchAssistantPluginStart } from './types';
|
||||
import type {
|
||||
SearchAssistantPluginSetup,
|
||||
SearchAssistantPluginStart,
|
||||
SearchAssistantPluginStartDependencies,
|
||||
} from './types';
|
||||
|
||||
import { registerFunctions } from './functions';
|
||||
|
||||
export class SearchAssistantPlugin
|
||||
implements Plugin<SearchAssistantPluginSetup, SearchAssistantPluginStart>
|
||||
implements
|
||||
Plugin<
|
||||
SearchAssistantPluginSetup,
|
||||
SearchAssistantPluginStart,
|
||||
{},
|
||||
SearchAssistantPluginStartDependencies
|
||||
>
|
||||
{
|
||||
constructor() {}
|
||||
isServerless: boolean;
|
||||
|
||||
constructor(context: PluginInitializerContext) {
|
||||
this.isServerless = context.env.packageInfo.buildFlavor === 'serverless';
|
||||
}
|
||||
|
||||
public setup() {
|
||||
return {};
|
||||
}
|
||||
|
||||
public start() {
|
||||
public start(coreStart: CoreStart, pluginsStart: SearchAssistantPluginStartDependencies) {
|
||||
pluginsStart.observabilityAIAssistant.service.register(registerFunctions(this.isServerless));
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ObservabilityAIAssistantServerStart } from '@kbn/observability-ai-assistant-plugin/server';
|
||||
import { ServerlessPluginStart } from '@kbn/serverless/server';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface SearchAssistantPluginSetup {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface SearchAssistantPluginStart {}
|
||||
|
||||
export interface SearchAssistantPluginStartDependencies {
|
||||
observabilityAIAssistant: ObservabilityAIAssistantServerStart;
|
||||
serverless?: ServerlessPluginStart;
|
||||
}
|
||||
|
|
|
@ -22,7 +22,8 @@
|
|||
"@kbn/config-schema",
|
||||
"@kbn/ai-assistant",
|
||||
"@kbn/i18n",
|
||||
"@kbn/shared-ux-router"
|
||||
"@kbn/shared-ux-router",
|
||||
"@kbn/serverless"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
StreamingChatResponseEvent,
|
||||
} from '@kbn/observability-ai-assistant-plugin/common';
|
||||
import { Readable } from 'stream';
|
||||
import { AssistantScope } from '@kbn/observability-ai-assistant-plugin/common/types';
|
||||
import type { AssistantScope } from '@kbn/ai-assistant-common';
|
||||
import { CreateTest } from '../../../common/config';
|
||||
|
||||
function decodeEvents(body: Readable | string) {
|
||||
|
|
|
@ -184,6 +184,7 @@
|
|||
"@kbn/mock-idp-utils",
|
||||
"@kbn/cloud-security-posture-common",
|
||||
"@kbn/saved-objects-management-plugin",
|
||||
"@kbn/alerting-types"
|
||||
"@kbn/alerting-types",
|
||||
"@kbn/ai-assistant-common"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
MessageRole,
|
||||
StreamingChatResponseEvent,
|
||||
} from '@kbn/observability-ai-assistant-plugin/common';
|
||||
import { AssistantScope } from '@kbn/observability-ai-assistant-plugin/common/types';
|
||||
import type { AssistantScope } from '@kbn/ai-assistant-common';
|
||||
import { Readable } from 'stream';
|
||||
import type { InternalRequestHeader, RoleCredentials } from '../../../../../../../shared/services';
|
||||
import { ObservabilityAIAssistantApiClient } from '../../../common/observability_ai_assistant_api_client';
|
||||
|
|
|
@ -99,5 +99,6 @@
|
|||
"@kbn/cloud-security-posture-common",
|
||||
"@kbn/security-plugin-types-common",
|
||||
"@kbn/core-saved-objects-import-export-server-internal",
|
||||
"@kbn/ai-assistant-common",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -3311,6 +3311,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/ai-assistant-common@link:x-pack/packages/kbn-ai-assistant-common":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/ai-assistant-management-plugin@link:src/plugins/ai_assistant_management/selection":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue