diff --git a/common/constants/llm.ts b/common/constants/llm.ts index 8a68dba2..176bda88 100644 --- a/common/constants/llm.ts +++ b/common/constants/llm.ts @@ -12,9 +12,6 @@ export const ASSISTANT_API = { SEND_MESSAGE: `${API_BASE}/send_message`, SESSION: `${API_BASE}/session`, SESSIONS: `${API_BASE}/sessions`, - PPL_GENERATOR: `${API_BASE}/generate_ppl`, - SUMMARIZATION: `${API_BASE}/summarize`, - AGENT_TEST: `${API_BASE}/agent_test`, FEEDBACK: `${API_BASE}/feedback`, ABORT_AGENT_EXECUTION: `${API_BASE}/abort`, REGENERATE: `${API_BASE}/regenerate`, diff --git a/common/utils/llm_chat/__tests__/traces.test.ts b/common/utils/llm_chat/__tests__/traces.test.ts deleted file mode 100644 index 4de6d1e9..00000000 --- a/common/utils/llm_chat/__tests__/traces.test.ts +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Run } from 'langchain/callbacks'; -import { AgentRun } from 'langchain/dist/callbacks/handlers/tracer'; -import { convertToTraces } from '../traces'; - -describe('Test', () => { - it('should convert runs to traces', () => { - const traces = convertToTraces(mockRuns); - expect(traces).toEqual([ - { - actions: [ - { - log: ' ```json\n{\n "action": "Get OpenSearch indices" \n}\n```', - tool: 'Get OpenSearch indices', - toolInput: '', - }, - ], - id: 'bbc4791c-601b-4c7c-ba62-409419e8ef41', - input: 'human input', - name: 'AgentExecutor', - output: 'ai output', - parentRunId: undefined, - startTime: 1692820695308, - type: 'chain', - }, - { - id: '3d7145a2-1cc1-43cb-9685-bfbe426f03d0', - input: '', - name: 'LLMChain', - output: 'suggestions', - parentRunId: undefined, - startTime: 1692820706240, - type: 'chain', - }, - { - id: 'ad3d36d6-ecba-4ca1-a14a-9fd54132d16b', - input: '', - name: 'Get OpenSearch indices', - output: 'tools output', - parentRunId: 'bbc4791c-601b-4c7c-ba62-409419e8ef41', - startTime: 1692820697545, - type: 'tool', - }, - { - id: '9c610e59-8abb-4f56-a9c8-5e0cb980ba15', - input: 'human message', - name: 'ChatAnthropic', - output: 'suggestions', - parentRunId: '3d7145a2-1cc1-43cb-9685-bfbe426f03d0', - startTime: 1692820706241, - type: 'llm', - }, - ]); - }); -}); - -type RecursivePartial = { - [P in keyof T]?: T[P] extends Array - ? Array> - : T[P] extends object | undefined - ? RecursivePartial - : T[P]; -}; - -// mock runs with only the fields that are being used for converting to traces -const partialMockRuns: Array> = [ - { - id: 'bbc4791c-601b-4c7c-ba62-409419e8ef41', - name: 'AgentExecutor', - start_time: 1692820695308, - inputs: { - input: 'human input', - chat_history: [ - { - lc: 1, - type: 'constructor', - id: ['langchain', 'schema', 'HumanMessage'], - kwargs: { content: 'human input', additional_kwargs: {} }, - }, - { - lc: 1, - type: 'constructor', - id: ['langchain', 'schema', 'AIMessage'], - kwargs: { content: 'ai output', additional_kwargs: {} }, - }, - ], - }, - execution_order: 1, - child_execution_order: 2, - run_type: 'chain', - child_runs: [ - { - id: 'ad3d36d6-ecba-4ca1-a14a-9fd54132d16b', - name: 'DynamicTool', - parent_run_id: 'bbc4791c-601b-4c7c-ba62-409419e8ef41', - start_time: 1692820697545, - inputs: {}, - execution_order: 2, - child_execution_order: 2, - run_type: 'tool', - child_runs: [], - end_time: 1692820697560, - outputs: { output: 'tools output' }, - }, - ], - actions: [ - { - tool: 'Get OpenSearch indices', - log: ' ```json\n{\n "action": "Get OpenSearch indices" \n}\n```', - toolInput: '', - }, - ], - end_time: 1692820706226, - outputs: { output: 'ai output' }, - }, - { - id: '3d7145a2-1cc1-43cb-9685-bfbe426f03d0', - name: 'LLMChain', - start_time: 1692820706240, - inputs: { - tools_description: 'tools description', - chat_history: 'human: human input\nai: ai output', - }, - execution_order: 1, - child_execution_order: 2, - run_type: 'chain', - child_runs: [ - { - id: '9c610e59-8abb-4f56-a9c8-5e0cb980ba15', - name: 'ChatAnthropic', - parent_run_id: '3d7145a2-1cc1-43cb-9685-bfbe426f03d0', - start_time: 1692820706241, - inputs: { - messages: [ - [ - { - lc: 1, - type: 'constructor', - id: ['langchain', 'schema', 'HumanMessage'], - kwargs: { content: 'human message', additional_kwargs: {} }, - }, - ], - ], - }, - execution_order: 2, - child_runs: [], - child_execution_order: 2, - run_type: 'llm', - end_time: 1692820709238, - outputs: { - generations: [ - [ - { - text: 'suggestions', - message: { - lc: 1, - type: 'constructor', - id: ['langchain', 'schema', 'AIMessage'], - kwargs: { content: 'suggestions', additional_kwargs: {} }, - }, - }, - ], - ], - }, - }, - ], - end_time: 1692820709238, - outputs: { text: 'suggestions' }, - }, -]; - -const mockRuns = partialMockRuns as Array; diff --git a/common/utils/llm_chat/traces.ts b/common/utils/llm_chat/traces.ts index 73ee3088..ab3dba7f 100644 --- a/common/utils/llm_chat/traces.ts +++ b/common/utils/llm_chat/traces.ts @@ -3,10 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Run } from 'langchain/callbacks'; -import { AgentRun } from 'langchain/dist/callbacks/handlers/tracer'; -import _ from 'lodash'; - export interface AgentFrameworkTrace { interactionId: string; parentInteractionId: string; @@ -16,62 +12,3 @@ export interface AgentFrameworkTrace { origin: string; traceNumber: number; } - -export interface LangchainTrace { - id: Run['id']; - parentRunId?: Run['parent_run_id']; - actions?: AgentRun['actions']; - type: Run['run_type']; - startTime: Run['start_time']; - name: string; - input: string; - output?: string; -} - -const getValue = (obj: Record, possibleKeys: string[]) => { - for (const key of possibleKeys) { - const value = _.get(obj, key); - if (value) return value; - } - return ''; -}; - -/** - * By default, tool traces have name 'DynamicTool'. Replace name for all tool - * traces with the tool used in parent run actions. - */ -const replaceToolNames = (traces: LangchainTrace[]) => { - return traces.map((trace) => ({ - ...trace, - ...(trace.type === 'tool' && { - name: _.get( - traces.find((t) => t.id === trace.parentRunId), - 'actions.0.tool', - trace.name - ), - }), - })); -}; - -const traverse = (runs: Array, traces: LangchainTrace[] = []) => { - traces.push( - ...runs.map((run) => ({ - id: run.id, - parentRunId: run.parent_run_id, - type: run.run_type, - startTime: run.start_time, - name: run.name, - input: getValue(run.inputs, ['input', 'question', 'messages.0.0.kwargs.content']), - output: run.outputs && getValue(run.outputs, ['output', 'text', 'generations.0.0.text']), - ...('actions' in run && { actions: run.actions }), - })) - ); - runs.forEach((run) => { - if (run.child_runs) traverse(run.child_runs, traces); - }); - return traces; -}; - -export const convertToTraces = (runs: Run[]) => { - return replaceToolNames(traverse(runs)); -}; diff --git a/package.json b/package.json index d79d30a9..ca933f15 100644 --- a/package.json +++ b/package.json @@ -22,9 +22,7 @@ "csv-parser": "^3.0.0", "dompurify": "^2.4.1", "jsdom": "^22.1.0", - "langchain": "^0.0.164", - "postinstall": "^0.7.4", - "web-streams-polyfill": "^3.2.1" + "postinstall": "^0.7.4" }, "devDependencies": { "@types/autosize": "^4.0.1", diff --git a/public/components/langchain_traces.tsx b/public/components/langchain_traces.tsx deleted file mode 100644 index e3dc83b5..00000000 --- a/public/components/langchain_traces.tsx +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - EuiAccordion, - EuiCodeBlock, - EuiEmptyPrompt, - EuiLoadingContent, - EuiSpacer, - EuiText, - EuiMarkdownFormat, - EuiHorizontalRule, -} from '@elastic/eui'; -import React from 'react'; -import { LangchainTrace } from '../../common/utils/llm_chat/traces'; -import { useFetchLangchainTraces } from '../hooks/use_fetch_langchain_traces'; - -// workaround to show LLM name as OpenSearch LLM -const formatRunName = (run: LangchainTrace) => { - if (run.type === 'llm') return 'OpenSearch LLM'; - return run.name; -}; - -interface LangchainTracesProps { - traceId: string; -} - -export const LangchainTraces: React.FC = (props) => { - const { data: traces, loading, error } = useFetchLangchainTraces(props.traceId); - - if (loading) { - return ( - <> - Loading... - - - ); - } - if (error) { - return ( - Error loading details} - body={error.toString()} - /> - ); - } - if (!traces?.length) { - return Data not available.; - } - - const question = traces[0].input; - const finalAnswer = traces[0].output; - const questionAndAnswer = ` - # How was this generated - #### Question - ${question} - #### Result - ${finalAnswer} - `; - - return ( - <> - {questionAndAnswer} - - - - -

Response

