Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
c9aab67
Added Remediation Steps to Log Ai Insight system promt
yuliia-fryshko Dec 22, 2025
44b44e2
edited system promt
yuliia-fryshko Dec 23, 2025
b64015b
added downstream dependencies
yuliia-fryshko Dec 28, 2025
a87c1dc
added trace data
yuliia-fryshko Dec 28, 2025
e5d48e5
Merge branch 'main' into edit-log-insight-system-promt-445
yuliia-fryshko Dec 29, 2025
e02947c
edit log ai system promt to respond base on log type
yuliia-fryshko Dec 29, 2025
769fee0
edited wording
yuliia-fryshko Dec 29, 2025
e50dd81
Merge branch 'main' into edit-log-insight-system-promt-445
yuliia-fryshko Dec 31, 2025
52ee4df
Update x-pack/solutions/observability/plugins/observability_agent_bui…
yuliia-fryshko Dec 31, 2025
ac070e8
Update x-pack/solutions/observability/plugins/observability_agent_bui…
yuliia-fryshko Dec 31, 2025
b8c86ee
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Dec 31, 2025
b0685ce
applied PR review suggestions
yuliia-fryshko Dec 31, 2025
55affb9
removed first occurence promt
yuliia-fryshko Dec 31, 2025
0b45ec5
Merge branch 'main' into edit-log-insight-system-promt-445
yuliia-fryshko Jan 4, 2026
3124e3a
Merge branch 'main' into edit-log-insight-system-promt-445
yuliia-fryshko Jan 9, 2026
e1309f1
Merge branch 'main' into edit-log-insight-system-promt-445
yuliia-fryshko Jan 13, 2026
2112716
Merge branch 'main' into edit-log-insight-system-promt-445
yuliia-fryshko Jan 13, 2026
a57c8be
Merge branch 'main' into edit-log-insight-system-promt-445
yuliia-fryshko Jan 13, 2026
384bced
prefetched data base on log severity
yuliia-fryshko Jan 14, 2026
2523a21
removed redundant fallback
yuliia-fryshko Jan 14, 2026
35b7291
added a condition to identify id the log is an error
yuliia-fryshko Jan 14, 2026
0e6ac58
rename getToolHandler import to getCorrelatedLogs
yuliia-fryshko Jan 14, 2026
2ad03d3
adjusted fields according to mapping ecs-otel
yuliia-fryshko Jan 16, 2026
ab652c6
adjusted correlated log sequence
yuliia-fryshko Jan 16, 2026
94135cc
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Jan 16, 2026
35ec4bd
added API test for Log AI Insight
yuliia-fryshko Jan 16, 2026
d9b647f
addressed review comment
yuliia-fryshko Jan 16, 2026
07ce96a
Merge branch 'main' into edit-log-insight-system-promt-445
yuliia-fryshko Jan 16, 2026
f4382fe
fix type check
yuliia-fryshko Jan 16, 2026
c5d3a6f
fixed check type
yuliia-fryshko Jan 19, 2026
ae8568f
Merge branch 'main' into edit-log-insight-system-promt-445
yuliia-fryshko Jan 19, 2026
6fd1ce8
Merge branch 'main' into edit-log-insight-system-promt-445
yuliia-fryshko Jan 21, 2026
33b4b6b
prefetch correlation logs for all log types
yuliia-fryshko Jan 22, 2026
6130807
fix merge conflict
yuliia-fryshko Jan 22, 2026
f009254
fix merge conflict
yuliia-fryshko Jan 22, 2026
5ce23e8
Merge branch 'main' into edit-log-insight-system-promt-445
yuliia-fryshko Jan 22, 2026
2423a38
search ecs fields instead of source
yuliia-fryshko Jan 22, 2026
e56b156
Merge branch 'main' into edit-log-insight-system-promt-445
yuliia-fryshko Jan 27, 2026
d70c2fd
Merge branch 'main' into edit-log-insight-system-promt-445
yuliia-fryshko Jan 28, 2026
c907227
fixed merge conflicts
yuliia-fryshko Jan 28, 2026
5dc626c
added categorised logs and default values for correlation tool handler
yuliia-fryshko Jan 28, 2026
73ccf32
Update x-pack/solutions/observability/plugins/observability_agent_bui…
yuliia-fryshko Jan 28, 2026
bd60b60
Update x-pack/solutions/observability/plugins/observability_agent_bui…
yuliia-fryshko Jan 28, 2026
cf98e3a
Update x-pack/solutions/observability/plugins/observability_agent_bui…
yuliia-fryshko Jan 28, 2026
5c124c5
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Jan 28, 2026
e9fd179
fix review comments
yuliia-fryshko Jan 28, 2026
8a6da49
Merge branch 'main' into edit-log-insight-system-promt-445
yuliia-fryshko Jan 28, 2026
7bbe53d
Update x-pack/solutions/observability/plugins/observability_agent_bui…
yuliia-fryshko Jan 28, 2026
b220f84
fixed tests
yuliia-fryshko Jan 28, 2026
7719f35
moved the function to utils
yuliia-fryshko Jan 28, 2026
15fa737
Update x-pack/solutions/observability/test/api_integration_deployment…
yuliia-fryshko Jan 29, 2026
c389435
Update x-pack/solutions/observability/plugins/observability_agent_bui…
yuliia-fryshko Jan 29, 2026
efb1d96
Update x-pack/solutions/observability/plugins/observability_agent_bui…
yuliia-fryshko Jan 29, 2026
1dea11f
Update x-pack/solutions/observability/test/api_integration_deployment…
yuliia-fryshko Jan 29, 2026
1973cbd
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Jan 29, 2026
775b93c
fixed review comments
yuliia-fryshko Jan 29, 2026
ab0c124
Merge branch 'main' into edit-log-insight-system-promt-445
yuliia-fryshko Jan 29, 2026
01b7d1b
Merge branch 'main' into edit-log-insight-system-promt-445
yuliia-fryshko Jan 29, 2026
ca63002
fixed merge conflict
yuliia-fryshko Jan 29, 2026
53f8492
fix duplicates, add shared test helpers
yuliia-fryshko Jan 29, 2026
e95c9c2
Changes from node scripts/lint_ts_projects --fix
kibanamachine Jan 29, 2026
0b5b0cd
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine Jan 29, 2026
58c53df
Merge branch 'main' into edit-log-insight-system-promt-445
yuliia-fryshko Jan 29, 2026
e6bee31
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Jan 29, 2026
841558f
Merge branch 'main' into edit-log-insight-system-promt-445
yuliia-fryshko Jan 29, 2026
8d98f5c
removed categorised logs; fixed check type
yuliia-fryshko Jan 29, 2026
3b2bda0
added ditributed trace data when error
yuliia-fryshko Jan 30, 2026
95c5a02
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Jan 30, 2026
a4af6d0
removed duplications of tests
yuliia-fryshko Jan 30, 2026
f752200
removed distributed trace
yuliia-fryshko Jan 30, 2026
5d835e2
fix tests
yuliia-fryshko Jan 30, 2026
306143a
Update x-pack/solutions/observability/plugins/observability_agent_bui…
yuliia-fryshko Jan 30, 2026
90e3885
created a function to call corelated logs
yuliia-fryshko Jan 30, 2026
6c67d8d
Update x-pack/solutions/observability/plugins/observability_agent_bui…
yuliia-fryshko Jan 30, 2026
b47a1e6
fixed review comments
yuliia-fryshko Jan 30, 2026
4ea551b
fixed check type
yuliia-fryshko Jan 30, 2026
8f6049e
fixed check type
yuliia-fryshko Jan 30, 2026
51a1ba8
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Jan 30, 2026
f2faa93
Merge branch 'main' into edit-log-insight-system-promt-445
yuliia-fryshko Jan 30, 2026
cac6cfc
Merge branch 'main' into edit-log-insight-system-promt-445
yuliia-fryshko Feb 2, 2026
430b890
Update x-pack/solutions/observability/plugins/observability_agent_bui…
yuliia-fryshko Feb 2, 2026
a3a965b
Update x-pack/solutions/observability/plugins/observability_agent_bui…
yuliia-fryshko Feb 2, 2026
6500993
Update x-pack/solutions/observability/plugins/observability_agent_bui…
yuliia-fryshko Feb 2, 2026
999af77
fixed review comment
yuliia-fryshko Feb 2, 2026
bbce724
fixed type check
yuliia-fryshko Feb 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ dependsOn:
- '@kbn/inference-plugin'
- '@kbn/server-route-repository'
- '@kbn/server-route-repository-utils'
- '@kbn/std'
- '@kbn/spaces-plugin'
- '@kbn/management-settings-ids'
- '@kbn/logging'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@ import type { Logger } from '@kbn/logging';
import { MessageRole } from '@kbn/inference-common';
import type { BoundInferenceClient } from '@kbn/inference-common';
import dedent from 'dedent';
import { concat, of } from 'rxjs';
import type { ObservabilityAgentBuilderDataRegistry } from '../../../data_registry/data_registry';
import type {
ObservabilityAgentBuilderCoreSetup,
ObservabilityAgentBuilderPluginSetupDependencies,
} from '../../../types';
import { fetchApmErrorContext } from './fetch_apm_error_context';
import { getEntityLinkingInstructions } from '../../../agent/register_observability_agent';
import type { AiInsightResult, ContextEvent } from '../types';
import { createAiInsightResult, type AiInsightResult } from '../types';

