Răsfoiți Sursa

Merge pull request #9138 from weseek/feat/153989-implement-openai-client-delegator

feat: Implement OpenAI client delegator
Yuki Takei 1 an în urmă
părinte
comite
0c81b7ee7c

+ 2 - 1
apps/app/package.json

@@ -63,7 +63,8 @@
     "@akebifiky/remark-simple-plantuml": "^1.0.2",
     "@akebifiky/remark-simple-plantuml": "^1.0.2",
     "@aws-sdk/client-s3": "3.454.0",
     "@aws-sdk/client-s3": "3.454.0",
     "@aws-sdk/s3-request-presigner": "3.454.0",
     "@aws-sdk/s3-request-presigner": "3.454.0",
-    "@azure/identity": "^4.3.0",
+    "@azure/identity": "^4.4.1",
+    "@azure/openai": "^2.0.0-beta.2",
     "@azure/storage-blob": "^12.16.0",
     "@azure/storage-blob": "^12.16.0",
     "@browser-bunyan/console-formatted-stream": "^1.8.0",
     "@browser-bunyan/console-formatted-stream": "^1.8.0",
     "@elastic/elasticsearch7": "npm:@elastic/elasticsearch@^7.17.0",
     "@elastic/elasticsearch7": "npm:@elastic/elasticsearch@^7.17.0",

+ 5 - 4
apps/app/src/interfaces/ai.ts

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

+ 3 - 3
apps/app/src/server/middlewares/certify-ai-service.ts

@@ -1,6 +1,6 @@
 import type { NextFunction, Request, Response } from 'express';
 import type { NextFunction, Request, Response } from 'express';
 
 
-import { aiServiceTypes } from '~/interfaces/ai';
+import { OpenaiServiceTypes } from '~/interfaces/ai';
 import { configManager } from '~/server/service/config-manager';
 import { configManager } from '~/server/service/config-manager';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
@@ -8,7 +8,7 @@ const logger = loggerFactory('growi:middlewares:certify-ai-service');
 
 
 export const certifyAiService = (req: Request, res: Response & { apiv3Err }, next: NextFunction): void => {
 export const certifyAiService = (req: Request, res: Response & { apiv3Err }, next: NextFunction): void => {
   const aiEnabled = configManager.getConfig('crowi', 'app:aiEnabled');
   const aiEnabled = configManager.getConfig('crowi', 'app:aiEnabled');
-  const aiServiceType = configManager.getConfig('crowi', 'app:aiServiceType');
+  const openaiServiceType = configManager.getConfig('crowi', 'app:openaiServiceType');
 
 
   if (!aiEnabled) {
   if (!aiEnabled) {
     const message = 'AI_ENABLED is not true';
     const message = 'AI_ENABLED is not true';
@@ -16,7 +16,7 @@ export const certifyAiService = (req: Request, res: Response & { apiv3Err }, nex
     return res.apiv3Err(message, 403);
     return res.apiv3Err(message, 403);
   }
   }
 
 
-  if (aiServiceType == null || !aiServiceTypes.includes(aiServiceType)) {
+  if (openaiServiceType == null || !OpenaiServiceTypes.includes(openaiServiceType)) {
     const message = 'AI_SERVICE_TYPE is missing or contains an invalid value';
     const message = 'AI_SERVICE_TYPE is missing or contains an invalid value';
     logger.error(message);
     logger.error(message);
     return res.apiv3Err(message, 403);
     return res.apiv3Err(message, 403);

+ 12 - 1
apps/app/src/server/routes/apiv3/openai/rebuild-vector-store.ts

@@ -1,8 +1,10 @@
+import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request, RequestHandler } from 'express';
 import type { Request, RequestHandler } from 'express';
 import type { ValidationChain } from 'express-validator';
 import type { ValidationChain } from 'express-validator';
 
 
 import type Crowi from '~/server/crowi';
 import type Crowi from '~/server/crowi';
 import { certifyAiService } from '~/server/middlewares/certify-ai-service';
 import { certifyAiService } from '~/server/middlewares/certify-ai-service';
+import { openaiService } from '~/server/service/openai/openai';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import { apiV3FormValidator } from '../../../middlewares/apiv3-form-validator';
 import { apiV3FormValidator } from '../../../middlewares/apiv3-form-validator';
@@ -24,7 +26,16 @@ export const rebuildVectorStoreHandlersFactory: RebuildVectorStoreFactory = (cro
   return [
   return [
     accessTokenParser, loginRequiredStrictly, adminRequired, certifyAiService, validator, apiV3FormValidator,
     accessTokenParser, loginRequiredStrictly, adminRequired, certifyAiService, validator, apiV3FormValidator,
     async(req: Request, res: ApiV3Response) => {
     async(req: Request, res: ApiV3Response) => {
-      return res.apiv3({});
+
+      try {
+        await openaiService.rebuildVectorStore();
+        return res.apiv3({});
+
+      }
+      catch (err) {
+        logger.error(err);
+        return res.apiv3Err(new ErrorV3('Vector Store rebuild failed'));
+      }
     },
     },
   ];
   ];
 };
 };

+ 2 - 2
apps/app/src/server/service/config-loader.ts

@@ -742,9 +742,9 @@ const ENV_VAR_NAME_TO_CONFIG_INFO = {
     type: ValueType.BOOLEAN,
     type: ValueType.BOOLEAN,
     default: false,
     default: false,
   },
   },
-  AI_SERVICE_TYPE: {
+  OPENAI_SERVICE_TYPE: {
     ns: 'crowi',
     ns: 'crowi',
-    key: 'app:aiServiceType',
+    key: 'app:openaiServiceType',
     type: ValueType.STRING,
     type: ValueType.STRING,
     default: null,
     default: null,
   },
   },

+ 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 });
+  }
+
+}

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

