Просмотр исходного кода

Merge pull request #9685 from weseek/feat/162314-enable-chat-with-additional-instruction

feat: Eable chat with additional instruction
Shun Miyazawa 1 год назад
Родитель
Сommit
85d0f172cf

+ 5 - 4
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantChatSidebar/AiAssistantChatSidebar.tsx

@@ -73,12 +73,13 @@ const AiAssistantChatSidebarSubstance: React.FC<AiAssistantChatSidebarSubstanceP
   });
   });
 
 
   useEffect(() => {
   useEffect(() => {
-    const getMessageData = async() => {
+    const fetchAndSetMessageData = async() => {
       const messageData = await mutateMessageData();
       const messageData = await mutateMessageData();
       if (messageData != null) {
       if (messageData != null) {
-        const reversedMessageData = messageData.data.slice().reverse();
+        const normalizedMessageData = messageData.data.filter(message => message.metadata?.shouldHideMessage !== 'true');
+
         setMessageLogs(() => {
         setMessageLogs(() => {
-          return reversedMessageData.map((message, index) => (
+          return normalizedMessageData.map((message, index) => (
             {
             {
               id: index.toString(),
               id: index.toString(),
               content: message.content[0].type === 'text' ? message.content[0].text.value : '',
               content: message.content[0].type === 'text' ? message.content[0].text.value : '',
@@ -90,7 +91,7 @@ const AiAssistantChatSidebarSubstance: React.FC<AiAssistantChatSidebarSubstanceP
     };
     };
 
 
     if (threadData != null) {
     if (threadData != null) {
-      getMessageData();
+      fetchAndSetMessageData();
     }
     }
   }, [mutateMessageData, threadData]);
   }, [mutateMessageData, threadData]);
 
 

+ 3 - 2
apps/app/src/features/openai/client/stores/message.tsx

@@ -1,9 +1,10 @@
-import type OpenAI from 'openai';
 import useSWRMutation, { type SWRMutationResponse } from 'swr/mutation';
 import useSWRMutation, { type SWRMutationResponse } from 'swr/mutation';
 
 
 import { apiv3Get } from '~/client/util/apiv3-client';
 import { apiv3Get } from '~/client/util/apiv3-client';
 
 
-export const useSWRMUTxMessages = (aiAssistantId: string, threadId?: string): SWRMutationResponse<OpenAI.Beta.Threads.Messages.MessagesPage | null> => {
+import type { MessageWithCustomMetaData } from '../../interfaces/message';
+
+export const useSWRMUTxMessages = (aiAssistantId: string, threadId?: string): SWRMutationResponse<MessageWithCustomMetaData | null> => {
   const key = threadId != null ? [`/openai/messages/${aiAssistantId}/${threadId}`] : null;
   const key = threadId != null ? [`/openai/messages/${aiAssistantId}/${threadId}`] : null;
   return useSWRMutation(
   return useSWRMutation(
     key,
     key,

+ 13 - 0
apps/app/src/features/openai/interfaces/message.ts

@@ -0,0 +1,13 @@
+import type OpenAI from 'openai';
+
+export const shouldHideMessageKey = 'shouldHideMessage';
+
+export type MessageWithCustomMetaData = Omit<OpenAI.Beta.Threads.Messages.MessagesPage, 'data'> & {
+  data: Array<OpenAI.Beta.Threads.Message & {
+    metadata?: {
+      shouldHideMessage?: 'true' | 'false',
+    }
+  }>;
+};
+
+export type MessageListParams = OpenAI.Beta.Threads.Messages.MessageListParams;

+ 3 - 2
apps/app/src/features/openai/server/routes/get-messages.ts

@@ -58,8 +58,9 @@ export const getMessagesFactory: GetMessagesFactory = (crowi) => {
           return res.apiv3Err(new ErrorV3('The specified AI assistant is not usable'), 400);
           return res.apiv3Err(new ErrorV3('The specified AI assistant is not usable'), 400);
         }
         }
 
 
-        const options = { limit, before, after };
-        const messages = await openaiService.getMessageData(threadId, req.user.lang, options);
+        const messages = await openaiService.getMessageData(threadId, req.user.lang, {
+          limit, before, after, order: 'asc',
+        });
 
 
         return res.apiv3({ messages });
         return res.apiv3({ messages });
       }
       }

+ 11 - 1
apps/app/src/features/openai/server/routes/message.ts

@@ -13,7 +13,9 @@ import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
 import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';
 import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
+import { shouldHideMessageKey } from '../../interfaces/message';
 import { MessageErrorCode, type StreamErrorCode } from '../../interfaces/message-error';
 import { MessageErrorCode, type StreamErrorCode } from '../../interfaces/message-error';
+import AiAssistantModel from '../models/ai-assistant';
 import { openaiClient } from '../services/client';
 import { openaiClient } from '../services/client';
 import { getStreamErrorCode } from '../services/getStreamErrorCode';
 import { getStreamErrorCode } from '../services/getStreamErrorCode';
 import { getOpenaiService } from '../services/openai';
 import { getOpenaiService } from '../services/openai';
@@ -69,13 +71,17 @@ export const postMessageHandlersFactory: PostMessageHandlersFactory = (crowi) =>
         return res.apiv3Err(new ErrorV3('The specified AI assistant is not usable'), 400);
         return res.apiv3Err(new ErrorV3('The specified AI assistant is not usable'), 400);
       }
       }
 
 
+      const aiAssistant = await AiAssistantModel.findById(aiAssistantId);
+      if (aiAssistant == null) {
+        return res.apiv3Err(new ErrorV3('AI assistant not found'), 404);
+      }
+
       let stream: AssistantStream;
       let stream: AssistantStream;
 
 
       try {
       try {
         const assistant = await getOrCreateChatAssistant();
         const assistant = await getOrCreateChatAssistant();
 
 
         const thread = await openaiClient.beta.threads.retrieve(threadId);
         const thread = await openaiClient.beta.threads.retrieve(threadId);
-
         stream = openaiClient.beta.threads.runs.stream(thread.id, {
         stream = openaiClient.beta.threads.runs.stream(thread.id, {
           assistant_id: assistant.id,
           assistant_id: assistant.id,
           additional_messages: [
           additional_messages: [
@@ -84,9 +90,13 @@ export const postMessageHandlersFactory: PostMessageHandlersFactory = (crowi) =>
               content: req.body.summaryMode
               content: req.body.summaryMode
                 ? 'Turn on summary mode: I will try to answer concisely, aiming for 1-3 sentences.'
                 ? 'Turn on summary mode: I will try to answer concisely, aiming for 1-3 sentences.'
                 : 'I will turn off summary mode and answer.',
                 : 'I will turn off summary mode and answer.',
+              metadata: {
+                [shouldHideMessageKey]: 'true',
+              },
             },
             },
             { role: 'user', content: req.body.userMessage },
             { role: 'user', content: req.body.userMessage },
           ],
           ],
+          additional_instructions: aiAssistant.additionalInstruction,
         });
         });
 
 
       }
       }

+ 5 - 1
apps/app/src/features/openai/server/services/client-delegator/azure-openai-client-delegator.ts

@@ -3,6 +3,9 @@ import type OpenAI from 'openai';
 import { AzureOpenAI } from 'openai';
 import { AzureOpenAI } from 'openai';
 import { type Uploadable } from 'openai/uploads';
 import { type Uploadable } from 'openai/uploads';
 
 
+import type { MessageListParams } from '../../../interfaces/message';
+
+
 import type { IOpenaiClientDelegator } from './interfaces';
 import type { IOpenaiClientDelegator } from './interfaces';
 
 
 
 
@@ -38,8 +41,9 @@ export class AzureOpenaiClientDelegator implements IOpenaiClientDelegator {
     return this.client.beta.threads.del(threadId);
     return this.client.beta.threads.del(threadId);
   }
   }
 
 
-  async getMessages(threadId: string, options?: { before: string, after: string, limit: number }): Promise<OpenAI.Beta.Threads.Messages.MessagesPage> {
+  async getMessages(threadId: string, options?: MessageListParams): Promise<OpenAI.Beta.Threads.Messages.MessagesPage> {
     return this.client.beta.threads.messages.list(threadId, {
     return this.client.beta.threads.messages.list(threadId, {
+      order: options?.order,
       limit: options?.limit,
       limit: options?.limit,
       before: options?.before,
       before: options?.before,
       after: options?.after,
       after: options?.after,

+ 3 - 1
apps/app/src/features/openai/server/services/client-delegator/interfaces.ts

@@ -1,11 +1,13 @@
 import type OpenAI from 'openai';
 import type OpenAI from 'openai';
 import type { Uploadable } from 'openai/uploads';
 import type { Uploadable } from 'openai/uploads';
 
 
+import type { MessageListParams } from '../../../interfaces/message';
+
 export interface IOpenaiClientDelegator {
 export interface IOpenaiClientDelegator {
   createThread(vectorStoreId: string): Promise<OpenAI.Beta.Threads.Thread>
   createThread(vectorStoreId: string): Promise<OpenAI.Beta.Threads.Thread>
   retrieveThread(threadId: string): Promise<OpenAI.Beta.Threads.Thread>
   retrieveThread(threadId: string): Promise<OpenAI.Beta.Threads.Thread>
   deleteThread(threadId: string): Promise<OpenAI.Beta.Threads.ThreadDeleted>
   deleteThread(threadId: string): Promise<OpenAI.Beta.Threads.ThreadDeleted>
-  getMessages(threadId: string, options?: { limit: number, before: string, after: string }): Promise<OpenAI.Beta.Threads.Messages.MessagesPage>
+  getMessages(threadId: string, options?: MessageListParams): Promise<OpenAI.Beta.Threads.Messages.MessagesPage>
   retrieveVectorStore(vectorStoreId: string): Promise<OpenAI.Beta.VectorStores.VectorStore>
   retrieveVectorStore(vectorStoreId: string): Promise<OpenAI.Beta.VectorStores.VectorStore>
   createVectorStore(name: string): Promise<OpenAI.Beta.VectorStores.VectorStore>
   createVectorStore(name: string): Promise<OpenAI.Beta.VectorStores.VectorStore>
   deleteVectorStore(vectorStoreId: string): Promise<OpenAI.Beta.VectorStores.VectorStoreDeleted>
   deleteVectorStore(vectorStoreId: string): Promise<OpenAI.Beta.VectorStores.VectorStoreDeleted>

+ 4 - 2
apps/app/src/features/openai/server/services/client-delegator/openai-client-delegator.ts

@@ -3,8 +3,9 @@ import { type Uploadable } from 'openai/uploads';
 
 
 import { configManager } from '~/server/service/config-manager';
 import { configManager } from '~/server/service/config-manager';
 
 
-import type { IOpenaiClientDelegator } from './interfaces';
+import type { MessageListParams } from '../../../interfaces/message';
 
 
+import type { IOpenaiClientDelegator } from './interfaces';
 
 
 export class OpenaiClientDelegator implements IOpenaiClientDelegator {
 export class OpenaiClientDelegator implements IOpenaiClientDelegator {
 
 
@@ -41,8 +42,9 @@ export class OpenaiClientDelegator implements IOpenaiClientDelegator {
     return this.client.beta.threads.del(threadId);
     return this.client.beta.threads.del(threadId);
   }
   }
 
 
-  async getMessages(threadId: string, options?: { before?: string, after?: string, limit?: number }): Promise<OpenAI.Beta.Threads.Messages.MessagesPage> {
+  async getMessages(threadId: string, options?: MessageListParams): Promise<OpenAI.Beta.Threads.Messages.MessagesPage> {
     return this.client.beta.threads.messages.list(threadId, {
     return this.client.beta.threads.messages.list(threadId, {
+      order: options?.order,
       limit: options?.limit,
       limit: options?.limit,
       before: options?.before,
       before: options?.before,
       after: options?.after,
       after: options?.after,

+ 3 - 6
apps/app/src/features/openai/server/services/openai.ts

@@ -30,6 +30,7 @@ import { OpenaiServiceTypes } from '../../interfaces/ai';
 import {
 import {
   type AccessibleAiAssistants, type AiAssistant, AiAssistantAccessScope, AiAssistantShareScope,
   type AccessibleAiAssistants, type AiAssistant, AiAssistantAccessScope, AiAssistantShareScope,
 } from '../../interfaces/ai-assistant';
 } from '../../interfaces/ai-assistant';
+import type { MessageListParams } from '../../interfaces/message';
 import AiAssistantModel, { type AiAssistantDocument } from '../models/ai-assistant';
 import AiAssistantModel, { type AiAssistantDocument } from '../models/ai-assistant';
 import { convertMarkdownToHtml } from '../utils/convert-markdown-to-html';
 import { convertMarkdownToHtml } from '../utils/convert-markdown-to-html';
 
 
@@ -69,9 +70,7 @@ export interface IOpenaiService {
   // getOrCreateVectorStoreForPublicScope(): Promise<VectorStoreDocument>;
   // getOrCreateVectorStoreForPublicScope(): Promise<VectorStoreDocument>;
   deleteExpiredThreads(limit: number, apiCallInterval: number): Promise<void>; // for CronJob
   deleteExpiredThreads(limit: number, apiCallInterval: number): Promise<void>; // for CronJob
   deleteObsolatedVectorStoreRelations(): Promise<void> // for CronJob
   deleteObsolatedVectorStoreRelations(): Promise<void> // for CronJob
-  getMessageData(
-    threadId: string, lang?: Lang, options?: { before?: string, after?: string, limit?: number }
-  ): Promise<OpenAI.Beta.Threads.Messages.MessagesPage>;
+  getMessageData(threadId: string, lang?: Lang, options?: MessageListParams): Promise<OpenAI.Beta.Threads.Messages.MessagesPage>;
   getVectorStoreRelation(aiAssistantId: string): Promise<VectorStoreDocument>
   getVectorStoreRelation(aiAssistantId: string): Promise<VectorStoreDocument>
   getVectorStoreRelationsByPageIds(pageId: Types.ObjectId[]): Promise<VectorStoreDocument[]>;
   getVectorStoreRelationsByPageIds(pageId: Types.ObjectId[]): Promise<VectorStoreDocument[]>;
   createVectorStoreFile(vectorStoreRelation: VectorStoreDocument, pages: PageDocument[]): Promise<void>;
   createVectorStoreFile(vectorStoreRelation: VectorStoreDocument, pages: PageDocument[]): Promise<void>;
@@ -200,9 +199,7 @@ class OpenaiService implements IOpenaiService {
     await ThreadRelationModel.deleteMany({ threadId: { $in: deletedThreadIds } });
     await ThreadRelationModel.deleteMany({ threadId: { $in: deletedThreadIds } });
   }
   }
 
 
-  async getMessageData(
-      threadId: string, lang?: Lang, options?: { limit: number, before: string, after: string },
-  ): Promise<OpenAI.Beta.Threads.Messages.MessagesPage> {
+  async getMessageData(threadId: string, lang?: Lang, options?: MessageListParams): Promise<OpenAI.Beta.Threads.Messages.MessagesPage> {
     const messages = await this.client.getMessages(threadId, options);
     const messages = await this.client.getMessages(threadId, options);
 
 
     for await (const message of messages.data) {
     for await (const message of messages.data) {