-
Notifications
You must be signed in to change notification settings - Fork 8.6k
[onechat] add first set of base tools #223367
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e0cbaba
e102b4d
76b86c0
4a592dc
83b8009
097c2c0
408ba49
bb64fb6
2243e44
55b66d8
71c68fc
06c2ec8
28e6b22
596e428
688c98a
9e3c077
46839ad
8138de3
44dd5a1
233f0ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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', | ||
| }; | ||
| 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') { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick: use an enum for this?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" | ||
| } |
There was a problem hiding this comment.
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).