@@ -0,0 +1,34 @@
+import { OpenaiServiceType } from '~/interfaces/ai';
+
+import { AzureOpenaiClientDelegator } from './azure-openai-client-delegator';
+import type { IOpenaiClientDelegator } from './interfaces';
+import { OpenaiClientDelegator } from './openai-client-delegator';
+
+type GetDelegatorOptions = {
+  openaiServiceType: OpenaiServiceType;
+}
+
+type IsAny<T> = 'dummy' extends (T & 'dummy') ? true : false;
+type Delegator<Opts extends GetDelegatorOptions> =
+  IsAny<Opts> extends true
+    ? IOpenaiClientDelegator
+    : Opts extends { openaiServiceType: 'openai' }
+      ? OpenaiClientDelegator
+      : Opts extends { openaiServiceType: 'azure-openai' }
+        ? AzureOpenaiClientDelegator
+        : IOpenaiClientDelegator;
+
+let instance;
+
+export const getClient = <Opts extends GetDelegatorOptions>(opts: Opts): Delegator<Opts> => {
+  // instanciate the client based on the service type
+  if (instance == null) {
+    if (opts.openaiServiceType === OpenaiServiceType.AZURE_OPENAI) {
+      instance = new AzureOpenaiClientDelegator();
+      return instance;
+    }
+    instance = new OpenaiClientDelegator();
+  }
+
+  return instance;
+};

+ 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 });
+  }
+
+}

+ 54 - 0
apps/app/src/server/service/openai/openai.ts

@@ -0,0 +1,54 @@
+import { Readable } from 'stream';
+
+import { PageGrant } from '@growi/core';
+import type { HydratedDocument } from 'mongoose';
+import mongoose from 'mongoose';
+import { toFile } from 'openai';
+
+import type { PageDocument, PageModel } from '~/server/models/page';
+import { configManager } from '~/server/service/config-manager';
+
+import { getClient } from './client-delegator';
+
+export interface IOpenaiService {
+  rebuildVectorStore(): Promise<void>;
+}
+class OpenaiService implements IOpenaiService {
+
+  constructor() {
+    const aiEnabled = configManager.getConfig('crowi', 'app:aiEnabled');
+    if (!aiEnabled) {
+      return;
+    }
+  }
+
+  private get client() {
+    const openaiServiceType = configManager.getConfig('crowi', 'app:openaiServiceType');
+    return getClient({ openaiServiceType });
+  }
+
+  async rebuildVectorStore() {
+    // TODO: https://redmine.weseek.co.jp/issues/154364
+
+    // Create all public pages VectorStoreFile
+    const page = mongoose.model<HydratedDocument<PageDocument>, PageModel>('Page');
+    const allPublicPages = await page.find({ grant: PageGrant.GRANT_PUBLIC }).populate('revision');
+
+    const filesPromise = allPublicPages
+      .filter(page => page.revision?.body != null && page.revision.body.length > 0)
+      .map(async(page) => {
+        const file = await toFile(Readable.from(page.revision.body), `${page._id}.md`);
+        return file;
+      });
+
+    if (filesPromise.length === 0) {
+      return;
+    }
+
+    const files = await Promise.all(filesPromise);
+    await this.client.uploadAndPoll(files);
+  }
+
+}
+
+export const openaiService = new OpenaiService();

