diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/functions/route.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/functions/route.ts index 8a61248d4e70e..c402a0506736f 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/functions/route.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/functions/route.ts @@ -68,7 +68,7 @@ const getFunctionsRoute = createObservabilityAIAssistantServerRoute({ systemMessage: getSystemMessageFromInstructions({ applicationInstructions: functionClient.getInstructions(), userInstructions, - adHocInstructions: [], + adHocInstructions: functionClient.getAdhocInstructions(), availableFunctionNames, }), }; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/chat_function_client/index.test.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/chat_function_client/index.test.ts index 3d83c470de0c5..0d911b497cbbb 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/chat_function_client/index.test.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/chat_function_client/index.test.ts @@ -7,6 +7,7 @@ import dedent from 'dedent'; import { ChatFunctionClient, GET_DATA_ON_SCREEN_FUNCTION_NAME } from '.'; import { FunctionVisibility } from '../../../common/functions/types'; +import { AdHocInstruction } from '../../../common/types'; describe('chatFunctionClient', () => { describe('when executing a function with invalid arguments', () => { @@ -86,6 +87,7 @@ describe('chatFunctionClient', () => { ]); const functions = client.getFunctions(); + const adHocInstructions = client.getAdhocInstructions(); expect(functions[0]).toEqual({ definition: { @@ -97,7 +99,7 @@ describe('chatFunctionClient', () => { respond: expect.any(Function), }); - expect(functions[0].definition.description).toContain( + expect(adHocInstructions[0].text).toContain( dedent(`my_dummy_data: My dummy data my_other_dummy_data: My other dummy data `) @@ -128,4 +130,52 @@ describe('chatFunctionClient', () => { }); }); }); + + describe('when adhoc instructions are provided', () => { + let client: ChatFunctionClient; + + beforeEach(() => { + client = new ChatFunctionClient([]); + }); + + describe('register an adhoc Instruction', () => { + it('should register a new adhoc instruction', () => { + const adhocInstruction: AdHocInstruction = { + text: 'Test adhoc instruction', + instruction_type: 'application_instruction', + }; + + client.registerAdhocInstruction(adhocInstruction); + + expect(client.getAdhocInstructions()).toContainEqual(adhocInstruction); + }); + }); + + describe('retrieve adHoc instructions', () => { + it('should return all registered adhoc instructions', () => { + const firstAdhocInstruction: AdHocInstruction = { + text: 'First adhoc instruction', + instruction_type: 'application_instruction', + }; + + const secondAdhocInstruction: AdHocInstruction = { + text: 'Second adhoc instruction', + instruction_type: 'application_instruction', + }; + + client.registerAdhocInstruction(firstAdhocInstruction); + client.registerAdhocInstruction(secondAdhocInstruction); + + const adhocInstructions = client.getAdhocInstructions(); + + expect(adhocInstructions).toEqual([firstAdhocInstruction, secondAdhocInstruction]); + }); + + it('should return an empty array if no adhoc instructions are registered', () => { + const adhocInstructions = client.getAdhocInstructions(); + + expect(adhocInstructions).toEqual([]); + }); + }); + }); }); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/chat_function_client/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/chat_function_client/index.ts index 97def121e8593..ad4b7f0a4fc92 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/chat_function_client/index.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/chat_function_client/index.ts @@ -10,13 +10,18 @@ import Ajv, { type ErrorObject, type ValidateFunction } from 'ajv'; import dedent from 'dedent'; import { compact, keyBy } from 'lodash'; import { FunctionVisibility, type FunctionResponse } from '../../../common/functions/types'; -import type { Message, ObservabilityAIAssistantScreenContextRequest } from '../../../common/types'; +import type { + AdHocInstruction, + Message, + ObservabilityAIAssistantScreenContextRequest, +} from '../../../common/types'; import { filterFunctionDefinitions } from '../../../common/utils/filter_function_definitions'; import type { FunctionCallChatFunction, FunctionHandler, FunctionHandlerRegistry, InstructionOrCallback, + RegisterAdHocInstruction, RegisterFunction, RegisterInstruction, } from '../types'; @@ -35,6 +40,8 @@ export const GET_DATA_ON_SCREEN_FUNCTION_NAME = 'get_data_on_screen'; export class ChatFunctionClient { private readonly instructions: InstructionOrCallback[] = []; + private readonly adhocInstructions: AdHocInstruction[] = []; + private readonly functionRegistry: FunctionHandlerRegistry = new Map(); private readonly validators: Map = new Map(); @@ -49,9 +56,7 @@ export class ChatFunctionClient { this.registerFunction( { name: GET_DATA_ON_SCREEN_FUNCTION_NAME, - description: dedent(`Get data that is on the screen: - ${allData.map((data) => `${data.name}: ${data.description}`).join('\n')} - `), + description: `Retrieve the structured data of content currently visible on the user's screen. Use this tool to understand what the user is viewing at this moment to provide more accurate and context-aware responses to their questions.`, visibility: FunctionVisibility.AssistantOnly, parameters: { type: 'object', @@ -75,6 +80,13 @@ export class ChatFunctionClient { }; } ); + + this.registerAdhocInstruction({ + text: `The ${GET_DATA_ON_SCREEN_FUNCTION_NAME} function will retrieve specific content from the user's screen by specifying a data key. Use this tool to provide context-aware responses. Available data: ${dedent( + allData.map((data) => `${data.name}: ${data.description}`).join('\n') + )}`, + instruction_type: 'application_instruction', + }); } this.actions.forEach((action) => { @@ -95,6 +107,10 @@ export class ChatFunctionClient { this.instructions.push(instruction); }; + registerAdhocInstruction: RegisterAdHocInstruction = (instruction: AdHocInstruction) => { + this.adhocInstructions.push(instruction); + }; + validate(name: string, parameters: unknown) { const validator = this.validators.get(name)!; if (!validator) { @@ -111,6 +127,10 @@ export class ChatFunctionClient { return this.instructions; } + getAdhocInstructions(): AdHocInstruction[] { + return this.adhocInstructions; + } + hasAction(name: string) { return !!this.actions.find((action) => action.name === name)!; } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.test.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.test.ts index 0476bda1af8a2..8da2a0d843b11 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.test.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.test.ts @@ -124,6 +124,7 @@ describe('Observability AI Assistant client', () => { getActions: jest.fn(), validate: jest.fn(), getInstructions: jest.fn(), + getAdhocInstructions: jest.fn(), } as any; let llmSimulator: LlmSimulator; @@ -173,6 +174,7 @@ describe('Observability AI Assistant client', () => { knowledgeBaseServiceMock.getUserInstructions.mockResolvedValue([]); functionClientMock.getInstructions.mockReturnValue(['system']); + functionClientMock.getAdhocInstructions.mockReturnValue([]); return new ObservabilityAIAssistantClient({ actionsClient: actionsClientMock, diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.ts index a050edc8008fb..162220ec7a7f1 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.ts @@ -47,12 +47,12 @@ import { } from '../../../common/conversation_complete'; import { CompatibleJSONSchema } from '../../../common/functions/types'; import { + AdHocInstruction, type Conversation, type ConversationCreateRequest, type ConversationUpdateRequest, type KnowledgeBaseEntry, type Message, - type AdHocInstruction, } from '../../../common/types'; import { withoutTokenCountEvents } from '../../../common/utils/without_token_count_events'; import { CONTEXT_FUNCTION_NAME } from '../../functions/context'; @@ -210,6 +210,9 @@ export class ObservabilityAIAssistantClient { const userInstructions$ = from(this.getKnowledgeBaseUserInstructions()).pipe(shareReplay()); + const registeredAdhocInstructions = functionClient.getAdhocInstructions(); + const allAdHocInstructions = adHocInstructions.concat(registeredAdhocInstructions); + // from the initial messages, override any system message with // the one that is based on the instructions (registered, request, kb) const messagesWithUpdatedSystemMessage$ = userInstructions$.pipe( @@ -219,7 +222,7 @@ export class ObservabilityAIAssistantClient { getSystemMessageFromInstructions({ applicationInstructions: functionClient.getInstructions(), userInstructions, - adHocInstructions, + adHocInstructions: allAdHocInstructions, availableFunctionNames: functionClient .getFunctions() .map((fn) => fn.definition.name), diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/operators/continue_conversation.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/operators/continue_conversation.ts index 66204c96f31cb..4c55d32362878 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/operators/continue_conversation.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/operators/continue_conversation.ts @@ -178,7 +178,7 @@ export function continueConversation({ chat, signal, functionCallsLeft, - adHocInstructions, + adHocInstructions = [], userInstructions, logger, disableFunctions, @@ -213,11 +213,14 @@ export function continueConversation({ disableFunctions, }); + const registeredAdhocInstructions = functionClient.getAdhocInstructions(); + const allAdHocInstructions = adHocInstructions.concat(registeredAdhocInstructions); + const messagesWithUpdatedSystemMessage = replaceSystemMessage( getSystemMessageFromInstructions({ applicationInstructions: functionClient.getInstructions(), userInstructions, - adHocInstructions, + adHocInstructions: allAdHocInstructions, availableFunctionNames: definitions.map((def) => def.name), }), initialMessages diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/types.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/types.ts index b00da8d6518fa..2df3f36163972 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/types.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/types.ts @@ -18,6 +18,7 @@ import type { Message, ObservabilityAIAssistantScreenContextRequest, InstructionOrPlainText, + AdHocInstruction, } from '../../common/types'; import type { ObservabilityAIAssistantRouteHandlerResources } from '../routes/types'; import { ChatFunctionClient } from './chat_function_client'; @@ -76,6 +77,8 @@ export type RegisterInstructionCallback = ({ export type RegisterInstruction = (...instruction: InstructionOrCallback[]) => void; +export type RegisterAdHocInstruction = (...instruction: AdHocInstruction[]) => void; + export type RegisterFunction = < TParameters extends CompatibleJSONSchema = any, TResponse extends FunctionResponse = any, diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/util/get_system_message_from_instructions.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/util/get_system_message_from_instructions.ts index b2797577883ba..570449673084b 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/util/get_system_message_from_instructions.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/util/get_system_message_from_instructions.ts @@ -45,7 +45,7 @@ export function getSystemMessageFromInstructions({ const adHocInstructionsWithId = adHocInstructions.map((adHocInstruction) => ({ ...adHocInstruction, - doc_id: adHocInstruction.doc_id ?? v4(), + doc_id: adHocInstruction?.doc_id ?? v4(), })); // split ad hoc instructions into user instructions and application instructions diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.test.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.test.ts index 479ffeaa40f4f..de02e4cf841ce 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.test.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.test.ts @@ -91,6 +91,7 @@ describe('observabilityAIAssistant rule_connector', () => { getFunctionClient: async () => ({ getFunctions: () => [], getInstructions: () => [], + getAdhocInstructions: () => [], }), }, context: { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.ts index 19f1408275e1f..59b883fef9c18 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.ts @@ -230,7 +230,7 @@ If available, include the link of the conversation at the end of your answer.` availableFunctionNames: functionClient.getFunctions().map((fn) => fn.definition.name), applicationInstructions: functionClient.getInstructions(), userInstructions: [], - adHocInstructions: [], + adHocInstructions: functionClient.getAdhocInstructions(), }), }, },