-
- {traces - .filter((run) => run.type === 'tool') - .filter((run) => run.input || run.output) - .map((run, i) => { - const stepContent = `Step ${i + 1} - ${formatRunName(run)}`; - return ( -
- - - {run.input && ( - - Input: {run.input} - - )} - {run.output && ( - - Output: {run.output} - - )} - - -
- ); - })} - - ); -}; diff --git a/public/components/langchain_traces_flyout_body.tsx b/public/components/langchain_traces_flyout_body.tsx deleted file mode 100644 index 6509d742..00000000 --- a/public/components/langchain_traces_flyout_body.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - EuiButtonEmpty, - EuiFlyoutBody, - EuiPage, - EuiPageBody, - EuiPageContentBody, - EuiPageHeader, - EuiButtonIcon, - EuiPageHeaderSection, -} from '@elastic/eui'; -import React from 'react'; -import { useChatContext } from '../contexts/chat_context'; -import { LangchainTraces } from './langchain_traces'; - -export const LangchainTracesFlyoutBody: React.FC = () => { - const chatContext = useChatContext(); - const traceId = chatContext.traceId; - if (!traceId) { - return null; - } - - // docked right or fullscreen with history open - const showBack = !chatContext.flyoutFullScreen || chatContext.preSelectedTabId === 'history'; - - return ( - - - - - - {showBack && ( - { - chatContext.setSelectedTabId(chatContext.flyoutFullScreen ? 'history' : 'chat'); - }} - iconType="arrowLeft" - > - Back - - )} - - - {!showBack && ( - { - chatContext.setSelectedTabId('chat'); - }} - /> - )} - - - - - - - - - ); -}; diff --git a/public/hooks/use_fetch_langchain_traces.ts b/public/hooks/use_fetch_langchain_traces.ts deleted file mode 100644 index 46ea41a3..00000000 --- a/public/hooks/use_fetch_langchain_traces.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Run } from 'langchain/callbacks'; -import { useEffect, useReducer } from 'react'; -import { SearchResponse } from '../../../../src/core/server'; -import { SearchRequest } from '../../../../src/plugins/data/common'; -import { DSL_BASE, DSL_SEARCH, LLM_INDEX } from '../../common/constants/llm'; -import { convertToTraces, LangchainTrace } from '../../common/utils/llm_chat/traces'; -import { useCore } from '../contexts/core_context'; -import { GenericReducer, genericReducer } from './fetch_reducer'; - -// TODO persist traces with chat objects -export const useFetchLangchainTraces = (traceId: string) => { - const core = useCore(); - const reducer: GenericReducer = genericReducer; - const [state, dispatch] = useReducer(reducer, { loading: false }); - - useEffect(() => { - const abortController = new AbortController(); - dispatch({ type: 'request' }); - if (!traceId) { - dispatch({ type: 'success', payload: undefined }); - return; - } - - const query: SearchRequest['body'] = { - query: { - term: { - trace_id: traceId, - }, - }, - sort: [ - { - start_time: { - order: 'asc', - }, - }, - ], - }; - - core.services.http - .post>(`${DSL_BASE}${DSL_SEARCH}`, { - body: JSON.stringify({ index: LLM_INDEX.TRACES, size: 100, ...query }), - signal: abortController.signal, - }) - .then((payload) => - dispatch({ - type: 'success', - payload: convertToTraces(payload.hits.hits.map((hit) => hit._source)), - }) - ) - .catch((error) => dispatch({ type: 'failure', error })); - - return () => abortController.abort(); - }, [traceId]); - - return { ...state }; -}; diff --git a/public/services/saved_object_manager.ts b/public/services/saved_object_manager.ts deleted file mode 100644 index 35ed00b2..00000000 --- a/public/services/saved_object_manager.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { SavedObjectsClientContract } from '../../../../src/core/public'; -import { SavedObjectService } from './saved_object_service'; - -export class SavedObjectManager { - private static instances: Map> = new Map(); - private constructor() {} - - public static getInstance( - savedObjectsClient: SavedObjectsClientContract, - savedObjectType: string - ) { - if (!SavedObjectManager.instances.has(savedObjectType)) { - SavedObjectManager.instances.set( - savedObjectType, - new SavedObjectService(savedObjectsClient, savedObjectType) - ); - } - return SavedObjectManager.instances.get(savedObjectType) as SavedObjectService; - } -} diff --git a/public/services/saved_object_service.ts b/public/services/saved_object_service.ts deleted file mode 100644 index cfd3defb..00000000 --- a/public/services/saved_object_service.ts +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { BehaviorSubject } from 'rxjs'; -import { SavedObjectsClientContract } from '../../../../src/core/public'; - -export class SavedObjectService { - private objects: Record | null>> = {}; - private loadingStatus: Record> = {}; - - constructor( - private readonly client: SavedObjectsClientContract, - private readonly savedObjectType: string - ) {} - - private setLoading(id: string, loading: boolean) { - if (!this.loadingStatus[id]) { - this.loadingStatus[id] = new BehaviorSubject(loading); - } else { - this.loadingStatus[id].next(loading); - } - } - - private async load(id: string) { - // set loading to true - this.setLoading(id, true); - - const savedObject = await this.client.get>(this.savedObjectType, id); - - // set loading to false - this.setLoading(id, false); - - if (!savedObject.error) { - this.objects[id].next(savedObject.attributes); - } - return savedObject; - } - - private async create(id: string, attributes: Partial) { - this.setLoading(id, true); - const newObject = await this.client.create>(this.savedObjectType, attributes, { - id, - }); - this.objects[id].next({ ...newObject.attributes }); - this.setLoading(id, false); - return newObject.attributes; - } - - private async update(id: string, attributes: Partial) { - this.setLoading(id, true); - const newObject = await this.client.update>(this.savedObjectType, id, attributes); - this.objects[id].next({ ...newObject.attributes }); - this.setLoading(id, false); - return newObject.attributes; - } - - private async initialize(id: string) { - if (!this.objects[id]) { - this.objects[id] = new BehaviorSubject | null>(null); - await this.load(id); - } - } - - public async get(id: string) { - await this.initialize(id); - return this.objects[id].getValue(); - } - - public get$(id: string) { - this.initialize(id); - return this.objects[id]; - } - - public getLoadingStatus$(id: string) { - return this.loadingStatus[id]; - } - - public async createOrUpdate(id: string, attributes: Partial) { - const currentObject = await this.load(id); - - if (currentObject.error) { - // Object not found, create a new object - if (currentObject.error.statusCode === 404) { - return await this.create(id, attributes); - } - } else { - // object found, update existing object - return await this.update(id, attributes); - } - } -} diff --git a/server/adaptors/constants/alerting.ts b/server/adaptors/constants/alerting.ts deleted file mode 100644 index d43b8fe8..00000000 --- a/server/adaptors/constants/alerting.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -export const API_ROUTE_PREFIX = '/_plugins/_alerting'; -export const MONITOR_BASE_API = `${API_ROUTE_PREFIX}/monitors`; -export const AD_BASE_API = `/_plugins/_anomaly_detection/detectors`; -export const DESTINATION_BASE_API = `${API_ROUTE_PREFIX}/destinations`; -export const EMAIL_ACCOUNT_BASE_API = `${DESTINATION_BASE_API}/email_accounts`; -export const EMAIL_GROUP_BASE_API = `${DESTINATION_BASE_API}/email_groups`; -export const DEFAULT_HEADERS = { - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'OpenSearch-Dashboards', -}; -export const CLUSTER = { - ADMIN: 'admin', - ALERTING: 'opensearch_alerting', - AD_ALERTING: 'alerting_ad', - DATA: 'data', -}; diff --git a/server/adaptors/opensearch_alerting_plugin.ts b/server/adaptors/opensearch_alerting_plugin.ts deleted file mode 100644 index c1dc4f43..00000000 --- a/server/adaptors/opensearch_alerting_plugin.ts +++ /dev/null @@ -1,355 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - AD_BASE_API, - API_ROUTE_PREFIX, - DESTINATION_BASE_API, - EMAIL_ACCOUNT_BASE_API, - EMAIL_GROUP_BASE_API, - MONITOR_BASE_API, -} from './constants/alerting'; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function OpenSearchAlertingPlugin(Client: any, config: any, components: any) { - const ca = components.clientAction.factory; - - Client.prototype.alerting = components.clientAction.namespaceFactory(); - const alerting = Client.prototype.alerting.prototype; - - alerting.getFindings = ca({ - url: { - fmt: `${API_ROUTE_PREFIX}/findings/_search`, - }, - needBody: true, - method: 'GET', - }); - - alerting.getMonitor = ca({ - url: { - fmt: `${MONITOR_BASE_API}/<%=monitorId%>`, - req: { - monitorId: { - type: 'string', - required: true, - }, - }, - }, - method: 'GET', - }); - - alerting.createMonitor = ca({ - url: { - fmt: `${MONITOR_BASE_API}?refresh=wait_for`, - }, - needBody: true, - method: 'POST', - }); - - alerting.deleteMonitor = ca({ - url: { - fmt: `${MONITOR_BASE_API}/<%=monitorId%>`, - req: { - monitorId: { - type: 'string', - required: true, - }, - }, - }, - method: 'DELETE', - }); - - // TODO DRAFT: May need to add 'refresh' assignment here again. - alerting.updateMonitor = ca({ - url: { - fmt: `${MONITOR_BASE_API}/<%=monitorId%>`, - req: { - monitorId: { - type: 'string', - required: true, - }, - }, - }, - needBody: true, - method: 'PUT', - }); - - alerting.getMonitors = ca({ - url: { - fmt: `${MONITOR_BASE_API}/_search`, - }, - needBody: true, - method: 'POST', - }); - - alerting.acknowledgeAlerts = ca({ - url: { - fmt: `${MONITOR_BASE_API}/<%=monitorId%>/_acknowledge/alerts`, - req: { - monitorId: { - type: 'string', - required: true, - }, - }, - }, - needBody: true, - method: 'POST', - }); - - alerting.getAlerts = ca({ - url: { - fmt: `${MONITOR_BASE_API}/alerts`, - }, - method: 'GET', - }); - - alerting.executeMonitor = ca({ - url: { - fmt: `${MONITOR_BASE_API}/_execute?dryrun=<%=dryrun%>`, - req: { - dryrun: { - type: 'string', - required: true, - }, - }, - }, - needBody: true, - method: 'POST', - }); - - alerting.getDestination = ca({ - url: { - fmt: `${DESTINATION_BASE_API}/<%=destinationId%>`, - req: { - destinationId: { - type: 'string', - required: true, - }, - }, - }, - method: 'GET', - }); - - alerting.searchDestinations = ca({ - url: { - fmt: `${DESTINATION_BASE_API}`, - }, - method: 'GET', - }); - - alerting.createDestination = ca({ - url: { - fmt: `${DESTINATION_BASE_API}?refresh=wait_for`, - }, - needBody: true, - method: 'POST', - }); - - alerting.updateDestination = ca({ - url: { - fmt: `${DESTINATION_BASE_API}/<%=destinationId%>?if_seq_no=<%=ifSeqNo%>&if_primary_term=<%=ifPrimaryTerm%>&refresh=wait_for`, - req: { - destinationId: { - type: 'string', - required: true, - }, - ifSeqNo: { - type: 'string', - required: true, - }, - ifPrimaryTerm: { - type: 'string', - required: true, - }, - }, - }, - needBody: true, - method: 'PUT', - }); - - alerting.deleteDestination = ca({ - url: { - fmt: `${DESTINATION_BASE_API}/<%=destinationId%>`, - req: { - destinationId: { - type: 'string', - required: true, - }, - }, - }, - method: 'DELETE', - }); - - alerting.getEmailAccount = ca({ - url: { - fmt: `${EMAIL_ACCOUNT_BASE_API}/<%=emailAccountId%>`, - req: { - emailAccountId: { - type: 'string', - required: true, - }, - }, - }, - method: 'GET', - }); - - alerting.getEmailAccounts = ca({ - url: { - fmt: `${EMAIL_ACCOUNT_BASE_API}/_search`, - }, - needBody: true, - method: 'POST', - }); - - alerting.createEmailAccount = ca({ - url: { - fmt: `${EMAIL_ACCOUNT_BASE_API}?refresh=wait_for`, - }, - needBody: true, - method: 'POST', - }); - - alerting.updateEmailAccount = ca({ - url: { - fmt: `${EMAIL_ACCOUNT_BASE_API}/<%=emailAccountId%>?if_seq_no=<%=ifSeqNo%>&if_primary_term=<%=ifPrimaryTerm%>&refresh=wait_for`, - req: { - emailAccountId: { - type: 'string', - required: true, - }, - ifSeqNo: { - type: 'string', - required: true, - }, - ifPrimaryTerm: { - type: 'string', - required: true, - }, - }, - }, - needBody: true, - method: 'PUT', - }); - - alerting.deleteEmailAccount = ca({ - url: { - fmt: `${EMAIL_ACCOUNT_BASE_API}/<%=emailAccountId%>`, - req: { - emailAccountId: { - type: 'string', - required: true, - }, - }, - }, - method: 'DELETE', - }); - - alerting.getEmailGroup = ca({ - url: { - fmt: `${EMAIL_GROUP_BASE_API}/<%=emailGroupId%>`, - req: { - emailGroupId: { - type: 'string', - required: true, - }, - }, - }, - method: 'GET', - }); - - alerting.getEmailGroups = ca({ - url: { - fmt: `${EMAIL_GROUP_BASE_API}/_search`, - }, - needBody: true, - method: 'POST', - }); - - alerting.createEmailGroup = ca({ - url: { - fmt: `${EMAIL_GROUP_BASE_API}?refresh=wait_for`, - }, - needBody: true, - method: 'POST', - }); - - alerting.updateEmailGroup = ca({ - url: { - fmt: `${EMAIL_GROUP_BASE_API}/<%=emailGroupId%>?if_seq_no=<%=ifSeqNo%>&if_primary_term=<%=ifPrimaryTerm%>&refresh=wait_for`, - req: { - emailGroupId: { - type: 'string', - required: true, - }, - ifSeqNo: { - type: 'string', - required: true, - }, - ifPrimaryTerm: { - type: 'string', - required: true, - }, - }, - }, - needBody: true, - method: 'PUT', - }); - - alerting.deleteEmailGroup = ca({ - url: { - fmt: `${EMAIL_GROUP_BASE_API}/<%=emailGroupId%>`, - req: { - emailGroupId: { - type: 'string', - required: true, - }, - }, - }, - method: 'DELETE', - }); - - alerting.getDetector = ca({ - url: { - fmt: `${AD_BASE_API}/<%=detectorId%>`, - req: { - detectorId: { - type: 'string', - required: true, - }, - }, - }, - method: 'GET', - }); - - alerting.searchDetectors = ca({ - url: { - fmt: `${AD_BASE_API}/_search`, - }, - needBody: true, - method: 'POST', - }); - - alerting.previewDetector = ca({ - url: { - fmt: `${AD_BASE_API}/<%=detectorId%>/_preview`, - req: { - detectorId: { - type: 'string', - required: true, - }, - }, - }, - needBody: true, - method: 'POST', - }); - - alerting.searchResults = ca({ - url: { - fmt: `${AD_BASE_API}/results/_search`, - }, - needBody: true, - method: 'POST', - }); -} diff --git a/server/adaptors/opensearch_observability_plugin.ts b/server/adaptors/opensearch_observability_plugin.ts deleted file mode 100644 index 88291401..00000000 --- a/server/adaptors/opensearch_observability_plugin.ts +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -const BASE_OBSERVABILITY_URI = '/_plugins/_observability'; -const OPENSEARCH_PANELS_API = { - OBJECT: `${BASE_OBSERVABILITY_URI}/object`, -}; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function OpenSearchObservabilityPlugin(Client: any, config: any, components: any) { - const clientAction = components.clientAction.factory; - - Client.prototype.observability = components.clientAction.namespaceFactory(); - const observability = Client.prototype.observability.prototype; - - // Get Object - observability.getObject = clientAction({ - url: { - fmt: OPENSEARCH_PANELS_API.OBJECT, - params: { - objectId: { - type: 'string', - }, - objectIdList: { - type: 'string', - }, - objectType: { - type: 'string', - }, - sortField: { - type: 'string', - }, - sortOrder: { - type: 'string', - }, - fromIndex: { - type: 'number', - }, - maxItems: { - type: 'number', - }, - name: { - type: 'string', - }, - lastUpdatedTimeMs: { - type: 'string', - }, - createdTimeMs: { - type: 'string', - }, - }, - }, - method: 'GET', - }); - - // Get Object by Id - observability.getObjectById = clientAction({ - url: { - fmt: `${OPENSEARCH_PANELS_API.OBJECT}/<%=objectId%>`, - req: { - objectId: { - type: 'string', - required: true, - }, - }, - }, - method: 'GET', - }); - - // Create new Object - observability.createObject = clientAction({ - url: { - fmt: OPENSEARCH_PANELS_API.OBJECT, - }, - method: 'POST', - needBody: true, - }); - - // Update Object by Id - observability.updateObjectById = clientAction({ - url: { - fmt: `${OPENSEARCH_PANELS_API.OBJECT}/<%=objectId%>`, - req: { - objectId: { - type: 'string', - required: true, - }, - }, - }, - method: 'PUT', - needBody: true, - }); - - // Delete Object by Id - observability.deleteObjectById = clientAction({ - url: { - fmt: `${OPENSEARCH_PANELS_API.OBJECT}/<%=objectId%>`, - req: { - objectId: { - type: 'string', - required: true, - }, - }, - }, - method: 'DELETE', - }); - - // Delete Object by Id List - observability.deleteObjectByIdList = clientAction({ - url: { - fmt: OPENSEARCH_PANELS_API.OBJECT, - params: { - objectIdList: { - type: 'string', - required: true, - }, - }, - }, - method: 'DELETE', - }); -} diff --git a/server/adaptors/ppl_plugin.ts b/server/adaptors/ppl_plugin.ts deleted file mode 100644 index 3c30a123..00000000 --- a/server/adaptors/ppl_plugin.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -// @ts-ignore -export const PPLPlugin = function (Client, config, components) { - const ca = components.clientAction.factory; - Client.prototype.ppl = components.clientAction.namespaceFactory(); - const ppl = Client.prototype.ppl.prototype; - - ppl.pplQuery = ca({ - url: { - fmt: `/_plugins/_ppl`, - params: { - format: { - type: 'string', - required: true, - }, - }, - }, - needBody: true, - method: 'POST', - }); - - ppl.sqlQuery = ca({ - url: { - fmt: `/_plugins/_sql`, - params: { - format: { - type: 'string', - required: true, - }, - }, - }, - needBody: true, - method: 'POST', - }); -}; diff --git a/server/fetch-polyfill.ts b/server/fetch-polyfill.ts deleted file mode 100644 index 97325599..00000000 --- a/server/fetch-polyfill.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -// fetch and web-streams-polyfill are needed to run langchain on node16 - -import fetch, { Headers, Request, Response } from 'node-fetch'; - -if (!globalThis.fetch) { - globalThis.fetch = (fetch as unknown) as typeof globalThis.fetch; - globalThis.Headers = (Headers as unknown) as typeof globalThis.Headers; - globalThis.Request = (Request as unknown) as typeof globalThis.Request; - globalThis.Response = (Response as unknown) as typeof globalThis.Response; -} diff --git a/server/olly/__tests__/__utils__/test_helpers.ts b/server/olly/__tests__/__utils__/test_helpers.ts deleted file mode 100644 index 6829a168..00000000 --- a/server/olly/__tests__/__utils__/test_helpers.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { IMessage } from '../../../../common/types/chat_saved_object_attributes'; -import { LangchainTrace } from '../../../../common/utils/llm_chat/traces'; - -export const createTrace = (options: Partial = {}): LangchainTrace => ({ - id: 'trace-id', - type: 'chain', - startTime: 0, - name: 'trace name', - input: 'input', - output: 'output', - ...options, -}); - -export const createMessage = (options: Partial = {}): IMessage => { - if (options.type === 'input') { - return { - type: 'input', - content: 'user input', - contentType: 'text', - ...options, - }; - } - - return { - type: 'output', - content: 'assistant output', - contentType: 'markdown', - traceId: 'session-id', - ...options, - } as IMessage; -}; diff --git a/server/olly/agents/agent_executor/opensearch_agent_executor.ts b/server/olly/agents/agent_executor/opensearch_agent_executor.ts deleted file mode 100644 index 168dfb72..00000000 --- a/server/olly/agents/agent_executor/opensearch_agent_executor.ts +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -/* eslint-disable max-classes-per-file */ -import { AgentExecutor } from 'langchain/agents'; -import { CallbackManagerForChainRun } from 'langchain/callbacks'; -import { AgentAction, AgentFinish, AgentStep, ChainValues } from 'langchain/schema'; -import { Tool } from 'langchain/tools'; -import { PreservedInputTool } from '../../tools/preserved_input_tool'; - -// redefined some classes not exported from langchain - -/** - * Tool that just returns the query. - * Used for exception tracking. - */ -class ExceptionTool extends Tool { - name = '_Exception'; - - description = 'Exception tool'; - - async _call(query: string) { - return query; - } -} -class ToolInputParsingException extends Error { - output?: string; - - constructor(message: string, output?: string) { - super(message); - this.output = output; - } -} -class OutputParserException extends Error { - llmOutput?: string; - - observation?: string; - - sendToLLM: boolean; - - constructor(message: string, llmOutput?: string, observation?: string, sendToLLM = false) { - super(message); - this.llmOutput = llmOutput; - this.observation = observation; - this.sendToLLM = sendToLLM; - - if (sendToLLM) { - if (observation === undefined || llmOutput === undefined) { - throw new Error( - "Arguments 'observation' & 'llmOutput' are required if 'sendToLlm' is true" - ); - } - } - } -} - -/** - * AgentExecutor that uses user question as tool input for {@link PreservedInputTool}. - */ -export class OpenSearchAgentExecutor extends AgentExecutor { - async _call(inputs: ChainValues, runManager?: CallbackManagerForChainRun): Promise { - const toolsByName = Object.fromEntries(this.tools.map((t) => [t.name.toLowerCase(), t])); - const steps: AgentStep[] = []; - let iterations = 0; - - const getOutput = async (finishStep: AgentFinish) => { - const { returnValues } = finishStep; - const additional = await this.agent.prepareForOutput(returnValues, steps); - - if (this.returnIntermediateSteps) { - return { ...returnValues, intermediateSteps: steps, ...additional }; - } - await runManager?.handleAgentEnd(finishStep); - return { ...returnValues, ...additional }; - }; - - while (this.maxIterations === undefined || iterations < this.maxIterations) { - let output; - try { - output = await this.agent.plan(steps, inputs, runManager?.getChild()); - } catch (e) { - if (e instanceof OutputParserException) { - let observation; - let text = e.message; - if (this.handleParsingErrors === true) { - if (e.sendToLLM) { - observation = e.observation; - text = e.llmOutput ?? ''; - } else { - observation = 'Invalid or incomplete response'; - } - } else if (typeof this.handleParsingErrors === 'string') { - observation = this.handleParsingErrors; - } else if (typeof this.handleParsingErrors === 'function') { - observation = this.handleParsingErrors(e); - } else { - throw e; - } - output = { - tool: '_Exception', - toolInput: observation, - log: text, - } as AgentAction; - } else { - throw e; - } - } - // Check if the agent has finished - if ('returnValues' in output) { - return getOutput(output); - } - - let actions: AgentAction[]; - if (Array.isArray(output)) { - actions = output as AgentAction[]; - } else { - actions = [output as AgentAction]; - } - - const newSteps = await Promise.all( - actions.map(async (action) => { - await runManager?.handleAgentAction(action); - const tool = - action.tool === '_Exception' - ? new ExceptionTool() - : toolsByName[action.tool?.toLowerCase()]; - let observation; - try { - observation = tool - ? await tool.call( - tool instanceof PreservedInputTool && inputs.input - ? inputs.input - : action.toolInput, - runManager?.getChild() - ) - : `${action.tool} is not a valid tool, try another one.`; - } catch (e) { - if (e instanceof ToolInputParsingException) { - if (this.handleParsingErrors === true) { - observation = 'Invalid or incomplete tool input. Please try again.'; - } else if (typeof this.handleParsingErrors === 'string') { - observation = this.handleParsingErrors; - } else if (typeof this.handleParsingErrors === 'function') { - observation = this.handleParsingErrors(e); - } else { - throw e; - } - observation = await new ExceptionTool().call(observation, runManager?.getChild()); - return { action, observation: observation ?? '' }; - } - } - - return { action, observation: observation ?? '' }; - }) - ); - - steps.push(...newSteps); - - const lastStep = steps[steps.length - 1]; - const lastTool = toolsByName[lastStep.action.tool?.toLowerCase()]; - - if (lastTool?.returnDirect) { - return getOutput({ - returnValues: { [this.agent.returnValues[0]]: lastStep.observation }, - log: '', - }); - } - - iterations += 1; - } - - const finish = await this.agent.returnStoppedResponse(this.earlyStoppingMethod, steps, inputs); - - return getOutput(finish); - } -} diff --git a/server/olly/agents/agent_factory/agent_factory.ts b/server/olly/agents/agent_factory/agent_factory.ts deleted file mode 100644 index 57121b7f..00000000 --- a/server/olly/agents/agent_factory/agent_factory.ts +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - AgentArgs, - AgentExecutor, - ChatAgent, - ChatConversationalAgent, - ChatConversationalCreatePromptArgs, - ChatCreatePromptArgs, - ZeroShotAgent, -} from 'langchain/agents'; -import { BaseLanguageModel } from 'langchain/base_language'; -import { Callbacks } from 'langchain/callbacks'; -import { LLMChain } from 'langchain/chains'; -import { BufferMemory } from 'langchain/memory'; -import { - ChatPromptTemplate, - HumanMessagePromptTemplate, - SystemMessagePromptTemplate, -} from 'langchain/prompts'; -import { DynamicTool } from 'langchain/tools'; -import { OpenSearchAgentExecutor } from '../agent_executor/opensearch_agent_executor'; -import { ChatConversationalAgentOutputLenientParser } from '../output_parsers/output_parsers'; -import { DEFAULT_HUMAN_MESSAGE, DEFAULT_SYSTEM_MESSAGE } from '../prompts/default_chat_prompts'; -import { - ZEROSHOT_CHAT_PREFIX, - ZEROSHOT_CHAT_SUFFIX, -} from '../prompts/default_zeroshot_chat_prompts'; -import { - ZEROSHOT_HUMAN_PROMPT_TEMPLATE, - ZEROSHOT_PROMPT_PREFIX, - ZEROSHOT_PROMPT_SUFFIX, -} from '../prompts/default_zeroshot_prompt'; - -type AgentTypes = 'zeroshot' | 'chat' | 'chat-zeroshot'; - -interface AgentPrompts { - /** String to put before the list of tools for a Zeroshot Agent */ - zeroshot_prompt_prefix?: string; - /** String to put after the list of tools for a Zeroshot Agent */ - zeroshot_prompt_suffix?: string; - /** String to put as human prompt template for a Zeroshot Agent */ - zeroshot_human_prompt?: string; - /** String to put before the list of tools for a ReAct conversation Agent */ - chat_system_message?: string; - /** String to put after the list of tools for a ReAct conversation Agent */ - chat_human_message?: string; - /** String to put before the list of tools for a Zeroshot chat Agent */ - zeroshot_chat_prefix?: string; - /** String to put after the list of tools for a Zeroshot chat Agent */ - zeroshot_chat_suffix?: string; -} - -export class AgentFactory { - agentTools: DynamicTool[] = []; - model: BaseLanguageModel; - executor: AgentExecutor; - executorType: AgentTypes; - agentArgs: AgentPrompts; - memory = new BufferMemory({ - returnMessages: true, - memoryKey: 'chat_history', - inputKey: 'input', - }); - - constructor( - agentType: AgentTypes, - agentTools: DynamicTool[], - agentArgs: AgentPrompts, - model: BaseLanguageModel, - callbacks: Callbacks, - customAgentMemory?: BufferMemory - ) { - this.executorType = agentType; - this.model = model; - this.agentTools = [...agentTools]; - this.agentArgs = agentArgs; - - switch (agentType) { - case 'zeroshot': { - const prompt = ZeroShotAgent.createPrompt(this.agentTools, { - prefix: this.agentArgs.zeroshot_prompt_prefix ?? ZEROSHOT_PROMPT_PREFIX, - suffix: this.agentArgs.zeroshot_prompt_suffix ?? ZEROSHOT_PROMPT_SUFFIX, - }); - const chatPrompt = ChatPromptTemplate.fromPromptMessages([ - new SystemMessagePromptTemplate(prompt), - HumanMessagePromptTemplate.fromTemplate( - this.agentArgs.zeroshot_human_prompt ?? ZEROSHOT_HUMAN_PROMPT_TEMPLATE - ), - ]); - const llmChain = new LLMChain({ - prompt: chatPrompt, - llm: this.model, - }); - const agent = new ZeroShotAgent({ - llmChain, - allowedTools: this.agentTools.map((tool) => tool.name), - }); - this.executor = AgentExecutor.fromAgentAndTools({ - agent, - tools: this.agentTools, - callbacks, - verbose: true, - }); - break; - } - - case 'chat-zeroshot': { - const convArgs: ChatCreatePromptArgs = { - prefix: this.agentArgs.zeroshot_chat_prefix ?? ZEROSHOT_CHAT_PREFIX, - suffix: this.agentArgs.zeroshot_chat_suffix ?? ZEROSHOT_CHAT_SUFFIX, - }; - this.executor = AgentExecutor.fromAgentAndTools({ - agent: ChatAgent.fromLLMAndTools(this.model, this.agentTools, convArgs), - tools: this.agentTools, - callbacks, - verbose: true, - }); - break; - } - - case 'chat': - default: { - const toolNames = this.agentTools.map((tool) => tool.name); - const outputParser = new ChatConversationalAgentOutputLenientParser({ toolNames }); - const convArgs: ChatConversationalCreatePromptArgs & AgentArgs = { - systemMessage: this.agentArgs.chat_system_message ?? DEFAULT_SYSTEM_MESSAGE, - humanMessage: this.agentArgs.chat_human_message ?? DEFAULT_HUMAN_MESSAGE, - outputParser, - }; - this.executor = new OpenSearchAgentExecutor({ - agent: ChatConversationalAgent.fromLLMAndTools(this.model, this.agentTools, convArgs), - tools: this.agentTools, - memory: customAgentMemory ?? this.memory, - callbacks, - verbose: true, - }); - break; - } - } - } - - public run = async (question: string, abortController?: AbortController) => { - const response = - this.executorType === 'zeroshot' - ? await this.executor.run(question) - : await this.executor.call({ input: question, signal: abortController?.signal }); - return response; - }; -} diff --git a/server/olly/agents/agent_helpers.ts b/server/olly/agents/agent_helpers.ts deleted file mode 100644 index bab95eb2..00000000 --- a/server/olly/agents/agent_helpers.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { BaseLanguageModel } from 'langchain/base_language'; -import { Callbacks } from 'langchain/callbacks'; -import { BufferMemory } from 'langchain/memory'; -import { DynamicTool } from 'langchain/tools'; -import { AgentFactory } from './agent_factory/agent_factory'; -import { - PARENT_AGENT_HUMAN_MESSAGE, - PARENT_AGENT_SYSTEM_MESSAGE, -} from './prompts/parent_agent_prompts'; - -export const chatAgentInit = ( - model: BaseLanguageModel, - pluginAgentTools: DynamicTool[], - callbacks: Callbacks, - memory?: BufferMemory -) => { - const chatAgent = new AgentFactory( - 'chat', - pluginAgentTools, - { - chat_system_message: PARENT_AGENT_SYSTEM_MESSAGE, - chat_human_message: PARENT_AGENT_HUMAN_MESSAGE, - }, - model, - callbacks, - memory - ); - return chatAgent; -}; diff --git a/server/olly/agents/output_parsers/__tests__/output_parsers.test.ts b/server/olly/agents/output_parsers/__tests__/output_parsers.test.ts deleted file mode 100644 index 1d72963d..00000000 --- a/server/olly/agents/output_parsers/__tests__/output_parsers.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { ChatConversationalAgentOutputLenientParser } from '../output_parsers'; - -describe('OutputParsers', () => { - const toolNames = ['tool 1', 'tool 2']; - - it('parses correct output', async () => { - const parser = new ChatConversationalAgentOutputLenientParser({ toolNames }); - const output = await parser.parse( - ' ```json\n{\n "action": "Query OpenSearch", \n "action_input": "GET /_search\\n{\\n \\"query\\": {\\n \\"range\\": {\\n \\"timestamp\\": {\\n \\"gte\\": \\"now-3d/d\\", \\n \\"lte\\": \\"now/d\\"\\n }\\n }\\n },\\n \\"aggs\\": {\\n \\"flights_per_hour\\": {\\n \\"date_histogram\\": {\\n \\"field\\": \\"timestamp\\", \\n \\"interval\\": \\"hour\\"\\n }\\n }\\n }\\n}"\n}\n```' - ); - expect(output).toMatchObject({ - tool: 'Query OpenSearch', - toolInput: - 'GET /_search\n{\n "query": {\n "range": {\n "timestamp": {\n "gte": "now-3d/d", \n "lte": "now/d"\n }\n }\n },\n "aggs": {\n "flights_per_hour": {\n "date_histogram": {\n "field": "timestamp", \n "interval": "hour"\n }\n }\n }\n}', - }); - }); - - it('parses tool input with new lines', async () => { - const parser = new ChatConversationalAgentOutputLenientParser({ toolNames }); - const output = await parser.parse( - ' ```\n{\n "action": "Query OpenSearch",\n "action_input": "source=accounts\n| where age = 39" \n}\n```' - ); - expect(output).toMatchObject({ - tool: 'Query OpenSearch', - toolInput: 'source=accounts\n| where age = 39', - }); - }); - - it('parses final answer with new lines', async () => { - const parser = new ChatConversationalAgentOutputLenientParser({ toolNames }); - const output = await parser.parse( - ' ```json\n{\n "action": "Final Answer",\n "action_input": "There were 0 flights in the past 3 days\naccording to the query results." \n}\n```' - ); - expect(output).toMatchObject({ - returnValues: { - output: 'There were 0 flights in the past 3 days\naccording to the query results.', - }, - }); - }); - - it('parses output with double backslashes', async () => { - const parser = new ChatConversationalAgentOutputLenientParser({ toolNames }); - const output = await parser.parse( - ' ```json\n{\\n \\"action\\": \\"Final Answer\\",\\n \\"action_input\\": \\"There was an error parsing the JSON response from the query tool. Here are some suggestions based on the error message: \\n\\n• Double check that the JSON response is properly formatted with correct syntax. \\n• Ensure the JSON response is wrapped in double quotes. \\n• Check that the JSON keys and strings are properly quoted. \\n• Confirm there are no trailing commas after the last JSON object or array element.\\"\\n}\\n```' - ); - expect(output).toMatchObject({ - returnValues: { - output: - 'There was an error parsing the JSON response from the query tool. Here are some suggestions based on the error message:\n\n • Double check that the JSON response is properly formatted with correct syntax.\n • Ensure the JSON response is wrapped in double quotes.\n • Check that the JSON keys and strings are properly quoted.\n • Confirm there are no trailing commas after the last JSON object or array element.', - }, - }); - }); - - it('parses output with unmatched quotes', async () => { - const parser = new ChatConversationalAgentOutputLenientParser({ toolNames }); - const output = await parser.parse( - ' ```json\n{\\n \\"action\\": \\"Final Answer\\",\\n \\"action_input\\": \\"There are 16 indices in your cluster according to the information provided. The indices include:\\n\\n- .plugins-ml-model-group \\n- .kibana_92668751_admin_1\\n- .chat-assistant-config\\n- .plugins-ml-task\\n- .plugins-ml-connector\\n- .opendistro_security\\n- .kibana_1\\n- .llm-traces\\n- .plugins-ml-config\\n- .opensearch-observability\\n- .plugins-ml-model\\n- opensearch_dashboards_sample_data_logs\\n- opensearch_dashboards_sample_data_flights\\n- security-auditlog-2023.09.27\\n- security-auditlog-2023.09.28\\n- opensearch_dashboards_sample_data_ecommerce\\n\\n```\\n}\\n```' - ); - expect(output).toMatchObject({ - returnValues: { - output: - 'There are 16 indices in your cluster according to the information provided. The indices include:\n\n- .plugins-ml-model-group\n - .kibana_92668751_admin_1\n- .chat-assistant-config\n- .plugins-ml-task\n- .plugins-ml-connector\n- .opendistro_security\n- .kibana_1\n- .llm-traces\n- .plugins-ml-config\n- .opensearch-observability\n- .plugins-ml-model\n- opensearch_dashboards_sample_data_logs\n- opensearch_dashboards_sample_data_flights\n- security-auditlog-2023.09.27\n- security-auditlog-2023.09.28\n- opensearch_dashboards_sample_data_ecommerce\n\n', - }, - }); - }); - - it('parses output with missing end quote', async () => { - const parser = new ChatConversationalAgentOutputLenientParser({ toolNames }); - const output = await parser.parse( - ' ```json\n{\\n \\"action\\": \\"Final Answer\\",\\n \\"action_input\\": \\"output}\\n```' - ); - expect(output).toMatchObject({ - returnValues: { - output: 'output', - }, - }); - }); - - it('throws exception if no JSON found', () => { - const parser = new ChatConversationalAgentOutputLenientParser({ toolNames }); - expect(parser.parse('Internal Error')).rejects.toThrowError(); - }); -}); diff --git a/server/olly/agents/output_parsers/output_parsers.ts b/server/olly/agents/output_parsers/output_parsers.ts deleted file mode 100644 index 2c35c49a..00000000 --- a/server/olly/agents/output_parsers/output_parsers.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -/* eslint-disable max-classes-per-file */ - -import { ChatConversationalAgentOutputParserWithRetries } from 'langchain/agents'; - -class OutputParserException extends Error { - output?: string; - - constructor(message: string, output?: string) { - super(message); - this.output = output; - } -} - -// Temporary workaround for LLM giving invalid JSON with '\n' in values -export class ChatConversationalAgentOutputLenientParser extends ChatConversationalAgentOutputParserWithRetries { - private getInnerJSONString(jsonOutput: string) { - if (jsonOutput.includes('```json')) { - jsonOutput = jsonOutput.split('```json')[1].trimStart(); - } else if (jsonOutput.includes('```')) { - const firstIndex = jsonOutput.indexOf('```'); - jsonOutput = jsonOutput.slice(firstIndex + 3).trimStart(); - } - const lastIndex = jsonOutput.lastIndexOf('```'); - if (lastIndex !== -1) { - jsonOutput = jsonOutput.slice(0, lastIndex).trimEnd(); - } - return jsonOutput; - } - - private createAgentResponse(response: { action: string; action_input: string }, text: string) { - if (response.action === 'Final Answer') { - return { returnValues: { output: response.action_input }, log: text }; - } - return { tool: response.action, toolInput: response.action_input, log: text }; - } - - async parse(text: string) { - return super - .parse(text) - .catch(() => { - const jsonOutput = text.trim().replace(/\n/g, ' '.repeat(15)); - const jsonStr = this.getInnerJSONString(jsonOutput); - const response = JSON.parse(JSON.stringify(JSON.parse(jsonStr)).replace(/( {15})/g, '\\n')); - return this.createAgentResponse(response, text); - }) - .catch(() => { - const jsonOutput = text - .trim() - .replace(/\\"/g, '"') - .replace(/\\n/g, '\n') - .replace(/\n/g, ' '.repeat(15)) - .replace(/```\s*}\s*```\s*/, '"}```') - .replace(/([^\s"])\s*}\s*```\s*/, '$1"}```'); - const jsonStr = this.getInnerJSONString(jsonOutput); - const response = JSON.parse(JSON.stringify(JSON.parse(jsonStr)).replace(/( {15})/g, '\\n')); - return this.createAgentResponse(response, text); - }) - .catch((e) => { - throw new OutputParserException(`Failed to parse. Text: "${text}". Error: ${e}`); - }); - } -} diff --git a/server/olly/agents/prompts/default_chat_prompts.ts b/server/olly/agents/prompts/default_chat_prompts.ts deleted file mode 100644 index 40e06bb9..00000000 --- a/server/olly/agents/prompts/default_chat_prompts.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -export const DEFAULT_SYSTEM_MESSAGE = `You are an Assistant to help OpenSearch users. - -Assistant is expert in OpenSearch and knows extensively about logs, traces, and metrics. It can answer open ended questions related to root cause and mitigation steps.`; - -export const DEFAULT_HUMAN_MESSAGE = `TOOLS ------- -Assistant can ask the user to use tools to look up information that may be helpful in answering the users original question. Assistant must follow the rules below: - -#01 Assistant must remember the context of the original question when answering with the final response. -#02 Assistant must not change user's question in any way when calling tools. -#03 Give answer in bullet points and be concise. -#04 When seeing 'Error when running tool' in the tool output, respond with suggestions based on the error message. -#05 Only answer if you know the answer with certainty. - -The tools the human can use are: - -{tools} - -{format_instructions} - -USER'S INPUT --------------------- -Here is the user's input (remember to respond with a markdown code snippet of a json blob with a single action, and NOTHING else): - -{{input}}`; diff --git a/server/olly/agents/prompts/default_zeroshot_chat_prompts.ts b/server/olly/agents/prompts/default_zeroshot_chat_prompts.ts deleted file mode 100644 index c58630ea..00000000 --- a/server/olly/agents/prompts/default_zeroshot_chat_prompts.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -export const ZEROSHOT_CHAT_PREFIX = `Answer the following questions as best you can. You have access to the following tools:`; -export const ZEROSHOT_CHAT_SUFFIX = `Begin! Reminder to always use the exact characters \`Final Answer\` when responding.`; diff --git a/server/olly/agents/prompts/default_zeroshot_prompt.ts b/server/olly/agents/prompts/default_zeroshot_prompt.ts deleted file mode 100644 index 46729048..00000000 --- a/server/olly/agents/prompts/default_zeroshot_prompt.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -export const ZEROSHOT_PROMPT_PREFIX = ` - You are an Observability assistant helping users to work with their OpenSearch clusters. You have help them to dive into the cluster data like logs, traces and metrics. - Also, you help them to check health, status and workings of the OpenSearch cluster itself. - You have access to the following tools:`; - -export const ZEROSHOT_PROMPT_SUFFIX = `Begin! Remember to not use any special characters when giving your final answer.`; - -export const ZEROSHOT_HUMAN_PROMPT_TEMPLATE = `{input} - -This was your previous work (but I haven't seen any of it! I only see what you return as final answer): -{agent_scratchpad}`; diff --git a/server/olly/agents/prompts/parent_agent_prompts.ts b/server/olly/agents/prompts/parent_agent_prompts.ts deleted file mode 100644 index 214b633c..00000000 --- a/server/olly/agents/prompts/parent_agent_prompts.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { DEFAULT_HUMAN_MESSAGE, DEFAULT_SYSTEM_MESSAGE } from './default_chat_prompts'; - -export const PARENT_AGENT_SYSTEM_MESSAGE = DEFAULT_SYSTEM_MESSAGE; - -export const PARENT_AGENT_HUMAN_MESSAGE = DEFAULT_HUMAN_MESSAGE; diff --git a/server/olly/agents/prompts/plugin_agent_prompts/alerting_conv_prompts.ts b/server/olly/agents/prompts/plugin_agent_prompts/alerting_conv_prompts.ts deleted file mode 100644 index 419ed5da..00000000 --- a/server/olly/agents/prompts/plugin_agent_prompts/alerting_conv_prompts.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { DEFAULT_SYSTEM_MESSAGE } from '../default_chat_prompts'; - -export const ALERTING_SYSTEM_MESSAGE = `${DEFAULT_SYSTEM_MESSAGE} - -This Assistant specializes in OpenSearch Alerting Plugin. It knows the details about Alerting APIs`; - -export const ALERTING_HUMAN_MESSGAE = `TOOLS ------- -Assistant can ask the user to use tools and iterate through them to look up information that may be helpful in answering the users original question. The tools the human can use are: - -{tools} - -{format_instructions} - -USER'S INPUT --------------------- -Here is the user's input (remember to respond with a markdown code snippet of a json blob with a single action, and NOTHING else): - -{{input}}`; diff --git a/server/olly/agents/prompts/plugin_agent_prompts/ppl_conv_prompts.ts b/server/olly/agents/prompts/plugin_agent_prompts/ppl_conv_prompts.ts deleted file mode 100644 index aa95895e..00000000 --- a/server/olly/agents/prompts/plugin_agent_prompts/ppl_conv_prompts.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { DEFAULT_HUMAN_MESSAGE } from '../default_chat_prompts'; - -export const PPL_AGENT_SYSTEM_MESSAGE = `PPL Assistant is a large language model trained by Anthropic and prompt-tuned by OpenSearch. - -PPL Assistant is designed to be able to assist with a wide range of tasks, from answering simple questions to providing in-depth explanations and discussions on everything around OpenSearch PPL (piped processing language). -PPL Assistant is able to generate human-like text based on the input it receives, allowing it to engage in natural-sounding conversations and provide responses that are coherent and relevant to the topic at hand. - -PPL Assistant is constantly learning and improving, and its capabilities are constantly evolving. It is able to process and understand large amounts of text, and can use this knowledge to provide accurate and informative responses on everything in and around OpenSearch PPL (piped processing language). -Additionally, PPL Assistant is able to generate its own text based on the input it receives, allowing it to engage in discussions and provide explanations and descriptions on . - -Overall, PPL Assistant is a powerful system that can help with OpenSearch PPL and provide valuable insights and information on OpenSearch PPL. Whether you need help with a specific question or just want to have a conversation about OpenSearch PPL, PPL Assistant is here to assist.`; - -export const PPL_AGENT_HUMAN_MESSAGE = DEFAULT_HUMAN_MESSAGE; diff --git a/server/olly/callbacks/__tests__/opensearch_tracer.test.ts b/server/olly/callbacks/__tests__/opensearch_tracer.test.ts deleted file mode 100644 index a1512974..00000000 --- a/server/olly/callbacks/__tests__/opensearch_tracer.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Run } from 'langchain/callbacks'; -import { opensearchClientMock } from '../../../../../../src/core/server/opensearch/client/mocks'; -import { LLM_INDEX } from '../../../../common/constants/llm'; -import { OpenSearchTracer } from '../opensearch_tracer'; - -class OpenSearchTracerTest extends OpenSearchTracer { - constructor(...args: ConstructorParameters) { - super(...args); - } - - public _persistRun(_run: Run) { - return super.persistRun(_run); - } -} - -describe('langchain opensearch tracer', () => { - let client: ReturnType; - const run = ({ - level: 0, - child_runs: [{ level: 1, child_runs: [{ level: 2 }] }, { level: 1 }], - } as unknown) as Run; - - beforeEach(() => { - client = opensearchClientMock.createOpenSearchClient(); - }); - - it('creates index', async () => { - client.indices.exists.mockResolvedValue( - opensearchClientMock.createSuccessTransportRequestPromise(false) - ); - const tracer = new OpenSearchTracerTest(client, 'test-session', []); - await tracer._persistRun(run); - expect(client.indices.create).toHaveBeenCalledWith( - expect.objectContaining({ - index: LLM_INDEX.TRACES, - body: { - settings: { index: expect.objectContaining({ mapping: { ignore_malformed: true } }) }, - mappings: expect.objectContaining({ dynamic: 'false' }), - }, - }) - ); - }); - - it('skips creating index if exists', async () => { - client.indices.exists.mockResolvedValue( - opensearchClientMock.createSuccessTransportRequestPromise(true) - ); - const tracer = new OpenSearchTracerTest(client, 'test-session', []); - await tracer._persistRun(run); - expect(client.indices.create).toHaveBeenCalledTimes(0); - }); - - it('converts and sends run as docs', async () => { - const runs: Run[] = []; - const tracer = new OpenSearchTracerTest(client, 'test-session', runs); - await tracer._persistRun(run); - expect(client.bulk).toHaveBeenCalledWith( - expect.objectContaining({ - body: expect.arrayContaining([ - { index: { _index: LLM_INDEX.TRACES } }, - { level: 0, trace_id: 'test-session' }, - { level: 1, trace_id: 'test-session' }, - { level: 2, trace_id: 'test-session' }, - ]), - }) - ); - expect(runs).toEqual([run]); - }); - - it('does not throw errors', async () => { - client.bulk.mockRejectedValue('failed to index'); - const tracer = new OpenSearchTracerTest(client, 'test-session', []); - const consoleError = jest.spyOn(console, 'error').mockImplementation(); - await expect(tracer._persistRun(run)).resolves.not.toThrowError(); - expect(consoleError).toHaveBeenCalledTimes(1); - consoleError.mockRestore(); - }); -}); diff --git a/server/olly/callbacks/opensearch_tracer.ts b/server/olly/callbacks/opensearch_tracer.ts deleted file mode 100644 index 7df9e8b4..00000000 --- a/server/olly/callbacks/opensearch_tracer.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { BaseTracer, Run } from 'langchain/callbacks'; -import { omit } from 'lodash'; -import { OpenSearchClient } from '../../../../../src/core/server'; -import { LLM_INDEX } from '../../../common/constants/llm'; - -export class OpenSearchTracer extends BaseTracer { - name = 'opensearch_tracer' as const; - - constructor(private client: OpenSearchClient, private traceId: string, private runs?: Run[]) { - super(); - } - - protected async persistRun(_run: Run) { - this.runs?.push(_run); - try { - await this.createIndex(); - await this.indexRun(_run); - } catch (error) { - console.error('failed to persist langchain trace', error); // do not crash server if request failed - } - } - - private async indexRun(run: Run) { - const body = this.flattenRunToDocs(run).flatMap((doc) => [ - { index: { _index: LLM_INDEX.TRACES } }, - doc, - ]); - return this.client.bulk({ refresh: true, body }); - } - - private flattenRunToDocs(run: Run, docs: Array> = []) { - docs.push({ trace_id: this.traceId, ...omit(run, 'child_runs') }); - if (run.child_runs) run.child_runs.forEach((childRun) => this.flattenRunToDocs(childRun, docs)); - return docs; - } - - private async createIndex() { - const existsResponse = await this.client.indices.exists({ index: LLM_INDEX.TRACES }); - if (!existsResponse.body) { - return this.client.indices.create({ - index: LLM_INDEX.TRACES, - body: { - settings: { - index: { - number_of_shards: '1', - auto_expand_replicas: '0-2', - mapping: { ignore_malformed: true }, - }, - }, - mappings: { - dynamic: 'false', - properties: { - actions: { properties: { tool: { type: 'keyword' } } }, - child_execution_order: { type: 'integer' }, - end_time: { type: 'date' }, - execution_order: { type: 'integer' }, - id: { type: 'keyword' }, - name: { type: 'keyword' }, - parent_run_id: { type: 'keyword' }, - trace_id: { type: 'keyword' }, - start_time: { type: 'date' }, - }, - }, - }, - }); - } - } -} diff --git a/server/olly/chains/filter_generator.ts b/server/olly/chains/filter_generator.ts deleted file mode 100644 index ad515afd..00000000 --- a/server/olly/chains/filter_generator.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { BaseLanguageModel } from 'langchain/base_language'; -import { Callbacks } from 'langchain/callbacks'; -import { LLMChain } from 'langchain/chains'; -import { StructuredOutputParser } from 'langchain/output_parsers'; -import { PromptTemplate } from 'langchain/prompts'; - -const template = ` -From the question, generate the user start and end times filters for their query. - -The start time is the beginning of the time period the user is asking about. For example, if the user asks about all traces "From 4 years ago...", that means the start time is 4 years ago. Other ways the user might signal a start time includes "after last week" (the start time is 1 week ago) or "since 2018" (the start time is 2018) - -The end time is the end of the time period the user is asking about. For example, if the user asks about all traces "before July 24th, 2022", the end time is July 24th, 2022. Other ways the user might signal an end time includes "till Feb" (the end time is Feburary, this year) or "Until next year" (the end time is next year) - -Time formats can be absolute or relative. - -If absolute, they are structured as "%year-%month-%dateT%hour:%minute.%second". An example would be "2004-08-01T16:03:02" meaning a time of August 1st, 2004, 4:03.022 PM, in the Pacific Time Zone. Another example would be the user specifying "2018", so your generation would be "2018-01-01T00:00:00". - -If relative, they are relative to "now", and are structured as "now+%n%u" for in the future, or "now-%n%u" for in the past. The %n is a number, and %u is a unit of measurement. For units, "s" means seconds, "m" means minutes, "h" means hours, "d" means days, "w" means weeks, "M" means months, and "y" means years. An example would be "now-3w" meaning 3 weeks before now, and "now+4y" meaning 4 years from now. - ---------------- - -Use the following steps to generate the start and end times: - -Step 1. Use the user's query to write the start time in either absolute or relative time format. If you cannot generate one, set it to 'now-15m'. - -Step 2. Use the user's query to write the end time in either absolute or relative time format. If you cannot generate one, set it to now. - -Step 3. Return those in a JSON object, where the keys are 'start_time' and 'end_time', and the values are the generated start and end times. - -{format_instructions} ---------------- - -Question: {question} - -`.trim(); - -const parser = StructuredOutputParser.fromNamesAndDescriptions({ - start_time: 'This is the start time', - end_time: 'This is the end time', -}); -const formatInstructions = parser.getFormatInstructions(); - -const prompt = new PromptTemplate({ - template, - inputVariables: ['question'], - partialVariables: { format_instructions: formatInstructions }, -}); - -export const requestTimesFiltersChain = async ( - model: BaseLanguageModel, - question: string, - callbacks?: Callbacks -) => { - const chain = new LLMChain({ llm: model, prompt }); - const output = await chain.call({ question }, callbacks); - return parser.parse(output.text); -}; diff --git a/server/olly/chains/generic_response.ts b/server/olly/chains/generic_response.ts deleted file mode 100644 index 4234d1d5..00000000 --- a/server/olly/chains/generic_response.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { BaseLanguageModel } from 'langchain/base_language'; -import { Callbacks } from 'langchain/callbacks'; -import { LLMChain } from 'langchain/chains'; -import { PromptTemplate } from 'langchain/prompts'; - -const template = ` -Use the following rules to respond to an input - -1. A relevant question is a question that asks about OpenSearch or about you. -2. If the input is an answer to a relevant question, say "input is a relevant answer". -3. If the input is a relevant question, then answer the question based on your own knowledge. -4. If the input is a question but not relevant, say "input is irrelevant". - -Input: -{question} -`.trim(); - -const prompt = new PromptTemplate({ - template, - inputVariables: ['question'], -}); - -export const requestGenericResponseChain = async ( - model: BaseLanguageModel, - question: string, - callbacks?: Callbacks -): Promise => { - const chain = new LLMChain({ llm: model, prompt }); - const output = await chain.call({ question }, callbacks); - if (output.text.includes('input is a relevant answer')) { - return question; - } - if (output.text.includes('input is irrelevant')) { - return 'I do not have any information in my expertise about the question, please ask OpenSearch related questions.'; - } - return output.text; -}; diff --git a/server/olly/chains/guessing_index.ts b/server/olly/chains/guessing_index.ts deleted file mode 100644 index c48cafec..00000000 --- a/server/olly/chains/guessing_index.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { BaseLanguageModel } from 'langchain/base_language'; -import { Callbacks } from 'langchain/callbacks'; -import { LLMChain } from 'langchain/chains'; -import { StructuredOutputParser } from 'langchain/output_parsers'; -import { PromptTemplate } from 'langchain/prompts'; - -const template = ` -From the given list of index names, pick the one that is the most relevant to the question. Use the following rules to pick: - -1. If the question contains the index, and the index is present in the list below, return the index as the response. -2. If the question contains the index, but the index is not present in the list below, make a guess and return the most relevant index from the list below. -3. If the question does not mention the exact index name, make a guess and return the most relevant index from the list below. -4. If there are multiple relevant indices with the same prefix and the question does not mention any specific one, return \`prefix*\`. - -{format_instructions} ----------------- - -Question: {question} -Index names: -{indexNames} -`.trim(); - -const parser = StructuredOutputParser.fromNamesAndDescriptions({ index: 'This is the index name' }); -const formatInstructions = parser.getFormatInstructions(); - -const prompt = new PromptTemplate({ - template, - inputVariables: ['question', 'indexNames'], - partialVariables: { format_instructions: formatInstructions }, -}); - -export const requestGuessingIndexChain = async ( - model: BaseLanguageModel, - question: string, - indexNameList: string[], - callbacks?: Callbacks -) => { - const chain = new LLMChain({ llm: model, prompt }); - const output = await chain.call({ question, indexNames: indexNameList.join('\n') }, callbacks); - return parser.parse(output.text); -}; diff --git a/server/olly/chains/ppl_generator.ts b/server/olly/chains/ppl_generator.ts deleted file mode 100644 index d97da684..00000000 --- a/server/olly/chains/ppl_generator.ts +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { BaseLanguageModel } from 'langchain/base_language'; -import { Callbacks } from 'langchain/callbacks'; -import { LLMChain } from 'langchain/chains'; -import { PromptTemplate } from 'langchain/prompts'; - -const template = ` -You will be given a question about some metrics from a user. -Use context provided to write a PPL query that can be used to retrieve the information. - -Here is a sample PPL query: -source=\`\` | where \`\` = '\`\`' - -Here are some sample questions and the PPL query to retrieve the information. The format for fields is -\`\`\` -- field_name: field_type (sample field value) -\`\`\` - -For example, below is a field called \`timestamp\`, it has a field type of \`date\`, and a sample value of it could look like \`1686000665919\`. -\`\`\` -- timestamp: date (1686000665919) -\`\`\` ----------------- - -The following text contains fields and questions/answers for the 'accounts' index - -Fields: -- account_number: long (101) -- address: text ("880 Holmes Lane") -- age: long (32) -- balance: long (39225) -- city: text ("Brogan") -- email: text ("amberduke@pyrami.com") -- employer: text ("Pyrami") -- firstname: text ("Amber") -- gender: text ("M") -- lastname: text ("Duke") -- state: text ("IL") -- registered_at: date (1686000665919) - -Question: Give me some documents in index 'accounts' -PPL: source=\`accounts\` | head - -Question: Give me 5 oldest people in index 'accounts' -PPL: source=\`accounts\` | sort -age | head 5 - -Question: Give me first names of 5 youngest people in index 'accounts' -PPL: source=\`accounts\` | sort +age | head 5 | fields \`firstname\` - -Question: Give me some addresses in index 'accounts' -PPL: source=\`accounts\` | fields \`address\` - -Question: Find the documents in index 'accounts' where firstname is 'Hattie' -PPL: source=\`accounts\` | where \`firstname\` = 'Hattie' - -Question: Find the emails where firstname is 'Hattie' or lastname is 'Frank' in index 'accounts' -PPL: source=\`accounts\` | where \`firstname\` = 'Hattie' OR \`lastname\` = 'frank' | fields \`email\` - -Question: Find the documents in index 'accounts' where firstname is not 'Hattie' and lastname is not 'Frank' -PPL: source=\`accounts\` | where \`firstname\` != 'Hattie' AND \`lastname\` != 'frank' - -Question: Find the emails that contain '.com' in index 'accounts' -PPL: source=\`accounts\` | where QUERY_STRING(['email'], '.com') | fields \`email\` - -Question: Find the documents in index 'accounts' where there is an email -PPL: source=\`accounts\` | where ISNOTNULL(\`email\`) - -Question: Count the number of documents in index 'accounts' -PPL: source=\`accounts\` | stats COUNT() AS \`count\` - -Question: Count the number of people with firstname 'Amber' in index 'accounts' -PPL: source=\`accounts\` | where \`firstname\` ='Amber' | stats COUNT() AS \`count\` - -Question: How many people are older than 33? index is 'accounts' -PPL: source=\`accounts\` | where \`age\` > 33 | stats COUNT() AS \`count\` - -Question: How many distinct ages? index is 'accounts' -PPL: source=\`accounts\` | stats DISTINCT_COUNT(age) AS \`distinct_count\` - -Question: How many males and females in index 'accounts'? -PPL: source=\`accounts\` | stats COUNT() AS \`count\` BY \`gender\` - -Question: What is the average, minimum, maximum age in 'accounts' index? -PPL: source=\`accounts\` | stats AVG(\`age\`) AS \`avg_age\`, MIN(\`age\`) AS \`min_age\`, MAX(\`age\`) AS \`max_age\` - -Question: Show all states sorted by average balance. index is 'accounts' -PPL: source=\`accounts\` | stats AVG(\`balance\`) AS \`avg_balance\` BY \`state\` | sort +avg_balance - ----------------- - -The following text contains fields and questions/answers for the 'ecommerce' index - -Fields: -- category: text ("Men's Clothing") -- currency: keyword ("EUR") -- customer_birth_date: date (null) -- customer_first_name: text ("Eddie") -- customer_full_name: text ("Eddie Underwood") -- customer_gender: keyword ("MALE") -- customer_id: keyword ("38") -- customer_last_name: text ("Underwood") -- customer_phone: keyword ("") -- day_of_week: keyword ("Monday") -- day_of_week_i: integer (0) -- email: keyword ("eddie@underwood-family.zzz") -- event.dataset: keyword ("sample_ecommerce") -- geoip.city_name: keyword ("Cairo") -- geoip.continent_name: keyword ("Africa") -- geoip.country_iso_code: keyword ("EG") -- geoip.location: geo_point ([object Object]) -- geoip.region_name: keyword ("Cairo Governorate") -- manufacturer: text ("Elitelligence,Oceanavigations") -- order_date: date (2023-06-05T09:28:48+00:00) -- order_id: keyword ("584677") -- products._id: text (null) -- products.base_price: half_float (null) -- products.base_unit_price: half_float (null) -- products.category: text (null) -- products.created_on: date (null) -- products.discount_amount: half_float (null) -- products.discount_percentage: half_float (null) -- products.manufacturer: text (null) -- products.min_price: half_float (null) -- products.price: half_float (null) -- products.product_id: long (null) -- products.product_name: text (null) -- products.quantity: integer (null) -- products.sku: keyword (null) -- products.tax_amount: half_float (null) -- products.taxful_price: half_float (null) -- products.taxless_price: half_float (null) -- products.unit_discount_amount: half_float (null) -- sku: keyword ("ZO0549605496,ZO0299602996") -- taxful_total_price: half_float (36.98) -- taxless_total_price: half_float (36.98) -- total_quantity: integer (2) -- total_unique_products: integer (2) -- type: keyword ("order") -- user: keyword ("eddie") - -Question: What is the average price of products in clothing category ordered in the last 7 days? index is 'ecommerce' -PPL: source=\`ecommerce\` | where QUERY_STRING(['category'], 'clothing') AND \`order_date\` > DATE_SUB(NOW(), INTERVAL 7 DAY) | stats AVG(\`taxful_total_price\`) AS \`avg_price\` - -Question: What is the average price of products in each city ordered today by every 2 hours? index is 'ecommerce' -PPL: source=\`ecommerce\` | where \`order_date\` > DATE_SUB(NOW(), INTERVAL 24 HOUR) | stats AVG(\`taxful_total_price\`) AS \`avg_price\` by SPAN(\`order_date\`, 2h) AS \`span\`, \`geoip.city_name\` - -Question: What is the total revenue of shoes each day in this week? index is 'ecommerce' -PPL: source=\`ecommerce\` | where QUERY_STRING(['category'], 'shoes') AND \`order_date\` > DATE_SUB(NOW(), INTERVAL 1 WEEK) | stats SUM(\`taxful_total_price\`) AS \`revenue\` by SPAN(\`order_date\`, 1d) AS \`span\` - ----------------- - -The following text contains fields and questions/answers for the 'events' index -Fields: -- timestamp: long (1686000665919) -- attributes.data_stream.dataset: text ("nginx.access") -- attributes.data_stream.namespace: text ("production") -- attributes.data_stream.type: text ("logs") -- body: text ("172.24.0.1 - - [02/Jun/2023:23:09:27 +0000] "GET / HTTP/1.1" 200 4955 "-" "Mozilla/5.0 zgrab/0.x"") -- communication.source.address: text ("127.0.0.1") -- communication.source.ip: text ("172.24.0.1") -- container_id: text (null) -- container_name: text (null) -- event.category: text ("web") -- event.domain: text ("nginx.access") -- event.kind: text ("event") -- event.name: text ("access") -- event.result: text ("success") -- event.type: text ("access") -- http.flavor: text ("1.1") -- http.request.method: text ("GET") -- http.response.bytes: long (4955) -- http.response.status_code: keyword ("200") -- http.url: text ("/") -- log: text (null) -- observerTime: date (1686000665919) -- source: text (null) -- span_id: text ("abcdef1010") -- trace_id: text ("102981ABCD2901") - -Question: What are recent logs with errors and contains word 'test'? index is 'events' -PPL: source=\`events\` | where QUERY_STRING(['http.response.status_code'], '4* OR 5*') AND QUERY_STRING(['body'], 'test') AND \`observerTime\` > DATE_SUB(NOW(), INTERVAL 5 MINUTE) - -Question: What is the total number of log with a status code other than 200 in 2023 Feburary? index is 'events' -PPL: source=\`events\` | where QUERY_STRING(['http.response.status_code'], '!200') AND \`observerTime\` >= '2023-03-01 00:00:00' AND \`observerTime\` < '2023-04-01 00:00:00' | stats COUNT() AS \`count\` - -Question: Count the number of business days that have web category logs last week? index is 'events' -PPL: source=\`events\` | where \`category\` = 'web' AND \`observerTime\` > DATE_SUB(NOW(), INTERVAL 1 WEEK) AND DAY_OF_WEEK(\`observerTime\`) >= 2 AND DAY_OF_WEEK(\`observerTime\`) <= 6 | stats DISTINCT_COUNT(DATE_FORMAT(\`observerTime\`, 'yyyy-MM-dd')) AS \`distinct_count\` - -Question: What are the top traces with largest bytes? index is 'events' -PPL: source=\`events\` | stats SUM(\`http.response.bytes\`) AS \`sum_bytes\` by \`trace_id\` | sort -sum_bytes | head - -Question: Give me log patterns? index is 'events' -PPL: source=\`events\` | patterns \`body\` | stats take(\`body\`, 1) AS \`sample_pattern\` by \`patterns_field\` | fields \`sample_pattern\` - -Question: Give me log patterns for logs with errors? index is 'events' -PPL: source=\`events\` | where QUERY_STRING(['http.response.status_code'], '4* OR 5*') | patterns \`body\` | stats take(\`body\`, 1) AS \`sample_pattern\` by \`patterns_field\` | fields \`sample_pattern\` - ----------------- - -Use the following steps to generate the PPL query: - -Step 1. Find all field entities in the question. - -Step 2. Pick the fields that are relevant to the question from the provided fields list using entities. Rules: -#01 Consider the field name, the field type, and the sample value when picking relevant fields. For example, if you need to filter flights departed from 'JFK', look for a \`text\` or \`keyword\` field with a field name such as 'departedAirport', and the sample value should be a 3 letter IATA airport code. Similarly, if you need a date field, look for a relevant field name with type \`date\` and not \`long\`. -#02 You must pick a field with \`date\` type when filtering on date/time. -#03 You must pick a field with \`date\` type when aggregating by time interval. -#04 You must not use the sample value in PPL query, unless it is relevant to the question. -#05 You must only pick fields that are relevant, and must pick the whole field name from the fields list. -#06 You must not use fields that are not in the fields list. -#07 You must not use the sample values unless relevant to the question. -#08 You must pick the field that contains a log line when asked about log patterns. Usually it is one of \`log\`, \`body\`, \`message\`. - -Step 3. Use the choosen fields to write the PPL query. Rules: -#01 Always use comparisons to filter date/time, eg. 'where \`timestamp\` > DATE_SUB(NOW(), INTERVAL 1 DAY)'; or by absolute time: "where \`timestamp\` > 'yyyy-MM-dd HH:mm:ss'", eg. "where \`timestamp\` < '2023-01-01 00:00:00'". Do not use \`DATE_FORMAT()\`. -#02 Only use PPL syntax and keywords appeared in the question or in the examples. -#03 If user asks for current or recent status, filter the time field for last 5 minutes. -#04 The field used in 'SPAN(\`\`, )' must have type \`date\`, not \`long\`. -#05 When aggregating by \`SPAN\` and another field, put \`SPAN\` after \`by\` and before the other field, eg. 'stats COUNT() AS \`count\` by SPAN(\`timestamp\`, 1d) AS \`span\`, \`category\`'. -#06 You must put values in quotes when filtering fields with \`text\` or \`keyword\` field type. -#07 To find documents that contain certain phrases in string fields, use \`QUERY_STRING\` which supports multiple fields and wildcard, eg. "where QUERY_STRING(['field1', 'field2'], 'prefix*')". -#08 To find 4xx and 5xx errors using status code, if the status code field type is numberic (eg. \`integer\`), then use 'where \`status_code\` >= 400'; if the field is a string (eg. \`text\` or \`keyword\`), then use "where QUERY_STRING(['status_code'], '4* OR 5*')". - ----------------- -Put your PPL query in tags. ----------------- - -{question} -`.trim(); - -const prompt = new PromptTemplate({ - template, - inputVariables: ['question'], -}); - -export const requestPPLGeneratorChain = async ( - model: BaseLanguageModel, - question: string, - callbacks?: Callbacks -): Promise<{ query: string }> => { - const chain = new LLMChain({ llm: model, prompt }); - const output = await chain.call({ question }, callbacks); - const match = output.text.match(/((.|[\r\n])+?)<\/ppl>/); - if (match && match[1]) - return { - query: match[1] - .replace(/[\r\n]/g, ' ') - .replace(/ISNOTNULL/g, 'isnotnull') // TODO remove after https://github.com/opensearch-project/sql/issues/2431 - .trim(), - }; - throw new Error(output.text); -}; diff --git a/server/olly/chains/query_suggestions_generator.ts b/server/olly/chains/query_suggestions_generator.ts deleted file mode 100644 index d556d6e4..00000000 --- a/server/olly/chains/query_suggestions_generator.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { BaseLanguageModel } from 'langchain/base_language'; -import { Callbacks } from 'langchain/callbacks'; -import { LLMChain } from 'langchain/chains'; -import { PromptTemplate } from 'langchain/prompts'; -import { OpenSearchClient } from '../../../../../src/core/server'; -import { generateFieldContext } from '../utils/ppl_generator'; - -export const requestQuerySuggestionsChain = async ( - model: BaseLanguageModel, - client: OpenSearchClient, - index: string, - callbacks?: Callbacks -) => { - const [mappings, sampleDoc] = await Promise.all([ - client.indices.getMapping({ index }), - client.search({ index, size: 1 }), - ]); - const fields = generateFieldContext(mappings, sampleDoc); - const prompt = new PromptTemplate({ - template: `OpenSearch index: {index} - -Recommend 2 or 3 possible questions on this index given the fields below. Only give the questions, do not give descriptions of questions and do not give PPL queries. - -The format for a field is -\`\`\` -- field_name: field_type (sample field value) -\`\`\` - -Fields: -${fields} - -Put each question in a tag.`, - inputVariables: ['index', 'fields'], - }); - - const chain = new LLMChain({ llm: model, prompt }); - const output = await chain.call({ index, fields }, callbacks); - const match = Array.from(output.text.matchAll(/((.|[\r\n])+?)<\/question>/g)).map( - (m) => (m as unknown[])[1] - ); - if (match.length === 0) throw new Error(output.text); - return match as string[]; -}; diff --git a/server/olly/chains/sort_generator.ts b/server/olly/chains/sort_generator.ts deleted file mode 100644 index bcd329fd..00000000 --- a/server/olly/chains/sort_generator.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { BaseLanguageModel } from 'langchain/base_language'; -import { Callbacks } from 'langchain/callbacks'; -import { LLMChain } from 'langchain/chains'; -import { StructuredOutputParser } from 'langchain/output_parsers'; -import { PromptTemplate } from 'langchain/prompts'; - -const template = ` -You will be given the query that the user asked, as 'userQuery', as well as a list of fields in the result. -Step 1: Determine the field in the list of fields most applicable to the user's question. For example, if the user asks about error rates, and a field exists named 'error_rates.value', that should be the field you choose. If none are applicable, choose the first field in the list of fields. -Step 2. Return those in a JSON object, where the key is 'field', along with the field to be sorted. -{format_instructions} ---------------- -Question: {question} -Fields: {fields} -`.trim(); - -const parser = StructuredOutputParser.fromNamesAndDescriptions({ - field: 'This is the field to sort the results by', -}); -const formatInstructions = parser.getFormatInstructions(); - -const prompt = new PromptTemplate({ - template, - inputVariables: ['question', 'fields'], - partialVariables: { format_instructions: formatInstructions }, -}); - -export const requestSortChain = async ( - model: BaseLanguageModel, - question: string, - fields: string, - callbacks?: Callbacks -) => { - const chain = new LLMChain({ llm: model, prompt }); - const output = await chain.call({ question, fields }, callbacks); - return parser.parse(output.text); -}; diff --git a/server/olly/chains/suggestions_generator.ts b/server/olly/chains/suggestions_generator.ts deleted file mode 100644 index 50816270..00000000 --- a/server/olly/chains/suggestions_generator.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { BaseLanguageModel } from 'langchain/base_language'; -import { Callbacks } from 'langchain/callbacks'; -import { LLMChain } from 'langchain/chains'; -import { BufferMemory } from 'langchain/memory'; -import { StructuredOutputParser } from 'langchain/output_parsers'; -import { PromptTemplate } from 'langchain/prompts'; -import { BaseMessage } from 'langchain/schema'; -import { Tool } from 'langchain/tools'; - -const template = ` -You will be given a chat history between OpenSearch Assistant and a Human. -Use the context provided to generate follow up questions the Human would ask to the Assistant. - -The Assistant can answer general questions about logs, traces and metrics. - -Assistant can access a set of tools listed below to answer questions given by the Human: -{tools_description} - - -Here's the chat history between the human and the Assistant. -{chat_history} - -Use the following steps to generate follow up questions Human may ask after the response of the Assistant: - -Step 1. Use the chat history to understand what human is trying to search and explore. - -Step 2. Understand what capabilities the assistant has with the set of tools it has access to. - -Step 3. Use the above context and generate follow up questions. - ----------------- -{format_instructions} ----------------- -`.trim(); - -const parser = StructuredOutputParser.fromNamesAndDescriptions({ - question1: 'This is the first follow up question', - question2: 'This is the second follow up question', -}); -const formatInstructions = parser.getFormatInstructions(); - -const prompt = new PromptTemplate({ - template, - inputVariables: ['tools_description', 'chat_history'], - partialVariables: { format_instructions: formatInstructions }, -}); - -const convertChatToString = (chatMessages: BaseMessage[]) => { - const chatString = chatMessages - .map((message) => `${message._getType()}: ${message.text}`) - .join('\n'); - return chatString; -}; - -export const requestSuggestionsChain = async ( - model: BaseLanguageModel, - tools: Tool[], - memory: BufferMemory, - callbacks?: Callbacks -) => { - const toolsContext = tools.map((tool) => `${tool.name}: ${tool.description}`).join('\n'); - - const chatHistory = memory.chatHistory; - const chatContext = convertChatToString(await chatHistory.getMessages()); - const chain = new LLMChain({ llm: model, prompt }); - const output = await chain.call( - { tools_description: toolsContext, chat_history: chatContext }, - callbacks - ); - return parser.parse(output.text); -}; diff --git a/server/olly/chains/summarization.ts b/server/olly/chains/summarization.ts deleted file mode 100644 index 6c685ed0..00000000 --- a/server/olly/chains/summarization.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { BaseLanguageModel } from 'langchain/base_language'; -import { Callbacks } from 'langchain/callbacks'; -import { loadQAStuffChain } from 'langchain/chains'; -import { Document } from 'langchain/document'; -import { OpenSearchClient } from '../../../../../src/core/server'; -import { SummarizationRequestSchema } from '../../routes/langchain_routes'; -import { truncate } from '../utils/utils'; -import { requestQuerySuggestionsChain } from './query_suggestions_generator'; - -interface SummarizationContext extends SummarizationRequestSchema { - client: OpenSearchClient; - model: BaseLanguageModel; -} - -const createPrompt = (context: SummarizationContext) => { - if (!context.isError) { - return `You will be given a search response, summarize it as a concise paragraph while considering the following: -User's question on index '${context.index}': ${context.question} -PPL (Piped Processing Language) query used: ${context.query} - -Give some documents to support your point. -Note that the output could be truncated, summarize what you see. Don't mention about total items returned and don't mention about the fact that output is truncated if you see 'Output is too long, truncated' in the response. -If you only see '{}', then there are no results matching the query. - -Skip the introduction; go straight into the summarization.`; - } - - return `You will be given an API response with errors, summarize it as a concise paragraph. Do not try to answer the user's question. -If the error cannot be fixed, eg. no such field or function not supported, then give suggestions to rephrase the question. -It is imperative that you must not give suggestions on how to fix the error or alternative PPL query. - -Consider the following: -User's question on index '${context.index}': ${context.question} -${context.query ? 'PPL (Piped Processing Language) query used: ' + context.query : ''} - -Skip the introduction; go straight into the summarization.`; -}; - -/** - * Generate a summary based on user question, corresponding PPL query, and - * query results. - * - * @param context - * @param callbacks - * @returns summarized text - */ -export const requestSummarizationChain = async ( - context: SummarizationContext, - callbacks?: Callbacks -) => { - const chain = loadQAStuffChain(context.model); - // vector search doesn't help much since the response is already retrieved based on user's question - const docs = [new Document({ pageContent: truncate(context.response) })]; - const question = createPrompt(context); - const [output, suggestions] = await Promise.all([ - chain.call({ input_documents: docs, question }, { callbacks }), - requestQuerySuggestionsChain(context.model, context.client, context.index, callbacks), - ]); - return { summary: output.text, suggestedQuestions: suggestions }; -}; diff --git a/server/olly/memory/__tests__/chat_agent_memory.test.ts b/server/olly/memory/__tests__/chat_agent_memory.test.ts deleted file mode 100644 index a3d4a2fb..00000000 --- a/server/olly/memory/__tests__/chat_agent_memory.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { createMessage } from '../../__tests__/__utils__/test_helpers'; -import { memoryInit } from '../chat_agent_memory'; - -describe('convert messages to memory', () => { - it('removes initial AI messages', async () => { - const memory = memoryInit([ - createMessage({ type: 'output', content: 'ai message 1' }), - createMessage({ type: 'output', content: 'ai message 2' }), - createMessage({ type: 'input', content: 'human message 1' }), - createMessage({ type: 'output', content: 'ai message 3' }), - ]); - const messages = await memory.chatHistory.getMessages(); - expect(messages).toMatchObject([{ content: 'human message 1' }, { content: 'ai message 3' }]); - }); - - it('returns empty history if no human input', async () => { - const memory = memoryInit([ - createMessage({ type: 'output', content: 'ai message 1' }), - createMessage({ type: 'output', content: 'ai message 2' }), - ]); - const messages = await memory.chatHistory.getMessages(); - expect(messages).toStrictEqual([]); - }); - - it('removes error outputs', async () => { - const memory = memoryInit([ - createMessage({ type: 'input', contentType: 'text', content: 'human message 1' }), - createMessage({ type: 'output', contentType: 'error', content: 'ai message 1' }), - ]); - const messages = await memory.chatHistory.getMessages(); - expect(messages).toStrictEqual([]); - }); - - it('removes unmatched input/output pairs', async () => { - const memory = memoryInit([ - createMessage({ type: 'input', content: 'human message 1' }), - createMessage({ type: 'input', content: 'human message 2' }), - createMessage({ type: 'input', content: 'human message 3' }), - createMessage({ type: 'output', contentType: 'error', content: 'ai message 1' }), - createMessage({ type: 'input', content: 'human message 4' }), - createMessage({ type: 'output', content: 'ai message 2' }), - createMessage({ type: 'input', content: 'human message 5' }), - createMessage({ type: 'input', content: 'human message 6' }), - createMessage({ type: 'output', content: 'ai message 3' }), - ]); - const messages = await memory.chatHistory.getMessages(); - expect(messages).toMatchObject([ - { content: 'human message 4' }, - { content: 'ai message 2' }, - { content: 'human message 6' }, - { content: 'ai message 3' }, - ]); - }); - - it('only returns the latest 5 input/output pairs', async () => { - const messageArr = Array.from({ length: 20 }, (_, i) => - createMessage({ - type: i % 2 === 0 ? 'input' : 'output', - content: `${i % 2 === 0 ? 'human' : 'ai'} message ${Math.floor(i / 2) + 1}`, - }) - ); - messageArr.splice(10, 0, createMessage({ type: 'output', content: 'ai message 5.5' })); - messageArr.splice(13, 0, createMessage({ type: 'output', content: 'ai message 6.5' })); - const memory = memoryInit(messageArr); - const messages = await memory.chatHistory.getMessages(); - expect(messages).toMatchObject([ - { content: 'human message 6' }, - { content: 'ai message 6' }, - { content: 'ai message 6.5' }, - { content: 'human message 7' }, - { content: 'ai message 7' }, - { content: 'human message 8' }, - { content: 'ai message 8' }, - { content: 'human message 9' }, - { content: 'ai message 9' }, - { content: 'human message 10' }, - { content: 'ai message 10' }, - ]); - }); -}); diff --git a/server/olly/memory/chat_agent_memory.ts b/server/olly/memory/chat_agent_memory.ts deleted file mode 100644 index 9193eef9..00000000 --- a/server/olly/memory/chat_agent_memory.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { BufferMemory, ChatMessageHistory } from 'langchain/memory'; -import { AIMessage, BaseMessage, HumanMessage } from 'langchain/schema'; -import { IMessage } from '../../../common/types/chat_saved_object_attributes'; - -const filterMessages = (messages: IMessage[]): IMessage[] => { - // remove error outputs, unmatched input/output pairs - const context = messages - .filter((message) => !(message.type === 'output' && message.contentType === 'error')) - .filter((message, i, arr) => !(message.type === 'input' && arr[i + 1]?.type !== 'output')); - // keep only the last 5 input/output pairs, where the first message must be human input - const inputIndices = context - .map((message, i) => { - if (message.type === 'input') return i; - }) - .filter((i) => i !== undefined) - .slice(-5); - return inputIndices.length === 0 ? [] : context.slice(inputIndices[0]); -}; - -const convertToBaseMessages = (messages: IMessage[]): BaseMessage[] => - messages.map((message) => - message.type === 'input' ? new HumanMessage(message.content) : new AIMessage(message.content) - ); - -/** - * Creates {@link BufferMemory} based on previous conversations with the - * following removed: initial AI messages because in claude the prompt must - * start by human, error outputs, inputs without corresponding output, - * conversations before the last 10 messages. - * - * @param messages - previous conversation messages - * @returns memory based on filtered history - */ -export const memoryInit = (messages: IMessage[]) => { - const pastMessages = convertToBaseMessages(filterMessages(messages)); - return new BufferMemory({ - chatHistory: new ChatMessageHistory(pastMessages), - returnMessages: true, - memoryKey: 'chat_history', - inputKey: 'input', - }); -}; diff --git a/server/olly/models/constants.ts b/server/olly/models/constants.ts deleted file mode 100644 index a239d9ea..00000000 --- a/server/olly/models/constants.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -export const ML_COMMONS_BASE_API = '/_plugins/_ml'; - -// Below params are inspired from langchain defaults -export const ANTHROPIC_DEFAULT_PARAMS = { - temperature: 0.0000001, - max_tokens_to_sample: 2048, -}; - -export const ASSISTANT_CONFIG_INDEX = '.chat-assistant-config'; -export const ASSISTANT_CONFIG_DOCUMENT = 'model-config'; diff --git a/server/olly/models/llm_model_factory.ts b/server/olly/models/llm_model_factory.ts deleted file mode 100644 index 3b641c6e..00000000 --- a/server/olly/models/llm_model_factory.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Client } from '@opensearch-project/opensearch'; -import { Callbacks } from 'langchain/callbacks'; -import { ChatAnthropic } from 'langchain/chat_models/anthropic'; -import { Embeddings } from 'langchain/embeddings/base'; -import { OpenAIEmbeddings } from 'langchain/embeddings/openai'; -import { OpenAI } from 'langchain/llms/openai'; -import { OpenSearchVectorStore } from 'langchain/vectorstores/opensearch'; -import { OpenSearchClient } from '../../../../../src/core/server'; -import { LLM_INDEX } from '../../../common/constants/llm'; -import { MLCommonsChatModel } from './mlcommons_chat_model'; -import { MLCommonsEmbeddingsModel } from './mlcommons_embedding_model'; - -type ModelName = 'claude' | 'openai' | 'ml-commons-claude'; - -interface CreateModelOptions { - client: OpenSearchClient; - callbacks?: Callbacks; - name?: ModelName; -} - -interface CreateEmbeddingsOptions { - client: OpenSearchClient; - name?: ModelName; -} - -interface CreateVectorStoreOptions { - embeddings: Embeddings; - client: OpenSearchClient; - indexName?: string; -} - -export class LLMModelFactory { - static createModel(options: CreateModelOptions) { - switch (options.name) { - case 'openai': - return new OpenAI({ temperature: 0.0000001, callbacks: options.callbacks }); - - case 'claude': - return new ChatAnthropic({ temperature: 0.0000001, callbacks: options.callbacks }); - - case 'ml-commons-claude': - default: - return new MLCommonsChatModel({ callbacks: options.callbacks }, options.client); - } - } - - static createEmbeddings(options: CreateEmbeddingsOptions) { - switch (options.name) { - case 'openai': - return new OpenAIEmbeddings(); - - case 'claude': - case 'ml-commons-claude': - default: - return new MLCommonsEmbeddingsModel(options.client); - } - } - - static createVectorStore(options: CreateVectorStoreOptions) { - const { embeddings, client, indexName = LLM_INDEX.VECTOR_STORE } = options; - return new OpenSearchVectorStore(embeddings, { client: client as Client, indexName }); - } -} diff --git a/server/olly/models/mlcommons_chat_model.ts b/server/olly/models/mlcommons_chat_model.ts deleted file mode 100644 index 3a0481d1..00000000 --- a/server/olly/models/mlcommons_chat_model.ts +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { AI_PROMPT, HUMAN_PROMPT } from '@anthropic-ai/sdk'; -import { BaseLanguageModelParams } from 'langchain/base_language'; -import { CallbackManagerForLLMRun } from 'langchain/callbacks'; -import { BaseChatModel } from 'langchain/chat_models/base'; -import { AIMessage, BaseMessage, ChatResult, LLMResult, MessageType } from 'langchain/schema'; -import { OpenSearchClient } from '../../../../../src/core/server'; -import { - ANTHROPIC_DEFAULT_PARAMS, - ASSISTANT_CONFIG_DOCUMENT, - ASSISTANT_CONFIG_INDEX, - ML_COMMONS_BASE_API, -} from './constants'; - -export class MLCommonsChatModel extends BaseChatModel { - opensearchClient: OpenSearchClient; - - constructor(fields: BaseLanguageModelParams, osClient: OpenSearchClient) { - super(fields); - this.opensearchClient = osClient; - } - - _combineLLMOutput?(...llmOutputs: Array): LLMResult['llmOutput'] { - return []; - } - - _llmType(): string { - return 'opensearch_mlcommons'; - } - getAnthropicPromptFromMessage(type: MessageType): string { - switch (type) { - case 'ai': - return AI_PROMPT; - case 'human': - return HUMAN_PROMPT; - case 'system': - return ''; - default: - throw new Error(`Unknown message type: ${type}`); - } - } - - private formatMessagesAsPrompt(messages: BaseMessage[]): string { - return ( - messages - .map((message) => { - const messagePrompt = this.getAnthropicPromptFromMessage(message._getType()); - return `${messagePrompt} ${message.content}`; - }) - .join('') + AI_PROMPT - ); - } - - async model_predict(prompt: string) { - const getResponse = await this.opensearchClient.get({ - id: ASSISTANT_CONFIG_DOCUMENT, - index: ASSISTANT_CONFIG_INDEX, - }); - if (!getResponse.body._source) throw new Error('Assistant config source not found.'); - const mlCommonsModelId = getResponse.body._source.model_id; - - const mlCommonsResponse = await this.opensearchClient.transport.request({ - method: 'POST', - path: `${ML_COMMONS_BASE_API}/models/${mlCommonsModelId}/_predict`, - body: { - parameters: { - ...ANTHROPIC_DEFAULT_PARAMS, - prompt, - }, - }, - }); - const respData = mlCommonsResponse.body.inference_results[0].output[0].dataAsMap; - return respData.completion || respData.message || 'Failed to request model'; - } - - async _call( - messages: BaseMessage[], - options: this['ParsedCallOptions'], - runManager?: CallbackManagerForLLMRun - ): Promise { - return await this.model_predict(this.formatMessagesAsPrompt(messages)); - } - - async _generate( - messages: BaseMessage[], - options: this['ParsedCallOptions'], - runManager?: CallbackManagerForLLMRun - ): Promise { - const text = await this._call(messages, options, runManager); - const message = new AIMessage(text); - return { - generations: [ - { - text: message.content, - message, - }, - ], - }; - } -} diff --git a/server/olly/models/mlcommons_embedding_model.ts b/server/olly/models/mlcommons_embedding_model.ts deleted file mode 100644 index 29c567e0..00000000 --- a/server/olly/models/mlcommons_embedding_model.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { ApiResponse } from '@opensearch-project/opensearch/lib/Transport'; -import { Embeddings, EmbeddingsParams } from 'langchain/embeddings/base'; -import { OpenSearchClient } from '../../../../../src/core/server'; -import { - ASSISTANT_CONFIG_DOCUMENT, - ASSISTANT_CONFIG_INDEX, - ML_COMMONS_BASE_API, -} from './constants'; - -interface MLCommonsEmbeddingsResponse { - inference_results: Array<{ - output: Array<{ - name: string; - data_type: string; - shape: number[]; - data: number[]; - }>; - }>; -} - -export class MLCommonsEmbeddingsModel extends Embeddings { - constructor(private opensearchClient: OpenSearchClient, params: EmbeddingsParams = {}) { - super(params); - } - - async getModelID() { - const getResponse = await this.opensearchClient.get({ - id: ASSISTANT_CONFIG_DOCUMENT, - index: ASSISTANT_CONFIG_INDEX, - }); - if (!getResponse.body._source) throw new Error('Assistant config source not found.'); - return getResponse.body._source.embeddings_model_id; - } - - async embedDocuments(documents: string[]): Promise { - const mlCommonsModelId = await this.getModelID(); - // reference: https://github.com/opensearch-project/opensearch-py-ml/blob/7b0066afa69294aa3d9c1a18976dad80ee74c037/opensearch_py_ml/ml_commons/ml_commons_client.py#L487 - const mlCommonsResponse = (await this.opensearchClient.transport.request({ - method: 'POST', - path: `${ML_COMMONS_BASE_API}/_predict/text_embedding/${mlCommonsModelId}`, - body: { - text_docs: documents, - target_response: ['sentence_embedding'], - }, - })) as ApiResponse; - return mlCommonsResponse.body.inference_results.map((inference) => inference.output[0].data); - } - - async embedQuery(document: string): Promise { - return (await this.embedDocuments([document]))[0]; - } -} diff --git a/server/olly/models/types.ts b/server/olly/models/types.ts deleted file mode 100644 index f51b7198..00000000 --- a/server/olly/models/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -interface AssistantConfigDoc { - model_id: string; - embeddings_model_id: string; -} diff --git a/server/olly/tools/preserved_input_tool.ts b/server/olly/tools/preserved_input_tool.ts deleted file mode 100644 index d9865d42..00000000 --- a/server/olly/tools/preserved_input_tool.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { DynamicTool } from 'langchain/tools'; - -export class PreservedInputTool extends DynamicTool {} diff --git a/server/olly/tools/tool_sets/aleritng_apis.ts b/server/olly/tools/tool_sets/aleritng_apis.ts deleted file mode 100644 index 192d82af..00000000 --- a/server/olly/tools/tool_sets/aleritng_apis.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { DynamicTool } from 'langchain/tools'; -import { protectCall } from '../../utils/utils'; -import { PluginToolsBase } from '../tools_base'; - -export class OSAlertingTools extends PluginToolsBase { - toolsList = [ - new DynamicTool({ - name: 'Search Alerting Monitors By Index', - description: - 'use this tool to search alerting monitors by index name in the OpenSearch cluster. This tool takes the index name as input', - func: protectCall((indexName: string) => this.searchAlertMonitorsByIndex(indexName)), - callbacks: this.callbacks, - }), - new DynamicTool({ - name: 'Get All Alerts', - description: 'use this tool to search all alerts triggered in the OpenSearch cluster.', - func: protectCall(() => this.getAllAlerts()), - callbacks: this.callbacks, - }), - ]; - - // TODO: This is temporarily a pass through call which needs to be deprecated - public searchAlertMonitorsByIndex = async (indexName: string) => { - const query = { - query: { - nested: { - path: 'monitor.inputs', - query: { - bool: { - must: [ - { - match: { - 'monitor.inputs.search.indices': indexName, - }, - }, - ], - }, - }, - }, - }, - }; - - const params = { body: query }; - const results = await this.observabilityClient.callAsCurrentUser( - 'alerting.getMonitors', - params - ); - return JSON.stringify(results.hits.hits); - }; - - public getAllAlerts = async () => { - const results = await this.observabilityClient.callAsCurrentUser('alerting.getAlerts'); - return JSON.stringify(results); - }; -} diff --git a/server/olly/tools/tool_sets/knowledges.ts b/server/olly/tools/tool_sets/knowledges.ts deleted file mode 100644 index 82526849..00000000 --- a/server/olly/tools/tool_sets/knowledges.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { RetrievalQAChain } from 'langchain/chains'; -import { DynamicTool } from 'langchain/tools'; -import { requestGenericResponseChain } from '../../chains/generic_response'; -import { LLMModelFactory } from '../../models/llm_model_factory'; -import { protectCall } from '../../utils/utils'; -import { PluginToolsBase } from '../tools_base'; - -export class KnowledgeTools extends PluginToolsBase { - chain = RetrievalQAChain.fromLLM( - this.model, - LLMModelFactory.createVectorStore({ - embeddings: this.embeddings, - client: this.opensearchClient, - }).asRetriever(), - { returnSourceDocuments: true } - ); - - toolsList = [ - new DynamicTool({ - name: 'Get ticket information', - description: - 'Use this tool to find tickets in the system with incidents that are relevant to a question about error causes. This tool takes the question as input.', - func: protectCall((query: string) => this.askVectorStore(query)), - callbacks: this.callbacks, - }), - new DynamicTool({ - name: 'Get generic information', - description: - 'Use this tool to answer a generic question that is not related to any specific OpenSearch cluster, for example, instructions on how to do something. This tool takes the question as input.', - returnDirect: true, - func: protectCall((query: string) => requestGenericResponseChain(this.model, query)), - callbacks: this.callbacks, - }), - ]; - - public async askVectorStore(query: string) { - const res = await this.chain.call({ query }); - return res.text; - } -} diff --git a/server/olly/tools/tool_sets/os_apis.ts b/server/olly/tools/tool_sets/os_apis.ts deleted file mode 100644 index 51e5d398..00000000 --- a/server/olly/tools/tool_sets/os_apis.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { DynamicTool } from 'langchain/tools'; -import { jsonToCsv, protectCall } from '../../utils/utils'; -import { PluginToolsBase } from '../tools_base'; - -export class OSAPITools extends PluginToolsBase { - toolsList = [ - new DynamicTool({ - name: 'Get OpenSearch indices', - description: - 'use this tool to get high-level information like (health, status, index, uuid, primary count, replica count, docs.count, docs.deleted, store.size, primary.store.size) about indices in a cluster, including backing indices for data streams in the OpenSearch cluster. This tool optionally takes the index name as input', - func: protectCall((indexName?: string) => this.catIndices(indexName)), - callbacks: this.callbacks, - }), - new DynamicTool({ - name: 'Check OpenSearch index existence', - description: - 'use this tool to check if a data stream, index, or alias exists in the OpenSearch cluster. This tool takes the index name as input', - func: protectCall((indexName: string) => this.indexExists(indexName)), - callbacks: this.callbacks, - }), - ]; - - public async catIndices(indexName = '') { - const catResponse = await this.opensearchClient.cat.indices({ - index: indexName, - format: 'json', - }); - const csv = jsonToCsv(catResponse.body); - return indexName === '' ? `There are ${csv.split('\n').length - 1} indices.\n${csv}` : csv; - } - - public async indexExists(indexName: string) { - const indexExistsResponse = await this.opensearchClient.indices.exists({ - index: indexName, - }); - - return indexExistsResponse.body - ? 'Index exists in the OpenSearch Cluster' - : 'One or more specified Index do not exist'; - } -} diff --git a/server/olly/tools/tool_sets/ppl.ts b/server/olly/tools/tool_sets/ppl.ts deleted file mode 100644 index 558be077..00000000 --- a/server/olly/tools/tool_sets/ppl.ts +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { DynamicTool } from 'langchain/tools'; -import { requestGuessingIndexChain } from '../../chains/guessing_index'; -import { requestPPLGeneratorChain } from '../../chains/ppl_generator'; -import { generateFieldContext } from '../../utils/ppl_generator'; -import { protectCall } from '../../utils/utils'; -import { PreservedInputTool } from '../preserved_input_tool'; -import { PluginToolsBase } from '../tools_base'; - -const PPL_DATASOURCES_REQUEST = - 'show datasources | where CONNECTOR_TYPE="PROMETHEUS" | fields DATASOURCE_NAME'; - -interface PPLResponse { - schema: Array<{ name: string; type: string }>; - datarows: unknown[][]; - total: number; - size: number; -} - -export class PPLTools extends PluginToolsBase { - static TOOL_NAMES = { - QUERY_OPENSEARCH: 'Query OpenSearch', - LOG_INFO: 'Get log info', - LOG_ERROR_INFO: 'Get log error info', - } as const; - - toolsList = [ - new PreservedInputTool({ - name: PPLTools.TOOL_NAMES.QUERY_OPENSEARCH, - description: - 'Use to generate and run a PPL Query to get results for a generic user question related to data stored in their OpenSearch cluster.', - func: protectCall(async (query: string) => { - const ppl = await this.generatePPL(query); - const results = await this.executePPL(ppl); - return `The PPL query is: ${ppl}\n\nThe results are:\n${JSON.stringify(results, null, 2)}`; - }), - callbacks: this.callbacks, - }), - /* new DynamicTool({ - name: 'Generate prometheus PPL query', - description: - 'Use this tool to generate a PPL query about metrics and prometheus. This tool take natural language question as input.', - func: swallowErrors((query: string) => this.generatePrometheusPPL(query)), - callbacks: this.callbacks, - }), */ - new DynamicTool({ - name: PPLTools.TOOL_NAMES.LOG_INFO, - description: - 'Use to get information of logs if the question contains an OpenSearch log index. The input should be the name of the index', - func: protectCall(async (index: string) => { - const ppl = await this.generatePPL(`Give me log patterns? index is '${index}'`); - const results = await this.executePPL(ppl); - return `The PPL query is: ${ppl}\n\nThe results are:\n${JSON.stringify(results, null, 2)}`; - }), - callbacks: this.callbacks, - }), - new DynamicTool({ - name: PPLTools.TOOL_NAMES.LOG_ERROR_INFO, - description: - 'Use to get information of logs with errors if the question contains an OpenSearch log index. The input should be the name of the index. The output is a representative log per each log pattern group.', - func: protectCall(async (index: string) => { - const ppl = await this.generatePPL( - `Give me log patterns for logs with errors? index is '${index}'` - ); - const results = await this.executePPL(ppl); - return `The PPL query is: ${ppl}\n\nThe results are:\n${JSON.stringify(results, null, 2)}`; - }), - callbacks: this.callbacks, - }), - ]; - - /** - * @returns non hidden OpenSearch index names as a list. - */ - private async getIndexNameList() { - const response = await this.opensearchClient.cat.indices({ format: 'json', h: 'index' }); - return response.body - .map((index) => index.index) - .filter( - (index) => index !== undefined && !/^(\.|security-auditlog-)/.test(index) - ) as string[]; - } - - private async getPrometheusMetricList() { - const response = await this.executePPL(PPL_DATASOURCES_REQUEST); - return Promise.all( - response.datarows.map(([dataSource]) => - this.executePPL(`source = ${dataSource}.information_schema.tables`).then((tables) => - tables.datarows.map((row) => { - const obj: { [k: string]: unknown } = {}; - row.forEach((value, i) => (obj[tables.schema[i].name] = value)); - return { - table: `${obj.TABLE_CATALOG}.${obj.TABLE_NAME}`, - type: obj.TABLE_TYPE as string, - description: obj.REMARKS as string, - }; - }) - ) - ) - ).then((responses) => responses.flat()); - } - - public async executePPL(query: string) { - const response: PPLResponse = await this.observabilityClient.callAsCurrentUser('ppl.pplQuery', { - body: { query }, - }); - return response; - } - - public async generatePrometheusPPL(question: string, index?: string) { - if (!index) { - const prometheusMetricList = await this.getPrometheusMetricList(); - const response = await requestGuessingIndexChain( - this.model, - question, - prometheusMetricList.map( - (metric) => `index: ${metric.table}, description: ${metric.description}` - ), - this.callbacks - ); - index = response.index; - } - return `source = ${index} | stats avg(@value) by span(@timestamp, 1h)`; - } - - public async generatePPL(question: string, index?: string) { - if (!index) { - const indexNameList = await this.getIndexNameList(); - const response = await requestGuessingIndexChain( - this.model, - question, - indexNameList, - this.callbacks - ); - index = response.index; - } - - const [mappings, sampleDoc] = await Promise.all([ - this.opensearchClient.indices.getMapping({ index }), - this.opensearchClient.search({ index, size: 1 }), - ]); - const fields = generateFieldContext(mappings, sampleDoc); - - const input = `Fields:\n${fields}\nQuestion: ${question}? index is \`${index}\``; - const ppl = await requestPPLGeneratorChain(this.model, input, this.callbacks); - ppl.query = ppl.query.replace(/`/g, ''); // workaround for https://github.com/opensearch-project/dashboards-observability/issues/509, https://github.com/opensearch-project/dashboards-observability/issues/557 - ppl.query = ppl.query.replace(/\bSPAN\(/g, 'span('); // workaround for https://github.com/opensearch-project/dashboards-observability/issues/759 - return ppl.query; - } -} diff --git a/server/olly/tools/tool_sets/saved_objects.ts b/server/olly/tools/tool_sets/saved_objects.ts deleted file mode 100644 index c81a395d..00000000 --- a/server/olly/tools/tool_sets/saved_objects.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { DynamicTool } from 'langchain/tools'; -import { SavedObjectAttributes } from '../../../../../../src/core/types'; -import { jsonToCsv, protectCall } from '../../utils/utils'; -import { PluginToolsBase } from '../tools_base'; - -export class SavedObjectsTools extends PluginToolsBase { - static TOOL_NAMES = { - FIND_VISUALIZATIONS: 'Find Visualizations', - } as const; - - toolsList = [ - new DynamicTool({ - name: SavedObjectsTools.TOOL_NAMES.FIND_VISUALIZATIONS, - description: - 'use this tool to find user created visualizations. This tool takes the visualization name as input and returns the first 3 matching visualizations', - func: protectCall((name: string) => this.findVisualizationsByName(name)), // use arrow function to pass through `this` - callbacks: this.callbacks, - }), - ]; - - public async findVisualizationsByName(name: string) { - const visualizations = await this.savedObjectsClient - .find({ - type: 'visualization', // VISUALIZE_EMBEDDABLE_TYPE - search: name, - perPage: 3, - }) - .then((response) => - response.saved_objects.map((visualization) => ({ - id: visualization.id, - title: visualization.attributes.title, - })) - ); - return jsonToCsv(visualizations); - } -} diff --git a/server/olly/tools/tool_sets/trace_tools/constants.ts b/server/olly/tools/tool_sets/trace_tools/constants.ts deleted file mode 100644 index bd683d00..00000000 --- a/server/olly/tools/tool_sets/trace_tools/constants.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -export const JAEGER_INDEX_NAME = '*jaeger-span-*'; -export const JAEGER_SERVICE_INDEX_NAME = '*jaeger-service*'; -export const DATA_PREPPER_INDEX_NAME = 'otel-v1-apm-span-*'; -export const DATA_PREPPER_SERVICE_INDEX_NAME = 'otel-v1-apm-service-map*'; -export const TRACE_ANALYTICS_DATE_FORMAT = 'MM/DD/YYYY HH:mm:ss'; -export const TRACE_ANALYTICS_PLOTS_DATE_FORMAT = 'MMM D, YYYY HH:mm:ss'; -export const SERVICE_MAP_MAX_NODES = 500; -// size limit when requesting edge related queries, not necessarily the number of edges -export const SERVICE_MAP_MAX_EDGES = 1000; -export const TRACES_MAX_NUM = 3000; diff --git a/server/olly/tools/tool_sets/trace_tools/filters.ts b/server/olly/tools/tool_sets/trace_tools/filters.ts deleted file mode 100644 index bd8f9dae..00000000 --- a/server/olly/tools/tool_sets/trace_tools/filters.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { BaseLanguageModel } from 'langchain/base_language'; -import { SearchRequest } from '../../../../../../../src/plugins/data/common'; -import { requestTimesFiltersChain } from '../../../chains/filter_generator'; -import { requestSortChain } from '../../../chains/sort_generator'; -import { TraceBucketName } from './queries'; - -export async function addFilters( - bodyQuery: SearchRequest['body'], - userQuery: string, - model: BaseLanguageModel -) { - const time = await requestTimesFiltersChain(model, userQuery); - const timeFilter = { - range: { - startTime: { - gte: time?.start_time, - lte: time?.end_time, - }, - }, - }; - const must = bodyQuery?.query?.bool?.must; - if (Array.isArray(must)) must.push(timeFilter); -} - -export async function getField( - userQuery: string, - keyword: TraceBucketName, - model: BaseLanguageModel -) { - const fields = { - trace_group_name: 'doc_count,average_latency.value,trace_count.value,error_rate.value', - traces: - 'key,doc_count,last_updated.value,last_updated.value_as_string,latency.value,error_count.doc_count,trace_group.doc_count_error_upper_bound,trace_group.sum_other_doc_count,trace_group.buckets.0.key,trace_group.buckets.0.doc_count', - service_name: - 'key,doc_count,error_count.doc_count,average_latency_nanos.value,average_latency.value,error_rate.value', - }; - const field = await requestSortChain(model, userQuery, fields[keyword]); - return field?.field; -} diff --git a/server/olly/tools/tool_sets/trace_tools/queries.ts b/server/olly/tools/tool_sets/trace_tools/queries.ts deleted file mode 100644 index 94db54ad..00000000 --- a/server/olly/tools/tool_sets/trace_tools/queries.ts +++ /dev/null @@ -1,774 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - AggregationsMultiBucketAggregate, - SearchRequest, -} from '@opensearch-project/opensearch/api/types'; -import { OpenSearchClient } from '../../../../../../../src/core/server'; -import { AggregationBucket, flatten, jsonToCsv, TraceAnalyticsMode } from '../../../utils/utils'; -import { - DATA_PREPPER_INDEX_NAME, - DATA_PREPPER_SERVICE_INDEX_NAME, - JAEGER_INDEX_NAME, - JAEGER_SERVICE_INDEX_NAME, - SERVICE_MAP_MAX_EDGES, - SERVICE_MAP_MAX_NODES, - TRACES_MAX_NUM, -} from './constants'; - -interface ServiceObject { - [key: string]: { - serviceName: string; - id: number; - traceGroups: Array<{ traceGroup: string; targetResource: string[] }>; - targetServices: string[]; - destServices: string[]; - latency?: number; - error_rate?: number; - throughput?: number; - throughputPerMinute?: number; - relatedServices?: string[]; // services appear in the same traces this service appears - }; -} - -export type TraceBucketName = 'trace_group_name' | 'traces' | 'service_name'; - -export async function getMode(opensearchClient: OpenSearchClient) { - const indexExistsResponse = await opensearchClient.indices.exists({ - index: DATA_PREPPER_INDEX_NAME, - }); - return indexExistsResponse ? 'data_prepper' : 'jaeger'; -} - -export async function runQuery( - opensearchClient: OpenSearchClient, - query: object, - mode: TraceAnalyticsMode, - keyword: TraceBucketName, - field?: string -) { - const response = await opensearchClient.search({ - index: mode === 'data_prepper' ? DATA_PREPPER_INDEX_NAME : JAEGER_INDEX_NAME, - body: query, - }); - if (!response.body.aggregations) return ''; - let buckets = (response.body.aggregations[keyword] as AggregationsMultiBucketAggregate< - AggregationBucket - >).buckets; - if (buckets.length === 0) { - return 'None found'; - } - buckets = flatten(buckets); - if (field) { - buckets = buckets.sort(function (a, b) { - // @ts-ignore - return a[field] - b[field]; - }); - } - - return jsonToCsv(buckets); -} - -export const getDashboardQuery = (mode: TraceAnalyticsMode) => { - if (mode === 'data_prepper') - return { - size: 0, - query: { - bool: { - must: [], - filter: [], - should: [], - must_not: [], - }, - }, - aggs: { - trace_group_name: { - terms: { - field: 'traceGroup', - size: 10000, - }, - aggs: { - average_latency: { - scripted_metric: { - init_script: 'state.traceIdToLatencyMap = [:];', - map_script: ` - if (doc.containsKey('traceGroupFields.durationInNanos') && !doc['traceGroupFields.durationInNanos'].empty) { - def traceId = doc['traceId'].value; - if (!state.traceIdToLatencyMap.containsKey(traceId)) { - state.traceIdToLatencyMap[traceId] = doc['traceGroupFields.durationInNanos'].value; - } - } - `, - combine_script: 'return state.traceIdToLatencyMap', - reduce_script: ` - def seenTraceIdsMap = [:]; - def totalLatency = 0.0; - def traceCount = 0.0; - - for (s in states) { - if (s == null) { - continue; - } - - for (entry in s.entrySet()) { - def traceId = entry.getKey(); - def traceLatency = entry.getValue(); - if (!seenTraceIdsMap.containsKey(traceId)) { - seenTraceIdsMap[traceId] = true; - totalLatency += traceLatency; - traceCount++; - } - } - } - - def average_latency_nanos = totalLatency / traceCount; - return Math.round(average_latency_nanos / 10000) / 100.0; - `, - }, - }, - trace_count: { - cardinality: { - field: 'traceId', - }, - }, - error_count: { - filter: { - term: { - 'traceGroupFields.statusCode': '2', - }, - }, - aggs: { - trace_count: { - cardinality: { - field: 'traceId', - }, - }, - }, - }, - error_rate: { - bucket_script: { - buckets_path: { - total: 'trace_count.value', - errors: 'error_count>trace_count.value', - }, - script: 'params.errors / params.total * 100', - }, - }, - }, - }, - }, - }; - else - return { - size: 0, - query: { - bool: { - must: [], - filter: [], - should: [], - must_not: [], - }, - }, - aggs: { - trace_group_name: { - multi_terms: { - terms: [ - { - field: 'process.serviceName', - }, - { - field: 'operationName', - }, - ], - order: { - latency: 'desc', - }, - size: 10000, - }, - aggs: { - latency: { - avg: { - field: 'duration', - }, - }, - average_latency: { - scripted_metric: { - init_script: 'state.traceIDToLatencyMap = [:];', - map_script: ` - if (doc.containsKey('duration') && !doc['duration'].empty) { - def traceID = doc['traceID'].value; - if (!state.traceIDToLatencyMap.containsKey(traceID)) { - state.traceIDToLatencyMap[traceID] = doc['duration'].value; - } - } - `, - combine_script: 'return state.traceIDToLatencyMap', - reduce_script: ` - def seenTraceIdsMap = [:]; - def totalLatency = 0.0; - def traceCount = 0.0; - - for (s in states) { - if (s == null) { - continue; - } - - for (entry in s.entrySet()) { - def traceID = entry.getKey(); - def traceLatency = entry.getValue(); - if (!seenTraceIdsMap.containsKey(traceID)) { - seenTraceIdsMap[traceID] = true; - totalLatency += traceLatency; - traceCount++; - } - } - } - - def average_latency_nanos = totalLatency / traceCount; - return Math.round(average_latency_nanos / 10) / 100.0; - `, - }, - }, - - trace_count: { - cardinality: { - field: 'traceID', - }, - }, - error_count: { - filter: { - term: { - 'tag.error': true, - }, - }, - aggs: { - trace_count: { - cardinality: { - field: 'traceID', - }, - }, - }, - }, - error_rate: { - bucket_script: { - buckets_path: { - total: 'trace_count.value', - errors: 'error_count>trace_count.value', - }, - script: 'params.errors / params.total * 100', - }, - }, - }, - }, - }, - }; -}; - -export const getTracesQuery = (mode: TraceAnalyticsMode) => { - const jaegerQuery: SearchRequest['body'] = { - size: 0, - query: { - bool: { - must: [], - filter: [], - should: [], - must_not: [], - }, - }, - aggs: { - traces: { - terms: { - field: 'traceID', - size: TRACES_MAX_NUM, - }, - aggs: { - latency: { - max: { - script: { - source: ` - if (doc.containsKey('duration') && !doc['duration'].empty) { - return Math.round(doc['duration'].value) / 1000.0 - } - - return 0 - `, - lang: 'painless', - }, - }, - }, - trace_group: { - terms: { - field: 'traceGroup', - size: 1, - }, - }, - error_count: { - filter: { - term: { - 'tag.error': true, - }, - }, - }, - last_updated: { - max: { - script: { - source: ` - if (doc.containsKey('startTime') && !doc['startTime'].empty && doc.containsKey('duration') && !doc['duration'].empty) { - return (Math.round(doc['duration'].value) + Math.round(doc['startTime'].value)) / 1000.0 - } - - return 0 - `, - lang: 'painless', - }, - }, - }, - }, - }, - }, - }; - const dataPrepperQuery: SearchRequest['body'] = { - size: 0, - query: { - bool: { - must: [], - filter: [], - should: [], - must_not: [], - }, - }, - aggs: { - traces: { - terms: { - field: 'traceId', - size: TRACES_MAX_NUM, - }, - aggs: { - latency: { - max: { - script: { - source: ` - if (doc.containsKey('traceGroupFields.durationInNanos') && !doc['traceGroupFields.durationInNanos'].empty) { - return Math.round(doc['traceGroupFields.durationInNanos'].value / 10000) / 100.0 - } - return 0 - `, - lang: 'painless', - }, - }, - }, - trace_group: { - terms: { - field: 'traceGroup', - size: 1, - }, - }, - error_count: { - filter: { - term: { - 'traceGroupFields.statusCode': '2', - }, - }, - }, - last_updated: { - max: { - field: 'traceGroupFields.endTime', - }, - }, - }, - }, - }, - }; - return mode === 'jaeger' ? jaegerQuery : dataPrepperQuery; -}; - -export const getServices = async (mode: TraceAnalyticsMode, openSearchClient: OpenSearchClient) => { - const map: ServiceObject = {}; - let id = 1; - const serviceNodesResponse = await openSearchClient.search({ - index: mode === 'jaeger' ? JAEGER_SERVICE_INDEX_NAME : DATA_PREPPER_SERVICE_INDEX_NAME, - body: getServiceNodesQuery(mode), - }); - - // @ts-ignore - serviceNodesResponse.body.aggregations.service_name.buckets.map( - (bucket: object) => - // @ts-ignore - (map[bucket.key as string] = { - // @ts-ignore - serviceName: bucket.key, - id: id++, - // @ts-ignore - traceGroups: bucket.trace_group.buckets.map((traceGroup: object) => ({ - // @ts-ignore - traceGroup: traceGroup.key, - // @ts-ignore - targetResource: traceGroup.target_resource.buckets.map((res: object) => res.key), - })), - targetServices: [], - destServices: [], - }) - ); - - const targets = {}; - const serviceEdgesTargetResponse = await openSearchClient.search({ - index: mode === 'jaeger' ? JAEGER_SERVICE_INDEX_NAME : DATA_PREPPER_SERVICE_INDEX_NAME, - body: getServiceEdgesQuery('target', mode), - }); - - // @ts-ignore - serviceEdgesTargetResponse.body.aggregations.service_name.buckets.map((bucket: object) => { - // @ts-ignore - bucket.resource.buckets.map((resource: object) => { - // @ts-ignore - resource.domain.buckets.map((domain: object) => { - // @ts-ignore - targets[resource.key + ':' + domain.key] = bucket.key; - }); - }); - }); - - const serviceEdgesDestResponse = await openSearchClient.search({ - index: mode === 'jaeger' ? JAEGER_SERVICE_INDEX_NAME : DATA_PREPPER_SERVICE_INDEX_NAME, - body: getServiceEdgesQuery('destination', mode), - }); - - // @ts-ignore - serviceEdgesDestResponse.body.aggregations.service_name.buckets.map((bucket: object) => { - // @ts-ignore - bucket.resource.buckets.map((resource: object) => { - // @ts-ignore - resource.domain.buckets.map((domain: object) => { - // @ts-ignore - const targetService = targets[resource.key + ':' + domain.key]; - if (targetService) { - // @ts-ignore - if (map[bucket.key].targetServices.indexOf(targetService) === -1) - // @ts-ignore - map[bucket.key].targetServices.push(targetService); - // @ts-ignore - if (map[targetService].destServices.indexOf(bucket.key) === -1) - // @ts-ignore - map[targetService].destServices.push(bucket.key); - } - }); - }); - }); - - return getServiceMetricsQuery(Object.keys(map), map, mode); -}; - -export const getServiceNodesQuery = (mode: TraceAnalyticsMode) => { - return { - size: 0, - query: { - bool: { - must: [], - filter: [], - should: [], - must_not: [], - }, - }, - aggs: { - service_name: { - terms: { - field: 'serviceName', - size: SERVICE_MAP_MAX_NODES, - }, - aggs: { - trace_group: { - terms: { - field: 'traceGroupName', - size: SERVICE_MAP_MAX_EDGES, - }, - aggs: { - target_resource: { - terms: { - field: 'target.resource', - size: SERVICE_MAP_MAX_EDGES, - }, - }, - }, - }, - }, - }, - }, - }; -}; - -export const getServiceEdgesQuery = ( - source: 'destination' | 'target', - mode: TraceAnalyticsMode -) => { - return { - size: 0, - query: { - bool: { - must: [], - filter: [], - should: [], - must_not: [], - }, - }, - aggs: { - service_name: { - terms: { - field: 'serviceName', - size: SERVICE_MAP_MAX_EDGES, - }, - aggs: { - resource: { - terms: { - field: `${source}.resource`, - size: SERVICE_MAP_MAX_EDGES, - }, - aggs: { - domain: { - terms: { - field: `${source}.domain`, - size: SERVICE_MAP_MAX_EDGES, - }, - }, - }, - }, - }, - }, - }, - }; -}; - -export const getServiceMetricsQuery = ( - serviceNames: string[], - map: ServiceObject, - mode: TraceAnalyticsMode -) => { - const targetResource = [].concat( - // @ts-ignore - ...Object.keys(map).map((service) => getServiceMapTargetResources(map, service)) - ); - const jaegerQuery = { - size: 0, - query: { - bool: { - must: [], - should: [], - must_not: [], - filter: [ - { - terms: { - 'process.serviceName': serviceNames, - }, - }, - { - bool: { - should: [ - { - bool: { - filter: [ - { - bool: { - must_not: { - term: { - references: { - value: [], - }, - }, - }, - }, - }, - ], - }, - }, - { - bool: { - must: { - term: { - references: { - value: [], - }, - }, - }, - }, - }, - ], - adjust_pure_negative: true, - boost: 1, - }, - }, - ], - }, - }, - aggregations: { - service_name: { - terms: { - field: 'process.serviceName', - size: SERVICE_MAP_MAX_NODES, - min_doc_count: 1, - shard_min_doc_count: 0, - show_term_doc_count_error: false, - order: [ - { - _count: 'desc', - }, - { - _key: 'asc', - }, - ], - }, - aggregations: { - average_latency_nanos: { - avg: { - field: 'duration', - }, - }, - average_latency: { - bucket_script: { - buckets_path: { - count: '_count', - latency: 'average_latency_nanos.value', - }, - script: 'Math.round(params.latency / 10) / 100.0', - }, - }, - error_count: { - filter: { - term: { - 'tag.error': true, - }, - }, - }, - error_rate: { - bucket_script: { - buckets_path: { - total: '_count', - errors: 'error_count._count', - }, - script: 'params.errors / params.total * 100', - }, - }, - }, - }, - }, - }; - - const dataPrepperQuery = { - size: 0, - query: { - bool: { - must: [], - should: [], - must_not: [], - filter: [ - { - terms: { - serviceName: serviceNames, - }, - }, - { - bool: { - should: [ - { - bool: { - filter: [ - { - bool: { - must_not: { - term: { - parentSpanId: { - value: '', - }, - }, - }, - }, - }, - { - terms: { - name: targetResource, - }, - }, - ], - }, - }, - { - bool: { - must: { - term: { - parentSpanId: { - value: '', - }, - }, - }, - }, - }, - ], - adjust_pure_negative: true, - boost: 1, - }, - }, - ], - }, - }, - aggregations: { - service_name: { - terms: { - field: 'serviceName', - size: SERVICE_MAP_MAX_NODES, - min_doc_count: 1, - shard_min_doc_count: 0, - show_term_doc_count_error: false, - order: [ - { - _count: 'desc', - }, - { - _key: 'asc', - }, - ], - }, - aggregations: { - average_latency_nanos: { - avg: { - field: 'durationInNanos', - }, - }, - average_latency: { - bucket_script: { - buckets_path: { - count: '_count', - latency: 'average_latency_nanos.value', - }, - script: 'Math.round(params.latency / 10000) / 100.0', - }, - }, - error_count: { - filter: { - term: { - 'status.code': '2', - }, - }, - }, - error_rate: { - bucket_script: { - buckets_path: { - total: '_count', - errors: 'error_count._count', - }, - script: 'params.errors / params.total * 100', - }, - }, - }, - }, - }, - }; - return mode === 'jaeger' ? jaegerQuery : dataPrepperQuery; -}; - -export function getServiceMapTargetResources(map: ServiceObject, serviceName: string) { - return ([] as string[]).concat.apply( - [], - [...map[serviceName].traceGroups.map((traceGroup) => [...traceGroup.targetResource])] - ); -} diff --git a/server/olly/tools/tool_sets/traces.ts b/server/olly/tools/tool_sets/traces.ts deleted file mode 100644 index 6bc0f0be..00000000 --- a/server/olly/tools/tool_sets/traces.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { DynamicTool } from 'langchain/tools'; -import { protectCall } from '../../utils/utils'; -import { PluginToolsBase } from '../tools_base'; -import { addFilters, getField } from './trace_tools/filters'; -import { - getDashboardQuery, - getMode, - getServices, - getTracesQuery, - runQuery, -} from './trace_tools/queries'; - -export class TracesTools extends PluginToolsBase { - static TOOL_NAMES = { - TRACE_GROUPS: 'Get trace groups', - SERVICES: 'Get trace services', - TRACES: 'Get traces', - } as const; - - toolsList = [ - new DynamicTool({ - name: TracesTools.TOOL_NAMES.TRACE_GROUPS, - description: - 'Use this to get information about each trace group. The input must be the entire original INPUT with no modification. The first line of the tool response is the column labels, which includes the key, doc_count, average_latency.value, trace_count.value, error_count.doc_count, error_count.trace_count.value, and error_rate.value. The key is the name of the trace group, the doc_count is the number of spans, the average_latency.value is the average latency of the trace group, measured in milliseconds. The trace_count.value is the number of traces in the trace group. The error_count.doc_count is the number of spans in the trace groups with errors, while the error_count.trace_count.value is the number of different traces in the trace group with errors. The error_rate.value is the percentage of traces in the trace group that has at least one error. There may be no trace groups', - func: protectCall(async (userQuery: string) => this.getTraceGroups(userQuery)), - callbacks: this.callbacks, - }), - new DynamicTool({ - name: TracesTools.TOOL_NAMES.TRACES, - description: - 'Use this to get information about each trace. The input must be the entire original INPUT with no modification. The tool response includes the key, doc_count, last_updated.value, last_updated.value_as_string, error_count.doc_count, trace_group.doc_count_error_upper_bound, trace_group.sum_other_doc_count, trace_group.buckets.0.key, and trace_groups.buckets.0.doc_count. The key is the ID of the trace. The doc_count is the number of spans in that particular trace. The last_updated.value_as_string is the last time that the trace was updated. The error_count.doc_count is how many spans in that trace has errors. The trace group.buckets.1.key is what trace group the trace belongs to. The other fields are irrelevant data.', - func: protectCall(async (userQuery: string) => this.getTraces(userQuery)), - callbacks: this.callbacks, - }), - new DynamicTool({ - name: TracesTools.TOOL_NAMES.SERVICES, - description: - 'Use this to get information about each service in trace analytics. The input must be the entire original INPUT with no modification. The tool response includes the key, doc_count, error_count.doc_count, average_latency_nanos.value, average_latency.value, and error_rate.value. The key is the name of the service. The doc_count is the number of spans in the service. The error_count.doc_count is the number of traces with errors in the service. The average_latency.value is the average latency in milliseconds. The error_rate.value is the percentage of traces that had an error.', - func: protectCall(async (userQuery: string) => this.getServices(userQuery)), - callbacks: this.callbacks, - }), - ]; - - // TODO merge LLM requests and make calls parallel if possible - public async getTraceGroups(userQuery: string) { - const keyword = 'trace_group_name'; - const field = await getField(userQuery, keyword, this.model); - const mode = await getMode(this.opensearchClient); - const query = getDashboardQuery(mode); - await addFilters(query, userQuery, this.model); - return await runQuery(this.opensearchClient, query, mode, keyword, field); - } - - public async getTraces(userQuery: string) { - const keyword = 'traces'; - const field = await getField(userQuery, keyword, this.model); - const mode = await getMode(this.opensearchClient); - const query = getTracesQuery(mode); - await addFilters(query, userQuery, this.model); - return await runQuery(this.opensearchClient, query, mode, keyword, field); - } - - public async getServices(userQuery: string) { - const keyword = 'service_name'; - const field = await getField(userQuery, keyword, this.model); - const mode = await getMode(this.opensearchClient); - const query = await getServices(mode, this.opensearchClient); - await addFilters(query, userQuery, this.model); - return await runQuery(this.opensearchClient, query, mode, keyword, field); - } -} diff --git a/server/olly/tools/tools_base.ts b/server/olly/tools/tools_base.ts deleted file mode 100644 index 683ad9e9..00000000 --- a/server/olly/tools/tools_base.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { BaseLanguageModel } from 'langchain/base_language'; -import { Callbacks } from 'langchain/callbacks'; -import { Embeddings } from 'langchain/embeddings/base'; -import { DynamicTool } from 'langchain/tools'; -import { - ILegacyScopedClusterClient, - OpenSearchClient, - SavedObjectsClientContract, -} from '../../../../../src/core/server'; - -export abstract class PluginToolsBase { - public abstract toolsList: DynamicTool[]; - - constructor( - protected model: BaseLanguageModel, - protected embeddings: Embeddings, - protected opensearchClient: OpenSearchClient, - protected observabilityClient: ILegacyScopedClusterClient, - protected savedObjectsClient: SavedObjectsClientContract, - protected callbacks: Callbacks - ) {} -} diff --git a/server/olly/tools/tools_helper.ts b/server/olly/tools/tools_helper.ts deleted file mode 100644 index e14625a0..00000000 --- a/server/olly/tools/tools_helper.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { PluginToolsBase } from './tools_base'; -import { OSAlertingTools } from './tool_sets/aleritng_apis'; -import { KnowledgeTools } from './tool_sets/knowledges'; -import { OSAPITools } from './tool_sets/os_apis'; -import { PPLTools } from './tool_sets/ppl'; -import { SavedObjectsTools } from './tool_sets/saved_objects'; -import { TracesTools } from './tool_sets/traces'; - -export const initTools = ( - // proper way to get parameters possibly needs typescript 4.2 https://github.com/microsoft/TypeScript/issues/35576 - ...args: ConstructorParameters -): PluginToolsBase[] => { - const pplTools = new PPLTools(...args); - const alertingTools = new OSAlertingTools(...args); - const knowledgeTools = new KnowledgeTools(...args); - const opensearchTools = new OSAPITools(...args); - const savedObjectsTools = new SavedObjectsTools(...args); - const tracesTools = new TracesTools(...args); - return [pplTools, alertingTools, knowledgeTools, opensearchTools, savedObjectsTools, tracesTools]; -}; diff --git a/server/olly/utils/__tests__/ppl_generator.test.ts b/server/olly/utils/__tests__/ppl_generator.test.ts deleted file mode 100644 index ee4e8902..00000000 --- a/server/olly/utils/__tests__/ppl_generator.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { ApiResponse } from '@opensearch-project/opensearch/.'; -import { IndicesGetMappingResponse } from '@opensearch-project/opensearch/api/types'; -import { SearchResponse } from 'elasticsearch'; -import { generateFieldContext } from '../ppl_generator'; - -describe('PPL generator utils', () => { - it('handles empty mappings', () => { - const fields = generateFieldContext( - ({ - body: { employee_nested: { mappings: {} } }, - } as unknown) as ApiResponse, - ({ - body: { - took: 0, - timed_out: false, - _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, - hits: { total: { value: 0, relation: 'gte' }, max_score: 1, hits: [] }, - }, - } as unknown) as ApiResponse> - ); - expect(fields).toEqual(''); - }); - - it('generates field context', () => { - const fields = generateFieldContext( - ({ - body: { - employee_nested: { - mappings: { - properties: { - comments: { - properties: { - date: { type: 'date' }, - likes: { type: 'long' }, - message: { - type: 'text', - fields: { keyword: { type: 'keyword', ignore_above: 256 } }, - }, - }, - }, - id: { type: 'long' }, - name: { type: 'keyword' }, - projects: { - properties: { - address: { - properties: { city: { type: 'keyword' }, state: { type: 'keyword' } }, - }, - name: { type: 'keyword' }, - started_year: { type: 'long' }, - }, - }, - title: { type: 'keyword' }, - }, - }, - }, - }, - } as unknown) as ApiResponse, - ({ - body: { - took: 0, - timed_out: false, - _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, - hits: { - total: { value: 10000, relation: 'gte' }, - max_score: 1, - hits: [ - { - _index: 'employee_nested', - _id: '-cIErYkBQjxNwHvKnmIS', - _score: 1, - _source: { - id: 4, - name: 'Susan Smith', - projects: [], - comments: [ - { date: '2018-06-23', message: 'I love New york', likes: 56 }, - { date: '2017-10-25', message: 'Today is good weather', likes: 22 }, - ], - }, - }, - ], - }, - }, - } as unknown) as ApiResponse> - ); - expect(fields).toEqual( - '- comments.date: date (null)\n- comments.likes: long (null)\n- comments.message: text (null)\n- id: long (4)\n- name: keyword ("Susan Smith")\n- projects.address.city: keyword (null)\n- projects.address.state: keyword (null)\n- projects.name: keyword (null)\n- projects.started_year: long (null)\n- title: keyword (null)' - ); - }); -}); diff --git a/server/olly/utils/__tests__/utils.test.ts b/server/olly/utils/__tests__/utils.test.ts deleted file mode 100644 index 4a2e9f21..00000000 --- a/server/olly/utils/__tests__/utils.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { MAX_OUTPUT_CHAR } from '../constants'; -import { flatten, jsonToCsv, protectCall } from '../utils'; - -describe('protect calls', () => { - it('should swallow errors for sync functions', async () => { - const tool = jest.fn().mockImplementation(() => { - throw new Error('failed to run in test'); - }); - const toolNoThrow = protectCall(tool); - const res = await toolNoThrow('input'); - expect(res).toEqual('Error when running tool: Error: failed to run in test'); - expect(toolNoThrow('input')).resolves.not.toThrowError(); - }); - - it('should swallow errors for async functions', async () => { - const tool = jest.fn().mockRejectedValue(new Error('failed to run in test')); - const toolNoThrow = protectCall(tool); - const res = await toolNoThrow('input'); - expect(res).toEqual('Error when running tool: Error: failed to run in test'); - expect(toolNoThrow('input')).resolves.not.toThrowError(); - }); - - it('should truncate text if output is too long', async () => { - const tool = jest.fn().mockResolvedValue('failed to run in test'.repeat(1000)); - const truncated = protectCall(tool); - const res = await truncated('input'); - expect(res).toContain('Output is too long, truncated'); - expect(res.length).toEqual(MAX_OUTPUT_CHAR); - }); -}); - -describe('utils', () => { - it('converts json to csv', () => { - const csv = jsonToCsv([ - { key1: 'value1', key2: 'value2', key3: 'value3' }, - { key4: 'value4', key5: 'value5', key6: 'value6' }, - { key7: 'value7', key8: 'value8', key9: 'value9' }, - ]); - expect(csv).toEqual( - 'row_number,key1,key2,key3\n1,value1,value2,value3\n2,value4,value5,value6\n3,value7,value8,value9' - ); - }); - - it('handles empty json', () => { - const csv = jsonToCsv([]); - expect(csv).toEqual('row_number\n'); - }); - - it('flattens nested objects', () => { - const flattened = flatten([ - { - key1: { key2: 'value1' }, - key3: { - key4: 'value2', - key5: { key6: 'value3', key7: [{ key8: 'value4' }, { key9: 'value5' }] }, - }, - }, - { key10: { key11: 'value6' } }, - ]); - expect(flattened).toEqual([ - { - 'key1.key2': 'value1', - 'key3.key4': 'value2', - 'key3.key5.key6': 'value3', - 'key3.key5.key7.0.key8': 'value4', - 'key3.key5.key7.1.key9': 'value5', - }, - { - 'key10.key11': 'value6', - }, - ]); - }); -}); diff --git a/server/olly/utils/output_builders/__tests__/build_outputs.test.ts b/server/olly/utils/output_builders/__tests__/build_outputs.test.ts deleted file mode 100644 index 77815a06..00000000 --- a/server/olly/utils/output_builders/__tests__/build_outputs.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { LangchainTrace } from '../../../../../common/utils/llm_chat/traces'; -import { createTrace } from '../../../__tests__/__utils__/test_helpers'; -import { buildOutputs } from '../build_outputs'; - -describe('build outputs', () => { - it('builds outputs', () => { - const traces: LangchainTrace[] = [createTrace(), createTrace({ type: 'tool' })]; - const outputs = buildOutputs( - 'test question', - 'agent response', - 'test-session', - { question1: 'test suggestion 1', question2: 'test suggestion 2' }, - traces - ); - expect(outputs).toEqual([ - { - content: 'agent response', - contentType: 'markdown', - traceId: 'test-session', - suggestedActions: [ - { actionType: 'send_as_input', message: 'test suggestion 1' }, - { actionType: 'send_as_input', message: 'test suggestion 2' }, - ], - toolsUsed: ['trace name'], - type: 'output', - }, - ]); - }); - - it('sanitizes markdown outputs', () => { - const outputs = buildOutputs( - 'test question', - 'normal text image !!!!!!![](http://evil.com/) ![image](http://evil.com/) [good link](https://link)', - 'test-session', - {}, - [] - ); - expect(outputs).toEqual([ - { - content: - 'normal text [](http://evil.com/) [image](http://evil.com/) [good link](https://link)', - contentType: 'markdown', - traceId: 'test-session', - suggestedActions: [], - toolsUsed: [], - type: 'output', - }, - ]); - }); - - it('builds outputs with object type response', () => { - const outputs = buildOutputs( - 'test question', - { output: 'agent response' }, - 'test-session', - {}, - [] - ); - expect(outputs).toEqual([ - { - content: 'agent response', - contentType: 'markdown', - traceId: 'test-session', - suggestedActions: [], - toolsUsed: [], - type: 'output', - }, - ]); - }); -}); diff --git a/server/olly/utils/output_builders/__tests__/ppl.test.ts b/server/olly/utils/output_builders/__tests__/ppl.test.ts deleted file mode 100644 index 1add32f6..00000000 --- a/server/olly/utils/output_builders/__tests__/ppl.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { LangchainTrace } from '../../../../../common/utils/llm_chat/traces'; -import { PPLTools } from '../../../tools/tool_sets/ppl'; -import { createMessage, createTrace } from '../../../__tests__/__utils__/test_helpers'; -import { buildPPLOutputs } from '../ppl'; - -describe('build ppl', () => { - it('builds ppl outputs', () => { - const traces: LangchainTrace[] = [ - createTrace({ - type: 'tool', - name: PPLTools.TOOL_NAMES.QUERY_OPENSEARCH, - output: - 'The PPL query is: source=opensearch_dashboards_sample_data_flights | stats COUNT() AS count by span(timestamp, 1h)\n', - }), - createTrace({ type: 'tool' }), - ]; - const outputs = buildPPLOutputs(traces, [createMessage()], 'input'); - expect(outputs).toEqual([ - createMessage(), - { - content: - 'source=opensearch_dashboards_sample_data_flights | stats COUNT() AS count by span(timestamp, 1h)', - contentType: 'ppl_visualization', - suggestedActions: [ - { - actionType: 'view_ppl_visualization', - message: 'View details', - metadata: { - query: - 'source=opensearch_dashboards_sample_data_flights | stats COUNT() AS count by span(timestamp, 1h)', - question: 'input', - }, - }, - ], - type: 'output', - }, - ]); - }); - - it('ignores non-ppl outputs', () => { - const traces: LangchainTrace[] = [ - createTrace({ - type: 'tool', - name: PPLTools.TOOL_NAMES.QUERY_OPENSEARCH, - output: 'Failed to generate', - }), - ]; - const outputs = buildPPLOutputs(traces, [createMessage()], 'input'); - expect(outputs).toEqual([createMessage()]); - }); -}); diff --git a/server/olly/utils/output_builders/__tests__/saved_objects.test.ts b/server/olly/utils/output_builders/__tests__/saved_objects.test.ts deleted file mode 100644 index 7989a077..00000000 --- a/server/olly/utils/output_builders/__tests__/saved_objects.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { LangchainTrace } from '../../../../../common/utils/llm_chat/traces'; -import { SavedObjectsTools } from '../../../tools/tool_sets/saved_objects'; -import { createTrace } from '../../../__tests__/__utils__/test_helpers'; -import { buildCoreVisualizations } from '../saved_objects'; - -describe('build saved objects', () => { - it('builds visualizations', () => { - const traces: LangchainTrace[] = [ - createTrace({ - type: 'tool', - name: SavedObjectsTools.TOOL_NAMES.FIND_VISUALIZATIONS, - output: - 'row_number,id,title\n' + - '1,id1,[Flights] Total Flights\n' + - '2,id2,[Flights] Controls\n' + - '3,id3,[Flights] Airline Carrier', - }), - ]; - const outputs = buildCoreVisualizations(traces, []); - expect(outputs).toEqual([ - { - content: 'id1', - contentType: 'visualization', - suggestedActions: [{ actionType: 'view_in_dashboards', message: 'View in Visualize' }], - type: 'output', - }, - { - content: 'id2', - contentType: 'visualization', - suggestedActions: [{ actionType: 'view_in_dashboards', message: 'View in Visualize' }], - type: 'output', - }, - { - content: 'id3', - contentType: 'visualization', - suggestedActions: [{ actionType: 'view_in_dashboards', message: 'View in Visualize' }], - type: 'output', - }, - ]); - }); -}); diff --git a/server/olly/utils/output_builders/__tests__/suggestions.test.ts b/server/olly/utils/output_builders/__tests__/suggestions.test.ts deleted file mode 100644 index eac59cc1..00000000 --- a/server/olly/utils/output_builders/__tests__/suggestions.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { createMessage } from '../../../__tests__/__utils__/test_helpers'; -import { buildSuggestions } from '../suggestions'; - -describe('build suggestions', () => { - it('builds suggestion outputs', () => { - const outputs = buildSuggestions( - { question1: 'test suggestion 1', question2: 'test suggestion 2' }, - [createMessage()] - ); - // @ts-expect-error - expect(outputs[0].suggestedActions).toEqual([ - { actionType: 'send_as_input', message: 'test suggestion 1' }, - { actionType: 'send_as_input', message: 'test suggestion 2' }, - ]); - }); - - it('builds empty suggestion outputs', () => { - const outputs = buildSuggestions({ ignored: 'test suggestion 1' }, [createMessage()]); - // @ts-expect-error - expect(outputs[0].suggestedActions).toEqual([]); - }); -}); diff --git a/server/olly/utils/output_builders/build_outputs.ts b/server/olly/utils/output_builders/build_outputs.ts deleted file mode 100644 index fb81cbe9..00000000 --- a/server/olly/utils/output_builders/build_outputs.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import createDOMPurify from 'dompurify'; -import { JSDOM } from 'jsdom'; -import { IMessage } from '../../../../common/types/chat_saved_object_attributes'; -import { LangchainTrace } from '../../../../common/utils/llm_chat/traces'; -import { buildPPLOutputs } from './ppl'; -import { buildCoreVisualizations } from './saved_objects'; -import { buildSuggestions, SuggestedQuestions } from './suggestions'; - -export const buildOutputs = ( - question: string, - agentResponse: AgentResponse, - traceId: string, - suggestions: SuggestedQuestions, - traces: LangchainTrace[] -) => { - const content = extractContent(agentResponse); - let outputs: IMessage[] = [ - { - type: 'output', - traceId, - content, - contentType: 'markdown', - }, - ]; - outputs = buildToolsUsed(traces, outputs); - outputs = buildPPLOutputs(traces, outputs, question); - outputs = buildCoreVisualizations(traces, outputs); - outputs = buildSuggestions(suggestions, outputs); - return sanitize(outputs); -}; - -const extractContent = (agentResponse: AgentResponse) => { - return typeof agentResponse === 'string' ? agentResponse : (agentResponse.output as string); -}; - -const buildToolsUsed = (traces: LangchainTrace[], outputs: IMessage[]) => { - const tools = traces.filter((trace) => trace.type === 'tool').map((tool) => tool.name); - if (outputs[0].type !== 'output') throw new Error('First output message type should be output.'); - outputs[0].toolsUsed = tools; - return outputs; -}; - -const sanitize = (outputs: IMessage[]) => { - const window = new JSDOM('').window; - const DOMPurify = createDOMPurify((window as unknown) as Window); - return outputs.map((output) => ({ - ...output, - ...(output.contentType === 'markdown' && { - content: DOMPurify.sanitize(output.content, { FORBID_TAGS: ['img'] }).replace(/!+\[/g, '['), - }), - })); -}; diff --git a/server/olly/utils/output_builders/ppl.ts b/server/olly/utils/output_builders/ppl.ts deleted file mode 100644 index 5c490dbb..00000000 --- a/server/olly/utils/output_builders/ppl.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { IMessage } from '../../../../common/types/chat_saved_object_attributes'; -import { LangchainTrace } from '../../../../common/utils/llm_chat/traces'; -import { PPLTools } from '../../tools/tool_sets/ppl'; -import { filterToolOutput } from './utils'; - -const extractPPLQueries = (content: string) => { - return Array.from(content.matchAll(/(^|[\n\r]|:)\s*(source\s*=\s*.+)/gi)).map( - (match) => match[2] - ); -}; - -export const buildPPLOutputs = ( - traces: LangchainTrace[], - outputs: IMessage[], - question: string -): IMessage[] => { - const ppls = traces - .filter(filterToolOutput(PPLTools.TOOL_NAMES.QUERY_OPENSEARCH)) - .flatMap((trace) => extractPPLQueries(trace.output)); - if (!ppls.length) return outputs; - - const statsPPLs = ppls.filter((ppl) => /\|\s*stats\s+[^|]+\sby\s/i.test(ppl)); - if (!statsPPLs.length) { - return outputs; - } - - const visOutputs: IMessage[] = statsPPLs.map((query) => ({ - type: 'output', - content: query, - contentType: 'ppl_visualization', - suggestedActions: [ - { - message: 'View details', - actionType: 'view_ppl_visualization', - metadata: { query, question }, - }, - ], - })); - - return outputs.concat(visOutputs); -}; diff --git a/server/olly/utils/output_builders/saved_objects.ts b/server/olly/utils/output_builders/saved_objects.ts deleted file mode 100644 index 18901099..00000000 --- a/server/olly/utils/output_builders/saved_objects.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { IMessage } from '../../../../common/types/chat_saved_object_attributes'; -import { LangchainTrace } from '../../../../common/utils/llm_chat/traces'; -import { SavedObjectsTools } from '../../tools/tool_sets/saved_objects'; -import { filterToolOutput } from './utils'; - -// TODO use a more robust CSV parsing library -const extractNthColumn = (csv: string, column: number) => { - const lines = csv.split(/\r?\n/).slice(1); - return lines - .map((line) => line.split(',').at(column)) - .filter((v: T | null | undefined): v is T => v !== null && v !== undefined); -}; - -export const buildCoreVisualizations = (traces: LangchainTrace[], outputs: IMessage[]) => { - const visualizationIds = traces - .filter(filterToolOutput(SavedObjectsTools.TOOL_NAMES.FIND_VISUALIZATIONS)) - .flatMap((trace) => extractNthColumn(trace.output, 1)); // second column is id field - - const visOutputs: IMessage[] = visualizationIds.map((id) => ({ - type: 'output', - content: id, - contentType: 'visualization', - suggestedActions: [ - { - message: 'View in Visualize', - actionType: 'view_in_dashboards', - }, - ], - })); - - return outputs.concat(visOutputs); -}; diff --git a/server/olly/utils/output_builders/suggestions.ts b/server/olly/utils/output_builders/suggestions.ts deleted file mode 100644 index f4e18e62..00000000 --- a/server/olly/utils/output_builders/suggestions.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { IMessage, ISuggestedAction } from '../../../../common/types/chat_saved_object_attributes'; -import { mergeMessages } from './utils'; - -export type SuggestedQuestions = Record; - -export const buildSuggestions = (suggestions: SuggestedQuestions, outputs: IMessage[]) => { - const suggestedActions: ISuggestedAction[] = []; - - if (suggestions.question1) { - suggestedActions.push({ - message: suggestions.question1, - actionType: 'send_as_input', - }); - } - - if (suggestions.question2) { - suggestedActions.push({ - message: suggestions.question2, - actionType: 'send_as_input', - }); - } - outputs[outputs.length - 1] = mergeMessages(outputs.at(-1)!, { suggestedActions }); - return outputs; -}; diff --git a/server/olly/utils/output_builders/utils.ts b/server/olly/utils/output_builders/utils.ts deleted file mode 100644 index a90687fe..00000000 --- a/server/olly/utils/output_builders/utils.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { mergeWith } from 'lodash'; -import { IMessage } from '../../../../common/types/chat_saved_object_attributes'; -import { LangchainTrace } from '../../../../common/utils/llm_chat/traces'; - -export const filterToolOutput = (toolName: string) => { - return (trace: LangchainTrace): trace is RequiredKey => - trace.type === 'tool' && - trace.name === toolName && - trace.output !== null && - trace.output !== undefined; -}; - -/** - * Merges a list of partial messages into a given IMessage object. - * @returns merged - */ -export const mergeMessages = (message: IMessage, ...messages: Array>) => { - return mergeWith( - message, - ...messages, - (obj: IMessage[keyof IMessage], src: IMessage[keyof IMessage]) => { - if (Array.isArray(obj)) return obj.concat(src); - } - ) as IMessage; -}; diff --git a/server/olly/utils/ppl_generator.ts b/server/olly/utils/ppl_generator.ts deleted file mode 100644 index 3102a843..00000000 --- a/server/olly/utils/ppl_generator.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { ApiResponse } from '@opensearch-project/opensearch/.'; -import { - IndicesGetMappingResponse, - MappingProperty, - SearchResponse, -} from '@opensearch-project/opensearch/api/types'; -import { get } from 'lodash'; - -/** - * @template T = unknown - mapping Context - * @template U = unknown - search Context - * @param mappings - mapping from get mappings request - * @param hits - search response that contains a sample document - * @returns a string that describes fields, types, and sample values - */ -export const generateFieldContext = ( - mappings: ApiResponse, - hits: ApiResponse, U> -) => { - const flattenedFields = flattenMappings(mappings); - const source = hits.body.hits.hits[0]?._source; - - return Object.entries(flattenedFields) - .filter(([, type]) => type !== 'alias') // PPL doesn't support 'alias' type - .map(([field, type]) => { - return `- ${field}: ${type} (${extractValue(source, field, type)})`; - }) - .join('\n'); -}; - -const extractValue = (source: unknown | undefined, field: string, type: string) => { - const value = get(source, field); - if (value === undefined) return null; - if (['text', 'keyword'].includes(type)) return `"${value}"`; - return value; -}; - -/** - * Flatten mappings response to an object of fields and types. - * - * @template T = unknown - Context - * @param mappings - mapping from get mappings request - * @returns an object of fields and types - */ -const flattenMappings = (mappings: ApiResponse) => { - const fields: Record = {}; - Object.values(mappings.body).forEach((body) => - parseProperties(body.mappings.properties, undefined, fields) - ); - return fields; -}; - -const parseProperties = ( - properties: Record | undefined, - prefixes: string[] = [], - fields: Record -) => { - Object.entries(properties || {}).forEach(([key, value]) => { - if (value.properties) { - parseProperties(value.properties, [...prefixes, key], fields); - } else { - fields[[...prefixes, key].join('.')] = value.type!; - } - }); - return fields; -}; diff --git a/server/olly/utils/utils.ts b/server/olly/utils/utils.ts deleted file mode 100644 index c1bbce77..00000000 --- a/server/olly/utils/utils.ts +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { DynamicToolInput } from 'langchain/tools'; -import { MAX_OUTPUT_CHAR } from './constants'; - -/** - * Use to wrap tool funcs to truncate when output is too long and swallow if - * output is an error. - * - * @param func - function for a tool - * @returns a string even when the function throws error - */ -export const protectCall = (func: DynamicToolInput['func']): DynamicToolInput['func'] => { - return async (...args) => { - let response; - try { - response = await func(...args); - } catch (error) { - response = `Error when running tool: ${error}`; - } - return truncate(response); - }; -}; - -export const truncate = (text: string, maxLength: number = MAX_OUTPUT_CHAR) => { - if (text.length <= maxLength) return text; - const tailMessage = '\n\nOutput is too long, truncated...'; - return text.slice(0, MAX_OUTPUT_CHAR - tailMessage.length) + tailMessage; -}; - -export const jsonToCsv = (json: object[]) => { - if (json.length === 0) return 'row_number\n'; - const rows = []; - - // Add header row with keys as column names - const header = Object.keys(json[0]); - rows.push(['row_number', ...header]); - - // Add data rows - json.forEach((obj, index) => { - const values = Object.values(obj); - const row = [index + 1, ...values]; - rows.push(row); - }); - - // Convert rows to CSV string - const csv = rows.map((row) => row.join(',')).join('\n'); - - return csv; -}; - -export const flatten = (response: AggregationBucket[]) => { - // Flattens each bucket in the response - for (const bucket in response) { - if (response.hasOwnProperty(bucket)) { - response[bucket] = flattenObject(response[bucket]); - } - } - return response; -}; - -function flattenObject(object: AggregationBucket, prefix = '') { - const result: Record = {}; - - // Recursively flattens object if it's an object or an array - for (const key in object) { - if (object.hasOwnProperty(key)) { - const combinedKey = prefix ? `${prefix}.${key}` : key; - const value = object[key]; - - if (typeof value === 'object') { - if (Array.isArray(value)) { - for (let i = 0; i < value.length; i++) { - const nestedObject = flattenObject(value[i], `${combinedKey}.${i}`); - Object.assign(result, nestedObject); - } - } else { - const nestedObject = flattenObject(value, combinedKey); - Object.assign(result, nestedObject); - } - } else { - result[combinedKey] = value.toString(); - } - } - } - return result; -} - -export type TraceAnalyticsMode = 'jaeger' | 'data_prepper'; -export interface AggregationBucket { - [key: string]: string | number | AggregationBucket | AggregationBucket[]; -} diff --git a/server/plugin.ts b/server/plugin.ts index 521d855c..e53abc54 100644 --- a/server/plugin.ts +++ b/server/plugin.ts @@ -4,24 +4,16 @@ */ import { first } from 'rxjs/operators'; -import 'web-streams-polyfill'; import { AssistantConfig } from '.'; import { CoreSetup, CoreStart, - ILegacyClusterClient, Logger, Plugin, PluginInitializerContext, } from '../../../src/core/server'; -import { OpenSearchAlertingPlugin } from './adaptors/opensearch_alerting_plugin'; -import { OpenSearchObservabilityPlugin } from './adaptors/opensearch_observability_plugin'; -import { PPLPlugin } from './adaptors/ppl_plugin'; -import './fetch-polyfill'; import { setupRoutes } from './routes/index'; -import { chatSavedObject } from './saved_objects/chat_saved_object'; import { AssistantPluginSetup, AssistantPluginStart, MessageParser } from './types'; -import { chatConfigSavedObject } from './saved_objects/chat_config_saved_object'; import { BasicInputOutputParser } from './parsers/basic_input_output_parser'; import { VisualizationCardParser } from './parsers/visualization_card_parser'; @@ -40,18 +32,11 @@ export class AssistantPlugin implements Plugin { + core.http.registerRouteHandlerContext('assistant_plugin', () => { return { config, logger: this.logger, - observabilityClient: openSearchObservabilityClient, }; }); @@ -60,9 +45,6 @@ export class AssistantPlugin implements Plugin ({ observability: { show: true, diff --git a/server/routes/feedback_routes.ts b/server/routes/feedback_routes.ts new file mode 100644 index 00000000..f7901052 --- /dev/null +++ b/server/routes/feedback_routes.ts @@ -0,0 +1,59 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { schema } from '@osd/config-schema'; +import { + HttpResponsePayload, + IOpenSearchDashboardsResponse, + IRouter, + ResponseError, +} from '../../../../src/core/server'; +import { ASSISTANT_API, LLM_INDEX } from '../../common/constants/llm'; + +export function registerFeedbackRoutes(router: IRouter) { + router.post( + { + path: ASSISTANT_API.FEEDBACK, + validate: { + body: schema.object({ + metadata: schema.object({ + user: schema.string(), + tenant: schema.string(), + type: schema.string(), + sessionId: schema.maybe(schema.string()), + traceId: schema.maybe(schema.string()), + error: schema.maybe(schema.boolean()), + selectedIndex: schema.maybe(schema.string()), + }), + input: schema.string(), + output: schema.string(), + correct: schema.boolean(), + expectedOutput: schema.string(), + comment: schema.string(), + }), + }, + }, + async ( + context, + request, + response + ): Promise> => { + try { + await context.core.opensearch.client.asCurrentUser.index({ + index: LLM_INDEX.FEEDBACK, + body: { ...request.body, timestamp: new Date().toISOString() }, + }); + + return response.ok(); + } catch (error) { + console.error(error); + return response.custom({ + statusCode: error.statusCode || 500, + body: error.message, + }); + } + } + ); +} diff --git a/server/routes/index.ts b/server/routes/index.ts index 093bb313..4ca3b445 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -6,9 +6,9 @@ import { RoutesOptions } from '../types'; import { IRouter } from '../../../../src/core/server'; import { registerChatRoutes } from './chat_routes'; -import { registerLangchainRoutes } from './langchain_routes'; +import { registerFeedbackRoutes } from './feedback_routes'; export function setupRoutes(router: IRouter, routeOptions: RoutesOptions) { registerChatRoutes(router, routeOptions); - registerLangchainRoutes(router); + registerFeedbackRoutes(router); } diff --git a/server/routes/langchain_routes.ts b/server/routes/langchain_routes.ts deleted file mode 100644 index a59e703f..00000000 --- a/server/routes/langchain_routes.ts +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { schema, TypeOf } from '@osd/config-schema'; -import { Run } from 'langchain/callbacks'; -import { LLMChain } from 'langchain/chains'; -import { PromptTemplate } from 'langchain/prompts'; -import { v4 as uuid } from 'uuid'; -import { - HttpResponsePayload, - ILegacyScopedClusterClient, - IOpenSearchDashboardsResponse, - IRouter, - ResponseError, -} from '../../../../src/core/server'; -import { ASSISTANT_API, LLM_INDEX } from '../../common/constants/llm'; -import { OpenSearchTracer } from '../olly/callbacks/opensearch_tracer'; -import { requestSummarizationChain } from '../olly/chains/summarization'; -import { LLMModelFactory } from '../olly/models/llm_model_factory'; -import { MLCommonsChatModel } from '../olly/models/mlcommons_chat_model'; -import { OllyChatService } from '../services/chat/olly_chat_service'; - -const pplGenerationRoute = { - path: ASSISTANT_API.PPL_GENERATOR, - validate: { - body: schema.object({ - index: schema.string(), - question: schema.string(), - }), - }, -}; -export type PPLGenerationRequestSchema = TypeOf; - -const summarizationRoute = { - path: ASSISTANT_API.SUMMARIZATION, - validate: { - body: schema.object({ - question: schema.string(), - response: schema.string(), - query: schema.maybe(schema.string()), - isError: schema.boolean(), - index: schema.string(), - }), - }, -}; -export type SummarizationRequestSchema = TypeOf; - -export function registerLangchainRoutes(router: IRouter) { - router.post( - pplGenerationRoute, - async ( - context, - request, - response - ): Promise> => { - const chatService = new OllyChatService(); - try { - const ppl = await chatService.generatePPL(context, request); - return response.ok({ body: ppl }); - } catch (error) { - context.assistant_plugin.logger.warn(error); - return response.custom({ statusCode: error.statusCode || 500, body: error.message }); - } - } - ); - - router.post( - summarizationRoute, - async ( - context, - request, - response - ): Promise> => { - try { - const runs: Run[] = []; - const traceId = uuid(); - const opensearchClient = context.core.opensearch.client.asCurrentUser; - const callbacks = [new OpenSearchTracer(opensearchClient, traceId, runs)]; - const model = LLMModelFactory.createModel({ client: opensearchClient }); - const chainResponse = await requestSummarizationChain( - { client: opensearchClient, model, ...request.body }, - callbacks - ); - return response.ok({ body: chainResponse }); - } catch (error) { - context.assistant_plugin.logger.warn(error); - return response.custom({ statusCode: error.statusCode || 500, body: error.message }); - } - } - ); - - router.post( - { - path: ASSISTANT_API.AGENT_TEST, - validate: { - body: schema.object({ - question: schema.string(), - }), - }, - }, - async ( - context, - request, - response - ): Promise> => { - try { - const { question } = request.body; - const opensearchObservabilityClient: ILegacyScopedClusterClient = context.assistant_plugin.observabilityClient.asScoped( - request - ); - console.log('########### START CHAIN ####################'); - // We can construct an LLMChain from a PromptTemplate and an LLM. - const model = new MLCommonsChatModel({}, context.core.opensearch.client.asCurrentUser); - const prompt = PromptTemplate.fromTemplate( - 'What is a good name for a company that makes {product}?' - ); - const chainA = new LLMChain({ llm: model, prompt }); - - // The result is an object with a `text` property. - const resA = await chainA.call({ product: 'colorful socks' }); - console.log('########### END CHAIN ####################'); - return response.ok({ body: resA }); - } catch (error) { - return response.custom({ - statusCode: error.statusCode || 500, - body: error.message, - }); - } - } - ); - - router.post( - { - path: ASSISTANT_API.FEEDBACK, - validate: { - body: schema.object({ - metadata: schema.object({ - user: schema.string(), - tenant: schema.string(), - type: schema.string(), - sessionId: schema.maybe(schema.string()), - traceId: schema.maybe(schema.string()), - error: schema.maybe(schema.boolean()), - selectedIndex: schema.maybe(schema.string()), - }), - input: schema.string(), - output: schema.string(), - correct: schema.boolean(), - expectedOutput: schema.string(), - comment: schema.string(), - }), - }, - }, - async ( - context, - request, - response - ): Promise> => { - try { - await context.core.opensearch.client.asCurrentUser.index({ - index: LLM_INDEX.FEEDBACK, - body: { ...request.body, timestamp: new Date().toISOString() }, - }); - - return response.ok(); - } catch (error) { - console.error(error); - return response.custom({ - statusCode: error.statusCode || 500, - body: error.message, - }); - } - } - ); -} diff --git a/server/saved_objects/chat_config_saved_object.ts b/server/saved_objects/chat_config_saved_object.ts deleted file mode 100644 index 83eea4a2..00000000 --- a/server/saved_objects/chat_config_saved_object.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { SavedObjectsType } from '../../../../src/core/server'; -import { CHAT_CONFIG_SAVED_OBJECT_TYPE } from '../../common/constants/saved_objects'; - -export const chatConfigSavedObject: SavedObjectsType = { - name: CHAT_CONFIG_SAVED_OBJECT_TYPE, - hidden: false, - namespaceType: 'agnostic', - mappings: { - dynamic: false, - properties: { - terms_accepted: { - type: 'boolean', - }, - }, - }, -}; diff --git a/server/saved_objects/chat_saved_object.ts b/server/saved_objects/chat_saved_object.ts deleted file mode 100644 index 5c5ef83b..00000000 --- a/server/saved_objects/chat_saved_object.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { SavedObjectsType } from '../../../../src/core/server'; -import { CHAT_SAVED_OBJECT } from '../../common/types/chat_saved_object_attributes'; - -export const chatSavedObject: SavedObjectsType = { - name: CHAT_SAVED_OBJECT, - hidden: false, - namespaceType: 'single', - management: { - defaultSearchField: 'title', - importableAndExportable: true, - icon: 'chatLeft', - getTitle(obj) { - return obj.attributes.title; - }, - }, - mappings: { - dynamic: false, - properties: { - title: { - type: 'text', - }, - version: { type: 'integer' }, - }, - }, - migrations: {}, -}; diff --git a/server/services/chat/chat_service.ts b/server/services/chat/chat_service.ts index e1e81b9a..ac15adf6 100644 --- a/server/services/chat/chat_service.ts +++ b/server/services/chat/chat_service.ts @@ -6,7 +6,6 @@ import { OpenSearchDashboardsRequest, RequestHandlerContext } from '../../../../../src/core/server'; import { IMessage, IInput } from '../../../common/types/chat_saved_object_attributes'; import { LLMRequestSchema } from '../../routes/chat_routes'; -import { PPLGenerationRequestSchema } from '../../routes/langchain_routes'; export interface ChatService { requestLLM( @@ -17,8 +16,6 @@ export interface ChatService { messages: IMessage[]; memoryId: string; }>; - generatePPL( - context: RequestHandlerContext, - request: OpenSearchDashboardsRequest - ): Promise; + + abortAgentExecution(sessionId: string): void; } diff --git a/server/services/chat/olly_chat_service.ts b/server/services/chat/olly_chat_service.ts index 93e08f9c..30e8c300 100644 --- a/server/services/chat/olly_chat_service.ts +++ b/server/services/chat/olly_chat_service.ts @@ -3,16 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { v4 as uuid } from 'uuid'; import { ApiResponse } from '@opensearch-project/opensearch'; -import { OpenSearchDashboardsRequest, RequestHandlerContext } from '../../../../../src/core/server'; +import { RequestHandlerContext } from '../../../../../src/core/server'; import { IMessage, IInput } from '../../../common/types/chat_saved_object_attributes'; -import { OpenSearchTracer } from '../../olly/callbacks/opensearch_tracer'; -import { LLMModelFactory } from '../../olly/models/llm_model_factory'; -import { PPLTools } from '../../olly/tools/tool_sets/ppl'; -import { PPLGenerationRequestSchema } from '../../routes/langchain_routes'; import { ChatService } from './chat_service'; -import { ML_COMMONS_BASE_API } from '../../olly/models/constants'; +import { ML_COMMONS_BASE_API } from '../../utils/constants'; const MEMORY_ID_FIELD = 'memory_id'; @@ -95,27 +90,4 @@ export class OllyChatService implements ChatService { OllyChatService.abortControllers.get(sessionId)?.abort(); } } - - generatePPL( - context: RequestHandlerContext, - request: OpenSearchDashboardsRequest - ): Promise { - const { index, question } = request.body; - const observabilityClient = context.assistant_plugin.observabilityClient.asScoped(request); - const opensearchClient = context.core.opensearch.client.asCurrentUser; - const savedObjectsClient = context.core.savedObjects.client; - const traceId = uuid(); - const callbacks = [new OpenSearchTracer(opensearchClient, traceId)]; - const model = LLMModelFactory.createModel({ client: opensearchClient }); - const embeddings = LLMModelFactory.createEmbeddings({ client: opensearchClient }); - const pplTools = new PPLTools( - model, - embeddings, - opensearchClient, - observabilityClient, - savedObjectsClient, - callbacks - ); - return pplTools.generatePPL(question, index); - } } diff --git a/server/services/storage/agent_framework_storage_service.ts b/server/services/storage/agent_framework_storage_service.ts index bb01126d..c2dd0f5e 100644 --- a/server/services/storage/agent_framework_storage_service.ts +++ b/server/services/storage/agent_framework_storage_service.ts @@ -16,7 +16,7 @@ import { GetSessionsSchema } from '../../routes/chat_routes'; import { StorageService } from './storage_service'; import { MessageParser } from '../../types'; import { MessageParserRunner } from '../../utils/message_parser_runner'; -import { ML_COMMONS_BASE_API } from '../../olly/models/constants'; +import { ML_COMMONS_BASE_API } from '../../utils/constants'; export interface SessionOptResponse { success: boolean; diff --git a/server/types.ts b/server/types.ts index 5b692036..948ed5aa 100644 --- a/server/types.ts +++ b/server/types.ts @@ -36,7 +36,6 @@ export interface RoutesOptions { declare module '../../../src/core/server' { interface RequestHandlerContext { assistant_plugin: { - observabilityClient: ILegacyClusterClient; logger: Logger; }; } diff --git a/server/olly/utils/constants.ts b/server/utils/constants.ts similarity index 61% rename from server/olly/utils/constants.ts rename to server/utils/constants.ts index c8ba0561..3b442fb8 100644 --- a/server/olly/utils/constants.ts +++ b/server/utils/constants.ts @@ -3,4 +3,4 @@ * SPDX-License-Identifier: Apache-2.0 */ -export const MAX_OUTPUT_CHAR = 6000; +export const ML_COMMONS_BASE_API = '/_plugins/_ml'; diff --git a/test/jest.config.js b/test/jest.config.js index 3a5ab955..f5a1419f 100644 --- a/test/jest.config.js +++ b/test/jest.config.js @@ -22,12 +22,9 @@ module.exports = { '/public/requests/', '/__utils__/', ], - // https://github.com/jestjs/jest/issues/6229#issuecomment-403539460 - transformIgnorePatterns: ['node_modules/(?!langchain|langsmith)'], moduleNameMapper: { '\\.(css|less|sass|scss)$': '/test/__mocks__/styleMock.js', '\\.(gif|ttf|eot|svg|png)$': '/test/__mocks__/fileMock.js', - '\\@algolia/autocomplete-theme-classic$': '/test/__mocks__/styleMock.js', '^!!raw-loader!.*': 'jest-raw-loader', }, testEnvironment: 'jsdom', diff --git a/test/setup.jest.ts b/test/setup.jest.ts index e5a6fc38..4ef34444 100644 --- a/test/setup.jest.ts +++ b/test/setup.jest.ts @@ -3,11 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -// import '@testing-library/jest-dom/extend-expect'; import { configure } from '@testing-library/react'; import { TextDecoder, TextEncoder } from 'util'; -import 'web-streams-polyfill'; -import '../server/fetch-polyfill'; configure({ testIdAttribute: 'data-test-subj' }); diff --git a/yarn.lock b/yarn.lock index 290e4f09..2d7dbacf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,20 +2,6 @@ # yarn lockfile v1 -"@anthropic-ai/sdk@^0.6.2": - version "0.6.2" - resolved "https://registry.yarnpkg.com/@anthropic-ai/sdk/-/sdk-0.6.2.tgz#4be415e6b1d948df6f8e03af84aedf102ec74b70" - integrity sha512-fB9PUj9RFT+XjkL+E9Ol864ZIJi+1P8WnbHspN3N3/GK2uSzjd0cbVIKTGgf4v3N8MwaQu+UWnU7C4BG/fap/g== - dependencies: - "@types/node" "^18.11.18" - "@types/node-fetch" "^2.6.4" - abort-controller "^3.0.0" - agentkeepalive "^4.2.1" - digest-fetch "^1.3.0" - form-data-encoder "1.7.2" - formdata-node "^4.3.2" - node-fetch "^2.6.7" - "@babel/code-frame@^7.0.0": version "7.22.13" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" @@ -134,24 +120,11 @@ "@types/tough-cookie" "*" parse5 "^7.0.0" -"@types/node-fetch@^2.6.4": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.4.tgz#1bc3a26de814f6bf466b25aeb1473fa1afe6a660" - integrity sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg== - dependencies: - "@types/node" "*" - form-data "^3.0.0" - "@types/node@*": version "20.6.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.6.0.tgz#9d7daa855d33d4efec8aea88cd66db1c2f0ebe16" integrity sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg== -"@types/node@^18.11.18": - version "18.17.15" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.17.15.tgz#31301a273b9ca7d568fe6d1c35ae52e0fb3f8d6a" - integrity sha512-2yrWpBk32tvV/JAd3HNHWuZn/VDN1P+72hWirHnvsvTGSqbANi+kSeuQR9yAHnbvaBvHDsoTdXV0Fe+iRtHLKA== - "@types/prop-types@*": version "15.7.5" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" @@ -173,11 +146,6 @@ "@types/scheduler" "*" csstype "^3.0.2" -"@types/retry@0.12.0": - version "0.12.0" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" - integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== - "@types/scheduler@*": version "0.16.3" resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" @@ -193,11 +161,6 @@ resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.4.tgz#2b38784cd16957d3782e8e2b31c03bc1d13b4d65" integrity sha512-IDaobHimLQhjwsQ/NMwRVfa/yL7L/wriQPMhw1ZJall0KX6E1oxk29XMDeilW5qTIg5aoiqf5Udy8U/51aNoQQ== -"@types/uuid@^9.0.1": - version "9.0.3" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.3.tgz#6cdd939b4316b4f81625de9f06028d848c4a1533" - integrity sha512-taHQQH/3ZyI3zP8M/puluDEIEvtQHVYcC6y3N8ijFtAd28+Ey/G4sg1u2gB01S8MwybLOKAp9/yCMu/uR5l3Ug== - "@types/yargs-parser@*": version "21.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" @@ -215,13 +178,6 @@ abab@^2.0.6: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - acorn-jsx@^5.2.0: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -239,13 +195,6 @@ agent-base@6: dependencies: debug "4" -agentkeepalive@^4.2.1: - version "4.5.0" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" - integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== - dependencies: - humanize-ms "^1.2.1" - ajv@^6.10.0, ajv@^6.10.2: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -299,11 +248,6 @@ ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" - integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== - ansi-styles@^6.0.0, ansi-styles@^6.1.0: version "6.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" @@ -316,11 +260,6 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - astral-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" @@ -341,26 +280,6 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base-64@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb" - integrity sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA== - -base64-js@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -binary-extensions@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - -binary-search@^1.3.5: - version "1.3.6" - resolved "https://registry.yarnpkg.com/binary-search/-/binary-search-1.3.6.tgz#e32426016a0c5092f0f3598836a1c7da3560565c" - integrity sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA== - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -388,11 +307,6 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camelcase@6: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - chalk@5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" @@ -420,11 +334,6 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -charenc@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" - integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== - ci-info@^3.2.0: version "3.8.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" @@ -498,11 +407,6 @@ commander@11.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-11.0.0.tgz#43e19c25dbedc8256203538e8d7e9346877a6f67" integrity sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ== -commander@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" - integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -528,11 +432,6 @@ cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -crypt@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" - integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== - cssstyle@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-3.0.0.tgz#17ca9c87d26eac764bb8cfd00583cff21ce0277a" @@ -568,11 +467,6 @@ debug@4, debug@4.3.4, debug@^4.0.1: dependencies: ms "2.1.2" -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== - decimal.js@^10.4.3: version "10.4.3" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" @@ -588,14 +482,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -digest-fetch@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/digest-fetch/-/digest-fetch-1.3.0.tgz#898e69264d00012a23cf26e8a3e40320143fc661" - integrity sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA== - dependencies: - base-64 "^0.1.0" - md5 "^2.3.0" - doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" @@ -751,16 +637,6 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - -eventemitter3@^4.0.4: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - eventemitter3@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" @@ -781,11 +657,6 @@ execa@7.2.0: signal-exit "^3.0.7" strip-final-newline "^3.0.0" -expr-eval@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/expr-eval/-/expr-eval-2.0.2.tgz#fa6f044a7b0c93fde830954eb9c5b0f7fbc7e201" - integrity sha512-4EMSHGOPSwAfBiibw3ndnP0AvjDWLsMvGOvWEZ2F96IGk0bIVdjQisOHxReSkE13mHcfbuCiXw+G4y0zv6N8Eg== - external-editor@^3.0.3: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" @@ -840,30 +711,11 @@ flat-cache@^2.0.1: rimraf "2.6.3" write "1.0.3" -flat@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" - integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== - flatted@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== -form-data-encoder@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040" - integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== - -form-data@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" - integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - form-data@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" @@ -873,14 +725,6 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -formdata-node@^4.3.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2" - integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ== - dependencies: - node-domexception "1.0.0" - web-streams-polyfill "4.0.0-beta.3" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -966,13 +810,6 @@ human-signals@^4.3.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== -humanize-ms@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" - integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== - dependencies: - ms "^2.0.0" - husky@^8.0.0: version "8.0.3" resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" @@ -1042,16 +879,6 @@ inquirer@^7.0.0: strip-ansi "^6.0.0" through "^2.3.6" -is-any-array@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-any-array/-/is-any-array-2.0.1.tgz#9233242a9c098220290aa2ec28f82ca7fa79899e" - integrity sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ== - -is-buffer@~1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -1116,13 +943,6 @@ jest-util@^29.0.0: graceful-fs "^4.2.9" picomatch "^2.2.3" -js-tiktoken@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/js-tiktoken/-/js-tiktoken-1.0.7.tgz#56933fcd2093e8304060dfde3071bda91812e6f5" - integrity sha512-biba8u/clw7iesNEWLOLwrNGoBP2lA+hTaBLs/D45pJdUPFXyxD6nhcDVtADChghv4GgyAiMKYMiRx7x6h7Biw== - dependencies: - base64-js "^1.5.1" - js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -1136,13 +956,6 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - jsdom@^22.1.0: version "22.1.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-22.1.0.tgz#0fca6d1a37fbeb7f4aac93d1090d782c56b611c8" @@ -1187,55 +1000,6 @@ json5@^2.2.3: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== -jsonpointer@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559" - integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== - -langchain@^0.0.164: - version "0.0.164" - resolved "https://registry.yarnpkg.com/langchain/-/langchain-0.0.164.tgz#3322c02b881fd70a16310183343eca832f181f44" - integrity sha512-XDyWU/wLtzJtux5adiFgW4ztgqGlA5r0+PaMXO+qRj9ZrUFPiDrvrUysZyr+iHG45/WUyBQj+Bo7g9dIP0XK2w== - dependencies: - "@anthropic-ai/sdk" "^0.6.2" - ansi-styles "^5.0.0" - binary-extensions "^2.2.0" - camelcase "6" - decamelize "^1.2.0" - expr-eval "^2.0.2" - flat "^5.0.2" - js-tiktoken "^1.0.7" - js-yaml "^4.1.0" - jsonpointer "^5.0.1" - langchainhub "~0.0.6" - langsmith "~0.0.31" - ml-distance "^4.0.0" - object-hash "^3.0.0" - openai "~4.4.0" - openapi-types "^12.1.3" - p-queue "^6.6.2" - p-retry "4" - uuid "^9.0.0" - yaml "^2.2.1" - zod "^3.22.3" - zod-to-json-schema "^3.20.4" - -langchainhub@~0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/langchainhub/-/langchainhub-0.0.6.tgz#9d2d06e4ce0807b4e8a31e19611f57aef990b54d" - integrity sha512-SW6105T+YP1cTe0yMf//7kyshCgvCTyFBMTgH2H3s9rTAR4e+78DA/BBrUL/Mt4Q5eMWui7iGuAYb3pgGsdQ9w== - -langsmith@~0.0.31: - version "0.0.36" - resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.0.36.tgz#398e07d773c8781adba97a8aed5f59640157fa45" - integrity sha512-hGbp/mMBxH+Tqbx3hP/yN7/ETZc+kA4QlyvyXyQza/sR1xLfmjTPNaVMz2NdwcD5QulMHrSlWxuBTXNF3BSlVg== - dependencies: - "@types/uuid" "^9.0.1" - commander "^10.0.1" - p-queue "^6.6.2" - p-retry "4" - uuid "^9.0.0" - levn@^0.3.0, levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" @@ -1310,15 +1074,6 @@ make-error@1.x: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== -md5@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" - integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== - dependencies: - charenc "0.0.2" - crypt "0.0.2" - is-buffer "~1.1.6" - merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -1373,52 +1128,11 @@ mkdirp@^0.5.1: dependencies: minimist "^1.2.6" -ml-array-mean@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/ml-array-mean/-/ml-array-mean-1.1.6.tgz#d951a700dc8e3a17b3e0a583c2c64abd0c619c56" - integrity sha512-MIdf7Zc8HznwIisyiJGRH9tRigg3Yf4FldW8DxKxpCCv/g5CafTw0RRu51nojVEOXuCQC7DRVVu5c7XXO/5joQ== - dependencies: - ml-array-sum "^1.1.6" - -ml-array-sum@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/ml-array-sum/-/ml-array-sum-1.1.6.tgz#d1d89c20793cd29c37b09d40e85681aa4515a955" - integrity sha512-29mAh2GwH7ZmiRnup4UyibQZB9+ZLyMShvt4cH4eTK+cL2oEMIZFnSyB3SS8MlsTh6q/w/yh48KmqLxmovN4Dw== - dependencies: - is-any-array "^2.0.0" - -ml-distance-euclidean@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ml-distance-euclidean/-/ml-distance-euclidean-2.0.0.tgz#3a668d236649d1b8fec96380b9435c6f42c9a817" - integrity sha512-yC9/2o8QF0A3m/0IXqCTXCzz2pNEzvmcE/9HFKOZGnTjatvBbsn4lWYJkxENkA4Ug2fnYl7PXQxnPi21sgMy/Q== - -ml-distance@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/ml-distance/-/ml-distance-4.0.1.tgz#4741d17a1735888c5388823762271dfe604bd019" - integrity sha512-feZ5ziXs01zhyFUUUeZV5hwc0f5JW0Sh0ckU1koZe/wdVkJdGxcP06KNQuF0WBTj8FttQUzcvQcpcrOp/XrlEw== - dependencies: - ml-array-mean "^1.1.6" - ml-distance-euclidean "^2.0.0" - ml-tree-similarity "^1.0.0" - -ml-tree-similarity@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/ml-tree-similarity/-/ml-tree-similarity-1.0.0.tgz#24705a107e32829e24d945e87219e892159c53f0" - integrity sha512-XJUyYqjSuUQkNQHMscr6tcjldsOoAekxADTplt40QKfwW6nd++1wHWV9AArl0Zvw/TIHgNaZZNvr8QGvE8wLRg== - dependencies: - binary-search "^1.3.5" - num-sort "^2.0.0" - ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.0.0: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - mute-stream@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" @@ -1434,18 +1148,6 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -node-domexception@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" - integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== - -node-fetch@^2.6.7: - version "2.7.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== - dependencies: - whatwg-url "^5.0.0" - npm-run-path@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.1.0.tgz#bc62f7f3f6952d9894bd08944ba011a6ee7b7e00" @@ -1453,21 +1155,11 @@ npm-run-path@^5.1.0: dependencies: path-key "^4.0.0" -num-sort@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/num-sort/-/num-sort-2.1.0.tgz#1cbb37aed071329fdf41151258bc011898577a9b" - integrity sha512-1MQz1Ed8z2yckoBeSfkQHHO9K1yDRxxtotKSJ9yvcTUUxSvfvzEq5GwBrjjHEpMlq/k5gvXdmJ1SbYxWtpNoVg== - nwsapi@^2.2.4: version "2.2.7" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.7.tgz#738e0707d3128cb750dddcfe90e4610482df0f30" integrity sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ== -object-hash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" - integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== - once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -1489,25 +1181,6 @@ onetime@^6.0.0: dependencies: mimic-fn "^4.0.0" -openai@~4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/openai/-/openai-4.4.0.tgz#dbaab326eb044ddec479951b245850c482678031" - integrity sha512-JN0t628Kh95T0IrXl0HdBqnlJg+4Vq0Bnh55tio+dfCnyzHvMLiWyCM9m726MAJD2YkDU4/8RQB6rNbEq9ct2w== - dependencies: - "@types/node" "^18.11.18" - "@types/node-fetch" "^2.6.4" - abort-controller "^3.0.0" - agentkeepalive "^4.2.1" - digest-fetch "^1.3.0" - form-data-encoder "1.7.2" - formdata-node "^4.3.2" - node-fetch "^2.6.7" - -openapi-types@^12.1.3: - version "12.1.3" - resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-12.1.3.tgz#471995eb26c4b97b7bd356aacf7b91b73e777dd3" - integrity sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw== - optionator@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -1525,34 +1198,6 @@ os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== - -p-queue@^6.6.2: - version "6.6.2" - resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" - integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== - dependencies: - eventemitter3 "^4.0.4" - p-timeout "^3.2.0" - -p-retry@4: - version "4.6.2" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" - integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== - dependencies: - "@types/retry" "0.12.0" - retry "^0.13.1" - -p-timeout@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" - integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== - dependencies: - p-finally "^1.0.0" - parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -1676,11 +1321,6 @@ restore-cursor@^4.0.0: onetime "^5.1.0" signal-exit "^3.0.2" -retry@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" - integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== - rfdc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" @@ -1923,11 +1563,6 @@ tr46@^4.1.1: dependencies: punycode "^2.3.0" -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - ts-jest@^29.1.0: version "29.1.1" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.1.tgz#f58fe62c63caf7bfcc5cc6472082f79180f0815b" @@ -1989,11 +1624,6 @@ url-parse@^1.5.3: querystringify "^2.1.1" requires-port "^1.0.0" -uuid@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" - integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== - v8-compile-cache@^2.0.3: version "2.4.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz#cdada8bec61e15865f05d097c5f4fd30e94dc128" @@ -2006,21 +1636,6 @@ w3c-xmlserializer@^4.0.0: dependencies: xml-name-validator "^4.0.0" -web-streams-polyfill@4.0.0-beta.3: - version "4.0.0-beta.3" - resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38" - integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug== - -web-streams-polyfill@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" - integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - webidl-conversions@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" @@ -2046,14 +1661,6 @@ whatwg-url@^12.0.0, whatwg-url@^12.0.1: tr46 "^4.1.1" webidl-conversions "^7.0.0" -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" @@ -2119,22 +1726,7 @@ yaml@2.3.1: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== -yaml@^2.2.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.2.tgz#f522db4313c671a0ca963a75670f1c12ea909144" - integrity sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg== - yargs-parser@^21.0.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -zod-to-json-schema@^3.20.4: - version "3.21.4" - resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.21.4.tgz#de97c5b6d4a25e9d444618486cb55c0c7fb949fd" - integrity sha512-fjUZh4nQ1s6HMccgIeE0VP4QG/YRGPmyjO9sAh890aQKPEk3nqbfUXhMFaC+Dr5KvYBm8BCyvfpZf2jY9aGSsw== - -zod@^3.22.3: - version "3.22.4" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff" - integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==