diff --git a/apps/yggdrasil-discord-client/src/app/discord-bot/controller/message.controller.ts b/apps/yggdrasil-discord-client/src/app/discord-bot/controller/message.controller.ts index 4e94fb6..92458a8 100644 --- a/apps/yggdrasil-discord-client/src/app/discord-bot/controller/message.controller.ts +++ b/apps/yggdrasil-discord-client/src/app/discord-bot/controller/message.controller.ts @@ -7,11 +7,19 @@ import { DataSourceType } from '../interface/data-source-type.enum'; export class MessageController { constructor(private readonly messageService: MessageService) {} + /** + * @deprecated in v1, use `chatbot:discord:llm:ai:chat` instead + */ @EventPattern('chatbot:discord:chat') async handleChatCreatedEvent(data: any) { await this.messageService.createResponse(data); } + @EventPattern('chatbot:discord:llm:ai:chat') + async handleLLMAIChatCreatedEvent(data: any) { + await this.messageService.createResponse(data); + } + @EventPattern('chatbot:discord:chat:url:docs') async handleUrlDocsCreatedEvent(data: any) { if (data.type === DataSourceType.URL) { diff --git a/apps/yggdrasil-discord-client/src/app/discord-bot/discord-bot.module.ts b/apps/yggdrasil-discord-client/src/app/discord-bot/discord-bot.module.ts index 26e3360..ffe3672 100644 --- a/apps/yggdrasil-discord-client/src/app/discord-bot/discord-bot.module.ts +++ b/apps/yggdrasil-discord-client/src/app/discord-bot/discord-bot.module.ts @@ -16,9 +16,15 @@ import { SetupKeywordService } from './services/setup-keyword/setup-keyword.serv import { MessageService } from './services/message/message.service'; import { DiscordClientService } from './services/discord-client/discord-client.service'; import { MessageController } from './controller/message.controller'; +import { LLMAIChatService } from './services/llmai/llmai.chat.service'; @Module({ - providers: [SetupKeywordService, MessageService, DiscordClientService], + providers: [ + SetupKeywordService, + MessageService, + DiscordClientService, + LLMAIChatService, + ], controllers: [MessageController], }) export class DiscordBotModule { diff --git a/apps/yggdrasil-discord-client/src/app/discord-bot/discord-bot.service.ts b/apps/yggdrasil-discord-client/src/app/discord-bot/discord-bot.service.ts index 9524c35..45dff17 100644 --- a/apps/yggdrasil-discord-client/src/app/discord-bot/discord-bot.service.ts +++ b/apps/yggdrasil-discord-client/src/app/discord-bot/discord-bot.service.ts @@ -9,7 +9,18 @@ import { validateYoutubeUrl } from './utils/validate-youtube-url'; import { DataSourceType } from './interface/data-source-type.enum'; import { ClientProxy } from '@nestjs/microservices'; import { DiscordClientService } from './services/discord-client/discord-client.service'; -import { lastValueFrom } from 'rxjs'; +import { + Subject, + catchError, + finalize, + from, + lastValueFrom, + of, + switchMap, + take, + takeUntil, + toArray, +} from 'rxjs'; @Injectable() export class DiscordBotService implements OnApplicationBootstrap { @@ -61,127 +72,176 @@ export class DiscordBotService implements OnApplicationBootstrap { }; // message create listeners - private messageCreateListeners = async ( - message: Message - ): Promise => { - if (message.author.bot) return; - - const isNotNeedToResponse = this.isNotNeedToResponse(message); - if (isNotNeedToResponse) { - this.asgardLogger.log('not prod and not develop channel, so no response'); - return; - } - - const isDisableBotChannel = this.isDisableBotChannel(message); - if (isDisableBotChannel) { - this.asgardLogger.log('this channel is disable bot channel'); - return; - } - - const isNeedToResponse = this.isNeedToResponse(message); - this.asgardLogger.verbose(`isUserCreateMessage: ${isNeedToResponse}`); + private messageCreateListeners = (message: Message) => { + this.messageCreateListenerObservers(message).subscribe({ + error: (e) => { + this.asgardLogger.error(e); + }, + complete: () => { + this.asgardLogger.log(`${message?.author} create listener complete`); + }, + }); + }; - // if message is a reply - this.asgardLogger.log( - `message is a reply: ${message?.reference ? true : false}` + private messageCreateListenerObservers(message: Message) { + const stopSignal$ = new Subject(); + return from([ + this.checkIsBotMessage(message), + this.checkShouldReply(message), + this.checkIsDisableBotChannel(message), + ]).pipe( + take(3), + toArray(), + switchMap((mods) => { + if (mods.some((mod) => mod === true)) { + stopSignal$.next('STOP'); + } + return of(message); + }), + switchMap(async (message) => { + const isNeedToResponse = this.isNeedToResponse(message); + this.asgardLogger.verbose(`isUserCreateMessage: ${isNeedToResponse}`); + + // if message is a reply + this.asgardLogger.log( + `message have reference: ${message?.reference ? true : false}` + ); + if (await this.isReferenceAndNeedToResponse(message)) { + this.asgardLogger.verbose('is a reply message'); + return this.botClient.emit('chatbot:discord:chat', message); + } + + if (!isNeedToResponse) { + return this.asgardLogger.log( + 'not user create message, so no response' + ); + } + + // if message is a url + const messageContent = message.content + .replace(`<@${this.options.discordOptions.discordBotClientId}>`, '') + .trim(); + + //validate youtube url + const isYtRequest = validateYoutubeUrl(messageContent); + if (isYtRequest) { + this.asgardLogger.log(`message is a youtube url: ${isYtRequest}`); + if ( + !this.options.discordOptions?.alphaWhitelistedUserIds?.includes( + message.author.id + ) + ) { + return this.botClient.emit('chatbot:discord:chat:reply', { + text: '🎁 目前只有白名單的人才能使用此功能,請聯絡管理員開通', + message, + }); + } + + return this.botClient.emit( + 'chatbot:discord:chat:url:youtube', + message + ); + } + + if (this.hasPDFAttachment(message)) { + return this.botClient.emit('chatbot:discord:chat:url:docs', { + message, + type: DataSourceType.PDF, + }); + } + + if (this.hasTXTAttachment(message)) { + return this.botClient.emit('chatbot:discord:chat:url:docs', { + message, + type: DataSourceType.TXT, + }); + } + + if (this.hasImageAttachment(message)) { + return this.botClient.emit('chatbot:discord:chat:url:docs', { + message, + type: DataSourceType.TXT, + }); + } + + if (messageContent.startsWith('url')) { + return this.botClient.emit('chatbot:discord:chat:url:docs', { + message, + type: DataSourceType.URL, + }); + } + + if (this.hasAudioWithMP3OrWAVAttachment(message)) { + return this.botClient.emit( + 'chatbot:discord:chat:audio:transcription', + message + ); + } + + if (this.hasAudioAttachment(message)) { + const transcriptionMessage = await lastValueFrom( + this.botClient.send( + 'chatbot:discord:chat:audio:transcription', + message + ) + ); + + if (!this.isPermanentChannel(message)) { + return this.asgardLogger.log( + 'not in permanent channel, so no response' + ); + } + + message.content = transcriptionMessage?.content; + } + + if (this.isEmptyMessage(message)) { + return this.asgardLogger.log('message is empty, so no response'); + } + + return this.botClient.emit('chatbot:discord:llm:ai:chat', message); + }), + takeUntil(stopSignal$), + catchError((e) => { + this.asgardLogger.error(e?.message, e?.stack, e); + return of(e); + }), + finalize(() => { + this.asgardLogger.debug('message create listener finalize'); + }) ); - if (await this.isReferenceAndNeedToResponse(message)) { - this.asgardLogger.verbose('is a reply message'); - this.botClient.emit('chatbot:discord:chat', message); - return; - } - - if (!isNeedToResponse) { - this.asgardLogger.log('not user create message, so no response'); - return; - } + } - // if message is a url - const messageContent = message.content - .replace(`<@${this.options.discordOptions.discordBotClientId}>`, '') - .trim(); - - //validate youtube url - const isYtRequest = validateYoutubeUrl(messageContent); - if (isYtRequest) { - this.asgardLogger.log(`message is a youtube url: ${isYtRequest}`); - if ( - !this.options.discordOptions?.alphaWhitelistedUserIds?.includes( - message.author.id - ) - ) { - this.botClient.emit('chatbot:discord:chat:reply', { - text: '🎁 目前只有白名單的人才能使用此功能,請聯絡管理員開通', - message, - }); - return; + private checkIsDisableBotChannel(message: Message): boolean { + return (() => { + const isDisableBotChannel = this.isDisableBotChannel(message); + if (isDisableBotChannel) { + this.asgardLogger.log('this channel is disable bot channel'); } - this.botClient.emit('chatbot:discord:chat:url:youtube', message); - return; - } - - if (this.hasPDFAttachment(message)) { - this.botClient.emit('chatbot:discord:chat:url:docs', { - message, - type: DataSourceType.PDF, - }); - return; - } - - if (this.hasTXTAttachment(message)) { - this.botClient.emit('chatbot:discord:chat:url:docs', { - message, - type: DataSourceType.TXT, - }); - - return; - } - - if (this.hasImageAttachment(message)) { - this.botClient.emit('chatbot:discord:chat:url:docs', { - message, - type: DataSourceType.TXT, - }); - - return; - } - - if (messageContent.startsWith('url')) { - this.botClient.emit('chatbot:discord:chat:url:docs', { - message, - type: DataSourceType.URL, - }); - - return; - } - - if (this.hasAudioWithMP3OrWAVAttachment(message)) { - this.botClient.emit('chatbot:discord:chat:audio:transcription', message); - return; - } + return isDisableBotChannel; + })(); + } - if (this.hasAudioAttachment(message)) { - const transcriptionMessage = await lastValueFrom( - this.botClient.send('chatbot:discord:chat:audio:transcription', message) - ); + private checkShouldReply(message: Message): boolean { + return (() => { + const isNotNeedToResponse = this.isNotNeedToResponse(message); - if (!this.isPermanentChannel(message)) { - this.asgardLogger.log('not in permanent channel, so no response'); - return; + if (isNotNeedToResponse) { + this.asgardLogger.log( + 'not prod and not develop channel, so no response' + ); } - message.content = transcriptionMessage?.content; - } - - if (this.isEmptyMessage(message)) { - this.asgardLogger.log('message is empty, so no response'); - return; - } + return isNotNeedToResponse; + })(); + } - this.botClient.emit('chatbot:discord:chat', message); - return; - }; + private checkIsBotMessage(message: Message): boolean { + return (() => { + if (message.author.bot) return true; + })(); + } private isNeedToResponse(message: Message) { const atBot = `<@${this.options.discordOptions.discordBotClientId}>`; diff --git a/apps/yggdrasil-discord-client/src/app/discord-bot/services/llmai/llmai.chat.service.spec.ts b/apps/yggdrasil-discord-client/src/app/discord-bot/services/llmai/llmai.chat.service.spec.ts new file mode 100644 index 0000000..b5c31b1 --- /dev/null +++ b/apps/yggdrasil-discord-client/src/app/discord-bot/services/llmai/llmai.chat.service.spec.ts @@ -0,0 +1,42 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { LLMAIChatService } from './llmai.chat.service'; +import { AsgardLoggerSupplement } from '@asgard-hub/nest-winston'; +import { MessageService } from '../message/message.service'; + +describe('LLMAIChatService', () => { + let service: LLMAIChatService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: AsgardLoggerSupplement.LOGGER_HELPER_SERVICE, + useValue: {}, + }, + { + provide: 'LLMAI_PACKAGE', + useValue: { + getService: jest.fn().mockImplementation(() => { + return {}; + }), + }, + }, + { + provide: 'DISCORD_BOT_MODULE_OPTIONS', + useValue: {}, + }, + { + provide: MessageService, + useValue: {}, + }, + LLMAIChatService, + ], + }).compile(); + + service = module.get(LLMAIChatService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/apps/yggdrasil-discord-client/src/app/discord-bot/services/llmai/llmai.chat.service.ts b/apps/yggdrasil-discord-client/src/app/discord-bot/services/llmai/llmai.chat.service.ts new file mode 100644 index 0000000..a4ea909 --- /dev/null +++ b/apps/yggdrasil-discord-client/src/app/discord-bot/services/llmai/llmai.chat.service.ts @@ -0,0 +1,79 @@ +import { Metadata } from '@grpc/grpc-js'; +import { Inject, Injectable } from '@nestjs/common'; +import { ClientGrpc } from '@nestjs/microservices'; +import { Message, TextChannel } from 'discord.js'; +import { ReplaySubject, switchMap, catchError } from 'rxjs'; +import { AsgardLogger, AsgardLoggerSupplement } from '@asgard-hub/nest-winston'; +import { DISCORD_BOT_MODULE_OPTIONS } from '../../constants/discord-bot.constants'; +import { + ChatOptions, + LLMAIService, +} from '../../interface/chatgpt.service.interface'; +import { DiscordBotModuleOptions } from '../../interface/discord-bot-module'; +import { MessageService } from '../message/message.service'; + +@Injectable() +export class LLMAIChatService { + private llmAIService: LLMAIService; + private metadata = new Metadata(); + + @Inject(AsgardLoggerSupplement.LOGGER_HELPER_SERVICE) + private readonly asgardLogger: AsgardLogger; + @Inject(DISCORD_BOT_MODULE_OPTIONS) + private readonly options: DiscordBotModuleOptions; + @Inject('LLMAI_PACKAGE') + private readonly llmaiGrpcClient: ClientGrpc; + @Inject() + private readonly messageService: MessageService; + + async onModuleInit() { + this.llmAIService = + this.llmaiGrpcClient.getService('LLMAIService'); + + this.metadata.set('authorization', this.options.config.rpcApiKey); + this.metadata.set('azure-service', `${this.options.config.isAzureService}`); + } + + async createResponse(message: Message) { + message = await this.messageService.getRefreshedMessage(message); + + // show typing + await (message.channel as TextChannel).sendTyping(); + const request$ = new ReplaySubject(); + request$.next({ + userInput: message.content, + }); + request$.complete(); + + const subscription = this.llmAIService + .chatStream(request$, this.metadata) + .pipe( + switchMap(async (result) => { + const isMessage = result.event === 'message'; + + const response = isMessage + ? JSON.parse(result.data).response + : result.data; + + isMessage && + (await this.messageService.sendMessageReply(response, message)); + + this.asgardLogger.log(`successfully send message: ${response}`); + }), + catchError(async (e) => { + this.asgardLogger.error(e); + await this.messageService.sendMessageReply( + `很遺憾,目前暫時無法服務您。請稍後再試, Message: ${e?.message}`, + message + ); + return; + }) + ) + .subscribe({ + complete: () => { + this.asgardLogger.log(`complete subscription`); + subscription.unsubscribe(); + }, + }); + } +} diff --git a/apps/yggdrasil-discord-client/src/app/discord-bot/services/message/message.service.ts b/apps/yggdrasil-discord-client/src/app/discord-bot/services/message/message.service.ts index d607484..4827b9a 100644 --- a/apps/yggdrasil-discord-client/src/app/discord-bot/services/message/message.service.ts +++ b/apps/yggdrasil-discord-client/src/app/discord-bot/services/message/message.service.ts @@ -8,7 +8,7 @@ import { ThreadChannel, } from 'discord.js'; import { List, Stack } from 'immutable'; -import { ReplaySubject, lastValueFrom, switchMap } from 'rxjs'; +import { ReplaySubject, catchError, lastValueFrom, of, switchMap } from 'rxjs'; import { DISCORD_BOT_MODULE_OPTIONS, DiSCORD_SPLIT_MESSAGE_TARGET, @@ -295,37 +295,46 @@ export class MessageService implements OnModuleInit { async createResponse(message: Message) { message = await this.getRefreshedMessage(message); - try { - // show typing - await (message.channel as TextChannel).sendTyping(); - const request$ = new ReplaySubject(); - request$.next({ - userInput: message.content, + // show typing + await (message.channel as TextChannel).sendTyping(); + const request$ = new ReplaySubject(); + request$.next({ + userInput: message.content, + }); + request$.complete(); + + const subscription = this.llmAIService + .chatStream(request$, this.metadata) + .pipe( + switchMap(async (result) => { + const isMessage = result.event === 'message'; + + const response = isMessage + ? JSON.parse(result.data).response + : result.data; + + isMessage && (await this.sendMessageReply(response, message)); + + this.asgardLogger.log(`successfully send message: ${response}`); + }), + catchError(async (e) => { + this.asgardLogger.error(e); + await this.sendMessageReply( + `很遺憾,目前暫時無法服務您。請稍後再試, Message: ${e?.message}`, + message + ); + return; + }) + ) + .subscribe({ + complete: () => { + this.asgardLogger.log(`complete subscription`); + subscription.unsubscribe(); + }, }); - request$.complete(); - this.llmAIService - .chatStream(request$, this.metadata) - .pipe( - switchMap(async (result) => { - await this.sendMessageReply(result?.data, message); - - this.asgardLogger.log( - `successfully send message: ${result?.data}` - ); - this.asgardLogger.log(`successfully send message`); - }) - ) - .subscribe(); - } catch (e) { - this.asgardLogger.error(e); - await this.sendMessageReply( - `很遺憾,目前暫時無法服務您。請稍後再試, Message: ${e?.message}`, - message - ); - } } - private async getRefreshedMessage(message: Message) { + async getRefreshedMessage(message: Message) { const client = this.discordClientService.dClient; const refreshedChannel: TextChannel = (await client.channels.fetch( message.channelId, diff --git a/libs/nest-langchain/package.json b/libs/nest-langchain/package.json index 4294de9..f29509a 100644 --- a/libs/nest-langchain/package.json +++ b/libs/nest-langchain/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "dependencies": { "tslib": "^2.3.0", - "langchain": "^0.0.129", + "langchain": "^0.0.135", "@nestjs/common": "^10.1.3", "zod": "^3.22.0", "uid": "^2.0.2" diff --git a/package-lock.json b/package-lock.json index 6bde48e..1e181f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "fluent-ffmpeg": "^2.1.2", "form-data": "^4.0.0", "ioredis": "^5.3.2", - "langchain": "^0.0.129", + "langchain": "^0.0.135", "mongoose": "^7.2.3", "nest-winston": "^1.9.2", "nodejieba": "^2.6.0", @@ -3012,6 +3012,7 @@ "version": "0.4.4", "resolved": "https://registry.npmjs.org/@mozilla/readability/-/readability-0.4.4.tgz", "integrity": "sha512-MCgZyANpJ6msfvVMi6+A0UAsvZj//4OHREYUB9f2087uXHVoU+H+SWhuihvb1beKpM323bReQPRio0WNk2+V6g==", + "optional": true, "peer": true, "engines": { "node": ">=14.0.0" @@ -5468,7 +5469,8 @@ "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==" + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "devOptional": true }, "node_modules/abbrev": { "version": "1.1.1", @@ -7402,6 +7404,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==", + "optional": true, "peer": true, "dependencies": { "rrweb-cssom": "^0.6.0" @@ -7434,6 +7437,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==", + "optional": true, "peer": true, "dependencies": { "abab": "^2.0.6", @@ -7478,6 +7482,7 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "optional": true, "peer": true }, "node_modules/decompress-response": { @@ -7753,6 +7758,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "optional": true, "peer": true, "dependencies": { "webidl-conversions": "^7.0.0" @@ -9717,6 +9723,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "optional": true, "peer": true, "dependencies": { "whatwg-encoding": "^2.0.0" @@ -10234,6 +10241,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "optional": true, "peer": true }, "node_modules/is-promise": { @@ -11164,6 +11172,7 @@ "version": "22.1.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz", "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==", + "optional": true, "peer": true, "dependencies": { "abab": "^2.0.6", @@ -11206,6 +11215,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "optional": true, "peer": true, "dependencies": { "entities": "^4.4.0" @@ -11468,9 +11478,9 @@ "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" }, "node_modules/langchain": { - "version": "0.0.129", - "resolved": "https://registry.npmjs.org/langchain/-/langchain-0.0.129.tgz", - "integrity": "sha512-KHJANXuwaq4CJ23HbLfp4c23S75UsI4Mh5yXIZT5bdsivLN4YmMphIudPMf27Y+5gZbiJzjlZFeM116M0RQ52Q==", + "version": "0.0.135", + "resolved": "https://registry.npmjs.org/langchain/-/langchain-0.0.135.tgz", + "integrity": "sha512-H/WdGi6o4BkkD6dU6KlC0U5TAOA22yiuf+kS6AmN16QPSTU25JtcZZK3kYx+0luoJE1iCsxrec09D7Fgy3hEUQ==", "dependencies": { "@anthropic-ai/sdk": "^0.5.7", "ansi-styles": "^5.0.0", @@ -11482,7 +11492,7 @@ "js-tiktoken": "^1.0.7", "js-yaml": "^4.1.0", "jsonpointer": "^5.0.1", - "langsmith": "~0.0.16", + "langsmith": "~0.0.26", "ml-distance": "^4.0.0", "object-hash": "^3.0.0", "openai": "^3.3.0", @@ -11524,6 +11534,8 @@ "@planetscale/database": "^1.8.0", "@qdrant/js-client-rest": "^1.2.0", "@raycast/api": "^1.55.2", + "@smithy/eventstream-codec": "^2.0.5", + "@smithy/util-utf8": "^2.0.0", "@supabase/postgrest-js": "^1.1.1", "@supabase/supabase-js": "^2.10.0", "@tensorflow-models/universal-sentence-encoder": "*", @@ -11531,6 +11543,7 @@ "@tensorflow/tfjs-core": "*", "@tigrisdata/vector": "^1.1.0", "@upstash/redis": "^1.20.6", + "@writerai/writer-sdk": "^0.40.2", "@xata.io/client": "^0.25.1", "@zilliz/milvus2-sdk-node": ">=2.2.7", "apify-client": "^2.7.1", @@ -11548,6 +11561,7 @@ "ignore": "^5.2.0", "ioredis": "^5.3.2", "jsdom": "*", + "langchainhub": "~0.0.3", "mammoth": "*", "mongodb": "^5.2.0", "mysql2": "^3.3.3", @@ -11567,7 +11581,9 @@ "typesense": "^1.5.3", "usearch": "^1.1.1", "vectordb": "^0.1.4", - "weaviate-ts-client": "^1.4.0" + "weaviate-ts-client": "^1.4.0", + "youtube-transcript": "^1.0.6", + "youtubei.js": "^5.8.0" }, "peerDependenciesMeta": { "@aws-crypto/sha256-js": { @@ -11627,6 +11643,9 @@ "@huggingface/inference": { "optional": true }, + "@mozilla/readability": { + "optional": true + }, "@notionhq/client": { "optional": true }, @@ -11645,6 +11664,12 @@ "@raycast/api": { "optional": true }, + "@smithy/eventstream-codec": { + "optional": true + }, + "@smithy/util-utf8": { + "optional": true + }, "@supabase/postgrest-js": { "optional": true }, @@ -11666,6 +11691,9 @@ "@upstash/redis": { "optional": true }, + "@writerai/writer-sdk": { + "optional": true + }, "@xata.io/client": { "optional": true }, @@ -11714,6 +11742,12 @@ "ioredis": { "optional": true }, + "jsdom": { + "optional": true + }, + "langchainhub": { + "optional": true + }, "mammoth": { "optional": true }, @@ -11773,6 +11807,12 @@ }, "weaviate-ts-client": { "optional": true + }, + "youtube-transcript": { + "optional": true + }, + "youtubei.js": { + "optional": true } } }, @@ -11823,9 +11863,9 @@ } }, "node_modules/langsmith": { - "version": "0.0.24", - "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.0.24.tgz", - "integrity": "sha512-wp04/ZPUZThVIJ817i4eqpyi2t/SWYeXBRbSr2I9S49AdSoou27X7ZGjmGyKL7U+bQRA7LrPgmcalhq0PUivWQ==", + "version": "0.0.26", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.0.26.tgz", + "integrity": "sha512-TecBjdgYGMxNaWp2L2X0OVgu8lge2WeQ5UpDXluwF3x+kH/WHFVSuR1RCuP+k2628GSVFvXxVIyXvzrHYxrZSw==", "dependencies": { "@types/uuid": "^9.0.1", "commander": "^10.0.1", @@ -13048,6 +13088,7 @@ "version": "2.2.7", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", + "optional": true, "peer": true }, "node_modules/nx": { @@ -14635,7 +14676,8 @@ "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "devOptional": true }, "node_modules/punycode": { "version": "2.3.0", @@ -14679,6 +14721,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "optional": true, "peer": true }, "node_modules/queue-microtask": { @@ -15002,7 +15045,8 @@ "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "devOptional": true }, "node_modules/requizzle": { "version": "0.2.4", @@ -15120,6 +15164,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", + "optional": true, "peer": true }, "node_modules/run-parallel": { @@ -15262,6 +15307,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "optional": true, "peer": true, "dependencies": { "xmlchars": "^2.2.0" @@ -16154,6 +16200,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "optional": true, "peer": true }, "node_modules/tapable": { @@ -16485,6 +16532,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "optional": true, "peer": true, "dependencies": { "psl": "^1.1.33", @@ -16500,6 +16548,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "optional": true, "peer": true, "engines": { "node": ">= 4.0.0" @@ -16509,6 +16558,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "optional": true, "peer": true, "dependencies": { "punycode": "^2.3.0" @@ -16938,6 +16988,7 @@ "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "optional": true, "peer": true, "dependencies": { "querystringify": "^2.1.1", @@ -17247,6 +17298,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "optional": true, "peer": true, "dependencies": { "xml-name-validator": "^4.0.0" @@ -17564,6 +17616,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "optional": true, "peer": true, "dependencies": { "iconv-lite": "0.6.3" @@ -17576,6 +17629,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -17588,6 +17642,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "optional": true, "peer": true, "engines": { "node": ">=12" @@ -17597,6 +17652,7 @@ "version": "12.0.1", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz", "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", + "optional": true, "peer": true, "dependencies": { "tr46": "^4.1.1", @@ -17734,6 +17790,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "optional": true, "peer": true, "engines": { "node": ">=12" @@ -17743,6 +17800,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "optional": true, "peer": true }, "node_modules/xmlcreate": { diff --git a/package.json b/package.json index 7cc4b48..2493b29 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "fluent-ffmpeg": "^2.1.2", "form-data": "^4.0.0", "ioredis": "^5.3.2", - "langchain": "^0.0.129", + "langchain": "^0.0.135", "mongoose": "^7.2.3", "nest-winston": "^1.9.2", "nodejieba": "^2.6.0",