+ 97 - 38
yarn.lock

@@ -740,6 +740,18 @@
   dependencies:
   dependencies:
     tslib "^2.5.0"
     tslib "^2.5.0"
 
 
+"@azure-rest/core-client@^2.2.0":
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/@azure-rest/core-client/-/core-client-2.2.0.tgz#53d23db3a490c52a6958e396ca606e7adef1ad32"
+  integrity sha512-2uPSZPRb2TRyYONl5IcsWhX7C1xbm6Gof/tcLlDnYg7fCVcEfASfySKZ9OTHxhNeFgo79LhrT6juEoxTHvrXkQ==
+  dependencies:
+    "@azure/abort-controller" "^2.0.0"
+    "@azure/core-auth" "^1.3.0"
+    "@azure/core-rest-pipeline" "^1.5.0"
+    "@azure/core-tracing" "^1.0.1"
+    "@azure/core-util" "^1.0.0"
+    tslib "^2.6.2"
+
 "@azure/abort-controller@^1.0.0":
 "@azure/abort-controller@^1.0.0":
   version "1.1.0"
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.1.0.tgz#788ee78457a55af8a1ad342acb182383d2119249"
   resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.1.0.tgz#788ee78457a55af8a1ad342acb182383d2119249"
@@ -763,6 +775,15 @@
     "@azure/core-util" "^1.1.0"
     "@azure/core-util" "^1.1.0"
     tslib "^2.2.0"
     tslib "^2.2.0"
 
 
+"@azure/core-auth@^1.8.0":
+  version "1.8.0"
+  resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.8.0.tgz#281b4a6d3309c3e7b15bcd967f01d4c79ae4a1d6"
+  integrity sha512-YvFMowkXzLbXNM11yZtVLhUCmuG0ex7JKOH366ipjmHBhL3vpDcPAeWF+jf0X+jVXwFqo3UhsWUq4kH0ZPdu/g==
+  dependencies:
+    "@azure/abort-controller" "^2.0.0"
+    "@azure/core-util" "^1.1.0"
+    tslib "^2.6.2"
+
 "@azure/core-client@^1.9.2":
 "@azure/core-client@^1.9.2":
   version "1.9.2"
   version "1.9.2"
   resolved "https://registry.yarnpkg.com/@azure/core-client/-/core-client-1.9.2.tgz#6fc69cee2816883ab6c5cdd653ee4f2ff9774f74"
   resolved "https://registry.yarnpkg.com/@azure/core-client/-/core-client-1.9.2.tgz#6fc69cee2816883ab6c5cdd653ee4f2ff9774f74"
@@ -828,6 +849,20 @@
     https-proxy-agent "^5.0.0"
     https-proxy-agent "^5.0.0"
     tslib "^2.2.0"
     tslib "^2.2.0"
 
 
+"@azure/core-rest-pipeline@^1.5.0":
+  version "1.17.0"
+  resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.17.0.tgz#55dafa1093553c549ed6d8dbca69aa505c7b3aa3"
+  integrity sha512-62Vv8nC+uPId3j86XJ0WI+sBf0jlqTqPUFCBNrGtlaUeQUIXWV/D8GE5A1d+Qx8H7OQojn2WguC8kChD6v0shA==
+  dependencies:
+    "@azure/abort-controller" "^2.0.0"
+    "@azure/core-auth" "^1.8.0"
+    "@azure/core-tracing" "^1.0.1"
+    "@azure/core-util" "^1.9.0"
+    "@azure/logger" "^1.0.0"
+    http-proxy-agent "^7.0.0"
+    https-proxy-agent "^7.0.0"
+    tslib "^2.6.2"
+
 "@azure/core-tracing@1.0.0-preview.13":
 "@azure/core-tracing@1.0.0-preview.13":
   version "1.0.0-preview.13"
   version "1.0.0-preview.13"
   resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz#55883d40ae2042f6f1e12b17dd0c0d34c536d644"
   resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz#55883d40ae2042f6f1e12b17dd0c0d34c536d644"