function getErrorAiInsightSystemPrompt({ urlPrefix }: { urlPrefix: string }) {
return dedent(`
Expand Down Expand Up @@ -120,13 +119,5 @@ export async function generateErrorAiInsight({
stream: true,
});

const streamWithContext$ = concat(
of<ContextEvent>({ type: 'context', context: errorContext }),
events$
);

return {
events$: streamWithContext$,
context: errorContext,
};
return createAiInsightResult(errorContext, events$);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ import dedent from 'dedent';
import { compact, isEmpty } from 'lodash';
import moment from 'moment';
import type { Observable } from 'rxjs';
import { concat, of } from 'rxjs';
import type { ObservabilityAgentBuilderDataRegistry } from '../../data_registry/data_registry';
import type { AiInsightResult, ContextEvent } from './types';
import { createAiInsightResult, type AiInsightResult } from './types';
import type {
ObservabilityAgentBuilderCoreSetup,
ObservabilityAgentBuilderPluginSetupDependencies,
Expand Down Expand Up @@ -81,12 +80,7 @@ export async function getAlertAiInsight({
context: relatedContext,
});

const streamWithContext$ = concat(
of<ContextEvent>({ type: 'context', context: relatedContext }),
events$
);

return { events$: streamWithContext$, context: relatedContext };
return createAiInsightResult(relatedContext, events$);
}

// Time window offsets in minutes before alert start
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,41 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import moment from 'moment';
import { MessageRole, type InferenceClient } from '@kbn/inference-common';
import type { IScopedClusterClient, KibanaRequest } from '@kbn/core/server';
import { safeJsonStringify } from '@kbn/std';
import type { Observable } from 'rxjs';
import type { ChatCompletionEvent, InferenceClient } from '@kbn/inference-common';
import { MessageRole } from '@kbn/inference-common';
import type { IScopedClusterClient, KibanaRequest, Logger } from '@kbn/core/server';
import dedent from 'dedent';
import { concat, of } from 'rxjs';
import type { ObservabilityAgentBuilderDataRegistry } from '../../data_registry/data_registry';
import type { ObservabilityAgentBuilderCoreSetup } from '../../types';
import { getLogDocumentById } from './get_log_document_by_id';
import { getLogDocumentById, type LogDocument } from './get_log_document_by_id';
import { getCorrelatedLogsForLogEntry } from '../../tools/get_correlated_logs/handler';
import { isWarningOrAbove } from '../../utils/warning_and_above_log_filter';
import { getEntityLinkingInstructions } from '../../agent/register_observability_agent';
import type { AiInsightResult, ContextEvent } from './types';
import { createAiInsightResult, type AiInsightResult } from './types';

export interface GetLogAiInsightsParams {
core: ObservabilityAgentBuilderCoreSetup;
index: string;
id: string;
dataRegistry: ObservabilityAgentBuilderDataRegistry;
inferenceClient: InferenceClient;
connectorId: string;
request: KibanaRequest;
esClient: IScopedClusterClient;
logger: Logger;
}

export async function getLogAiInsights({
core,
index,
id,
request,
esClient,
dataRegistry,
inferenceClient,
connectorId,
request,
logger,
}: GetLogAiInsightsParams): Promise<AiInsightResult> {
const systemPrompt = dedent(`
You are assisting an SRE who is viewing a log entry in the Kibana Logs UI.
Using the provided data produce a concise, action-oriented response.

${getEntityLinkingInstructions({ urlPrefix: core.http.basePath.get(request) })}
`);

const logEntry = await getLogDocumentById({
esClient: esClient.asCurrentUser,
index,
Expand All @@ -54,6 +49,47 @@ export async function getLogAiInsights({
throw new Error('Log entry not found');
}

const context = await fetchLogContext({
core,
logger,
esClient,
index,
id,
logEntry,
});

const events$ = generateLogSummary({
inferenceClient,
connectorId,
logEntry,
context,
core,
request,
});

return createAiInsightResult(context, events$);
}

async function fetchLogContext({
core,
logger,
esClient,
index,
id,
logEntry,
}: {
core: ObservabilityAgentBuilderCoreSetup;
logger: Logger;
esClient: IScopedClusterClient;
index: string;
id: string;
logEntry: LogDocument;
}): Promise<string> {
const logTimestamp = logEntry['@timestamp'];
const logTime = moment(logTimestamp);
const windowStart = logTime.clone().subtract(60, 'minutes').toISOString();
const windowEnd = logTime.clone().add(60, 'minutes').toISOString();

let context = dedent(`
<LogEntryIndex>
${index}
Expand All @@ -63,47 +99,97 @@ export async function getLogAiInsights({
</LogEntryId>
<LogEntryFields>
\`\`\`json
${safeJsonStringify(logEntry)}
${JSON.stringify(logEntry, null, 2)}
\`\`\`
</LogEntryFields>
`);

if (logEntry.service?.name && logEntry.service?.environment) {
const serviceSummary = await dataRegistry.getData('apmServiceSummary', {
request,
serviceName: logEntry.service?.name,
serviceEnvironment: logEntry.service?.environment,
start: moment(logEntry['@timestamp']).subtract(24, 'hours').toISOString(),
end: moment(logEntry['@timestamp']).add(24, 'hours').toISOString(),
let correlatedLogsResult;
try {
const { sequences } = await getCorrelatedLogsForLogEntry({
core,
logger,
esClient,
index,
start: windowStart,
end: windowEnd,
logId: id,
});
correlatedLogsResult = sequences[0];
} catch (error) {
logger.debug(`Failed to fetch correlated logs: ${error.message}`);
}

if (correlatedLogsResult?.logs?.length) {
context += dedent(`
<ServiceSummary>
<CorrelatedLogSequence>
Time window: ${windowStart} to ${windowEnd}
\`\`\`json
${safeJsonStringify(serviceSummary)}
${JSON.stringify(correlatedLogsResult, null, 2)}
\`\`\`
</ServiceSummary>
</CorrelatedLogSequence>
`);
}

const events$ = inferenceClient.chatComplete({
return context;
}

function generateLogSummary({
inferenceClient,
connectorId,
logEntry,
context,
core,
request,
}: {
inferenceClient: InferenceClient;
connectorId: string;
logEntry: LogDocument;
context: string;
core: ObservabilityAgentBuilderCoreSetup;
request: KibanaRequest;
}): Observable<ChatCompletionEvent> {
const isErrorOrWarning = isWarningOrAbove(logEntry);
const entityLinkingInstructions = getEntityLinkingInstructions({
urlPrefix: core.http.basePath.get(request),
});

const systemPrompt = isErrorOrWarning
? dedent(`
You are an expert SRE assistant analyzing an ${logEntry['log.level']} log entry. Provide a thorough investigation:

- **What happened**: Summarize the error in plain language
- **Where it originated**: Identify the service, component, or code path
- **Root cause analysis**: Use the available context to identify potential causes
- **Impact**: Note any affected downstream services or dependencies
- **Next steps**: Suggest specific actions for investigation or remediation

Base your analysis strictly on the provided data.

${entityLinkingInstructions}
`)
: dedent(`
You are an expert SRE assistant analyzing an ${logEntry['log.level']} log entry. Keep it concise:

- Explain what the log message means in context
- Identify the source (service, host, container)
- If CorrelatedLogSequence is available, use it to provide additional context

Base your analysis strictly on the provided data. Be specific and reference actual field values.

${entityLinkingInstructions}
`);

const userPrompt = dedent(`
${context}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you consider adding context to system prompt instead of user prompt? Pros/cons?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! For me the context is the user's input data to analyze, so userPromt is a right point to add it. Also this part will be available for the chat with agent later as a part of attachments

Analyze this log entry and generate a summary explaining what it means.
Ensure the analysis is grounded in the provided context, and concise.
`);

return inferenceClient.chatComplete({
connectorId,
system: systemPrompt,
messages: [
{
role: MessageRole.User,
content:
dedent(`Explain this log message: what it means, where it is from, whether it is expected, and if it is an issue.
${context}
`),
},
],
messages: [{ role: MessageRole.User, content: userPrompt }],
stream: true,
});

const streamWithContext$ = concat(of<ContextEvent>({ type: 'context', context }), events$);

return {
events$: streamWithContext$,
context,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,28 @@

import type { ElasticsearchClient } from '@kbn/core/server';

export interface LogDocument {
const LOG_DOCUMENT_FIELDS = [
'@timestamp',
'message',
'log.level',
'service.name',
'trace.id',
'span.id',
'http.response.status_code',
'error.exception.message',
] as const;

type FieldKeys = (typeof LOG_DOCUMENT_FIELDS)[number];

export type LogDocument = {
'log.level'?: string;
'@timestamp'?: string;
service?: {
name?: string;
environment?: string;
};
[key: string]: unknown;
}
message?: string;
'http.response.status_code'?: number;
'error.exception.message'?: string;
} & {
[K in FieldKeys]?: unknown;
};

export const getLogDocumentById = async ({
esClient,
Expand All @@ -25,10 +39,26 @@ export const getLogDocumentById = async ({
index: string;
id: string;
}): Promise<LogDocument | undefined> => {
const result = await esClient.get<LogDocument>({
const result = await esClient.search({
Comment thread
sorenlouv marked this conversation as resolved.
index,
id,
size: 1,
_source: false,
fields: [...LOG_DOCUMENT_FIELDS],
query: {
ids: { values: [id] },
},
});

return result._source;
const hit = result.hits.hits[0];

if (!hit?.fields) {
return undefined;
}

return Object.fromEntries(
Object.entries(hit.fields).map(([key, value]) => [
key,
Array.isArray(value) && value.length === 1 ? value[0] : value,
])
) as LogDocument;
Comment on lines +58 to +63
Copy link
Copy Markdown
Contributor

@sorenlouv sorenlouv Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return Object.fromEntries(
Object.entries(hit.fields).map(([key, value]) => [
key,
Array.isArray(value) && value.length === 1 ? value[0] : value,
])
) as LogDocument;
return unwrapEsFields<LogDocument>(hit?.fields);

I'm adding this util to x-pack/solutions/observability/plugins/observability_agent_builder/server/utils/unwrap_es_fields.ts. If you add it to your branch, and there shouldn't be merge conflicts

/**
 * Elasticsearch `fields` response returns all values as arrays.
 * This utility unwraps single-element arrays to their first value,
 * while preserving multi-element arrays.
 */
export function unwrapEsFields<T = Record<string, unknown>>(
  fields: Record<string, unknown[] | undefined> | undefined
): T {
  return Object.fromEntries(
    Object.entries(fields ?? {}).map(([key, value]) => [key, unwrapEsFieldValue(value)])
  ) as T;
}

/**
 * Get a single field value from ES fields response, unwrapping single-element arrays.
 * Multi-element arrays are preserved.
 */
export function getEsField<T = unknown>(
  fields: Record<string, unknown[] | undefined> | undefined,
  key: string
): T | undefined {
  return unwrapEsFieldValue(fields?.[key]) as T | undefined;
}

function unwrapEsFieldValue(value: unknown): unknown {
  return Array.isArray(value) && value.length === 1 ? value[0] : value;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sorenlouv , was this the utility function you were planning to create, or would you like me to add it? I just can’t find it in main

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The util is in #250331. Not in main yet

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

};
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export function getObservabilityAgentBuilderAiInsightsRouteRepository(): ServerR
id: t.string,
}),
}),
handler: async ({ request, core, dataRegistry, params, response, logger }) => {
handler: async ({ request, core, dataRegistry, params, response, logger, plugins }) => {
const { index, id } = params.body;

const [coreStart, startDeps] = await core.getStartServices();
Expand All @@ -149,7 +149,7 @@ export function getObservabilityAgentBuilderAiInsightsRouteRepository(): ServerR
connectorId,
request,
esClient,
dataRegistry,
logger,
});

return response.ok({
Expand Down
Loading
Loading