Browse Source

refactor delegator

Yuki Takei 1 year ago
parent
commit
ebd3c9c633

+ 2 - 1
apps/app/src/interfaces/ai.ts

@@ -1,5 +1,6 @@
 export const aiServiceType = {
   OPEN_AI: 'openai',
+  AZURE_OPENAI: 'azure-openai',
 } as const;
-
+export type aiServiceType = typeof aiServiceType[keyof typeof aiServiceType];
 export const aiServiceTypes = Object.values(aiServiceType);

+ 41 - 0
apps/app/src/server/service/openai/client-delegator/azure-openai-client-delegator.ts

@@ -0,0 +1,41 @@
+import { DefaultAzureCredential, getBearerTokenProvider } from '@azure/identity';
+import type OpenAI from 'openai';
+import { AzureOpenAI } from 'openai';
+import { type Uploadable } from 'openai/uploads';
+
+import type { IOpenaiClientDelegator } from './interfaces';
+
+
+export class AzureOpenaiClientDelegator implements IOpenaiClientDelegator {
+
+  private client: AzureOpenAI;
+
+  private openaiVectorStoreId: string;
+
+  constructor() {
+    // Retrieve Azure OpenAI related values from environment variables
+    const credential = new DefaultAzureCredential();
+    const scope = 'https://cognitiveservices.azure.com/.default';
+    const azureADTokenProvider = getBearerTokenProvider(credential, scope);
+    this.client = new AzureOpenAI({ azureADTokenProvider });
+
+    // TODO: initialize openaiVectorStoreId property
+  }
+
+  async getVectorStoreFiles(): Promise<OpenAI.Beta.VectorStores.Files.VectorStoreFilesPage> {
+    return this.client.beta.vectorStores.files.list(this.openaiVectorStoreId);
+  }
+
+  async deleteVectorStoreFiles(fileId: string): Promise<OpenAI.Beta.VectorStores.Files.VectorStoreFileDeleted> {
+    return this.client.beta.vectorStores.files.del(this.openaiVectorStoreId, fileId);
+  }
+
+  async deleteFile(fileId: string): Promise<OpenAI.Files.FileDeleted> {
+    return this.client.files.del(fileId);
+  }
+
+  async uploadAndPoll(files: Uploadable[]): Promise<OpenAI.Beta.VectorStores.FileBatches.VectorStoreFileBatch> {
+    return this.client.beta.vectorStores.fileBatches.uploadAndPoll(this.openaiVectorStoreId, { files });
+  }
+
+}

+ 21 - 0
apps/app/src/server/service/openai/client-delegator/get-client.ts

@@ -0,0 +1,21 @@
+import { aiServiceType } from '~/interfaces/ai';
+
+import { AzureOpenaiClientDelegator } from './azure-openai-client-delegator';
+import type { IOpenaiClientDelegator } from './interfaces';
+import { OpenaiClientDelegator } from './openai-client-delegator';
+
+type GetDelegatorOptions = {
+  aiServiceType: aiServiceType;
+}
+type Delegator<Opts = GetDelegatorOptions> = Opts extends { aiServiceType: 'openai' }
+  ? OpenaiClientDelegator
+  : Opts extends { aiServiceType: 'azure-openai' }
+    ? AzureOpenaiClientDelegator
+    : IOpenaiClientDelegator;
+
+export const getClient = (opts: GetDelegatorOptions): Delegator => {
+  if (opts.aiServiceType === aiServiceType.AZURE_OPENAI) {
+    return new AzureOpenaiClientDelegator();
+  }
+  return new OpenaiClientDelegator();
+};

+ 1 - 0
apps/app/src/server/service/openai/client-delegator/index.ts

@@ -0,0 +1 @@
+export * from './get-client';

+ 9 - 0
apps/app/src/server/service/openai/client-delegator/interfaces.ts

@@ -0,0 +1,9 @@
+import type OpenAI from 'openai';
+import type { Uploadable } from 'openai/uploads';
+
+export interface IOpenaiClientDelegator {
+  getVectorStoreFiles(): Promise<OpenAI.Beta.VectorStores.Files.VectorStoreFilesPage>;
+  deleteVectorStoreFiles(fileId: string): Promise<OpenAI.Beta.VectorStores.Files.VectorStoreFileDeleted>;
+  deleteFile(fileId: string): Promise<OpenAI.Files.FileDeleted>;
+  uploadAndPoll(files: Uploadable[]): Promise<OpenAI.Beta.VectorStores.FileBatches.VectorStoreFileBatch>;
+}

+ 47 - 0
apps/app/src/server/service/openai/client-delegator/openai-client-delegator.ts

