diff --git a/docs/DEPLOY_OPTION.md b/docs/DEPLOY_OPTION.md index a0160a12..0dd45cea 100644 --- a/docs/DEPLOY_OPTION.md +++ b/docs/DEPLOY_OPTION.md @@ -332,3 +332,18 @@ context の `dashboard` に `true` を設定します。(デフォルトは `fal 続いて、Amazon Bedrock のログの出力を設定します。[Amazon Bedrock の Settings](https://console.aws.amazon.com/bedrock/home#settings) を開き、Model invocation logging を有効化します。Select the logging destinations には CloudWatch Logs only を選択してください。(S3 にも出力したい場合、Both S3 and CloudWatch Logs を選択しても構いません。) また、Log group name には `npm run cdk:deploy` 時に出力された `GenerativeAiUseCasesDashboardStack.BedrockLogGroup` を指定してください。(例: `GenerativeAiUseCasesDashboardStack-LogGroupAAAAAAAA-BBBBBBBBBBBB`) Service role は任意の名前で新規に作成してください。なお、Model invocation logging の設定は、context で `modelRegion` として指定しているリージョンで行うことに留意してください。 設定完了後、`npm run cdk:deploy` 時に出力された `GenerativeAiUseCasesDashboardStack.DashboardUrl` を開いてください。 + +## ファイルアップロード機能の有効化 + +PDF や Excel などのファイルをアップロードしてテキストを抽出する、ファイルアップロード機能を利用することができます。対応しているファイルは、csv, doc, docx, md, pdf, ppt, pptx, tsv, xlsx です。 + +**[packages/cdk/cdk.json](/packages/cdk/cdk.json) を編集** +``` +{ + "context": { + "recognizeFileEnabled": true + } +} +``` + +ファイルアップロード機能は ECS (Fargate) 上で実行されます。そのため、有効化すると VPC が新たに作成されます。 \ No newline at end of file diff --git a/packages/cdk/cdk.json b/packages/cdk/cdk.json index 82bd6a67..a4d15ed1 100644 --- a/packages/cdk/cdk.json +++ b/packages/cdk/cdk.json @@ -37,13 +37,13 @@ "agentRegion": "us-east-1", "searchAgentEnabled": false, "searchApiKey": "", - "agents": [ - ], + "agents": [], "allowedIpV4AddressRanges": null, "allowedIpV6AddressRanges": null, "allowedCountryCodes": null, "dashboard": false, "anonymousUsageTracking": true, + "recognizeFileEnabled": false, "@aws-cdk/aws-lambda:recognizeLayerVersion": true, "@aws-cdk/core:checkSecretUsage": true, "@aws-cdk/core:target-partitions": [ @@ -81,4 +81,4 @@ "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true } -} +} \ No newline at end of file diff --git a/packages/cdk/lib/construct/index.ts b/packages/cdk/lib/construct/index.ts index e09ee4ca..2afa2d94 100644 --- a/packages/cdk/lib/construct/index.ts +++ b/packages/cdk/lib/construct/index.ts @@ -6,4 +6,4 @@ export * from './rag'; export * from './transcribe'; export * from './common-web-acl'; export * from './agent'; -export * from './file'; +export * from './recognize-file'; diff --git a/packages/cdk/lib/construct/file.ts b/packages/cdk/lib/construct/recognize-file.ts similarity index 79% rename from packages/cdk/lib/construct/file.ts rename to packages/cdk/lib/construct/recognize-file.ts index 554bb2b6..8aab963f 100644 --- a/packages/cdk/lib/construct/file.ts +++ b/packages/cdk/lib/construct/recognize-file.ts @@ -1,6 +1,8 @@ import { Duration, RemovalPolicy } from 'aws-cdk-lib'; import { AuthorizationType, + CfnMethod, + CfnStage, CognitoUserPoolsAuthorizer, ConnectionType, Integration, @@ -30,13 +32,13 @@ import { LogGroup } from 'aws-cdk-lib/aws-logs'; import { NetworkLoadBalancedFargateService } from 'aws-cdk-lib/aws-ecs-patterns'; import { ServiceLinkedRole } from 'upsert-slr'; -export interface FileProps { +export interface RecognizeFileProps { userPool: UserPool; api: RestApi; } -export class File extends Construct { - constructor(scope: Construct, id: string, props: FileProps) { +export class RecognizeFile extends Construct { + constructor(scope: Construct, id: string, props: RecognizeFileProps) { super(scope, id); // S3 (File Bucket) @@ -124,6 +126,7 @@ export class File extends Construct { memoryLimitMiB: 1024, cpu: 512, taskDefinition: taskDefinition, + publicLoadBalancer: false, } ); @@ -138,6 +141,11 @@ export class File extends Construct { }); // API Gateway + const stage = props.api.deploymentStage.node.defaultChild as CfnStage; + stage.variables = { + vpcLinkId: link.vpcLinkId, + }; + const authorizer = new CognitoUserPoolsAuthorizer(this, 'Authorizer', { cognitoUserPools: [props.userPool], }); @@ -150,7 +158,7 @@ export class File extends Construct { const fileResource = props.api.root.addResource('file'); // POST: /file/recognize - fileResource.addResource('recognize').addMethod( + const recognizeMethod = fileResource.addResource('recognize').addMethod( 'POST', new Integration({ type: IntegrationType.HTTP_PROXY, @@ -163,6 +171,16 @@ export class File extends Construct { commonAuthorizerProps ); + // REST API の Method で VPC Link を参照すると、正しく削除できない問題がある。 + // ステージ変数を利用するとその問題を回避できるため、ConnectionId (ここでは VPC Link ID) をステージ変数経由で使うように変更している。 + // (L2 Construct では ConnectionId を設定できないため、L1 Construct のプロパティを直接変更している) + // 参考: https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-known-issues.html + const cfnRecognizeMethod = recognizeMethod.node.defaultChild as CfnMethod; + cfnRecognizeMethod.addPropertyOverride( + 'Integration.ConnectionId', + '${stageVariables.vpcLinkId}' + ); + // POST: /file/url fileResource .addResource('url') diff --git a/packages/cdk/lib/construct/web.ts b/packages/cdk/lib/construct/web.ts index 54d9631a..0e03882a 100644 --- a/packages/cdk/lib/construct/web.ts +++ b/packages/cdk/lib/construct/web.ts @@ -23,6 +23,7 @@ export interface WebProps { samlCognitoDomainName: string; samlCognitoFederatedIdentityProviderName: string; agentNames: string[]; + recognizeFileEnabled: boolean; } export class Web extends Construct { @@ -117,9 +118,12 @@ export class Web extends Construct { VITE_APP_IMAGE_MODEL_IDS: JSON.stringify(props.imageGenerationModelIds), VITE_APP_ENDPOINT_NAMES: JSON.stringify(props.endpointNames), VITE_APP_SAMLAUTH_ENABLED: props.samlAuthEnabled.toString(), - VITE_APP_SAML_COGNITO_DOMAIN_NAME: props.samlCognitoDomainName.toString(), - VITE_APP_SAML_COGNITO_FEDERATED_IDENTITY_PROVIDER_NAME: props.samlCognitoFederatedIdentityProviderName.toString(), + VITE_APP_SAML_COGNITO_DOMAIN_NAME: + props.samlCognitoDomainName.toString(), + VITE_APP_SAML_COGNITO_FEDERATED_IDENTITY_PROVIDER_NAME: + props.samlCognitoFederatedIdentityProviderName.toString(), VITE_APP_AGENT_NAMES: JSON.stringify(props.agentNames), + VITE_APP_RECOGNIZE_FILE_ENABLED: props.recognizeFileEnabled.toString(), }, }); diff --git a/packages/cdk/lib/generative-ai-use-cases-stack.ts b/packages/cdk/lib/generative-ai-use-cases-stack.ts index 6ba027be..04c0d1ce 100644 --- a/packages/cdk/lib/generative-ai-use-cases-stack.ts +++ b/packages/cdk/lib/generative-ai-use-cases-stack.ts @@ -8,7 +8,7 @@ import { Rag, Transcribe, CommonWebAcl, - File, + RecognizeFile, } from './construct'; import { CfnWebACLAssociation } from 'aws-cdk-lib/aws-wafv2'; import * as cognito from 'aws-cdk-lib/aws-cognito'; @@ -53,6 +53,9 @@ export class GenerativeAiUseCasesStack extends Stack { const samlCognitoFederatedIdentityProviderName: string = this.node.tryGetContext('samlCognitoFederatedIdentityProviderName')!; const agentEnabled = this.node.tryGetContext('agentEnabled') || false; + const recognizeFileEnabled: boolean = this.node.tryGetContext( + 'recognizeFileEnabled' + )!; if (typeof ragEnabled !== 'boolean') { throw new Error(errorMessageForBooleanContext('ragEnabled')); @@ -66,6 +69,10 @@ export class GenerativeAiUseCasesStack extends Stack { throw new Error(errorMessageForBooleanContext('samlAuthEnabled')); } + if (typeof recognizeFileEnabled !== 'boolean') { + throw new Error(errorMessageForBooleanContext('recognizeFileEnabled')); + } + const auth = new Auth(this, 'Auth', { selfSignUpEnabled, allowedSignUpEmailDomains, @@ -117,6 +124,7 @@ export class GenerativeAiUseCasesStack extends Stack { samlCognitoDomainName, samlCognitoFederatedIdentityProviderName, agentNames: api.agentNames, + recognizeFileEnabled, }); if (ragEnabled) { @@ -132,10 +140,12 @@ export class GenerativeAiUseCasesStack extends Stack { api: api.api, }); - new File(this, 'File', { - userPool: auth.userPool, - api: api.api, - }); + if (recognizeFileEnabled) { + new RecognizeFile(this, 'RecognizeFile', { + userPool: auth.userPool, + api: api.api, + }); + } new CfnOutput(this, 'Region', { value: this.region, @@ -205,6 +215,10 @@ export class GenerativeAiUseCasesStack extends Stack { value: JSON.stringify(api.agentNames), }); + new CfnOutput(this, 'RecognizeFileEnabled', { + value: recognizeFileEnabled.toString(), + }); + this.userPool = auth.userPool; this.userPoolClient = auth.client; } diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx index 28a5539c..3b888565 100644 --- a/packages/web/src/App.tsx +++ b/packages/web/src/App.tsx @@ -29,6 +29,8 @@ import useInterUseCases from './hooks/useInterUseCases'; const ragEnabled: boolean = import.meta.env.VITE_APP_RAG_ENABLED === 'true'; const agentEnabled: boolean = import.meta.env.VITE_APP_AGENT_ENABLED === 'true'; +const recognizeFileEnabled: boolean = + import.meta.env.VITE_APP_RECOGNIZE_FILE_ENABLED === 'true'; const items: ItemProps[] = [ { @@ -107,12 +109,14 @@ const items: ItemProps[] = [ icon: , display: 'tool' as const, }, - { - label: 'ファイルアップロード', - to: '/file', - icon: , - display: 'tool' as const, - }, + recognizeFileEnabled + ? { + label: 'ファイルアップロード', + to: '/file', + icon: , + display: 'tool' as const, + } + : null, ragEnabled ? { label: 'Kendra 検索', diff --git a/packages/web/src/main.tsx b/packages/web/src/main.tsx index dad6f3d7..251f771d 100644 --- a/packages/web/src/main.tsx +++ b/packages/web/src/main.tsx @@ -29,6 +29,8 @@ const ragEnabled: boolean = import.meta.env.VITE_APP_RAG_ENABLED === 'true'; const samlAuthEnabled: boolean = import.meta.env.VITE_APP_SAMLAUTH_ENABLED === 'true'; const agentEnabled: boolean = import.meta.env.VITE_APP_AGENT_ENABLED === 'true'; +const recognizeFileEnabled: boolean = + import.meta.env.VITE_APP_RECOGNIZE_FILE_ENABLED === 'true'; const routes: RouteObject[] = [ { @@ -79,10 +81,12 @@ const routes: RouteObject[] = [ path: '/transcribe', element: , }, - { - path: '/file', - element: , - }, + recognizeFileEnabled + ? { + path: '/file', + element: , + } + : null, ragEnabled ? { path: '/rag', diff --git a/packages/web/src/pages/Setting.tsx b/packages/web/src/pages/Setting.tsx index 950c78cb..8b3564e9 100644 --- a/packages/web/src/pages/Setting.tsx +++ b/packages/web/src/pages/Setting.tsx @@ -11,6 +11,8 @@ import { PiGithubLogoFill, PiArrowSquareOut } from 'react-icons/pi'; const ragEnabled: boolean = import.meta.env.VITE_APP_RAG_ENABLED === 'true'; const agentEnabled: boolean = import.meta.env.VITE_APP_AGENT_ENABLED === 'true'; +const recognizeFileEnabled: boolean = + import.meta.env.VITE_APP_RECOGNIZE_FILE_ENABLED === 'true'; const SettingItem = (props: { name: string; @@ -45,7 +47,7 @@ const Setting = () => { return (
-
+
設定情報
@@ -74,6 +76,10 @@ const Setting = () => { /> +
生成 AI
diff --git a/packages/web/src/vite-env.d.ts b/packages/web/src/vite-env.d.ts index cf4ff0a2..42002996 100644 --- a/packages/web/src/vite-env.d.ts +++ b/packages/web/src/vite-env.d.ts @@ -16,6 +16,7 @@ interface ImportMetaEnv { readonly VITE_APP_IMAGE_MODEL_IDS: string; readonly VITE_APP_ENDPOINT_NAMES: string; readonly VITE_APP_AGENT_NAMES: string; + readonly VITE_APP_RECOGNIZE_FILE_ENABLED: string; } interface ImportMeta { diff --git a/setup-env.sh b/setup-env.sh index c028cb86..cf1c4359 100644 --- a/setup-env.sh +++ b/setup-env.sh @@ -30,3 +30,4 @@ export VITE_APP_SAMLAUTH_ENABLED=`stack_output SamlAuthEnabled` export VITE_APP_SAML_COGNITO_DOMAIN_NAME=`stack_output SamlCognitoDomainName` export VITE_APP_SAML_COGNITO_FEDERATED_IDENTITY_PROVIDER_NAME=`stack_output SamlCognitoFederatedIdentityProviderName` export VITE_APP_AGENT_NAMES=`stack_output AgentNames` +export VITE_APP_RECOGNIZE_FILE_ENABLED=`stack_output RecognizeFileEnabled`