@@ -843,6 +878,14 @@
   dependencies:
   dependencies:
     tslib "^2.2.0"
     tslib "^2.2.0"
 
 
+"@azure/core-util@^1.0.0", "@azure/core-util@^1.9.0":
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.10.0.tgz#cf3163382d40343972848c914869864df5d44bdb"
+  integrity sha512-dqLWQsh9Nro1YQU+405POVtXnwrIVqPyfUzc4zXCbThTg7+vNNaiMkwbX9AMXKyoFYFClxmB3s25ZFr3+jZkww==
+  dependencies:
+    "@azure/abort-controller" "^2.0.0"
+    tslib "^2.6.2"
+
 "@azure/core-util@^1.1.0", "@azure/core-util@^1.1.1", "@azure/core-util@^1.2.0", "@azure/core-util@^1.3.0", "@azure/core-util@^1.6.1":
 "@azure/core-util@^1.1.0", "@azure/core-util@^1.1.1", "@azure/core-util@^1.2.0", "@azure/core-util@^1.3.0", "@azure/core-util@^1.6.1":
   version "1.9.0"
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.9.0.tgz#469afd7e6452d5388b189f90d33f7756b0b210d1"
   resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.9.0.tgz#469afd7e6452d5388b189f90d33f7756b0b210d1"
@@ -851,10 +894,10 @@
     "@azure/abort-controller" "^2.0.0"
     "@azure/abort-controller" "^2.0.0"
     tslib "^2.6.2"
     tslib "^2.6.2"
 
 
-"@azure/identity@^4.3.0":
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/@azure/identity/-/identity-4.3.0.tgz#e8da6b3bf1df4de1511e813a7166a4b5b4a99ca1"
-  integrity sha512-LHZ58/RsIpIWa4hrrE2YuJ/vzG1Jv9f774RfTTAVDZDriubvJ0/S5u4pnw4akJDlS0TiJb6VMphmVUFsWmgodQ==
+"@azure/identity@^4.4.1":
+  version "4.4.1"
+  resolved "https://registry.yarnpkg.com/@azure/identity/-/identity-4.4.1.tgz#490fa2ad26786229afa36411892bb53dfa3478d3"
+  integrity sha512-DwnG4cKFEM7S3T+9u05NstXU/HN0dk45kPOinUyNKsn5VWwpXd9sbPKEg6kgJzGbm1lMuhx9o31PVbCtM5sfBA==
   dependencies:
   dependencies:
     "@azure/abort-controller" "^1.0.0"
     "@azure/abort-controller" "^1.0.0"
     "@azure/core-auth" "^1.5.0"
     "@azure/core-auth" "^1.5.0"
@@ -863,7 +906,7 @@
     "@azure/core-tracing" "^1.0.0"
     "@azure/core-tracing" "^1.0.0"
     "@azure/core-util" "^1.3.0"
     "@azure/core-util" "^1.3.0"
     "@azure/logger" "^1.0.0"
     "@azure/logger" "^1.0.0"
-    "@azure/msal-browser" "^3.11.1"
+    "@azure/msal-browser" "^3.14.0"
     "@azure/msal-node" "^2.9.2"
     "@azure/msal-node" "^2.9.2"
     events "^3.0.0"
     events "^3.0.0"
     jws "^4.0.0"
     jws "^4.0.0"
@@ -878,18 +921,23 @@
   dependencies:
   dependencies:
     tslib "^2.2.0"
     tslib "^2.2.0"
 
 
