Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0e45c52
wip
stephmilovic Nov 11, 2025
461686c
fix tool description and schema
stephmilovic Nov 11, 2025
5996a99
rm
stephmilovic Nov 11, 2025
779e84c
fixes
stephmilovic Nov 11, 2025
771f19e
Merge branch 'main' into product_docs_ab_tool
stephmilovic Nov 12, 2025
8252602
Changes from node scripts/lint_ts_projects --fix
kibanamachine Nov 12, 2025
f2ee19c
Merge branch 'product_docs_ab_tool' of github.com:stephmilovic/kibana…
stephmilovic Nov 12, 2025
b1d415c
Merge branch 'main' into product_docs_ab_tool
stephmilovic Nov 12, 2025
15f004b
shorter description
stephmilovic Nov 12, 2025
67e00dd
product documentation registration only when available
stephmilovic Nov 12, 2025
99b53ec
update tool services tests
stephmilovic Nov 12, 2025
b356eef
promises for types
stephmilovic Nov 12, 2025
caa6709
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Nov 12, 2025
ccf5860
Merge branch 'main' into product_docs_ab_tool
stephmilovic Nov 14, 2025
85f22ee
refactor
stephmilovic Nov 14, 2025
6be7a9f
more fixing
stephmilovic Nov 14, 2025
2856fb6
Changes from node scripts/lint_ts_projects --fix
kibanamachine Nov 14, 2025
789b159
rm async
stephmilovic Nov 14, 2025
6f4bea1
Merge branch 'product_docs_ab_tool' of github.com:stephmilovic/kibana…
stephmilovic Nov 14, 2025
5afd16c
revert plugin
stephmilovic Nov 14, 2025
24b046d
dynamic inference id
stephmilovic Nov 14, 2025
e71f2dd
Merge branch 'main' into product_docs_ab_tool
elasticmachine Nov 17, 2025
7382505
rm connector inferenceId code
stephmilovic Nov 18, 2025
414df60
Merge branch 'main' into product_docs_ab_tool
stephmilovic Nov 18, 2025
0911d2a
Merge remote-tracking branch 'upstream/main' into product_docs_ab_tool
stephmilovic Nov 25, 2025
63b605a
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine Nov 25, 2025
d9a976f
Merge remote-tracking branch 'upstream/main' into product_docs_ab_tool
stephmilovic Nov 30, 2025
bad8112
move to new plugin
stephmilovic Nov 30, 2025
c52a35e
productDocumentationTool
stephmilovic Nov 30, 2025
5eaae5c
Changes from node scripts/lint_ts_projects --fix
kibanamachine Nov 30, 2025
6409546
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine Nov 30, 2025
3282c73
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Nov 30, 2025
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 @@ -24,6 +24,7 @@ export const platformCoreTools = {
generateEsql: platformCoreTool('generate_esql'),
executeEsql: platformCoreTool('execute_esql'),
createVisualization: platformCoreTool('create_visualization'),
productDocumentation: platformCoreTool('product_documentation'),
} as const;

