diff --git a/x-pack/platform/packages/shared/agent-builder/agent-builder-server/agents/builtin_definition.ts b/x-pack/platform/packages/shared/agent-builder/agent-builder-server/agents/builtin_definition.ts index a3599fafcde50..eb03c2efc2688 100644 --- a/x-pack/platform/packages/shared/agent-builder/agent-builder-server/agents/builtin_definition.ts +++ b/x-pack/platform/packages/shared/agent-builder/agent-builder-server/agents/builtin_definition.ts @@ -13,6 +13,14 @@ import type { AgentDefinition, AgentConfiguration } from '@kbn/agent-builder-com /** Same type for now */ export type BuiltInAgentConfiguration = AgentConfiguration; +/** + * Context passed to dynamic configuration handlers. + */ +export interface AgentConfigContext { + request: KibanaRequest; + spaceId: string; +} + /** * Represents a built-in agent definition, as registered by the consumers using the agents setup contract. */ @@ -20,7 +28,9 @@ export type BuiltInAgentDefinition = Pick< AgentDefinition, 'id' | 'name' | 'description' | 'labels' | 'avatar_icon' | 'avatar_symbol' | 'avatar_color' > & { - configuration: BuiltInAgentConfiguration; + configuration: + | BuiltInAgentConfiguration + | ((ctx: AgentConfigContext) => MaybePromise); /** * Optional dynamic availability configuration. */ diff --git a/x-pack/platform/packages/shared/agent-builder/agent-builder-server/agents/index.ts b/x-pack/platform/packages/shared/agent-builder/agent-builder-server/agents/index.ts index 57fa6519c1d48..4f97ba704e03b 100644 --- a/x-pack/platform/packages/shared/agent-builder/agent-builder-server/agents/index.ts +++ b/x-pack/platform/packages/shared/agent-builder/agent-builder-server/agents/index.ts @@ -24,6 +24,7 @@ export type { export type { BuiltInAgentDefinition, BuiltInAgentConfiguration, + AgentConfigContext, AgentAvailabilityContext, AgentAvailabilityHandler, AgentAvailabilityResult, diff --git a/x-pack/platform/plugins/shared/agent_builder/server/services/agents/builtin/provider.ts b/x-pack/platform/plugins/shared/agent_builder/server/services/agents/builtin/provider.ts index 223c954b5550e..2bd8dc779a68f 100644 --- a/x-pack/platform/plugins/shared/agent_builder/server/services/agents/builtin/provider.ts +++ b/x-pack/platform/plugins/shared/agent_builder/server/services/agents/builtin/provider.ts @@ -7,7 +7,7 @@ import type { KibanaRequest } from '@kbn/core-http-server'; import { AgentType, createAgentNotFoundError } from '@kbn/agent-builder-common'; -import type { BuiltInAgentDefinition } from '@kbn/agent-builder-server/agents'; +import type { BuiltInAgentDefinition, AgentConfigContext } from '@kbn/agent-builder-server/agents'; import type { BuiltinAgentRegistry } from './registry'; import type { AgentProviderFn, ReadonlyAgentProvider } from '../agent_source'; import type { InternalAgentDefinition } from '../agent_registry'; @@ -19,20 +19,24 @@ export const createBuiltinProviderFn = ({ registry: BuiltinAgentRegistry; }): AgentProviderFn => { const availabilityCache = new AgentAvailabilityCache(); - return ({ request }) => { - return registryToProvider({ registry, request, availabilityCache }); + return ({ request, space }) => { + return registryToProvider({ registry, request, space, availabilityCache }); }; }; const registryToProvider = ({ registry, request, + space, availabilityCache, }: { registry: BuiltinAgentRegistry; request: KibanaRequest; + space: string; availabilityCache: AgentAvailabilityCache; }): ReadonlyAgentProvider => { + const configContext: AgentConfigContext = { request, spaceId: space }; + return { id: 'builtin', readonly: true, @@ -44,12 +48,14 @@ const registryToProvider = ({ if (!definition) { throw createAgentNotFoundError({ agentId }); } - return toInternalDefinition({ definition, availabilityCache }); + return toInternalDefinition({ definition, availabilityCache, configContext }); }, list: (opts) => { const definitions = registry.list(); return Promise.all( - definitions.map((definition) => toInternalDefinition({ definition, availabilityCache })) + definitions.map((definition) => + toInternalDefinition({ definition, availabilityCache, configContext }) + ) ); }, }; @@ -58,12 +64,20 @@ const registryToProvider = ({ export const toInternalDefinition = async ({ definition, availabilityCache, + configContext, }: { definition: BuiltInAgentDefinition; availabilityCache: AgentAvailabilityCache; + configContext: AgentConfigContext; }): Promise => { + const configuration = + typeof definition.configuration === 'function' + ? await definition.configuration(configContext) + : definition.configuration; + return { ...definition, + configuration, type: AgentType.chat, readonly: true, isAvailable: async (ctx) => { diff --git a/x-pack/solutions/observability/plugins/observability_agent_builder/server/agent/register_observability_agent.ts b/x-pack/solutions/observability/plugins/observability_agent_builder/server/agent/register_observability_agent.ts index d6abefb0e1243..a288c6708eb66 100644 --- a/x-pack/solutions/observability/plugins/observability_agent_builder/server/agent/register_observability_agent.ts +++ b/x-pack/solutions/observability/plugins/observability_agent_builder/server/agent/register_observability_agent.ts @@ -36,16 +36,21 @@ export async function registerObservabilityAgent({ return getAgentBuilderResourceAvailability({ core, request, logger }); }, }, - configuration: { - instructions: - dedent(`You are an observability specialist agent that helps Site Reliability Engineers (SREs) investigate incidents and understand system health. - + configuration: ({ request }) => { + const urlPrefix = core.http.basePath.get(request); + + return { + instructions: + dedent(`You are an observability specialist agent that helps Site Reliability Engineers (SREs) investigate incidents and understand system health. + ${getInvestigationInstructions()} ${getReasoningInstructions()} ${getFieldDiscoveryInstructions()} ${getKqlInstructions()} + ${getEntityLinkingInstructions({ urlPrefix })} `), - tools: [{ tool_ids: OBSERVABILITY_AGENT_TOOL_IDS }], + tools: [{ tool_ids: OBSERVABILITY_AGENT_TOOL_IDS }], + }; }, }); @@ -55,7 +60,7 @@ export async function registerObservabilityAgent({ function getInvestigationInstructions() { return dedent(` ### INVESTIGATION APPROACH - + Follow a progressive workflow - start broad, then narrow down: 1. **Triage**: What's the severity? How many users/services affected? 2. **Scope**: Which components are affected? What's the blast radius? @@ -69,7 +74,7 @@ function getInvestigationInstructions() { function getReasoningInstructions() { return dedent(` ### REASONING PRINCIPLES - + - **Be quantitative**: Quote specific metrics (error rate %, latency ms, throughput rpm). Avoid vague terms like "high" without numbers. - **Correlation ≠ causation**: Look for temporal sequence (what happened FIRST) and causal mechanism. - **Consider all layers**: Infrastructure (CPU, memory, disk) → Application (latency, throughput, failure rate) → Dependencies (databases, caches, external APIs). @@ -97,3 +102,31 @@ function getKqlInstructions() { - Use quotes for exact phrases in text fields: \`message: "connection refused"\` `); } + +/** + * Entity Linking instructions for the Observability Agent. + * Instructs the LLM to format entities as clickable links using Kibana's relative URL paths. + */ +export function getEntityLinkingInstructions({ urlPrefix }: { urlPrefix: string }): string { + return dedent(` + ### Entity Linking Guidelines + Use markdown for readability. When referencing entities, create clickable links. + IMPORTANT: Do NOT wrap links in backticks - backticks prevent links from being clickable. + + | Entity | Link Format | Example | + |--------|-------------|---------| + | Service | [](${urlPrefix}/app/apm/services/) | "The [payments](${urlPrefix}/app/apm/services/payments) service is experiencing high latency." | + | Transaction | [](${urlPrefix}/app/apm/services//transactions) | "The transaction [POST /checkout](${urlPrefix}/app/apm/services/payments/transactions) took 500ms." | + | Trace | [](${urlPrefix}/app/apm/link-to/trace/) | "See trace [8bc26008603e16819bd6fcfb80fceff5](${urlPrefix}/app/apm/link-to/trace/8bc26008603e16819bd6fcfb80fceff5)" | + | Error | [](${urlPrefix}/app/apm/services//errors/) | "Error [upstream-5xx](${urlPrefix}/app/apm/services/catalog-api/errors/upstream-5xx) suggests a dependency failure." | + | Service Errors | [errors](${urlPrefix}/app/apm/services//errors) | "Review all [errors](${urlPrefix}/app/apm/services/frontend/errors) for the [frontend](${urlPrefix}/app/apm/services/frontend) service." | + | Service Logs | [logs](${urlPrefix}/app/apm/services//logs) | "Check [logs](${urlPrefix}/app/apm/services/frontend/logs) for the [frontend](${urlPrefix}/app/apm/services/frontend) service." | + | Host | [](${urlPrefix}/app/metrics/detail/host/) | "Host [web-01](${urlPrefix}/app/metrics/detail/host/web-01) is experiencing high CPU usage." | + | Service Map | [Service Map](${urlPrefix}/app/apm/services//service-map) | "Check the [Service Map](${urlPrefix}/app/apm/services/payments/service-map) to see dependencies." | + | Dependencies | [Dependencies](${urlPrefix}/app/apm/services//dependencies) | "View [Dependencies](${urlPrefix}/app/apm/services/catalog-api/dependencies) to identify upstream issues." | + | Alert | [](${urlPrefix}/app/observability/alerts/) | "Alert [alert-uuid-123](${urlPrefix}/app/observability/alerts/alert-uuid-123) was triggered." | + | Alert Rules | [](${urlPrefix}/app/observability/alerts/rules/) | "Alert Rule [alert-uuid-123](${urlPrefix}/app/observability/alerts/rules/alert-uuid-123)." | + | Logs Explorer | [Logs](${urlPrefix}/app/logs) | "View [Logs](${urlPrefix}/app/logs) to investigate the issue further." | + +`); +} diff --git a/x-pack/solutions/observability/plugins/observability_agent_builder/server/routes/ai_insights/apm_error/generate_error_ai_insight.ts b/x-pack/solutions/observability/plugins/observability_agent_builder/server/routes/ai_insights/apm_error/generate_error_ai_insight.ts index 210803256541d..5f0e01decf8fb 100644 --- a/x-pack/solutions/observability/plugins/observability_agent_builder/server/routes/ai_insights/apm_error/generate_error_ai_insight.ts +++ b/x-pack/solutions/observability/plugins/observability_agent_builder/server/routes/ai_insights/apm_error/generate_error_ai_insight.ts @@ -17,34 +17,39 @@ import type { ObservabilityAgentBuilderPluginSetupDependencies, } from '../../../types'; import { fetchApmErrorContext } from './fetch_apm_error_context'; +import { getEntityLinkingInstructions } from '../../../agent/register_observability_agent'; import type { AiInsightResult, ContextEvent } from '../types'; -const ERROR_AI_INSIGHT_SYSTEM_PROMPT = dedent(` - You are an expert SRE Assistant within Elastic Observability. Your job is to analyze an APM error using ONLY the provided context (APM trace items, related errors, downstream dependencies, and log categories). - - Output structure (concise, Markdown): - - Error summary (1-2 sentences): What is observed and why it matters. - - Failure pinpoint: Whether failure is in application code vs dependency. Name the likely failing component/endpoint. Reference specific fields or key frames if available. - - Impact: Scope and severity (services/endpoints and the extent of the error if evident). - - Immediate actions (2-4): Ordered, concrete steps (config/network checks, retries/backoff, circuit breakers, targeted tracing/logging). - - Open questions: Short list of unknowns and the quickest queries to resolve them, if any (this is strictly optional and should not be present if there are no open questions). - - Guardrails: - - Strict Factuality: Only mention signals present in the JSON. If a signal is missing, do not mention it. - - Only assert a cause if multiple signals support it. Otherwise mark Assessment "Inconclusive". - - Prefer corroborated explanations. If only one source supports it, state that support is limited. - - Do NOT repeat raw stacks verbatim (reference only key frames/fields). - - Conciseness: Use bullet points. Avoid flowery language. Be direct and technical. - - Available context tags: - - : Full error document (exception, message, stacktrace, labels) - - : Transaction linked to the error (if present) - - : Downstream dependencies for the erroring service - - : Span/transaction samples with service, name, type, eventOutcome, statusCode, duration, httpUrl, downstreamServiceResource - - : Related errors within the trace (type, message, culprit, spanId, timestampUs) - - : Service aggregates for the trace (serviceName, count, errorCount) - - : Categorized log patterns tied to the trace (errorCategory, docCount, sampleMessage) -`); +function getErrorAiInsightSystemPrompt({ urlPrefix }: { urlPrefix: string }) { + return dedent(` + You are an expert SRE Assistant within Elastic Observability. Your job is to analyze an APM error using ONLY the provided context (APM trace items, related errors, downstream dependencies, and log categories). + + Output structure (concise, Markdown): + - Error summary (1-2 sentences): What is observed and why it matters. + - Failure pinpoint: Whether failure is in application code vs dependency. Name the likely failing component/endpoint. Reference specific fields or key frames if available. + - Impact: Scope and severity (services/endpoints and the extent of the error if evident). + - Immediate actions (2-4): Ordered, concrete steps (config/network checks, retries/backoff, circuit breakers, targeted tracing/logging). + - Open questions: Short list of unknowns and the quickest queries to resolve them, if any (this is strictly optional and should not be present if there are no open questions). + + Guardrails: + - Strict Factuality: Only mention signals present in the JSON. If a signal is missing, do not mention it. + - Only assert a cause if multiple signals support it. Otherwise mark Assessment "Inconclusive". + - Prefer corroborated explanations. If only one source supports it, state that support is limited. + - Do NOT repeat raw stacks verbatim (reference only key frames/fields). + - Conciseness: Use bullet points. Avoid flowery language. Be direct and technical. + + Available context tags: + - : Full error document (exception, message, stacktrace, labels) + - : Transaction linked to the error (if present) + - : Downstream dependencies for the erroring service + - : Span/transaction samples with service, name, type, eventOutcome, statusCode, duration, httpUrl, downstreamServiceResource + - : Related errors within the trace (type, message, culprit, spanId, timestampUs) + - : Service aggregates for the trace (serviceName, count, errorCount) + - : Categorized log patterns tied to the trace (errorCategory, docCount, sampleMessage) + + ${getEntityLinkingInstructions({ urlPrefix })} + `); +} const buildUserPrompt = (errorContext: string) => { return dedent(` @@ -87,6 +92,8 @@ export async function generateErrorAiInsight({ inferenceClient, dataRegistry, }: GenerateErrorAiInsightParams): Promise { + const urlPrefix = core.http.basePath.get(request); + const errorContext = await fetchApmErrorContext({ core, plugins, @@ -103,7 +110,7 @@ export async function generateErrorAiInsight({ const userPrompt = buildUserPrompt(errorContext); const events$ = inferenceClient.chatComplete({ - system: ERROR_AI_INSIGHT_SYSTEM_PROMPT, + system: getErrorAiInsightSystemPrompt({ urlPrefix }), messages: [ { role: MessageRole.User, diff --git a/x-pack/solutions/observability/plugins/observability_agent_builder/server/routes/ai_insights/get_alert_ai_insights.ts b/x-pack/solutions/observability/plugins/observability_agent_builder/server/routes/ai_insights/get_alert_ai_insights.ts index cdf12ecf3a15e..9a2d19e00514e 100644 --- a/x-pack/solutions/observability/plugins/observability_agent_builder/server/routes/ai_insights/get_alert_ai_insights.ts +++ b/x-pack/solutions/observability/plugins/observability_agent_builder/server/routes/ai_insights/get_alert_ai_insights.ts @@ -20,6 +20,7 @@ import type { ObservabilityAgentBuilderPluginSetupDependencies, } from '../../types'; import { getToolHandler as getLogCategories } from '../../tools/get_log_categories/handler'; +import { getEntityLinkingInstructions } from '../../agent/register_observability_agent'; /** * These types are derived from the generated alerts-as-data schemas: @@ -62,6 +63,8 @@ export async function getAlertAiInsight({ request, logger, }: GetAlertAiInsightParams): Promise { + const urlPrefix = core.http.basePath.get(request); + const relatedContext = await fetchAlertContext({ core, plugins, @@ -74,6 +77,7 @@ export async function getAlertAiInsight({ inferenceClient, connectorId, alertDoc, + urlPrefix, context: relatedContext, }); @@ -208,11 +212,13 @@ async function fetchAlertContext({ function generateAlertSummary({ inferenceClient, + urlPrefix, connectorId, alertDoc, context, }: { inferenceClient: InferenceClient; + urlPrefix: string; connectorId: string; alertDoc: AlertDocForInsight; context: string; @@ -242,6 +248,8 @@ function generateAlertSummary({ 3) Log categories: error messages and exception patterns 4) Errors: exception patterns with downstream context 5) Service summary: instance counts, versions, anomalies, and metadata + + ${getEntityLinkingInstructions({ urlPrefix })} `); const alertDetails = `\`\`\`json\n${JSON.stringify(alertDoc, null, 2)}\n\`\`\``; diff --git a/x-pack/solutions/observability/plugins/observability_agent_builder/server/routes/ai_insights/get_log_ai_insights.ts b/x-pack/solutions/observability/plugins/observability_agent_builder/server/routes/ai_insights/get_log_ai_insights.ts index 1b8b8de19ace5..61fc3aa41fa44 100644 --- a/x-pack/solutions/observability/plugins/observability_agent_builder/server/routes/ai_insights/get_log_ai_insights.ts +++ b/x-pack/solutions/observability/plugins/observability_agent_builder/server/routes/ai_insights/get_log_ai_insights.ts @@ -11,10 +11,13 @@ import { safeJsonStringify } from '@kbn/std'; 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 { getEntityLinkingInstructions } from '../../agent/register_observability_agent'; import type { AiInsightResult, ContextEvent } from './types'; export interface GetLogAiInsightsParams { + core: ObservabilityAgentBuilderCoreSetup; index: string; id: string; dataRegistry: ObservabilityAgentBuilderDataRegistry; @@ -25,6 +28,7 @@ export interface GetLogAiInsightsParams { } export async function getLogAiInsights({ + core, index, id, request, @@ -35,7 +39,10 @@ export async function getLogAiInsights({ }: GetLogAiInsightsParams): Promise { 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.`); + Using the provided data produce a concise, action-oriented response. + + ${getEntityLinkingInstructions({ urlPrefix: core.http.basePath.get(request) })} + `); const logEntry = await getLogDocumentById({ esClient: esClient.asCurrentUser, diff --git a/x-pack/solutions/observability/plugins/observability_agent_builder/server/routes/ai_insights/route.ts b/x-pack/solutions/observability/plugins/observability_agent_builder/server/routes/ai_insights/route.ts index a376c25844a99..c31deb68002e2 100644 --- a/x-pack/solutions/observability/plugins/observability_agent_builder/server/routes/ai_insights/route.ts +++ b/x-pack/solutions/observability/plugins/observability_agent_builder/server/routes/ai_insights/route.ts @@ -142,6 +142,7 @@ export function getObservabilityAgentBuilderAiInsightsRouteRepository(): ServerR const esClient = coreStart.elasticsearch.client.asScoped(request); const result = await getLogAiInsights({ + core, index, id, inferenceClient,