Skip to content

Commit df70115

Browse files
[8.x] [Obs Ai Assistant] Add system message (#209773) (#211396)
# Backport This will backport the following commits from `main` to `8.x`: - [[Obs Ai Assistant] Add system message (#209773)](#209773) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Arturo Lidueña","email":"[email protected]"},"sourceCommit":{"committedDate":"2025-02-17T10:12:56Z","message":"[Obs Ai Assistant] Add system message (#209773)\n\nFix: System Message Missing in Inference Plugin\r\nCloses #209548\r\n## Summary\r\n\r\nA regression was introduced in 8.18\r\n([#199286](#199286)), where the\r\nsystem message is no longer passed to the inference plugin and,\r\nconsequently, the LLM.\r\n\r\nCurrently, only user messages are being sent, which impacts conversation\r\nguidance and guardrails. The system message is crucial for steering\r\nresponses and maintaining contextual integrity.\r\n\r\nThe filtering of the system message happens here:\r\n\r\nhttps://github.com/elastic/kibana/blob/771a080ffa99e501e72c9cb98c833795769483ae/x-pack/platform/plugins/shared/observability_ai_assistant/server/service/client/index.ts#L510-L512\r\n\r\nFix Approach\r\n- Ensure the `system` message is included as a parameter in\r\n`inferenceClient.chatComplete.`\r\n```typescript\r\nconst options = {\r\n connectorId,\r\n system,\r\n messages: convertMessagesForInference(messages),\r\n toolChoice,\r\n tools,\r\n functionCalling: (simulateFunctionCalling ? 'simulated' : 'native') as FunctionCallingMode,\r\n };\r\n if (stream) {\r\n return defer(() =>\r\n this.dependencies.inferenceClient.chatComplete({\r\n ...options,\r\n stream: true,\r\n })\r\n ).pipe(\r\n convertInferenceEventsToStreamingEvents(),\r\n instrumentAndCountTokens(name),\r\n failOnNonExistingFunctionCall({ functions }),\r\n tap((event) => {\r\n if (\r\n event.type === StreamingChatResponseEventType.ChatCompletionChunk &&\r\n this.dependencies.logger.isLevelEnabled('trace')\r\n ) {\r\n this.dependencies.logger.trace(`Received chunk: ${JSON.stringify(event.message)}`);\r\n }\r\n }),\r\n shareReplay()\r\n ) as TStream extends true\r\n ? Observable<ChatCompletionChunkEvent | TokenCountEvent | ChatCompletionMessageEvent>\r\n : never;\r\n } else {\r\n return this.dependencies.inferenceClient.chatComplete({\r\n ...options,\r\n stream: false,\r\n }) as TStream extends true ? never : Promise<ChatCompleteResponse>;\r\n }\r\n }\r\n ```\r\n- Add an API test to verify that the system message is correctly passed to the LLM.","sha":"0ae28aa8bc73ce682166ffd5666828b1fdb7c017","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","v9.0.0","Team:Obs AI Assistant","ci:project-deploy-observability","backport:version","v9.1.0","v8.19.0"],"title":"[Obs Ai Assistant] Add system message","number":209773,"url":"https://github.com/elastic/kibana/pull/209773","mergeCommit":{"message":"[Obs Ai Assistant] Add system message (#209773)\n\nFix: System Message Missing in Inference Plugin\r\nCloses #209548\r\n## Summary\r\n\r\nA regression was introduced in 8.18\r\n([#199286](#199286)), where the\r\nsystem message is no longer passed to the inference plugin and,\r\nconsequently, the LLM.\r\n\r\nCurrently, only user messages are being sent, which impacts conversation\r\nguidance and guardrails. The system message is crucial for steering\r\nresponses and maintaining contextual integrity.\r\n\r\nThe filtering of the system message happens here:\r\n\r\nhttps://github.com/elastic/kibana/blob/771a080ffa99e501e72c9cb98c833795769483ae/x-pack/platform/plugins/shared/observability_ai_assistant/server/service/client/index.ts#L510-L512\r\n\r\nFix Approach\r\n- Ensure the `system` message is included as a parameter in\r\n`inferenceClient.chatComplete.`\r\n```typescript\r\nconst options = {\r\n connectorId,\r\n system,\r\n messages: convertMessagesForInference(messages),\r\n toolChoice,\r\n tools,\r\n functionCalling: (simulateFunctionCalling ? 'simulated' : 'native') as FunctionCallingMode,\r\n };\r\n if (stream) {\r\n return defer(() =>\r\n this.dependencies.inferenceClient.chatComplete({\r\n ...options,\r\n stream: true,\r\n })\r\n ).pipe(\r\n convertInferenceEventsToStreamingEvents(),\r\n instrumentAndCountTokens(name),\r\n failOnNonExistingFunctionCall({ functions }),\r\n tap((event) => {\r\n if (\r\n event.type === StreamingChatResponseEventType.ChatCompletionChunk &&\r\n this.dependencies.logger.isLevelEnabled('trace')\r\n ) {\r\n this.dependencies.logger.trace(`Received chunk: ${JSON.stringify(event.message)}`);\r\n }\r\n }),\r\n shareReplay()\r\n ) as TStream extends true\r\n ? Observable<ChatCompletionChunkEvent | TokenCountEvent | ChatCompletionMessageEvent>\r\n : never;\r\n } else {\r\n return this.dependencies.inferenceClient.chatComplete({\r\n ...options,\r\n stream: false,\r\n }) as TStream extends true ? never : Promise<ChatCompleteResponse>;\r\n }\r\n }\r\n ```\r\n- Add an API test to verify that the system message is correctly passed to the LLM.","sha":"0ae28aa8bc73ce682166ffd5666828b1fdb7c017"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.x"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/209773","number":209773,"mergeCommit":{"message":"[Obs Ai Assistant] Add system message (#209773)\n\nFix: System Message Missing in Inference Plugin\r\nCloses #209548\r\n## Summary\r\n\r\nA regression was introduced in 8.18\r\n([#199286](#199286)), where the\r\nsystem message is no longer passed to the inference plugin and,\r\nconsequently, the LLM.\r\n\r\nCurrently, only user messages are being sent, which impacts conversation\r\nguidance and guardrails. The system message is crucial for steering\r\nresponses and maintaining contextual integrity.\r\n\r\nThe filtering of the system message happens here:\r\n\r\nhttps://github.com/elastic/kibana/blob/771a080ffa99e501e72c9cb98c833795769483ae/x-pack/platform/plugins/shared/observability_ai_assistant/server/service/client/index.ts#L510-L512\r\n\r\nFix Approach\r\n- Ensure the `system` message is included as a parameter in\r\n`inferenceClient.chatComplete.`\r\n```typescript\r\nconst options = {\r\n connectorId,\r\n system,\r\n messages: convertMessagesForInference(messages),\r\n toolChoice,\r\n tools,\r\n functionCalling: (simulateFunctionCalling ? 'simulated' : 'native') as FunctionCallingMode,\r\n };\r\n if (stream) {\r\n return defer(() =>\r\n this.dependencies.inferenceClient.chatComplete({\r\n ...options,\r\n stream: true,\r\n })\r\n ).pipe(\r\n convertInferenceEventsToStreamingEvents(),\r\n instrumentAndCountTokens(name),\r\n failOnNonExistingFunctionCall({ functions }),\r\n tap((event) => {\r\n if (\r\n event.type === StreamingChatResponseEventType.ChatCompletionChunk &&\r\n this.dependencies.logger.isLevelEnabled('trace')\r\n ) {\r\n this.dependencies.logger.trace(`Received chunk: ${JSON.stringify(event.message)}`);\r\n }\r\n }),\r\n shareReplay()\r\n ) as TStream extends true\r\n ? Observable<ChatCompletionChunkEvent | TokenCountEvent | ChatCompletionMessageEvent>\r\n : never;\r\n } else {\r\n return this.dependencies.inferenceClient.chatComplete({\r\n ...options,\r\n stream: false,\r\n }) as TStream extends true ? never : Promise<ChatCompleteResponse>;\r\n }\r\n }\r\n ```\r\n- Add an API test to verify that the system message is correctly passed to the LLM.","sha":"0ae28aa8bc73ce682166ffd5666828b1fdb7c017"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Arturo Lidueña <[email protected]>
1 parent b002747 commit df70115

File tree

41 files changed

+282
-609
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+282
-609
lines changed

x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/chat_body.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ export function ChatBody({
368368
paddingSize="m"
369369
className={animClassName}
370370
>
371-
{connectors.connectors?.length === 0 || messages.length === 1 ? (
371+
{connectors.connectors?.length === 0 || messages.length === 0 ? (
372372
<WelcomeMessage
373373
connectors={connectors}
374374
knowledgeBase={knowledgeBase}

x-pack/platform/packages/shared/kbn-ai-assistant/src/chat/chat_item_avatar.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import React from 'react';
99
import { UserAvatar } from '@kbn/user-profile-components';
10-
import { EuiAvatar, EuiLoadingSpinner } from '@elastic/eui';
10+
import { EuiLoadingSpinner } from '@elastic/eui';
1111
import type { AuthenticatedUser } from '@kbn/security-plugin/common';
1212
import { MessageRole } from '@kbn/observability-ai-assistant-plugin/public';
1313
import { AssistantAvatar } from '@kbn/ai-assistant-icon';
@@ -34,9 +34,6 @@ export function ChatItemAvatar({ currentUser, role, loading }: ChatAvatarProps)
3434
case MessageRole.Function:
3535
return <AssistantAvatar name="Elastic Assistant" color="subdued" size="m" />;
3636

37-
case MessageRole.System:
38-
return <EuiAvatar name="system" iconType="dot" color="subdued" />;
39-
4037
default:
4138
return null;
4239
}

x-pack/platform/packages/shared/kbn-ai-assistant/src/hooks/use_conversation.test.tsx

Lines changed: 7 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -116,16 +116,8 @@ describe('useConversation', () => {
116116
});
117117
});
118118

119-
it('returns only the system message', () => {
120-
expect(hookResult.result.current.messages).toEqual([
121-
{
122-
'@timestamp': expect.any(String),
123-
message: {
124-
content: '',
125-
role: MessageRole.System,
126-
},
127-
},
128-
]);
119+
it('returns empty messages', () => {
120+
expect(hookResult.result.current.messages).toEqual([]);
129121
});
130122

131123
it('returns a ready state', () => {
@@ -157,15 +149,8 @@ describe('useConversation', () => {
157149
});
158150
});
159151

160-
it('returns the system message and the initial messages', () => {
152+
it('returns the initial messages', () => {
161153
expect(hookResult.result.current.messages).toEqual([
162-
{
163-
'@timestamp': expect.any(String),
164-
message: {
165-
content: '',
166-
role: MessageRole.System,
167-
},
168-
},
169154
{
170155
'@timestamp': expect.any(String),
171156
message: {
@@ -183,14 +168,8 @@ describe('useConversation', () => {
183168
conversation: {
184169
id: 'my-conversation-id',
185170
},
171+
systemMessage: 'System',
186172
messages: [
187-
{
188-
'@timestamp': new Date().toISOString(),
189-
message: {
190-
role: MessageRole.System,
191-
content: 'System',
192-
},
193-
},
194173
{
195174
'@timestamp': new Date().toISOString(),
196175
message: {
@@ -218,14 +197,8 @@ describe('useConversation', () => {
218197
conversation: {
219198
id: 'my-conversation-id',
220199
},
200+
systemMessage: 'System',
221201
messages: [
222-
{
223-
'@timestamp': expect.any(String),
224-
message: {
225-
content: 'System',
226-
role: MessageRole.System,
227-
},
228-
},
229202
{
230203
'@timestamp': expect.any(String),
231204
message: {
@@ -239,13 +212,6 @@ describe('useConversation', () => {
239212

240213
it('sets messages to the messages of the conversation', () => {
241214
expect(hookResult.result.current.messages).toEqual([
242-
{
243-
'@timestamp': expect.any(String),
244-
message: {
245-
content: expect.any(String),
246-
role: MessageRole.System,
247-
},
248-
},
249215
{
250216
'@timestamp': expect.any(String),
251217
message: {
@@ -255,10 +221,6 @@ describe('useConversation', () => {
255221
},
256222
]);
257223
});
258-
259-
it('overrides the system message', () => {
260-
expect(hookResult.result.current.messages[0].message.content).toBe('');
261-
});
262224
});
263225

264226
describe('with a conversation id that fails to load', () => {
@@ -282,21 +244,14 @@ describe('useConversation', () => {
282244
});
283245

284246
it('resets the messages', () => {
285-
expect(hookResult.result.current.messages.length).toBe(1);
247+
expect(hookResult.result.current.messages.length).toBe(0);
286248
});
287249
});
288250

289251
describe('when chat completes', () => {
290252
const subject: Subject<StreamingChatResponseEventWithoutError> = new Subject();
291253
let onConversationUpdate: jest.Mock;
292254
const expectedMessages = [
293-
{
294-
'@timestamp': expect.any(String),
295-
message: {
296-
role: MessageRole.System,
297-
content: '',
298-
},
299-
},
300255
{
301256
'@timestamp': expect.any(String),
302257
message: {
@@ -333,6 +288,7 @@ describe('useConversation', () => {
333288
conversation: {
334289
id: 'my-conversation-id',
335290
},
291+
systemMessage: '',
336292
messages: expectedMessages,
337293
},
338294
(request as any).params.body

x-pack/platform/packages/shared/kbn-ai-assistant/src/utils/builders.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,6 @@ export function buildMessage(params: BuildMessageProps): Message {
3232
);
3333
}
3434

35-
export function buildSystemMessage(
36-
params?: Omit<BuildMessageProps, 'message'> & {
37-
message: DeepPartial<Omit<Message['message'], 'role'>>;
38-
}
39-
) {
40-
return buildMessage(
41-
// @ts-expect-error upgrade typescript v5.1.6
42-
merge({}, params, {
43-
message: { role: MessageRole.System },
44-
})
45-
);
46-
}
47-
4835
export function buildUserMessage(
4936
params?: Omit<BuildMessageProps, 'message'> & {
5037
message?: DeepPartial<Omit<Message['message'], 'role'>>;
@@ -117,7 +104,8 @@ export function buildConversation(params?: Partial<Conversation>): Conversation
117104
title: '',
118105
last_updated: '',
119106
},
120-
messages: [buildSystemMessage()],
107+
systemMessage: '',
108+
messages: [],
121109
labels: {},
122110
numeric_labels: {},
123111
namespace: '',

x-pack/platform/packages/shared/kbn-ai-assistant/src/utils/create_mock_chat_service.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import type { DeeplyMockedKeys } from '@kbn/utility-types-jest';
99
import {
1010
FunctionDefinition,
11-
MessageRole,
1211
ObservabilityAIAssistantChatService,
1312
} from '@kbn/observability-ai-assistant-plugin/public';
1413
import { BehaviorSubject } from 'rxjs';
@@ -25,13 +24,7 @@ export const createMockChatService = (): MockedChatService => {
2524
hasFunction: jest.fn().mockReturnValue(false),
2625
hasRenderFunction: jest.fn().mockReturnValue(true),
2726
renderFunction: jest.fn(),
28-
getSystemMessage: jest.fn().mockReturnValue({
29-
'@timestamp': new Date().toISOString(),
30-
message: {
31-
role: MessageRole.System,
32-
content: '',
33-
},
34-
}),
27+
getSystemMessage: jest.fn().mockReturnValue('system message'),
3528
getScopes: jest.fn(),
3629
};
3730
return mockChatService;

x-pack/platform/packages/shared/kbn-ai-assistant/src/utils/get_role_translation.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,6 @@ export function getRoleTranslation(role: MessageRole) {
1515
});
1616
}
1717

18-
if (role === MessageRole.System) {
19-
return i18n.translate('xpack.aiAssistant.chatTimeline.messages.system.label', {
20-
defaultMessage: 'System',
21-
});
22-
}
23-
2418
return i18n.translate('xpack.aiAssistant.chatTimeline.messages.elasticAssistant.label', {
2519
defaultMessage: 'Elastic Assistant',
2620
});

x-pack/platform/packages/shared/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.test.tsx

Lines changed: 1 addition & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,6 @@ describe('getTimelineItemsFromConversation', () => {
5959
},
6060
chatState: ChatState.Ready,
6161
messages: [
62-
{
63-
'@timestamp': new Date().toISOString(),
64-
message: {
65-
role: MessageRole.System,
66-
content: 'System',
67-
},
68-
},
6962
{
7063
'@timestamp': new Date().toISOString(),
7164
message: {
@@ -77,7 +70,7 @@ describe('getTimelineItemsFromConversation', () => {
7770
onActionClick: jest.fn(),
7871
});
7972
});
80-
it('excludes the system message', () => {
73+
it('includes the opening message and the user message', () => {
8174
expect(items.length).toBe(2);
8275
expect(items[0].title).toBe('started a conversation');
8376
});
@@ -113,13 +106,6 @@ describe('getTimelineItemsFromConversation', () => {
113106
hasConnector: true,
114107
chatState: ChatState.Ready,
115108
messages: [
116-
{
117-
'@timestamp': new Date().toISOString(),
118-
message: {
119-
role: MessageRole.System,
120-
content: 'System',
121-
},
122-
},
123109
{
124110
'@timestamp': new Date().toISOString(),
125111
message: {
@@ -204,13 +190,6 @@ describe('getTimelineItemsFromConversation', () => {
204190
hasConnector: true,
205191
chatState: ChatState.Ready,
206192
messages: [
207-
{
208-
'@timestamp': new Date().toISOString(),
209-
message: {
210-
role: MessageRole.System,
211-
content: 'System',
212-
},
213-
},
214193
{
215194
'@timestamp': new Date().toISOString(),
216195
message: {
@@ -282,13 +261,6 @@ describe('getTimelineItemsFromConversation', () => {
282261
hasConnector: true,
283262
chatState: ChatState.Ready,
284263
messages: [
285-
{
286-
'@timestamp': new Date().toISOString(),
287-
message: {
288-
role: MessageRole.System,
289-
content: 'System',
290-
},
291-
},
292264
{
293265
'@timestamp': new Date().toISOString(),
294266
message: {
@@ -364,13 +336,6 @@ describe('getTimelineItemsFromConversation', () => {
364336
},
365337
chatState: ChatState.Ready,
366338
messages: [
367-
{
368-
'@timestamp': new Date().toISOString(),
369-
message: {
370-
role: MessageRole.System,
371-
content: 'System',
372-
},
373-
},
374339
{
375340
'@timestamp': new Date().toISOString(),
376341
message: {
@@ -415,13 +380,6 @@ describe('getTimelineItemsFromConversation', () => {
415380
hasConnector: true,
416381
chatState: ChatState.Ready,
417382
messages: [
418-
{
419-
'@timestamp': new Date().toISOString(),
420-
message: {
421-
role: MessageRole.System,
422-
content: 'System',
423-
},
424-
},
425383
{
426384
'@timestamp': new Date().toISOString(),
427385
message: {
@@ -491,13 +449,6 @@ describe('getTimelineItemsFromConversation', () => {
491449
hasConnector: true,
492450
chatState: ChatState.Loading,
493451
messages: [
494-
{
495-
'@timestamp': new Date().toISOString(),
496-
message: {
497-
role: MessageRole.System,
498-
content: 'System',
499-
},
500-
},
501452
{
502453
'@timestamp': new Date().toISOString(),
503454
message: {

x-pack/platform/packages/shared/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,6 @@ export function getTimelineItemsfromConversation({
8080
payload: ChatActionClickPayload;
8181
}) => void;
8282
}): ChatTimelineItem[] {
83-
const messagesWithoutSystem = messages.filter(
84-
(message) => message.message.role !== MessageRole.System
85-
);
86-
8783
const items: ChatTimelineItem[] = [
8884
{
8985
id: v4(),
@@ -100,18 +96,16 @@ export function getTimelineItemsfromConversation({
10096
}),
10197
role: MessageRole.User,
10298
},
103-
...messagesWithoutSystem.map((message, index) => {
99+
...messages.map((message, index) => {
104100
const id = v4();
105101

106102
let title: React.ReactNode = '';
107103
let content: string | undefined;
108104
let element: React.ReactNode | undefined;
109105

110106
const prevFunctionCall =
111-
message.message.name &&
112-
messagesWithoutSystem[index - 1] &&
113-
messagesWithoutSystem[index - 1].message.function_call
114-
? messagesWithoutSystem[index - 1].message.function_call
107+
message.message.name && messages[index - 1] && messages[index - 1].message.function_call
108+
? messages[index - 1].message.function_call
115109
: undefined;
116110

117111
let role = message.message.function_call?.trigger || message.message.role;

x-pack/platform/plugins/private/translations/translations/fr-FR.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9840,7 +9840,6 @@
98409840
"xpack.aiAssistant.chatTimeline.actions.editPrompt": "Modifier l'invite",
98419841
"xpack.aiAssistant.chatTimeline.actions.inspectPrompt": "Inspecter l'invite",
98429842
"xpack.aiAssistant.chatTimeline.messages.elasticAssistant.label": "Assistant d'Elastic",
9843-
"xpack.aiAssistant.chatTimeline.messages.system.label": "Système",
98449843
"xpack.aiAssistant.chatTimeline.messages.user.label": "Vous",
98459844
"xpack.aiAssistant.checkingKbAvailability": "Vérification de la disponibilité de la base de connaissances",
98469845
"xpack.aiAssistant.conversationList.dateGroupTitle.lastMonth": "Le mois dernier",

x-pack/platform/plugins/private/translations/translations/ja-JP.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9715,7 +9715,6 @@
97159715
"xpack.aiAssistant.chatTimeline.actions.editPrompt": "プロンプトを編集",
97169716
"xpack.aiAssistant.chatTimeline.actions.inspectPrompt": "プロンプトを検査",
97179717
"xpack.aiAssistant.chatTimeline.messages.elasticAssistant.label": "Elastic Assistant",
9718-
"xpack.aiAssistant.chatTimeline.messages.system.label": "システム",
97199718
"xpack.aiAssistant.chatTimeline.messages.user.label": "あなた",
97209719
"xpack.aiAssistant.checkingKbAvailability": "ナレッジベースの利用可能性を確認中",
97219720
"xpack.aiAssistant.conversationList.dateGroupTitle.lastMonth": "先月",

0 commit comments

Comments
 (0)