-"@azure/msal-browser@^3.11.1":
-  version "3.17.0"
-  resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-3.17.0.tgz#dee9ccae586239e7e0708b261f7ffa5bc7e00fb7"
-  integrity sha512-csccKXmW2z7EkZ0I3yAoW/offQt+JECdTIV/KrnRoZyM7wCSsQWODpwod8ZhYy7iOyamcHApR9uCh0oD1M+0/A==
+"@azure/msal-browser@^3.14.0":
+  version "3.24.0"
+  resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-3.24.0.tgz#3208047672d0b0c943b0bef5f995d510d6582ae4"
+  integrity sha512-JGNV9hTYAa7lsum9IMIibn2kKczAojNihGo1hi7pG0kNrcKej530Fl6jxwM05A44/6I079CSn6WxYxbVhKUmWg==
   dependencies:
   dependencies:
-    "@azure/msal-common" "14.12.0"
+    "@azure/msal-common" "14.15.0"
 
 
 "@azure/msal-common@14.12.0":
 "@azure/msal-common@14.12.0":
   version "14.12.0"
   version "14.12.0"
   resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-14.12.0.tgz#844abe269b071f8fa8949dadc2a7b65bbb147588"
   resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-14.12.0.tgz#844abe269b071f8fa8949dadc2a7b65bbb147588"
   integrity sha512-IDDXmzfdwmDkv4SSmMEyAniJf6fDu3FJ7ncOjlxkDuT85uSnLEhZi3fGZpoR7T4XZpOMx9teM9GXBgrfJgyeBw==
   integrity sha512-IDDXmzfdwmDkv4SSmMEyAniJf6fDu3FJ7ncOjlxkDuT85uSnLEhZi3fGZpoR7T4XZpOMx9teM9GXBgrfJgyeBw==
 
 
+"@azure/msal-common@14.15.0":
+  version "14.15.0"
+  resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-14.15.0.tgz#0e27ac0bb88fe100f4f8d1605b64d5c268636a55"
+  integrity sha512-ImAQHxmpMneJ/4S8BRFhjt1MZ3bppmpRPYYNyzeQPeFN288YKbb8TmmISQEbtfkQ1BPASvYZU5doIZOPBAqENQ==
+
 "@azure/msal-node@^2.9.2":
 "@azure/msal-node@^2.9.2":
   version "2.9.2"
   version "2.9.2"
   resolved "https://registry.yarnpkg.com/@azure/msal-node/-/msal-node-2.9.2.tgz#e6d3c1661012c1bd0ef68e328f73a2fdede52931"
   resolved "https://registry.yarnpkg.com/@azure/msal-node/-/msal-node-2.9.2.tgz#e6d3c1661012c1bd0ef68e328f73a2fdede52931"
@@ -899,6 +947,14 @@
     jsonwebtoken "^9.0.0"
     jsonwebtoken "^9.0.0"
     uuid "^8.3.0"
     uuid "^8.3.0"
 
 
+"@azure/openai@^2.0.0-beta.2":
+  version "2.0.0-beta.2"
+  resolved "https://registry.yarnpkg.com/@azure/openai/-/openai-2.0.0-beta.2.tgz#67a475b5b84358e2950fa4259b33ebc73f1fa6cf"
+  integrity sha512-cElfZcBno4h3OWxZPvqqqtDUQ7jcGANlzF1oC9bigRiKe/0bAfBmOSYqPyb6Gaf+ngBVo9IWJs/5ZWNAVSvkqQ==
+  dependencies:
+    "@azure-rest/core-client" "^2.2.0"
+    tslib "^2.6.3"
+
 "@azure/storage-blob@^12.16.0":
 "@azure/storage-blob@^12.16.0":
   version "12.16.0"
   version "12.16.0"
   resolved "https://registry.yarnpkg.com/@azure/storage-blob/-/storage-blob-12.16.0.tgz#c41fb1e538d6f6e2a6756bfcc69382eededf4fa1"
   resolved "https://registry.yarnpkg.com/@azure/storage-blob/-/storage-blob-12.16.0.tgz#c41fb1e538d6f6e2a6756bfcc69382eededf4fa1"
@@ -5136,6 +5192,13 @@ agent-base@^7.0.2:
   dependencies:
   dependencies:
     debug "^4.3.4"
     debug "^4.3.4"
 
 
+agent-base@^7.1.0:
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317"
+  integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==
+  dependencies:
+    debug "^4.3.4"
+
 agentkeepalive@^4.2.1:
 agentkeepalive@^4.2.1:
   version "4.5.0"
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923"
   resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923"
