Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,7 @@ x-pack/platform/packages/shared/ml/runtime_field_utils @elastic/ml-ui
x-pack/platform/packages/shared/ml/trained_models_utils @elastic/ml-ui
x-pack/platform/packages/shared/onechat/onechat-browser @elastic/workchat-eng
x-pack/platform/packages/shared/onechat/onechat-common @elastic/workchat-eng
x-pack/platform/packages/shared/onechat/onechat-genai-utils @elastic/workchat-eng
x-pack/platform/packages/shared/onechat/onechat-server @elastic/workchat-eng
x-pack/platform/packages/shared/security/api_key_management @elastic/kibana-security
x-pack/platform/packages/shared/security/form_components @elastic/kibana-security
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,7 @@
"@kbn/oidc-provider-plugin": "link:x-pack/test/security_api_integration/plugins/oidc_provider",
"@kbn/onechat-browser": "link:x-pack/platform/packages/shared/onechat/onechat-browser",
"@kbn/onechat-common": "link:x-pack/platform/packages/shared/onechat/onechat-common",
"@kbn/onechat-genai-utils": "link:x-pack/platform/packages/shared/onechat/onechat-genai-utils",
"@kbn/onechat-plugin": "link:x-pack/platform/plugins/shared/onechat",
"@kbn/onechat-server": "link:x-pack/platform/packages/shared/onechat/onechat-server",
"@kbn/open-telemetry-instrumented-plugin": "link:src/platform/test/common/plugins/otel_metrics",
Expand Down
2 changes: 2 additions & 0 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -1406,6 +1406,8 @@
"@kbn/onechat-browser/*": ["x-pack/platform/packages/shared/onechat/onechat-browser/*"],
"@kbn/onechat-common": ["x-pack/platform/packages/shared/onechat/onechat-common"],
"@kbn/onechat-common/*": ["x-pack/platform/packages/shared/onechat/onechat-common/*"],
"@kbn/onechat-genai-utils": ["x-pack/platform/packages/shared/onechat/onechat-genai-utils"],
"@kbn/onechat-genai-utils/*": ["x-pack/platform/packages/shared/onechat/onechat-genai-utils/*"],
"@kbn/onechat-plugin": ["x-pack/platform/plugins/shared/onechat"],
"@kbn/onechat-plugin/*": ["x-pack/platform/plugins/shared/onechat/*"],
"@kbn/onechat-server": ["x-pack/platform/packages/shared/onechat/onechat-server"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { StructuredToolIdentifier } from '../tools/tools';
export enum ChatAgentEventType {
toolCall = 'toolCall',
toolResult = 'toolResult',
reasoning = 'reasoning',
messageChunk = 'messageChunk',
messageComplete = 'messageComplete',
roundComplete = 'roundComplete',
Expand Down Expand Up @@ -53,6 +54,18 @@ export const isToolResultEvent = (event: OnechatEvent<string, any>): event is To
return event.type === ChatAgentEventType.toolResult;
};

// reasoning

export interface ReasoningEventData {
reasoning: string;
}

export type ReasoningEvent = ChatAgentEventBase<ChatAgentEventType.reasoning, ReasoningEventData>;

export const isReasoningEvent = (event: OnechatEvent<string, any>): event is ReasoningEvent => {
return event.type === ChatAgentEventType.reasoning;
};

// Message chunk

export interface MessageChunkEventData {
Expand Down Expand Up @@ -116,6 +129,7 @@ export const isRoundCompleteEvent = (
export type ChatAgentEvent =
| ToolCallEvent
| ToolResultEvent
| ReasoningEvent
| MessageChunkEvent
| MessageCompleteEvent
| RoundCompleteEvent;
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export {
type ToolResultEventData,
type ToolCallEvent,
type ToolCallEventData,
type ReasoningEvent,
type ReasoningEventData,
type MessageChunkEventData,
type MessageChunkEvent,
type MessageCompleteEventData,
Expand All @@ -27,6 +29,7 @@ export {
type RoundCompleteEvent,
isToolCallEvent,
isToolResultEvent,
isReasoningEvent,
isMessageChunkEvent,
isMessageCompleteEvent,
isRoundCompleteEvent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export {
createBuiltinToolId,
builtinToolProviderId,
unknownToolProviderId,
BuiltinToolIds,
BuiltinTags,
} from './tools';
export {
OnechatErrorCode,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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.
*/

/**
* Ids of built-in onechat tools
*/
export const BuiltinToolIds = {
indexExplorer: 'index_explorer',
relevanceSearch: 'relevance_search',
naturalLanguageSearch: 'nl_search',
listIndices: 'list_indices',
getIndexMapping: 'get_index_mapping',
getDocumentById: 'get_document_by_id',
generateEsql: 'generate_esql',
executeEsql: 'execute_esql',
researcherAgent: 'researcher_agent',
};

/**
* Common set of tags used for platform tools.
*/
export const BuiltinTags = {
/**
* Tag associated to tools related to data retrieval
*/
retrieval: 'retrieval',
Comment on lines +27 to +30
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.

Starting to see a pattern where we assign tags to tools to allow easily filtering for specific profiles (e.g search agent get assigned all the retrieval tools).

};
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ export {
builtinToolProviderId,
unknownToolProviderId,
} from './tools';
export { BuiltinToolIds, BuiltinTags } from './constants';
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# @kbn/onechat-genai-utils

Empty package generated by @kbn/generate
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 type { KibanaRequest } from '@kbn/core-http-server';
import type { PlainIdToolIdentifier, ToolProviderId, ToolDescriptor } from '@kbn/onechat-common';
import type { ToolProvider, ExecutableTool } from '@kbn/onechat-server';

export interface ByToolIdRule {
type: 'by_tool_id';
providerId: ToolProviderId;
toolIds: PlainIdToolIdentifier[];
}

export interface ByProviderIdRule {
type: 'by_provider_id';
providerId: ToolProviderId;
}

export type ToolFilterRule = ByToolIdRule | ByProviderIdRule;

const matches = (rule: ToolFilterRule, tool: ToolDescriptor): boolean => {
if (rule.type === 'by_tool_id') {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nitpick: use an enum for this?

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.

To be honest, I've been traumatized by enums and the fact they are not abstract (type) and require a concrete import, so I'm trying to avoid using them as much as possible, unless forced to because of some TS inference magical type I want to build.

But this is a very personal preference.

return tool.meta.providerId === rule.providerId && rule.toolIds.includes(tool.id);
} else if (rule.type === 'by_provider_id') {
return tool.meta.providerId === rule.providerId;
} else {
throw new Error('Unknown rule type');
}
};

const anyMatch = (rules: ToolFilterRule[], tool: ToolDescriptor): boolean => {
return rules.some((rule) => matches(rule, tool));
};

export const filterProviderTools = async ({
provider,
rules,
request,
}: {
provider: ToolProvider;
rules: ToolFilterRule[];
request: KibanaRequest;
}): Promise<ExecutableTool[]> => {
const tools = await provider.list({ request });
return tools.filter((tool) => anyMatch(rules, tool));
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* 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.
*/

export {
filterProviderTools,
type ByProviderIdRule,
type ToolFilterRule,
type ByToolIdRule,
} from './compose_provider';
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.
*/

export {
esqlResponseToJson,
flattenMappings,
cleanupMapping,
type MappingField,
} from './tools/utils';
export {
getDocumentById,
type GetDocumentByIdResult,
getIndexMappings,
type GetIndexMappingEntry,
type GetIndexMappingsResult,
executeEsql,
type EsqlResponse,
listIndices,
type ListIndexInfo,
} from './tools/steps';
export {
indexExplorer,
type IndexExplorerResponse,
generateEsql,
type GenerateEsqlResponse,
relevanceSearch,
type RelevanceSearchResponse,
naturalLanguageSearch,
type NaturalLanguageSearchResponse,
} from './tools';
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* 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.
*/

module.exports = {
preset: '@kbn/test/jest_node',
rootDir: '../../../../../..',
roots: ['<rootDir>/x-pack/platform/packages/shared/onechat/onechat-genai-utils'],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "shared-server",
"id": "@kbn/onechat-genai-utils",
"owner": "@elastic/workchat-eng",
"group": "platform",
"visibility": "shared"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* 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 type { AIMessageChunk } from '@langchain/core/messages';
import { StreamEvent as LangchainStreamEvent } from '@langchain/core/tracers/log_stream';
import {
ChatAgentEventType,
MessageChunkEvent,
ReasoningEvent,
MessageCompleteEvent,
} from '@kbn/onechat-common/agents';
import { extractTextContent } from './messages';

export const matchGraphName = (event: LangchainStreamEvent, graphName: string): boolean => {
return event.metadata.graphName === graphName;
};

export const matchGraphNode = (event: LangchainStreamEvent, nodeName: string): boolean => {
return event.metadata.langgraph_node === nodeName;
};

export const matchEvent = (event: LangchainStreamEvent, eventName: string): boolean => {
return event.event === eventName;
};

export const matchName = (event: LangchainStreamEvent, name: string): boolean => {
return event.name === name;
};

export const hasTag = (event: LangchainStreamEvent, tag: string): boolean => {
return (event.tags ?? []).includes(tag);
};

export const createTextChunkEvent = (
chunk: AIMessageChunk,
{ defaultMessageId = 'unknown' }: { defaultMessageId?: string } = {}
): MessageChunkEvent => {
return {
type: ChatAgentEventType.messageChunk,
data: {
messageId: chunk.id ?? defaultMessageId,
textChunk: extractTextContent(chunk),
},
};
};

export const createMessageEvent = (
content: string,
{ messageId = 'unknown' }: { messageId?: string } = {}
): MessageCompleteEvent => {
return {
type: ChatAgentEventType.messageComplete,
data: {
messageId,
messageContent: content,
},
};
};

export const createReasoningEvent = (reasoning: string): ReasoningEvent => {
return {
type: ChatAgentEventType.reasoning,
data: {
reasoning,
},
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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.
*/

export {
matchGraphName,
matchGraphNode,
matchName,
matchEvent,
hasTag,
createTextChunkEvent,
createMessageEvent,
createReasoningEvent,
} from './graph_events';
export { extractTextContent } from './messages';
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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 { BaseMessage, MessageContentComplex } from '@langchain/core/messages';

/**
* Extract the text content from a langchain message or chunk.
*/
export const extractTextContent = (message: BaseMessage): string => {
if (typeof message.content === 'string') {
return message.content;
} else {
let content = '';
for (const item of message.content as MessageContentComplex[]) {
if (item.type === 'text') {
content += item.text;
}
}
return content;
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "@kbn/onechat-genai-utils",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0"
}
Loading