From a25b1762c11f3b8b459410f725e0eb0026d4928a Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Mon, 20 Nov 2023 14:15:50 +0800 Subject: [PATCH 1/8] feat: add mechannism to register messageParser Signed-off-by: SuZhou-Joe --- server/plugin.ts | 26 +++++++++++-- server/routes/chat_routes.ts | 3 +- server/routes/index.ts | 5 ++- .../storage/saved_objects_storage_service.ts | 6 ++- server/types.ts | 33 +++++++++++++++++ server/utils/message_parser_helper.test.ts | 24 ++++++++++++ server/utils/message_parser_helper.ts | 15 ++++++++ server/utils/message_parser_runner.test.ts | 37 +++++++++++++++++++ server/utils/message_parser_runner.ts | 19 ++++++++++ 9 files changed, 161 insertions(+), 7 deletions(-) create mode 100644 server/utils/message_parser_helper.test.ts create mode 100644 server/utils/message_parser_helper.ts create mode 100644 server/utils/message_parser_runner.test.ts create mode 100644 server/utils/message_parser_runner.ts diff --git a/server/plugin.ts b/server/plugin.ts index 3b3fc35f..47107178 100644 --- a/server/plugin.ts +++ b/server/plugin.ts @@ -18,11 +18,12 @@ 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 } from './types'; +import { AssistantPluginSetup, AssistantPluginStart, MessageParser } from './types'; import { chatConfigSavedObject } from './saved_objects/chat_config_saved_object'; export class AssistantPlugin implements Plugin { private readonly logger: Logger; + private messageParsers: MessageParser[] = []; constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); @@ -46,7 +47,9 @@ export class AssistantPlugin implements Plugin { + const findItem = this.messageParsers.find((item) => item.id === messageParser.id); + if (findItem) { + throw new Error(`There is already a messageParser whose id is ${messageParser.id}`); + } + + this.messageParsers.push(messageParser); + }, + removeMessageParser: (parserId: MessageParser['id']) => { + const findIndex = this.messageParsers.findIndex((item) => item.id === parserId); + if (findIndex < 0) { + this.logger.error(`There is not a messageParser whose id is ${parserId}`); + } + + this.messageParsers.splice(findIndex, 1); + }, + }; } public start(core: CoreStart) { diff --git a/server/routes/chat_routes.ts b/server/routes/chat_routes.ts index 01329e1f..18bb4680 100644 --- a/server/routes/chat_routes.ts +++ b/server/routes/chat_routes.ts @@ -16,6 +16,7 @@ import { OllyChatService } from '../services/chat/olly_chat_service'; import { SavedObjectsStorageService } from '../services/storage/saved_objects_storage_service'; import { IMessage, IInput } from '../../common/types/chat_saved_object_attributes'; import { AgentFrameworkStorageService } from '../services/storage/agent_framework_storage_service'; +import { RoutesOptions } from '../types'; const llmRequestRoute = { path: ASSISTANT_API.SEND_MESSAGE, @@ -104,7 +105,7 @@ const updateSessionRoute = { }, }; -export function registerChatRoutes(router: IRouter) { +export function registerChatRoutes(router: IRouter, routeOptions: RoutesOptions) { const createStorageService = (context: RequestHandlerContext) => new AgentFrameworkStorageService(context.core.opensearch.client.asCurrentUser); const createChatService = () => new OllyChatService(); diff --git a/server/routes/index.ts b/server/routes/index.ts index ae33e1c3..0b2d7eee 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -3,11 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { RoutesOptions } from '../types'; import { IRouter } from '../../../../src/core/server'; import { registerChatRoutes } from './chat_routes'; import { registerLangchainRoutes } from './langchain_routes'; -export function setupRoutes(router: IRouter) { - registerChatRoutes(router); +export function setupRoutes(router: IRouter, options: RoutesOptions) { + registerChatRoutes(router, options); registerLangchainRoutes(router); } diff --git a/server/services/storage/saved_objects_storage_service.ts b/server/services/storage/saved_objects_storage_service.ts index 78fcffb4..f85bba48 100644 --- a/server/services/storage/saved_objects_storage_service.ts +++ b/server/services/storage/saved_objects_storage_service.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { MessageParser } from '../../types'; import { SavedObjectsClientContract } from '../../../../../src/core/server'; import { CHAT_SAVED_OBJECT, @@ -15,7 +16,10 @@ import { GetSessionsSchema } from '../../routes/chat_routes'; import { StorageService } from './storage_service'; export class SavedObjectsStorageService implements StorageService { - constructor(private readonly client: SavedObjectsClientContract) {} + constructor( + private readonly client: SavedObjectsClientContract, + private readonly messageParsers: MessageParser[] + ) {} private convertUpdatedTimeField(updatedAt: string | undefined) { return updatedAt ? new Date(updatedAt).getTime() : undefined; diff --git a/server/types.ts b/server/types.ts index bb72cc4b..3c1ffca5 100644 --- a/server/types.ts +++ b/server/types.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { IMessage } from '../common/types/chat_saved_object_attributes'; import { ILegacyClusterClient, Logger } from '../../../src/core/server'; // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -10,6 +11,38 @@ export interface AssistantPluginSetup {} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface AssistantPluginStart {} +export interface IMessageParserHelper { + addMessage: (message: IMessage) => boolean; +} + +export interface Interaction { + input: string; + response: string; +} + +export interface MessageParser { + /** + * The id of the parser, should be unique among the parsers. + */ + id: string; + /** + * Order declare the order message parser will be execute. + * parser with order 2 will be execute before parser with order 1. + */ + order?: number; + /** + * parserProvider is the callback that will be triggered in each message + */ + parserProvider: ( + interaction: Interaction, + messageParserHelper: IMessageParserHelper + ) => Promise; +} + +export interface RoutesOptions { + messageParsers: MessageParser[]; +} + declare module '../../../src/core/server' { interface RequestHandlerContext { assistant_plugin: { diff --git a/server/utils/message_parser_helper.test.ts b/server/utils/message_parser_helper.test.ts new file mode 100644 index 00000000..7799892a --- /dev/null +++ b/server/utils/message_parser_helper.test.ts @@ -0,0 +1,24 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { MessageParserHelper } from './message_parser_helper'; + +describe('MessageParserHelper', () => { + it('return with correct message', async () => { + const messageParserHelper = new MessageParserHelper(); + messageParserHelper.addMessage({ + type: 'output', + contentType: 'markdown', + content: 'output', + }); + expect(messageParserHelper.messages).toEqual([ + { + type: 'output', + contentType: 'markdown', + content: 'output', + }, + ]); + }); +}); diff --git a/server/utils/message_parser_helper.ts b/server/utils/message_parser_helper.ts new file mode 100644 index 00000000..36ff0963 --- /dev/null +++ b/server/utils/message_parser_helper.ts @@ -0,0 +1,15 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { IMessage } from '../../common/types/chat_saved_object_attributes'; +import { IMessageParserHelper } from '../types'; + +export class MessageParserHelper implements IMessageParserHelper { + public messages: IMessage[] = []; + addMessage(message: IMessage) { + this.messages.push(message); + return true; + } +} diff --git a/server/utils/message_parser_runner.test.ts b/server/utils/message_parser_runner.test.ts new file mode 100644 index 00000000..03ce3a1b --- /dev/null +++ b/server/utils/message_parser_runner.test.ts @@ -0,0 +1,37 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { MessageParserRunner } from './message_parser_runner'; + +describe('MessageParserRunner', () => { + it('run with correct result', async () => { + const messageParserRunner = new MessageParserRunner([ + { + id: 'test', + parserProvider(interaction, messageParserHelper) { + messageParserHelper.addMessage({ + type: 'output', + contentType: 'markdown', + content: interaction.response, + }); + return Promise.resolve(''); + }, + }, + ]); + + expect( + await messageParserRunner.run({ + response: 'output', + input: 'input', + }) + ).toEqual([ + { + type: 'output', + contentType: 'markdown', + content: 'output', + }, + ]); + }); +}); diff --git a/server/utils/message_parser_runner.ts b/server/utils/message_parser_runner.ts new file mode 100644 index 00000000..3545d02d --- /dev/null +++ b/server/utils/message_parser_runner.ts @@ -0,0 +1,19 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { IMessage } from '../../common/types/chat_saved_object_attributes'; +import { Interaction, MessageParser } from '../types'; +import { MessageParserHelper } from './message_parser_helper'; + +export class MessageParserRunner { + constructor(private readonly messageParsers: MessageParser[]) {} + async run(interaction: Interaction): Promise { + const messageParserHelper = new MessageParserHelper(); + for (const messageParser of this.messageParsers) { + await messageParser.parserProvider(interaction, messageParserHelper); + } + return messageParserHelper.messages; + } +} From 17174c9b340dafc7ac00cbcf8406d18d07280f42 Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Mon, 20 Nov 2023 15:41:10 +0800 Subject: [PATCH 2/8] feat: sort when run Signed-off-by: SuZhou-Joe --- server/types.ts | 6 ++- server/utils/message_parser_runner.test.ts | 63 ++++++++++++++++++++++ server/utils/message_parser_runner.ts | 8 ++- 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/server/types.ts b/server/types.ts index 3c1ffca5..f6200284 100644 --- a/server/types.ts +++ b/server/types.ts @@ -26,8 +26,10 @@ export interface MessageParser { */ id: string; /** - * Order declare the order message parser will be execute. - * parser with order 2 will be execute before parser with order 1. + * Order field declares the order message parser will be execute. + * parser with order 2 will be executed after parser with order 1. + * If not specified, the default order will be 999. + * @default 999 */ order?: number; /** diff --git a/server/utils/message_parser_runner.test.ts b/server/utils/message_parser_runner.test.ts index 03ce3a1b..6c0bde65 100644 --- a/server/utils/message_parser_runner.test.ts +++ b/server/utils/message_parser_runner.test.ts @@ -34,4 +34,67 @@ describe('MessageParserRunner', () => { }, ]); }); + + it('run with correct result when different order is present', async () => { + const messageParserRunner = new MessageParserRunner([ + { + id: 'testA', + order: 2, + parserProvider(interaction, messageParserHelper) { + messageParserHelper.addMessage({ + type: 'output', + contentType: 'markdown', + content: 'A', + }); + return Promise.resolve(''); + }, + }, + { + id: 'testNoOrder', + parserProvider(interaction, messageParserHelper) { + messageParserHelper.addMessage({ + type: 'output', + contentType: 'markdown', + content: 'NoOrder', + }); + return Promise.resolve(''); + }, + }, + { + id: 'testB', + order: 1, + parserProvider(interaction, messageParserHelper) { + messageParserHelper.addMessage({ + type: 'output', + contentType: 'markdown', + content: 'B', + }); + return Promise.resolve(''); + }, + }, + ]); + + expect( + await messageParserRunner.run({ + response: 'output', + input: 'input', + }) + ).toEqual([ + { + type: 'output', + contentType: 'markdown', + content: 'B', + }, + { + type: 'output', + contentType: 'markdown', + content: 'A', + }, + { + type: 'output', + contentType: 'markdown', + content: 'NoOrder', + }, + ]); + }); }); diff --git a/server/utils/message_parser_runner.ts b/server/utils/message_parser_runner.ts index 3545d02d..b091c6de 100644 --- a/server/utils/message_parser_runner.ts +++ b/server/utils/message_parser_runner.ts @@ -11,7 +11,13 @@ export class MessageParserRunner { constructor(private readonly messageParsers: MessageParser[]) {} async run(interaction: Interaction): Promise { const messageParserHelper = new MessageParserHelper(); - for (const messageParser of this.messageParsers) { + const sortedParsers = [...this.messageParsers]; + sortedParsers.sort((parserA, parserB) => { + const { order: orderA = 999 } = parserA; + const { order: orderB = 999 } = parserB; + return orderA - orderB; + }); + for (const messageParser of sortedParsers) { await messageParser.parserProvider(interaction, messageParserHelper); } return messageParserHelper.messages; From 8fd7c0591686b06f861e3c340210e3ef34146aee Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Mon, 20 Nov 2023 15:42:08 +0800 Subject: [PATCH 3/8] feat: sort when run Signed-off-by: SuZhou-Joe --- server/utils/message_parser_runner.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/server/utils/message_parser_runner.test.ts b/server/utils/message_parser_runner.test.ts index 6c0bde65..a86353e0 100644 --- a/server/utils/message_parser_runner.test.ts +++ b/server/utils/message_parser_runner.test.ts @@ -49,6 +49,18 @@ describe('MessageParserRunner', () => { return Promise.resolve(''); }, }, + { + id: 'testOrder1000', + order: 1000, + parserProvider(interaction, messageParserHelper) { + messageParserHelper.addMessage({ + type: 'output', + contentType: 'markdown', + content: 'order1000', + }); + return Promise.resolve(''); + }, + }, { id: 'testNoOrder', parserProvider(interaction, messageParserHelper) { @@ -95,6 +107,11 @@ describe('MessageParserRunner', () => { contentType: 'markdown', content: 'NoOrder', }, + { + type: 'output', + contentType: 'markdown', + content: 'order1000', + }, ]); }); }); From aac7903d2c8813e703860c686558c090e4848cbb Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Mon, 20 Nov 2023 17:30:19 +0800 Subject: [PATCH 4/8] feat: add doc change Signed-off-by: SuZhou-Joe --- CHANGELOG.md | 7 +++++++ server/README.md | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 server/README.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..3577f8af --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# CHANGELOG + +Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) + +### 📈 Features/Enhancements + +- Add support for registerMessageParser ([#5](https://github.com/opensearch-project/dashboards-assistant/pull/5)) \ No newline at end of file diff --git a/server/README.md b/server/README.md new file mode 100644 index 00000000..619bb070 --- /dev/null +++ b/server/README.md @@ -0,0 +1,22 @@ +# `registerMessageParser` — Register your customized parser logic into Chatbot. + +**Interaction** refers to a question-answer pair in Chatbot application. In most cases, an interaction consists of two messages: an `Input` message and an `Output` message. However, as the Chatbot evolves to become more powerful, it may display new messages such as visualizations, data explorers, or data grids. Therefore, it is crucial to implement a mechanism that allows other plugins to register their customized parser logic based on each interaction body. + + +## API + +### registerMessageParser + +``` +dashboardAssistant.registerMessageParser({ + id: "foo_parser", + parserProvider: async (interaction, messageParserHelper) => { + if (interaction.additional_info?.visualizationId) { + messageParserHelper.addMessage({ + contentType: "visualization", + content: interaction.additional_info.visualizationId + }) + } + } +}) +``` From 02523c312b791f838e21011fc88d6615e8236bda Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Mon, 20 Nov 2023 17:32:24 +0800 Subject: [PATCH 5/8] feat: add diagram Signed-off-by: SuZhou-Joe --- server/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/README.md b/server/README.md index 619bb070..4008477a 100644 --- a/server/README.md +++ b/server/README.md @@ -2,6 +2,8 @@ **Interaction** refers to a question-answer pair in Chatbot application. In most cases, an interaction consists of two messages: an `Input` message and an `Output` message. However, as the Chatbot evolves to become more powerful, it may display new messages such as visualizations, data explorers, or data grids. Therefore, it is crucial to implement a mechanism that allows other plugins to register their customized parser logic based on each interaction body. +![message parser](https://github.com/opensearch-project/dashboards-assistant/assets/13493605/b4ec1ff8-5339-4119-ad20-b2c31057bb0b) + ## API From 8b48a7c2e12d27fd1754514e8ed883cfde57ffc3 Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Wed, 22 Nov 2023 13:26:02 +0800 Subject: [PATCH 6/8] feat: optimize code Signed-off-by: SuZhou-Joe --- server/README.md | 25 ++++--- .../storage/saved_objects_storage_service.ts | 6 +- server/types.ts | 5 +- server/utils/message_parser_helper.test.ts | 24 ------ server/utils/message_parser_helper.ts | 15 ---- server/utils/message_parser_runner.test.ts | 75 ++++++++++--------- server/utils/message_parser_runner.ts | 13 +++- 7 files changed, 65 insertions(+), 98 deletions(-) delete mode 100644 server/utils/message_parser_helper.test.ts delete mode 100644 server/utils/message_parser_helper.ts diff --git a/server/README.md b/server/README.md index 4008477a..b7905c80 100644 --- a/server/README.md +++ b/server/README.md @@ -4,21 +4,24 @@ ![message parser](https://github.com/opensearch-project/dashboards-assistant/assets/13493605/b4ec1ff8-5339-4119-ad20-b2c31057bb0b) - ## API ### registerMessageParser -``` +```typescript dashboardAssistant.registerMessageParser({ - id: "foo_parser", - parserProvider: async (interaction, messageParserHelper) => { - if (interaction.additional_info?.visualizationId) { - messageParserHelper.addMessage({ - contentType: "visualization", - content: interaction.additional_info.visualizationId - }) - } + id: 'foo_parser', + parserProvider: async (interaction) => { + if (interaction.input) { + return [ + { + type: 'input', + contentType: 'text', + content: interaction.input, + }, + ]; } -}) + return []; + }, +}); ``` diff --git a/server/services/storage/saved_objects_storage_service.ts b/server/services/storage/saved_objects_storage_service.ts index f85bba48..78fcffb4 100644 --- a/server/services/storage/saved_objects_storage_service.ts +++ b/server/services/storage/saved_objects_storage_service.ts @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { MessageParser } from '../../types'; import { SavedObjectsClientContract } from '../../../../../src/core/server'; import { CHAT_SAVED_OBJECT, @@ -16,10 +15,7 @@ import { GetSessionsSchema } from '../../routes/chat_routes'; import { StorageService } from './storage_service'; export class SavedObjectsStorageService implements StorageService { - constructor( - private readonly client: SavedObjectsClientContract, - private readonly messageParsers: MessageParser[] - ) {} + constructor(private readonly client: SavedObjectsClientContract) {} private convertUpdatedTimeField(updatedAt: string | undefined) { return updatedAt ? new Date(updatedAt).getTime() : undefined; diff --git a/server/types.ts b/server/types.ts index f6200284..dbcda88d 100644 --- a/server/types.ts +++ b/server/types.ts @@ -35,10 +35,7 @@ export interface MessageParser { /** * parserProvider is the callback that will be triggered in each message */ - parserProvider: ( - interaction: Interaction, - messageParserHelper: IMessageParserHelper - ) => Promise; + parserProvider: (interaction: Interaction) => Promise; } export interface RoutesOptions { diff --git a/server/utils/message_parser_helper.test.ts b/server/utils/message_parser_helper.test.ts deleted file mode 100644 index 7799892a..00000000 --- a/server/utils/message_parser_helper.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { MessageParserHelper } from './message_parser_helper'; - -describe('MessageParserHelper', () => { - it('return with correct message', async () => { - const messageParserHelper = new MessageParserHelper(); - messageParserHelper.addMessage({ - type: 'output', - contentType: 'markdown', - content: 'output', - }); - expect(messageParserHelper.messages).toEqual([ - { - type: 'output', - contentType: 'markdown', - content: 'output', - }, - ]); - }); -}); diff --git a/server/utils/message_parser_helper.ts b/server/utils/message_parser_helper.ts deleted file mode 100644 index 36ff0963..00000000 --- a/server/utils/message_parser_helper.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { IMessage } from '../../common/types/chat_saved_object_attributes'; -import { IMessageParserHelper } from '../types'; - -export class MessageParserHelper implements IMessageParserHelper { - public messages: IMessage[] = []; - addMessage(message: IMessage) { - this.messages.push(message); - return true; - } -} diff --git a/server/utils/message_parser_runner.test.ts b/server/utils/message_parser_runner.test.ts index a86353e0..97238ecf 100644 --- a/server/utils/message_parser_runner.test.ts +++ b/server/utils/message_parser_runner.test.ts @@ -10,13 +10,14 @@ describe('MessageParserRunner', () => { const messageParserRunner = new MessageParserRunner([ { id: 'test', - parserProvider(interaction, messageParserHelper) { - messageParserHelper.addMessage({ - type: 'output', - contentType: 'markdown', - content: interaction.response, - }); - return Promise.resolve(''); + parserProvider(interaction) { + return Promise.resolve([ + { + type: 'output', + contentType: 'markdown', + content: interaction.response, + }, + ]); }, }, ]); @@ -40,48 +41,52 @@ describe('MessageParserRunner', () => { { id: 'testA', order: 2, - parserProvider(interaction, messageParserHelper) { - messageParserHelper.addMessage({ - type: 'output', - contentType: 'markdown', - content: 'A', - }); - return Promise.resolve(''); + parserProvider() { + return Promise.resolve([ + { + type: 'output', + contentType: 'markdown', + content: 'A', + }, + ]); }, }, { id: 'testOrder1000', order: 1000, - parserProvider(interaction, messageParserHelper) { - messageParserHelper.addMessage({ - type: 'output', - contentType: 'markdown', - content: 'order1000', - }); - return Promise.resolve(''); + parserProvider() { + return Promise.resolve([ + { + type: 'output', + contentType: 'markdown', + content: 'order1000', + }, + ]); }, }, { id: 'testNoOrder', - parserProvider(interaction, messageParserHelper) { - messageParserHelper.addMessage({ - type: 'output', - contentType: 'markdown', - content: 'NoOrder', - }); - return Promise.resolve(''); + parserProvider(interaction) { + return Promise.resolve([ + { + type: 'output', + contentType: 'markdown', + content: 'NoOrder', + }, + ]); }, }, { id: 'testB', order: 1, - parserProvider(interaction, messageParserHelper) { - messageParserHelper.addMessage({ - type: 'output', - contentType: 'markdown', - content: 'B', - }); - return Promise.resolve(''); + parserProvider() { + return Promise.resolve([ + { + type: 'output', + contentType: 'markdown', + content: 'B', + }, + ]); }, }, ]); diff --git a/server/utils/message_parser_runner.ts b/server/utils/message_parser_runner.ts index b091c6de..97972d17 100644 --- a/server/utils/message_parser_runner.ts +++ b/server/utils/message_parser_runner.ts @@ -5,21 +5,26 @@ import { IMessage } from '../../common/types/chat_saved_object_attributes'; import { Interaction, MessageParser } from '../types'; -import { MessageParserHelper } from './message_parser_helper'; export class MessageParserRunner { constructor(private readonly messageParsers: MessageParser[]) {} async run(interaction: Interaction): Promise { - const messageParserHelper = new MessageParserHelper(); const sortedParsers = [...this.messageParsers]; sortedParsers.sort((parserA, parserB) => { const { order: orderA = 999 } = parserA; const { order: orderB = 999 } = parserB; return orderA - orderB; }); + let results: IMessage[] = []; for (const messageParser of sortedParsers) { - await messageParser.parserProvider(interaction, messageParserHelper); + let tempResult: IMessage[] = []; + try { + tempResult = await messageParser.parserProvider(interaction); + } catch (e) { + tempResult = []; + } + results = [...results, ...tempResult]; } - return messageParserHelper.messages; + return results; } } From 8331a95d5573c3b03dbae77a8632a57e9c3e9b86 Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Wed, 22 Nov 2023 13:32:05 +0800 Subject: [PATCH 7/8] feat: add error handling Signed-off-by: SuZhou-Joe --- server/utils/message_parser_runner.test.ts | 28 ++++++++++++++++++++++ server/utils/message_parser_runner.ts | 6 +++++ 2 files changed, 34 insertions(+) diff --git a/server/utils/message_parser_runner.test.ts b/server/utils/message_parser_runner.test.ts index 97238ecf..e931f47a 100644 --- a/server/utils/message_parser_runner.test.ts +++ b/server/utils/message_parser_runner.test.ts @@ -119,4 +119,32 @@ describe('MessageParserRunner', () => { }, ]); }); + + it('Do not append messages that are throwed with error or not an array', async () => { + const messageParserRunner = new MessageParserRunner([ + { + id: 'test_with_error', + parserProvider() { + throw new Error('error'); + }, + }, + { + id: 'test_with_incorrect_format_of_return', + parserProvider() { + return Promise.resolve({ + type: 'output', + contentType: 'markdown', + content: 'order1000', + }); + }, + }, + ]); + + expect( + await messageParserRunner.run({ + response: 'output', + input: 'input', + }) + ).toEqual([]); + }); }); diff --git a/server/utils/message_parser_runner.ts b/server/utils/message_parser_runner.ts index 97972d17..2f5d7d59 100644 --- a/server/utils/message_parser_runner.ts +++ b/server/utils/message_parser_runner.ts @@ -20,6 +20,12 @@ export class MessageParserRunner { let tempResult: IMessage[] = []; try { tempResult = await messageParser.parserProvider(interaction); + /** + * Make sure the tempResult is an array. + */ + if (!Array.isArray(tempResult)) { + tempResult = []; + } } catch (e) { tempResult = []; } From d598bff4e1c0bf616c6214bb54c954749f272c6a Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Wed, 22 Nov 2023 15:34:26 +0800 Subject: [PATCH 8/8] feat: optimize Signed-off-by: SuZhou-Joe --- public/tabs/chat/chat_page.tsx | 2 +- server/plugin.ts | 4 +--- server/routes/chat_routes.ts | 11 +++++++---- server/routes/index.ts | 4 ++-- server/types.ts | 8 ++++---- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/public/tabs/chat/chat_page.tsx b/public/tabs/chat/chat_page.tsx index 6ca8893f..f8101d2b 100644 --- a/public/tabs/chat/chat_page.tsx +++ b/public/tabs/chat/chat_page.tsx @@ -6,11 +6,11 @@ import { EuiFlyoutBody, EuiFlyoutFooter, EuiPage, EuiPageBody, EuiSpacer } from '@elastic/eui'; import React, { useCallback, useState } from 'react'; import cs from 'classnames'; +import { useObservable } from 'react-use'; import { useChatContext } from '../../contexts/chat_context'; import { useChatState } from '../../hooks/use_chat_state'; import { ChatPageContent } from './chat_page_content'; import { ChatInputControls } from './controls/chat_input_controls'; -import { useObservable } from 'react-use'; import { useCore } from '../../contexts/core_context'; interface ChatPageProps { diff --git a/server/plugin.ts b/server/plugin.ts index 47107178..7559d9af 100644 --- a/server/plugin.ts +++ b/server/plugin.ts @@ -47,9 +47,7 @@ export class AssistantPlugin implements Plugin new AgentFrameworkStorageService(context.core.opensearch.client.asCurrentUser); const createChatService = () => new OllyChatService(); @@ -134,7 +134,7 @@ export function registerChatRoutes(router: IRouter, routeOptions: RoutesOptions) body: { messages: finalMessage.messages, sessionId: outputs.memoryId, - title: finalMessage.title + title: finalMessage.title, }, }); } catch (error) { @@ -272,13 +272,16 @@ export function registerChatRoutes(router: IRouter, routeOptions: RoutesOptions) const outputs = await chatService.requestLLM( { messages, input, sessionId }, context, - request as any + // @ts-ignore + request ); const title = input.content.substring(0, 50); const saveMessagesResponse = await storageService.saveMessages( title, sessionId, - [...messages, input, ...outputs.messages].filter((message) => message.content !== 'AbortError') + [...messages, input, ...outputs.messages].filter( + (message) => message.content !== 'AbortError' + ) ); return response.ok({ body: { ...saveMessagesResponse, title }, diff --git a/server/routes/index.ts b/server/routes/index.ts index 0b2d7eee..52426cf2 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -8,7 +8,7 @@ import { IRouter } from '../../../../src/core/server'; import { registerChatRoutes } from './chat_routes'; import { registerLangchainRoutes } from './langchain_routes'; -export function setupRoutes(router: IRouter, options: RoutesOptions) { - registerChatRoutes(router, options); +export function setupRoutes(router: IRouter) { + registerChatRoutes(router); registerLangchainRoutes(router); } diff --git a/server/types.ts b/server/types.ts index dbcda88d..625f51ba 100644 --- a/server/types.ts +++ b/server/types.ts @@ -11,13 +11,13 @@ export interface AssistantPluginSetup {} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface AssistantPluginStart {} -export interface IMessageParserHelper { - addMessage: (message: IMessage) => boolean; -} - export interface Interaction { input: string; response: string; + conversation_id: string; + interaction_id: string; + create_time: string; + additional_info: Record; } export interface MessageParser {