mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Obs AI Assistant] remove AdHocInstruction (#212621)
## Refactor Instruction Handling: Remove Adhoc Instructions and Standardize API Closes #211190 ## Summary This PR removes the concept of *Adhoc Instructions* and standardizes how instructions are handled across the system. The `/complete` API now explicitly accepts **user instructions**, and redundant functions have been removed or replaced. ## Changes Implemented ### Renamed API Parameter - The `/complete` API’s `instructions` parameter is now `userInstructions`. - Application instructions can no longer be sent via the API (future support can be added if needed). ### Removed Redundant Functions - Deleted `getAdhocInstructions` and `registerAdhocInstruction`. - API-passed instructions are now treated as **user instructions**. - Updated function calls to use `getInstructions` and `registerInstruction` instead. ### Refactored Function Calls - Replaced `registerAdhocInstruction` with `registerInstruction`. ## Impact & Benefits - **Simplifies** instruction handling by removing unnecessary complexity. - **Aligns** API behavior with internal instruction management.
This commit is contained in:
parent
b9472b6ade
commit
56219f1c90
15 changed files with 119 additions and 166 deletions
|
@ -94,14 +94,6 @@ export interface Instruction {
|
|||
text: string;
|
||||
}
|
||||
|
||||
export interface AdHocInstruction {
|
||||
id?: string;
|
||||
text: string;
|
||||
instruction_type: 'user_instruction' | 'application_instruction';
|
||||
}
|
||||
|
||||
export type InstructionOrPlainText = string | Instruction;
|
||||
|
||||
export enum KnowledgeBaseType {
|
||||
// user instructions are included in the system prompt regardless of the user's input
|
||||
UserInstruction = 'user_instruction',
|
||||
|
|
|
@ -20,7 +20,7 @@ import type {
|
|||
Message,
|
||||
ObservabilityAIAssistantScreenContext,
|
||||
PendingMessage,
|
||||
AdHocInstruction,
|
||||
Instruction,
|
||||
} from '../common/types';
|
||||
import type { TelemetryEventTypeWithPayload } from './analytics';
|
||||
import type { ObservabilityAIAssistantAPIClient } from './api';
|
||||
|
@ -71,7 +71,7 @@ export interface ObservabilityAIAssistantChatService {
|
|||
except: string[];
|
||||
};
|
||||
signal: AbortSignal;
|
||||
instructions?: AdHocInstruction[];
|
||||
instructions?: Array<string | Instruction>;
|
||||
scopes: AssistantScope[];
|
||||
}) => Observable<StreamingChatResponseEventWithoutError>;
|
||||
getFunctions: (options?: {
|
||||
|
|
|
@ -9,6 +9,7 @@ import { toBooleanRt } from '@kbn/io-ts-utils';
|
|||
import { context as otelContext } from '@opentelemetry/api';
|
||||
import * as t from 'io-ts';
|
||||
import { from, map } from 'rxjs';
|
||||
import { v4 } from 'uuid';
|
||||
import { Readable } from 'stream';
|
||||
import { AssistantScope } from '@kbn/ai-assistant-common';
|
||||
import { aiAssistantSimulatedFunctionCalling } from '../..';
|
||||
|
@ -20,6 +21,7 @@ import { observableIntoStream } from '../../service/util/observable_into_stream'
|
|||
import { withAssistantSpan } from '../../service/util/with_assistant_span';
|
||||
import { recallAndScore } from '../../utils/recall/recall_and_score';
|
||||
import { createObservabilityAIAssistantServerRoute } from '../create_observability_ai_assistant_server_route';
|
||||
import { Instruction } from '../../../common/types';
|
||||
import { assistantScopeType, functionRt, messageRt, screenContextRt } from '../runtime_types';
|
||||
import { ObservabilityAIAssistantRouteHandlerResources } from '../types';
|
||||
|
||||
|
@ -40,14 +42,11 @@ const chatCompleteBaseRt = t.type({
|
|||
}),
|
||||
]),
|
||||
instructions: t.array(
|
||||
t.intersection([
|
||||
t.partial({ id: t.string }),
|
||||
t.union([
|
||||
t.string,
|
||||
t.type({
|
||||
id: t.string,
|
||||
text: t.string,
|
||||
instruction_type: t.union([
|
||||
t.literal('user_instruction'),
|
||||
t.literal('application_instruction'),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
),
|
||||
|
@ -251,7 +250,7 @@ async function chatComplete(
|
|||
title,
|
||||
persist,
|
||||
screenContexts,
|
||||
instructions,
|
||||
instructions: userInstructionsOrStrings,
|
||||
disableFunctions,
|
||||
scopes,
|
||||
},
|
||||
|
@ -269,6 +268,16 @@ async function chatComplete(
|
|||
scopes,
|
||||
});
|
||||
|
||||
const userInstructions: Instruction[] | undefined = userInstructionsOrStrings?.map(
|
||||
(userInstructionOrString) =>
|
||||
typeof userInstructionOrString === 'string'
|
||||
? {
|
||||
text: userInstructionOrString,
|
||||
id: v4(),
|
||||
}
|
||||
: userInstructionOrString
|
||||
);
|
||||
|
||||
const response$ = client.complete({
|
||||
messages,
|
||||
connectorId,
|
||||
|
@ -277,7 +286,7 @@ async function chatComplete(
|
|||
persist,
|
||||
signal,
|
||||
functionClient,
|
||||
instructions,
|
||||
userInstructions,
|
||||
simulateFunctionCalling,
|
||||
disableFunctions,
|
||||
});
|
||||
|
|
|
@ -53,7 +53,7 @@ const getFunctionsRoute = createObservabilityAIAssistantServerRoute({
|
|||
|
||||
const client = await service.getClient({ request });
|
||||
|
||||
const [functionClient, userInstructions] = await Promise.all([
|
||||
const [functionClient, kbUserInstructions] = await Promise.all([
|
||||
service.getFunctionClient({
|
||||
signal: controller.signal,
|
||||
resources,
|
||||
|
@ -73,8 +73,8 @@ const getFunctionsRoute = createObservabilityAIAssistantServerRoute({
|
|||
functionDefinitions,
|
||||
systemMessage: getSystemMessageFromInstructions({
|
||||
applicationInstructions: functionClient.getInstructions(),
|
||||
userInstructions,
|
||||
adHocInstructions: functionClient.getAdhocInstructions(),
|
||||
kbUserInstructions,
|
||||
apiUserInstructions: [],
|
||||
availableFunctionNames,
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
import dedent from 'dedent';
|
||||
import { ChatFunctionClient, GET_DATA_ON_SCREEN_FUNCTION_NAME } from '.';
|
||||
import { FunctionVisibility } from '../../../common/functions/types';
|
||||
import { AdHocInstruction } from '../../../common/types';
|
||||
import { Logger } from '@kbn/logging';
|
||||
import { RegisterInstructionCallback } from '../types';
|
||||
|
||||
describe('chatFunctionClient', () => {
|
||||
describe('when executing a function with invalid arguments', () => {
|
||||
|
@ -89,7 +89,7 @@ describe('chatFunctionClient', () => {
|
|||
]);
|
||||
|
||||
const functions = client.getFunctions();
|
||||
const adHocInstructions = client.getAdhocInstructions();
|
||||
const instructions = client.getInstructions();
|
||||
|
||||
expect(functions[0]).toEqual({
|
||||
definition: {
|
||||
|
@ -101,7 +101,11 @@ describe('chatFunctionClient', () => {
|
|||
respond: expect.any(Function),
|
||||
});
|
||||
|
||||
expect(adHocInstructions[0].text).toContain(
|
||||
expect(
|
||||
(instructions[0] as RegisterInstructionCallback)({
|
||||
availableFunctionNames: [GET_DATA_ON_SCREEN_FUNCTION_NAME],
|
||||
})
|
||||
).toContain(
|
||||
dedent(`my_dummy_data: My dummy data
|
||||
my_other_dummy_data: My other dummy data
|
||||
`)
|
||||
|
@ -134,48 +138,39 @@ describe('chatFunctionClient', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('when adhoc instructions are provided', () => {
|
||||
describe('when 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',
|
||||
};
|
||||
describe('register an Instruction', () => {
|
||||
it('should register a new instruction', () => {
|
||||
const instruction = 'Test instruction';
|
||||
|
||||
client.registerAdhocInstruction(adhocInstruction);
|
||||
client.registerInstruction(instruction);
|
||||
|
||||
expect(client.getAdhocInstructions()).toContainEqual(adhocInstruction);
|
||||
expect(client.getInstructions()).toContainEqual(instruction);
|
||||
});
|
||||
});
|
||||
|
||||
describe('retrieve adHoc instructions', () => {
|
||||
it('should return all registered adhoc instructions', () => {
|
||||
const firstAdhocInstruction: AdHocInstruction = {
|
||||
text: 'First adhoc instruction',
|
||||
instruction_type: 'application_instruction',
|
||||
};
|
||||
describe('retrieve instructions', () => {
|
||||
it('should return all registered instructions', () => {
|
||||
const firstInstruction = 'First instruction';
|
||||
|
||||
const secondAdhocInstruction: AdHocInstruction = {
|
||||
text: 'Second adhoc instruction',
|
||||
instruction_type: 'application_instruction',
|
||||
};
|
||||
const secondInstruction = 'Second instruction';
|
||||
|
||||
client.registerAdhocInstruction(firstAdhocInstruction);
|
||||
client.registerAdhocInstruction(secondAdhocInstruction);
|
||||
client.registerInstruction(firstInstruction);
|
||||
client.registerInstruction(secondInstruction);
|
||||
|
||||
const adhocInstructions = client.getAdhocInstructions();
|
||||
const instructions = client.getInstructions();
|
||||
|
||||
expect(adhocInstructions).toEqual([firstAdhocInstruction, secondAdhocInstruction]);
|
||||
expect(instructions).toEqual([firstInstruction, secondInstruction]);
|
||||
});
|
||||
|
||||
it('should return an empty array if no adhoc instructions are registered', () => {
|
||||
const adhocInstructions = client.getAdhocInstructions();
|
||||
it('should return an empty array if no instructions are registered', () => {
|
||||
const adhocInstructions = client.getInstructions();
|
||||
|
||||
expect(adhocInstructions).toEqual([]);
|
||||
});
|
||||
|
|
|
@ -11,18 +11,13 @@ import dedent from 'dedent';
|
|||
import { compact, keyBy } from 'lodash';
|
||||
import { Logger } from '@kbn/logging';
|
||||
import { FunctionVisibility, type FunctionResponse } from '../../../common/functions/types';
|
||||
import type {
|
||||
AdHocInstruction,
|
||||
Message,
|
||||
ObservabilityAIAssistantScreenContextRequest,
|
||||
} from '../../../common/types';
|
||||
import type { Message, ObservabilityAIAssistantScreenContextRequest } from '../../../common/types';
|
||||
import { filterFunctionDefinitions } from '../../../common/utils/filter_function_definitions';
|
||||
import type {
|
||||
FunctionCallChatFunction,
|
||||
FunctionHandler,
|
||||
FunctionHandlerRegistry,
|
||||
InstructionOrCallback,
|
||||
RegisterAdHocInstruction,
|
||||
RegisterFunction,
|
||||
RegisterInstruction,
|
||||
} from '../types';
|
||||
|
@ -41,7 +36,6 @@ 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<string, ValidateFunction> = new Map();
|
||||
|
@ -82,12 +76,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.registerInstruction(({ availableFunctionNames }) =>
|
||||
availableFunctionNames.includes(GET_DATA_ON_SCREEN_FUNCTION_NAME)
|
||||
? `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')
|
||||
)}`
|
||||
: undefined
|
||||
);
|
||||
}
|
||||
|
||||
this.actions.forEach((action) => {
|
||||
|
@ -108,10 +103,6 @@ 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) {
|
||||
|
@ -128,10 +119,6 @@ export class ChatFunctionClient {
|
|||
return this.instructions;
|
||||
}
|
||||
|
||||
getAdhocInstructions(): AdHocInstruction[] {
|
||||
return this.adhocInstructions;
|
||||
}
|
||||
|
||||
hasAction(name: string) {
|
||||
return !!this.actions.find((action) => action.name === name)!;
|
||||
}
|
||||
|
|
|
@ -130,7 +130,6 @@ describe('Observability AI Assistant client', () => {
|
|||
getActions: jest.fn(),
|
||||
validate: jest.fn(),
|
||||
getInstructions: jest.fn(),
|
||||
getAdhocInstructions: jest.fn(),
|
||||
} as any;
|
||||
|
||||
let llmSimulator: LlmSimulator;
|
||||
|
@ -177,7 +176,6 @@ describe('Observability AI Assistant client', () => {
|
|||
knowledgeBaseServiceMock.getUserInstructions.mockResolvedValue([]);
|
||||
|
||||
functionClientMock.getInstructions.mockReturnValue([EXPECTED_STORED_SYSTEM_MESSAGE]);
|
||||
functionClientMock.getAdhocInstructions.mockReturnValue([]);
|
||||
|
||||
return new ObservabilityAIAssistantClient({
|
||||
config: {} as ObservabilityAIAssistantConfig,
|
||||
|
|
|
@ -46,7 +46,7 @@ import {
|
|||
import { convertMessagesForInference } from '../../../common/convert_messages_for_inference';
|
||||
import { CompatibleJSONSchema } from '../../../common/functions/types';
|
||||
import {
|
||||
type AdHocInstruction,
|
||||
type Instruction,
|
||||
type Conversation,
|
||||
type ConversationCreateRequest,
|
||||
type ConversationUpdateRequest,
|
||||
|
@ -172,7 +172,7 @@ export class ObservabilityAIAssistantClient {
|
|||
functionClient,
|
||||
connectorId,
|
||||
simulateFunctionCalling = false,
|
||||
instructions: adHocInstructions = [],
|
||||
userInstructions: apiUserInstructions = [],
|
||||
messages: initialMessages,
|
||||
signal,
|
||||
persist,
|
||||
|
@ -191,7 +191,7 @@ export class ObservabilityAIAssistantClient {
|
|||
title?: string;
|
||||
isPublic?: boolean;
|
||||
kibanaPublicUrl?: string;
|
||||
instructions?: AdHocInstruction[];
|
||||
userInstructions?: Instruction[];
|
||||
simulateFunctionCalling?: boolean;
|
||||
disableFunctions?:
|
||||
| boolean
|
||||
|
@ -207,18 +207,16 @@ export class ObservabilityAIAssistantClient {
|
|||
const conversationId = persist ? predefinedConversationId || v4() : '';
|
||||
|
||||
if (persist && !isConversationUpdate && kibanaPublicUrl) {
|
||||
adHocInstructions.push({
|
||||
instruction_type: 'application_instruction',
|
||||
text: `This conversation will be persisted in Kibana and available at this url: ${
|
||||
functionClient.registerInstruction(
|
||||
`This conversation will be persisted in Kibana and available at this url: ${
|
||||
kibanaPublicUrl + `/app/observabilityAIAssistant/conversations/${conversationId}`
|
||||
}.`,
|
||||
});
|
||||
}.`
|
||||
);
|
||||
}
|
||||
|
||||
const userInstructions$ = from(this.getKnowledgeBaseUserInstructions()).pipe(shareReplay());
|
||||
|
||||
const registeredAdhocInstructions = functionClient.getAdhocInstructions();
|
||||
const allAdHocInstructions = adHocInstructions.concat(registeredAdhocInstructions);
|
||||
const kbUserInstructions$ = from(this.getKnowledgeBaseUserInstructions()).pipe(
|
||||
shareReplay()
|
||||
);
|
||||
|
||||
// if it is:
|
||||
// - a new conversation
|
||||
|
@ -243,12 +241,12 @@ export class ObservabilityAIAssistantClient {
|
|||
tracer: completeTracer,
|
||||
}).pipe(shareReplay());
|
||||
|
||||
const systemMessage$ = userInstructions$.pipe(
|
||||
map((userInstructions) => {
|
||||
const systemMessage$ = kbUserInstructions$.pipe(
|
||||
map((kbUserInstructions) => {
|
||||
return getSystemMessageFromInstructions({
|
||||
applicationInstructions: functionClient.getInstructions(),
|
||||
userInstructions,
|
||||
adHocInstructions: allAdHocInstructions,
|
||||
kbUserInstructions,
|
||||
apiUserInstructions,
|
||||
availableFunctionNames: functionClient.getFunctions().map((fn) => fn.definition.name),
|
||||
});
|
||||
}),
|
||||
|
@ -257,8 +255,8 @@ export class ObservabilityAIAssistantClient {
|
|||
|
||||
// we continue the conversation here, after resolving both the materialized
|
||||
// messages and the knowledge base instructions
|
||||
const nextEvents$ = forkJoin([systemMessage$, userInstructions$]).pipe(
|
||||
switchMap(([systemMessage, userInstructions]) => {
|
||||
const nextEvents$ = forkJoin([systemMessage$, kbUserInstructions$]).pipe(
|
||||
switchMap(([systemMessage, kbUserInstructions]) => {
|
||||
// if needed, inject a context function request here
|
||||
const contextRequest = functionClient.hasFunction(CONTEXT_FUNCTION_NAME)
|
||||
? getContextFunctionRequestIfNeeded(initialMessages)
|
||||
|
@ -285,8 +283,8 @@ export class ObservabilityAIAssistantClient {
|
|||
// start out with the max number of function calls
|
||||
functionCallsLeft: MAX_FUNCTION_CALLS,
|
||||
functionClient,
|
||||
userInstructions,
|
||||
adHocInstructions,
|
||||
kbUserInstructions,
|
||||
apiUserInstructions,
|
||||
signal,
|
||||
logger: this.dependencies.logger,
|
||||
disableFunctions,
|
||||
|
|
|
@ -28,7 +28,7 @@ import {
|
|||
MessageOrChatEvent,
|
||||
} from '../../../../common/conversation_complete';
|
||||
import { FunctionVisibility } from '../../../../common/functions/types';
|
||||
import { AdHocInstruction, Instruction } from '../../../../common/types';
|
||||
import { Instruction } from '../../../../common/types';
|
||||
import { createFunctionResponseMessage } from '../../../../common/utils/create_function_response_message';
|
||||
import { emitWithConcatenatedMessage } from '../../../../common/utils/emit_with_concatenated_message';
|
||||
import type { ChatFunctionClient } from '../../chat_function_client';
|
||||
|
@ -173,8 +173,8 @@ export function continueConversation({
|
|||
chat,
|
||||
signal,
|
||||
functionCallsLeft,
|
||||
adHocInstructions = [],
|
||||
userInstructions,
|
||||
apiUserInstructions = [],
|
||||
kbUserInstructions,
|
||||
logger,
|
||||
disableFunctions,
|
||||
tracer,
|
||||
|
@ -186,8 +186,8 @@ export function continueConversation({
|
|||
chat: AutoAbortedChatFunction;
|
||||
signal: AbortSignal;
|
||||
functionCallsLeft: number;
|
||||
adHocInstructions: AdHocInstruction[];
|
||||
userInstructions: Instruction[];
|
||||
apiUserInstructions: Instruction[];
|
||||
kbUserInstructions: Instruction[];
|
||||
logger: Logger;
|
||||
disableFunctions:
|
||||
| boolean
|
||||
|
@ -323,8 +323,8 @@ export function continueConversation({
|
|||
functionCallsLeft: nextFunctionCallsLeft,
|
||||
functionClient,
|
||||
signal,
|
||||
userInstructions,
|
||||
adHocInstructions,
|
||||
kbUserInstructions,
|
||||
apiUserInstructions,
|
||||
logger,
|
||||
disableFunctions,
|
||||
tracer,
|
||||
|
|
|
@ -14,11 +14,7 @@ import type {
|
|||
FunctionDefinition,
|
||||
FunctionResponse,
|
||||
} from '../../common/functions/types';
|
||||
import type {
|
||||
Message,
|
||||
ObservabilityAIAssistantScreenContextRequest,
|
||||
AdHocInstruction,
|
||||
} from '../../common/types';
|
||||
import type { Message, ObservabilityAIAssistantScreenContextRequest } from '../../common/types';
|
||||
import type { ObservabilityAIAssistantRouteHandlerResources } from '../routes/types';
|
||||
import { ChatFunctionClient } from './chat_function_client';
|
||||
import type { ObservabilityAIAssistantClient } from './client';
|
||||
|
@ -76,8 +72,6 @@ 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,
|
||||
|
|
|
@ -14,8 +14,8 @@ describe('getSystemMessageFromInstructions', () => {
|
|||
expect(
|
||||
getSystemMessageFromInstructions({
|
||||
applicationInstructions: ['first', 'second'],
|
||||
userInstructions: [],
|
||||
adHocInstructions: [],
|
||||
kbUserInstructions: [],
|
||||
apiUserInstructions: [],
|
||||
availableFunctionNames: [],
|
||||
})
|
||||
).toEqual(`first\n\nsecond`);
|
||||
|
@ -30,8 +30,8 @@ describe('getSystemMessageFromInstructions', () => {
|
|||
return availableFunctionNames[0];
|
||||
},
|
||||
],
|
||||
userInstructions: [],
|
||||
adHocInstructions: [],
|
||||
kbUserInstructions: [],
|
||||
apiUserInstructions: [],
|
||||
availableFunctionNames: ['myFunction'],
|
||||
})
|
||||
).toEqual(`first\n\nmyFunction`);
|
||||
|
@ -41,12 +41,11 @@ describe('getSystemMessageFromInstructions', () => {
|
|||
expect(
|
||||
getSystemMessageFromInstructions({
|
||||
applicationInstructions: ['first'],
|
||||
userInstructions: [{ id: 'second', text: 'second from kb' }],
|
||||
adHocInstructions: [
|
||||
kbUserInstructions: [{ id: 'second', text: 'second from kb' }],
|
||||
apiUserInstructions: [
|
||||
{
|
||||
id: 'second',
|
||||
text: 'second from adhoc instruction',
|
||||
instruction_type: 'user_instruction',
|
||||
},
|
||||
],
|
||||
availableFunctionNames: [],
|
||||
|
@ -58,8 +57,8 @@ describe('getSystemMessageFromInstructions', () => {
|
|||
expect(
|
||||
getSystemMessageFromInstructions({
|
||||
applicationInstructions: ['first'],
|
||||
userInstructions: [{ id: 'second', text: 'second_kb' }],
|
||||
adHocInstructions: [],
|
||||
kbUserInstructions: [{ id: 'second', text: 'second_kb' }],
|
||||
apiUserInstructions: [],
|
||||
availableFunctionNames: [],
|
||||
})
|
||||
).toEqual(`first\n\n${USER_INSTRUCTIONS_HEADER}\n\nsecond_kb`);
|
||||
|
@ -74,8 +73,8 @@ describe('getSystemMessageFromInstructions', () => {
|
|||
return undefined;
|
||||
},
|
||||
],
|
||||
userInstructions: [],
|
||||
adHocInstructions: [],
|
||||
kbUserInstructions: [],
|
||||
apiUserInstructions: [],
|
||||
availableFunctionNames: [],
|
||||
})
|
||||
).toEqual(`first`);
|
||||
|
|
|
@ -5,9 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { compact, partition, uniqBy } from 'lodash';
|
||||
import { v4 } from 'uuid';
|
||||
import { AdHocInstruction, Instruction } from '../../../common/types';
|
||||
import { compact, uniqBy } from 'lodash';
|
||||
import { Instruction } from '../../../common/types';
|
||||
import { withTokenBudget } from '../../../common/utils/with_token_budget';
|
||||
import { InstructionOrCallback } from '../types';
|
||||
|
||||
|
@ -22,16 +21,16 @@ export function getSystemMessageFromInstructions({
|
|||
// application instructions registered by the functions. These will be displayed first
|
||||
applicationInstructions,
|
||||
|
||||
// instructions provided by the user. These will be displayed after the application instructions and only if they fit within the token budget
|
||||
userInstructions: kbUserInstructions,
|
||||
// instructions provided by the user via the KB. These will be displayed after the application instructions and only if they fit within the token budget
|
||||
kbUserInstructions,
|
||||
|
||||
// ad-hoc instruction. Can be either user or application instruction
|
||||
adHocInstructions,
|
||||
// instructions provided by the user via the API. These will be displayed after the application instructions and only if they fit within the token budget
|
||||
apiUserInstructions,
|
||||
availableFunctionNames,
|
||||
}: {
|
||||
applicationInstructions: InstructionOrCallback[];
|
||||
userInstructions: Instruction[];
|
||||
adHocInstructions: AdHocInstruction[];
|
||||
kbUserInstructions: Instruction[];
|
||||
apiUserInstructions: Instruction[];
|
||||
availableFunctionNames: string[];
|
||||
}): string {
|
||||
const allApplicationInstructions = compact(
|
||||
|
@ -43,28 +42,16 @@ export function getSystemMessageFromInstructions({
|
|||
})
|
||||
);
|
||||
|
||||
const adHocInstructionsWithId = adHocInstructions.map((adHocInstruction) => ({
|
||||
...adHocInstruction,
|
||||
id: adHocInstruction?.id ?? v4(),
|
||||
}));
|
||||
|
||||
// split ad hoc instructions into user instructions and application instructions
|
||||
const [adHocUserInstructions, adHocApplicationInstructions] = partition(
|
||||
adHocInstructionsWithId,
|
||||
(instruction) => instruction.instruction_type === 'user_instruction'
|
||||
);
|
||||
|
||||
// all adhoc instructions and KB instructions.
|
||||
// adhoc instructions will be prioritized over Knowledge Base instructions if the id is the same
|
||||
// all api user instructions and KB instructions.
|
||||
// api instructions will be prioritized over Knowledge Base instructions if the id is the same
|
||||
const allUserInstructions = withTokenBudget(
|
||||
uniqBy([...adHocUserInstructions, ...kbUserInstructions], (i) => i.id),
|
||||
uniqBy([...apiUserInstructions, ...kbUserInstructions], (i) => i.id),
|
||||
1000
|
||||
);
|
||||
|
||||
return [
|
||||
// application instructions
|
||||
...allApplicationInstructions,
|
||||
...adHocApplicationInstructions,
|
||||
|
||||
// user instructions
|
||||
...(allUserInstructions.length ? [USER_INSTRUCTIONS_HEADER, ...allUserInstructions] : []),
|
||||
|
|
|
@ -113,7 +113,7 @@ describe('observabilityAIAssistant rule_connector', () => {
|
|||
getFunctionClient: async () => ({
|
||||
getFunctions: () => [],
|
||||
getInstructions: () => [],
|
||||
getAdhocInstructions: () => [],
|
||||
registerInstruction: () => [],
|
||||
}),
|
||||
},
|
||||
context: {},
|
||||
|
|
|
@ -34,12 +34,12 @@ import {
|
|||
import { concatenateChatCompletionChunks } from '@kbn/observability-ai-assistant-plugin/common/utils/concatenate_chat_completion_chunks';
|
||||
import { CompatibleJSONSchema } from '@kbn/observability-ai-assistant-plugin/common/functions/types';
|
||||
import { AlertDetailsContextualInsightsService } from '@kbn/observability-plugin/server/services';
|
||||
import { AdHocInstruction } from '@kbn/observability-ai-assistant-plugin/common/types';
|
||||
import { EXECUTE_CONNECTOR_FUNCTION_NAME } from '@kbn/observability-ai-assistant-plugin/server/functions/execute_connector';
|
||||
import { ObservabilityAIAssistantClient } from '@kbn/observability-ai-assistant-plugin/server';
|
||||
import { ChatFunctionClient } from '@kbn/observability-ai-assistant-plugin/server/service/chat_function_client';
|
||||
import { ActionsClient } from '@kbn/actions-plugin/server';
|
||||
import { PublicMethodsOf } from '@kbn/utility-types';
|
||||
import { RegisterInstructionCallback } from '@kbn/observability-ai-assistant-plugin/server/service/types';
|
||||
import { convertSchemaToOpenApi } from './convert_schema_to_open_api';
|
||||
import { OBSERVABILITY_AI_ASSISTANT_CONNECTOR_ID } from '../../common/rule_connector';
|
||||
import { ALERT_STATUSES } from '../../common/constants';
|
||||
|
@ -253,32 +253,32 @@ async function executeAlertsChatCompletion(
|
|||
});
|
||||
});
|
||||
|
||||
const backgroundInstruction: AdHocInstruction = {
|
||||
instruction_type: 'application_instruction',
|
||||
text: dedent(
|
||||
`You are called as a background process because alerts have changed state.
|
||||
const backgroundInstruction = dedent(
|
||||
`You are called as a background process because alerts have changed state.
|
||||
As a background process you are not interacting with a user. Because of that DO NOT ask for user
|
||||
input if tasked to execute actions. You can generate multiple responses in a row.
|
||||
If available, include the link of the conversation at the end of your answer.`
|
||||
),
|
||||
};
|
||||
);
|
||||
|
||||
functionClient.registerInstruction(backgroundInstruction);
|
||||
|
||||
const hasSlackConnector = !!connectorsList.filter(
|
||||
(connector) => connector.actionTypeId === '.slack'
|
||||
).length;
|
||||
|
||||
if (hasSlackConnector && functionClient.hasFunction(EXECUTE_CONNECTOR_FUNCTION_NAME)) {
|
||||
const slackConnectorInstruction: AdHocInstruction = {
|
||||
instruction_type: 'application_instruction',
|
||||
text: dedent(
|
||||
`The execute_connector function can be used to invoke Kibana connectors.
|
||||
if (hasSlackConnector) {
|
||||
const slackConnectorInstruction: RegisterInstructionCallback = ({ availableFunctionNames }) =>
|
||||
availableFunctionNames.includes(EXECUTE_CONNECTOR_FUNCTION_NAME)
|
||||
? dedent(
|
||||
`The execute_connector function can be used to invoke Kibana connectors.
|
||||
To send to the Slack connector, you need the following arguments:
|
||||
- the "id" of the connector
|
||||
- the "params" parameter that you will fill with the message
|
||||
Please include both "id" and "params.message" in the function arguments when executing the Slack connector..`
|
||||
),
|
||||
};
|
||||
functionClient.registerAdhocInstruction(slackConnectorInstruction);
|
||||
)
|
||||
: undefined;
|
||||
|
||||
functionClient.registerInstruction(slackConnectorInstruction);
|
||||
}
|
||||
|
||||
const alertsContext = await getAlertsContext(
|
||||
|
@ -312,7 +312,6 @@ If available, include the link of the conversation at the end of your answer.`
|
|||
connectorId: params.connector,
|
||||
signal: new AbortController().signal,
|
||||
kibanaPublicUrl: (await resources.plugins.core.start()).http.basePath.publicBaseUrl,
|
||||
instructions: [backgroundInstruction],
|
||||
messages: [
|
||||
{
|
||||
'@timestamp': new Date().toISOString(),
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
MessageAddEvent,
|
||||
type StreamingChatResponseEvent,
|
||||
} from '@kbn/observability-ai-assistant-plugin/common/conversation_complete';
|
||||
import { type AdHocInstruction } from '@kbn/observability-ai-assistant-plugin/common/types';
|
||||
import { type Instruction } from '@kbn/observability-ai-assistant-plugin/common/types';
|
||||
import type { ChatCompletionChunkToolCall } from '@kbn/inference-common';
|
||||
import { ChatCompletionStreamParams } from 'openai/lib/ChatCompletionStream';
|
||||
import {
|
||||
|
@ -51,7 +51,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon
|
|||
conversationResponse,
|
||||
}: {
|
||||
actions?: Array<Pick<FunctionDefinition, 'name' | 'description' | 'parameters'>>;
|
||||
instructions?: AdHocInstruction[];
|
||||
instructions?: Array<string | Instruction>;
|
||||
format?: 'openai' | 'default';
|
||||
conversationResponse: string | ToolMessage;
|
||||
}) {
|
||||
|
@ -160,12 +160,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon
|
|||
|
||||
before(async () => {
|
||||
const { conversationSimulator } = await addInterceptorsAndCallComplete({
|
||||
instructions: [
|
||||
{
|
||||
text: 'This is a random instruction',
|
||||
instruction_type: 'user_instruction',
|
||||
},
|
||||
],
|
||||
instructions: ['This is a random instruction'],
|
||||
actions: [action],
|
||||
conversationResponse: {
|
||||
tool_calls: [toolCallMock],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue