Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,13 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# CDK
node_modules/

# CDK asset staging directory
cdk.out
cdk.context.json
**/cdk.out
package-lock.json
!source/resources/lib/
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,25 @@ Open in your browser: `http://<your-ec2-public-ip>`

Note: Use HTTP instead of HTTPS.

## CDK Deployment Guide

### 1. Prepare CDK Pre-requisites

Please follow the instructions in the [CDK Workshop](https://cdkworkshop.com/15-prerequisites.html) to install the CDK toolkit.

### 2. Deploy the CDK Stack

```bash
cd generative-bi-using-rag/source/resources
npm install
npx cdk deploy
```

### 3. Access the Streamlit Web UI
After the CDK stack is deployed, wait around 10 minutes for the initialization to complete. Then, open the Streamlit Web UI in your browser: `http://<your-ec2-public-ip>`

Note: Use HTTP instead of HTTPS.

## How to use custom data sources with the demo app
1. First create the corresponding Data Profile in Data Connection Management and Data Profile Management.
2. After selecting the Data Profile, start asking questions. For simple questions, the LLM can directly generate the correct SQL. If the generated SQL is incorrect, try adding more annotations to the Schema.
Expand Down
6 changes: 6 additions & 0 deletions source/resources/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.ts
!*.d.ts

# CDK asset staging directory
.cdk.staging
cdk.out
15 changes: 15 additions & 0 deletions source/resources/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Welcome to your CDK TypeScript project

You should explore the contents of this project. It demonstrates a CDK app with an instance of a stack (`ResourcesStack`)
which contains an Amazon SQS queue that is subscribed to an Amazon SNS topic.

The `cdk.json` file tells the CDK Toolkit how to execute your app.

## Useful commands

* `npm run build` compile typescript to js
* `npm run watch` watch for changes and compile
* `npm run test` perform the jest unit tests
* `cdk deploy` deploy this stack to your default AWS account/region
* `cdk diff` compare deployed stack with current state
* `cdk synth` emits the synthesized CloudFormation template
14 changes: 14 additions & 0 deletions source/resources/bin/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import { MainStack } from '../lib/main-stack';

// for development, use account/region from cdk cli
const devEnv = {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
};

const app = new cdk.App();
new MainStack(app, 'GenBiMainStack', { env: devEnv });

app.synth();
64 changes: 64 additions & 0 deletions source/resources/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"app": "npx ts-node --prefer-ts-exts bin/main.ts",
"watch": {
"include": [
"**"
],
"exclude": [
"README.md",
"cdk*.json",
"**/*.d.ts",
"**/*.js",
"tsconfig.json",
"package*.json",
"yarn.lock",
"node_modules",
"test"
]
},
"context": {
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/core:target-partitions": [
"aws",
"aws-cn"
],
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
"@aws-cdk/aws-iam:minimizePolicies": true,
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
"@aws-cdk/core:enablePartitionLiterals": true,
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
"@aws-cdk/aws-iam:standardizedServicePrincipals": true,
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
"@aws-cdk/aws-route53-patters:useCertificate": true,
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
"@aws-cdk/aws-redshift:columnId": true,
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
"@aws-cdk/aws-kms:aliasNameRef": true,
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true
}
}
8 changes: 8 additions & 0 deletions source/resources/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
testEnvironment: 'node',
roots: ['<rootDir>/test'],
testMatch: ['**/*.test.ts'],
transform: {
'^.+\\.tsx?$': 'ts-jest'
}
};
94 changes: 94 additions & 0 deletions source/resources/lib/ec2/ec2-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@

import { NestedStack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';

import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as iam from "aws-cdk-lib/aws-iam";
import { Asset } from 'aws-cdk-lib/aws-s3-assets';

import path from "path";

export class Ec2Stack extends NestedStack {
_vpc;
_securityGroup;
_instanceId;
_dnsName;
_publicIP;

constructor(scope: Construct, id: string, props: StackProps = {}) {
super(scope, id, props);
this._vpc = ec2.Vpc.fromLookup(this, "VPC", {
isDefault: true,
});

this._securityGroup = new ec2.SecurityGroup(this, 'GenBiSecurityGroup', {
vpc: this._vpc,
description: 'Genaratie Bi Security Group'
});

this._securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH Access')
this._securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP Access')
this._securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(5000), 'Allow API Access')
this._securityGroup.addIngressRule(this._securityGroup, ec2.Port.allTraffic(), 'Allow Self Access')

const role = new iam.Role(this, 'ec2Role', {
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com')
})

// Add bedrock:* permissions to the role
role.addToPolicy(new iam.PolicyStatement({
actions: ['bedrock:*'],
resources: ['*'],
}));

// Add dynamodb:* permissions to the role
role.addToPolicy(new iam.PolicyStatement({
actions: ['dynamodb:*'],
resources: ['*'],
}));

const ami = new ec2.AmazonLinux2023ImageSsmParameter({
kernel: ec2.AmazonLinux2023Kernel.KERNEL_6_1,
})

// Create the instance using the Security Group, AMI, and KeyPair defined in the VPC created
// TODO: Change to M5 instance type
const ec2Instance = new ec2.Instance(this, 'GenBiInstance', {
vpc: this._vpc,
instanceType: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.XLARGE),
machineImage: ami,
securityGroup: this._securityGroup,
vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC, },
role: role,
blockDevices: [{
deviceName: '/dev/xvda',
volume: ec2.BlockDeviceVolume.ebs(100), // 100 GB root volume
}],
});

const installDockerAsset = new Asset(this, 'InstallDockerAsset', { path: path.join(__dirname, 'user_data/install_docker.sh') });
const installDockerLocalPath = ec2Instance.userData.addS3DownloadCommand({
bucket: installDockerAsset.bucket,
bucketKey: installDockerAsset.s3ObjectKey,
});

const setupAppAsset = new Asset(this, 'SetupAppAsset', { path: path.join(__dirname, 'user_data/setup_app.sh') });
const setupAppLocalPath = ec2Instance.userData.addS3DownloadCommand({
bucket: setupAppAsset.bucket,
bucketKey: setupAppAsset.s3ObjectKey,
});

ec2Instance.userData.addExecuteFileCommand({
filePath: installDockerLocalPath,
});
ec2Instance.userData.addExecuteFileCommand({
filePath: setupAppLocalPath
});
installDockerAsset.grantRead(ec2Instance.role);
setupAppAsset.grantRead(ec2Instance.role);

this._instanceId = ec2Instance.instanceId;
this._dnsName = ec2Instance.instancePublicDnsName;
this._publicIP = ec2Instance.instancePublicIp;
}
}
27 changes: 27 additions & 0 deletions source/resources/lib/ec2/user_data/install_docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Sleep 60 seconds to avoid yum failures
# Ref: https://repost.aws/questions/QUgNz4VGCFSC2TYekM-6GiDQ/dnf-yum-both-fails-while-being-executed-on-instance-bootstrap-on-amazon-linux-2023
sleep 60

sudo su - ec2-user

# Install components
sudo yum install docker python3-pip git -y && sudo pip3 install -U awscli

# Remove python3-requests to avoid conflict with docker-compose
# Ref: https://stackoverflow.com/questions/76443104/error-cannot-uninstall-requests-2-25-1-record-file-not-found-hint-the-packag
sudo yum -y remove python3-requests

sudo pip3 install docker-compose

# Fix docker python wrapper 7.0 SSL version issue
sudo pip3 install docker==6.1.3

# Configure components
sudo systemctl enable docker
sudo systemctl start docker
echo "finishing starting docker"
sudo usermod -aG docker $USER
echo "finishing adding user to docker group"

# Exit the terminal
exit
29 changes: 29 additions & 0 deletions source/resources/lib/ec2/user_data/setup_app.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Log in as user ec2-user

# Configure OpenSearch server parameters
sudo sh -c "echo 'vm.max_map_count=262144' > /etc/sysctl.conf" && sudo sysctl -p

# Clone the code
git clone https://github.com/aws-samples/generative-bi-using-rag.git

# Config the Environment Variable in .env file, modify AWS_DEFAULT_REGION to the region same as the EC2 instance.
cd generative-bi-using-rag/application && cp .env.template .env

# Build docker images locally
docker-compose build

# Start all services
docker-compose up -d

# Wait 3 minutes for MySQL and OpenSearch to initialize
sleep 180

cd initial_data && wget https://github.com/fengxu1211/generative-bi-using-rag/raw/demo_data/application/initial_data/init_mysql_db.sql.zip

unzip init_mysql_db.sql.zip && cd ..

docker exec nlq-mysql sh -c "mysql -u root -ppassword -D llm < /opt/data/init_mysql_db.sql"

docker exec nlq-webserver python opensearch_deploy.py

echo "All services are started successfully. Please access the application at http://<ec2-public-ip>"
18 changes: 18 additions & 0 deletions source/resources/lib/main-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Duration, Stack, StackProps, CfnOutput } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { Ec2Stack } from './ec2/ec2-stack';

export class MainStack extends Stack {
constructor(scope: Construct, id: string, props: StackProps = {}) {
super(scope, id, props);

const _Ec2Stack = new Ec2Stack(this, 'ec2-Stack', {
env: props.env,
});

new CfnOutput(this, 'Ec2PublicIP', {
value: _Ec2Stack._publicIP,
description: 'Public IP of the EC2 instance',
});
}
}
26 changes: 26 additions & 0 deletions source/resources/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "resources",
"version": "0.1.0",
"bin": {
"resources": "bin/resources.js"
},
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"test": "jest",
"cdk": "cdk"
},
"devDependencies": {
"@types/jest": "^29.5.8",
"@types/node": "20.9.0",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"aws-cdk": "2.108.0",
"ts-node": "^10.9.1",
"typescript": "~5.2.2"
},
"dependencies": {
"aws-cdk-lib": "2.108.0",
"constructs": "^10.0.0"
}
}
32 changes: 32 additions & 0 deletions source/resources/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": [
"es2020",
"dom"
],
"declaration": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": false,
"inlineSourceMap": true,
"inlineSources": true,
"experimentalDecorators": true,
"strictPropertyInitialization": false,
"esModuleInterop": true,
"typeRoots": [
"./node_modules/@types"
]
},
"exclude": [
"node_modules",
"cdk.out"
]
}