@@ -10247,6 +10310,14 @@ http-proxy-agent@^5.0.0:
     agent-base "6"
     agent-base "6"
     debug "4"
     debug "4"
 
 
+http-proxy-agent@^7.0.0:
+  version "7.0.2"
+  resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e"
+  integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==
+  dependencies:
+    agent-base "^7.1.0"
+    debug "^4.3.4"
+
 http-signature@~1.2.0:
 http-signature@~1.2.0:
   version "1.2.0"
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
   resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
@@ -10269,6 +10340,14 @@ https-proxy-agent@^5.0.0:
     agent-base "6"
     agent-base "6"
     debug "4"
     debug "4"
 
 
+https-proxy-agent@^7.0.0:
+  version "7.0.5"
+  resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz#9e8b5013873299e11fab6fd548405da2d6c602b2"
+  integrity sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==
+  dependencies:
+    agent-base "^7.0.2"
+    debug "4"
+
 https-proxy-agent@^7.0.2:
 https-proxy-agent@^7.0.2:
   version "7.0.2"
   version "7.0.2"
   resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz#e2645b846b90e96c6e6f347fb5b2e41f1590b09b"
   resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz#e2645b846b90e96c6e6f347fb5b2e41f1590b09b"
@@ -17023,7 +17102,7 @@ string-template@>=1.0.0:
   resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96"
   resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96"
   integrity sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y=
   integrity sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y=
 
 
-"string-width-cjs@npm:string-width@^4.2.0":
+"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
   version "4.2.3"
   version "4.2.3"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -17041,15 +17120,6 @@ string-width@=4.2.2:
     is-fullwidth-code-point "^3.0.0"
     is-fullwidth-code-point "^3.0.0"
     strip-ansi "^6.0.0"
     strip-ansi "^6.0.0"
 
 
-"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
-  version "4.2.3"
-  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
-  integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
-  dependencies:
-    emoji-regex "^8.0.0"
-    is-fullwidth-code-point "^3.0.0"
-    strip-ansi "^6.0.1"
-
 string-width@^5.0.1, string-width@^5.1.2:
 string-width@^5.0.1, string-width@^5.1.2:
   version "5.1.2"
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
@@ -17133,7 +17203,7 @@ stringify-entities@^4.0.0:
     character-entities-html4 "^2.0.0"
     character-entities-html4 "^2.0.0"
     character-entities-legacy "^3.0.0"
     character-entities-legacy "^3.0.0"
 
 
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
   version "6.0.1"
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
   integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
   integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -17147,13 +17217,6 @@ strip-ansi@^3.0.0:
   dependencies:
   dependencies:
     ansi-regex "^2.0.0"
     ansi-regex "^2.0.0"
 
 
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
-  version "6.0.1"
-  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
-  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
-  dependencies:
-    ansi-regex "^5.0.1"
-
 strip-ansi@^7.0.1, strip-ansi@^7.1.0:
 strip-ansi@^7.0.1, strip-ansi@^7.1.0:
   version "7.1.0"
   version "7.1.0"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -17969,6 +18032,11 @@ tslib@^1.11.1, tslib@^1.8.1:
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
   integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
   integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
 
 
+tslib@^2.6.3:
+  version "2.7.0"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01"
+  integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==
+
 tsscmp@1.0.6:
 tsscmp@1.0.6:
   version "1.0.6"
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb"
   resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb"
@@ -18940,7 +19008,7 @@ word-wrap@^1.2.3:
   resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
   resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
   integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
   integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
 
 
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
   version "7.0.0"
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
   integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
   integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -18958,15 +19026,6 @@ wrap-ansi@^6.2.0:
     string-width "^4.1.0"
     string-width "^4.1.0"
     strip-ansi "^6.0.0"
     strip-ansi "^6.0.0"
 
 
-wrap-ansi@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
-  integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
-  dependencies:
-    ansi-styles "^4.0.0"
-    string-width "^4.1.0"
-    strip-ansi "^6.0.0"
-
 wrap-ansi@^8.1.0:
 wrap-ansi@^8.1.0:
   version "8.1.0"
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"