@@ -0,0 +1,47 @@
+import OpenAI from 'openai';
+import { type Uploadable } from 'openai/uploads';
+
+import { configManager } from '~/server/service/config-manager';
+
+import type { IOpenaiClientDelegator } from './interfaces';
+
+
+export class OpenaiClientDelegator implements IOpenaiClientDelegator {
+
+  private client: OpenAI;
+
+  private openaiVectorStoreId: string;
+
+  constructor() {
+    // Retrieve OpenAI related values from environment variables
+    const apiKey = configManager.getConfig('crowi', 'app:openaiApiKey');
+    const vectorStoreId = configManager.getConfig('crowi', 'app:openaiVectorStoreId');
+
+    const isValid = [apiKey, vectorStoreId].every(value => value != null);
+    if (!isValid) {
+      throw new Error("Environment variables required to use OpenAI's API are not set");
+    }
+
+    this.openaiVectorStoreId = vectorStoreId;
+
+    // initialize client
+    this.client = new OpenAI({ apiKey });
+  }
+
+  async getVectorStoreFiles(): Promise<OpenAI.Beta.VectorStores.Files.VectorStoreFilesPage> {
+    return this.client.beta.vectorStores.files.list(this.openaiVectorStoreId);
+  }
+
+  async deleteVectorStoreFiles(fileId: string): Promise<OpenAI.Beta.VectorStores.Files.VectorStoreFileDeleted> {
+    return this.client.beta.vectorStores.files.del(this.openaiVectorStoreId, fileId);
+  }
+
+  async deleteFile(fileId: string): Promise<OpenAI.Files.FileDeleted> {
+    return this.client.files.del(fileId);
+  }
+
+  async uploadAndPoll(files: Uploadable[]): Promise<OpenAI.Beta.VectorStores.FileBatches.VectorStoreFileBatch> {
+    return this.client.beta.vectorStores.fileBatches.uploadAndPoll(this.openaiVectorStoreId, { files });
+  }
+
+}

+ 0 - 69
apps/app/src/server/service/openai/openai-client-delegator.ts

@@ -1,69 +0,0 @@
-import { DefaultAzureCredential, getBearerTokenProvider } from '@azure/identity';
-import OpenAI, { AzureOpenAI } from 'openai';
-import { type Uploadable } from 'openai/uploads';
-
-import { aiServiceType as serviceType, aiServiceTypes } from '~/interfaces/ai';
-import { configManager } from '~/server/service/config-manager';
-
-type Client<isOpenai = boolean> = isOpenai extends true ? OpenAI : AzureOpenAI;
-
-export default class OpenaiClient {
-
-  private client: Client<boolean>;
-
-  private openaiVectorStoreId: string;
-
-  constructor() {
-    const aiEnabled = configManager.getConfig('crowi', 'app:aiEnabled');
-    const aiServiceType = configManager.getConfig('crowi', 'app:aiServiceType');
-
-    if (!aiEnabled) {
-      throw new Error('AI_ENABLED is not true');
-    }
-
-    if (aiServiceType == null || !aiServiceTypes.includes(aiServiceType)) {
-      throw new Error('AI_SERVICE_TYPE is missing or contains an invalid value');
-    }
-
-    // Retrieve OpenAI related values from environment variables
-    if (aiServiceType === serviceType.OPEN_AI) {
-      const apiKey = configManager.getConfig('crowi', 'app:openaiApiKey');
-      const vectorStoreId = configManager.getConfig('crowi', 'app:openaiVectorStoreId');
-
-      const isValid = [apiKey, vectorStoreId].every(value => value != null);
-      if (!isValid) {
-        throw new Error("Environment variables required to use OpenAI's API are not set");
-      }
-
-      this.openaiVectorStoreId = vectorStoreId;
-
-      // initialize client
-      this.client = new OpenAI({ apiKey }) as Client<true>;
-    }
-
-    // Retrieve Azure OpenAI related values from environment variables
-    else {
-      const credential = new DefaultAzureCredential();
-      const scope = 'https://cognitiveservices.azure.com/.default';
-      const azureADTokenProvider = getBearerTokenProvider(credential, scope);
-      this.client = new AzureOpenAI({ azureADTokenProvider }) as Client<false>;
-    }
-  }
-
-  async getVectorStoreFiles(): Promise<OpenAI.Beta.VectorStores.Files.VectorStoreFilesPage> {
-    return this.client.beta.vectorStores.files.list(this.openaiVectorStoreId);
-  }
-
-  async deleteVectorStoreFiles(fileId: string): Promise<OpenAI.Beta.VectorStores.Files.VectorStoreFileDeleted> {
-    return this.client.beta.vectorStores.files.del(this.openaiVectorStoreId, fileId);
-  }
-
-  async deleteFile(fileId: string): Promise<OpenAI.Files.FileDeleted> {
-    return this.client.files.del(fileId);
-  }
-
-  async uploadAndPoll(files: Uploadable[]): Promise<OpenAI.Beta.VectorStores.FileBatches.VectorStoreFileBatch> {
-    return this.client.beta.vectorStores.fileBatches.uploadAndPoll(this.openaiVectorStoreId, { files });
-  }
-
-}

+ 6 - 4
apps/app/src/server/service/openai/openai.ts

@@ -8,21 +8,23 @@ import { toFile } from 'openai';
 import type { PageDocument, PageModel } from '~/server/models/page';
 import { configManager } from '~/server/service/config-manager';
 
-import OpenaiClient from './openai-client-delegator';
+import { getClient } from './client-delegator';
 
 export interface IOpenaiService {
   rebuildVectorStore(): Promise<void>;
 }
 class OpenaiService implements IOpenaiService {
 
-  private client: OpenaiClient;
-
   constructor() {
     const aiEnabled = configManager.getConfig('crowi', 'app:aiEnabled');
     if (!aiEnabled) {
       return;
     }
-    this.client = new OpenaiClient();
+  }
+
+  private get client() {
+    const aiServiceType = configManager.getConfig('crowi', 'app:aiServiceType');
+    return getClient(aiServiceType);
   }
 
   async rebuildVectorStore() {