/**
Expand Down
3 changes: 2 additions & 1 deletion x-pack/platform/plugins/shared/onechat/kibana.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
"cloud",
"licenseManagement",
"workflowsManagement",
"usageCollection"
"usageCollection",
"llmTasks"
],
"extraPublicDirs": []
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export { generateEsqlTool } from './generate_esql';
export { executeEsqlTool } from './execute_esql';
export { searchTool } from './search';
export { createVisualizationTool } from './create_visualization';
export { productDocumentationTool } from './product_documentation';
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { z } from '@kbn/zod';
import { platformCoreTools, ToolType } from '@kbn/onechat-common';
import { defaultInferenceEndpoints, InferenceConnectorType } from '@kbn/inference-common';
import type { BuiltinToolDefinition } from '@kbn/onechat-server';
import { createErrorResult } from '@kbn/onechat-server';
import { ToolResultType } from '@kbn/onechat-common/tools/tool_result';
import type { CoreSetup } from '@kbn/core/server';
import type { RetrieveDocumentationResultDoc } from '@kbn/llm-tasks-plugin/server';
import type { OnechatStartDependencies, OnechatPluginStart } from '../../../../types';
import { createModelProvider } from '../../../runner/model_provider';

const productDocumentationSchema = z.object({
query: z.string().describe('Search query to retrieve documentation about Elastic products'),
product: z
.enum(['kibana', 'elasticsearch', 'observability', 'security'])
.optional()
.describe('Product to filter by: "kibana", "elasticsearch", "observability", or "security"'),
max: z
.number()
.optional()
.default(3)
.describe('Maximum number of documents to return. Defaults to 3.'),
});

export const productDocumentationTool = (
coreSetup: CoreSetup<OnechatStartDependencies, OnechatPluginStart>
): BuiltinToolDefinition<typeof productDocumentationSchema> => {
// Create a closure that will resolve llmTasks when the handler is called
const getLlmTasks = async () => {
const [, plugins] = await coreSetup.getStartServices();
return plugins.llmTasks;
};

const baseTool: BuiltinToolDefinition<typeof productDocumentationSchema> = {
id: platformCoreTools.productDocumentation,
type: ToolType.builtin,
description: `Search and retrieve documentation about Elastic products (Kibana, Elasticsearch, Elastic Security, Elastic Observability).`,
schema: productDocumentationSchema,
handler: async ({ query, product, max = 3 }, { modelProvider, logger, request }) => {
const llmTasks = await getLlmTasks();
if (!llmTasks) {
return {
results: [
createErrorResult({
message:
'Product documentation tool is not available. LlmTasks plugin is not available.',
}),
],
};
}

try {
// Get the default model to extract the connector
const model = await modelProvider.getDefaultModel();
const connector = model.connector;

// Try to get inferenceId from the connector, fall back to default ELSER endpoint
let inferenceId = defaultInferenceEndpoints.ELSER;
if (connector.type === InferenceConnectorType.Inference && connector.config?.inferenceId) {
inferenceId = connector.config.inferenceId;
}

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.

@pgayvallet not sure this is the best way to make the inference endpoint configurable, but not sure another way at the moment. Do you think we should go forward with getting it off the connector, should I just default it to ELSER or do you see another way forward?

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.

I don't have a better idea. Now that we're getting "unified" through AB, I imagine we should have a platform level setting for the embedding model we use for our semantic_text fields at some point. Until we do...

One question - is that inferenceId on the inference connector config something users can already configure to specify an embedding model to use for a specific connector? if so, I'm fine using it. Otherwise I would just KISS and default to using ELSER until we figure out how to do it better.

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.

I think inferenceId is only on inference connector type:

  elastic-llm:
    name: Elastic LLM
    actionTypeId: .inference
    exposeConfig: true
    config:
      provider: 'elastic'
      taskType: 'chat_completion'
      inferenceId: '.rainbow-sprinkles-elastic'
      providerConfig:
        model_id: 'rainbow-sprinkles'

And considering we're expecting .elser-2-elasticsearch or .multilingual-e5-small-elasticsearch, that doesn't seem right. I'll hardcode to ELSER for now and leave a comment


// Retrieve documentation
const result = await llmTasks.retrieveDocumentation({
searchTerm: query,
products: product ? [product as any] : undefined,
max,
connectorId: connector.connectorId,
request,
inferenceId,
});

if (!result.success || result.documents.length === 0) {
return {
results: [
createErrorResult({
message: 'No documentation found for the given query.',
metadata: {
query,
product: product || 'all',
},
}),
],
};
}

// Return documentation results
return {
results: result.documents.map((doc: RetrieveDocumentationResultDoc) => ({
type: ToolResultType.resource,
data: {
reference: {
url: doc.url,
title: doc.title,
},
partial: doc.summarized,
content: {
title: doc.title,
url: doc.url,
content: doc.content,
summarized: doc.summarized,
},
},
})),
};
} catch (error) {
logger.error(`Error retrieving product documentation: ${error.message}`);
return {
results: [
createErrorResult({
message: `Failed to retrieve product documentation: ${error.message}`,
}),
],
};
}
},
tags: [],
availability: {
cacheMode: 'space',
handler: async ({ request }) => {
try {
const [, plugins] = await coreSetup.getStartServices();
const llmTasks = plugins.llmTasks;

if (!llmTasks) {
return { status: 'unavailable' };
}

// Try to get inferenceId from the default connector using modelProvider
let inferenceId = defaultInferenceEndpoints.ELSER;
try {
const modelProvider = createModelProvider({
inference: plugins.inference,
request,
});
const model = await modelProvider.getDefaultModel();
const connector = model.connector;
// For inference connectors, inferenceId is stored in config.inferenceId
if (
connector.type === InferenceConnectorType.Inference &&
connector.config?.inferenceId
) {
inferenceId = connector.config.inferenceId;
}
} catch {
// If we can't get the connector, fall back to default ELSER endpoint
}

const isAvailable =
(await llmTasks.retrieveDocumentationAvailable({
inferenceId,
})) ?? false;

return {
status: isAvailable ? 'available' : 'unavailable',
};
} catch (error) {
return { status: 'unavailable' };
}
},
},
};

return baseTool;
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
listIndicesTool,
indexExplorerTool,
createVisualizationTool,
productDocumentationTool,
} from './definitions';
import type {
OnechatSetupDependencies,
Expand All @@ -42,6 +43,7 @@ export const registerBuiltinTools = ({
listIndicesTool(),
indexExplorerTool(),
createVisualizationTool(),
productDocumentationTool(coreSetup),
];

tools.forEach((tool) => {
Expand Down
2 changes: 2 additions & 0 deletions x-pack/platform/plugins/shared/onechat/server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type { SpacesPluginSetup, SpacesPluginStart } from '@kbn/spaces-plugin/se
import type { InferenceServerSetup, InferenceServerStart } from '@kbn/inference-plugin/server';
import type { WorkflowsServerPluginSetup } from '@kbn/workflows-management-plugin/server';
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
import type { LlmTasksPluginStart } from '@kbn/llm-tasks-plugin/server';
import type { BuiltInAgentDefinition } from '@kbn/onechat-server/agents';
import type { ToolsServiceSetup, ToolRegistry } from './services/tools';
import type { AttachmentServiceSetup } from './services/attachments';
Expand All @@ -32,6 +33,7 @@ export interface OnechatStartDependencies {
licensing: LicensingPluginStart;
cloud?: CloudStart;
spaces?: SpacesPluginStart;
llmTasks?: LlmTasksPluginStart;
}

export interface AttachmentsSetup {
Expand Down
3 changes: 2 additions & 1 deletion x-pack/platform/plugins/shared/onechat/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"@kbn/maps-plugin",
"@kbn/shared-ux-utility",
"@kbn/usage-collection-plugin",
"@kbn/core-notifications-browser"
"@kbn/core-notifications-browser",
"@kbn/llm-tasks-plugin"
]
}