nlp.service.ts•9.56 kB
import { ResourceQuery } from '../types';
import { logger } from '../utils/logger';
interface ParsedCommand {
intent: 'create' | 'delete' | 'list' | 'help' | 'unknown';
resourceType?: 'ec2' | 's3' | 'dynamodb' | 'lambda' | 'iam-role';
parameters?: Record<string, any>;
resourceId?: string;
confidence: number;
}
export class NLPService {
private resourcePatterns = {
s3: /\b(bucket|s3|storage)\b/i,
ec2: /\b(instance|server|ec2|vm|virtual machine)\b/i,
dynamodb: /\b(table|dynamodb|database|db)\b/i,
lambda: /\b(function|lambda|serverless)\b/i,
'iam-role': /\b(role|iam|permissions)\b/i,
};
private actionPatterns = {
create: /\b(create|deploy|launch|setup|configure)\b/i,
delete: /\b(delete|remove|destroy)\b/i,
list: /\b(show|list|get|see|display)\b/i,
};
private instanceTypes = ['t2.micro', 't2.small', 't2.medium', 't2.large', 't3.micro', 't3.small'];
parseNaturalLanguage(input: string): ParsedCommand {
const lowerInput = input.toLowerCase();
logger.info(`Parsing natural language input: ${input}`);
const intent = this.detectIntent(lowerInput);
if (intent === 'list') {
return {
intent: 'list',
confidence: 0.9
};
}
if (intent === 'unknown') {
return {
intent: 'unknown',
confidence: 0.1
};
}
const resourceType = this.detectResourceType(lowerInput);
if (!resourceType) {
return {
intent: 'unknown',
confidence: 0.3
};
}
const parameters = this.extractParameters(lowerInput, resourceType);
let resourceId: string | undefined;
if (intent === 'delete') {
resourceId = this.extractResourceId(lowerInput);
}
return {
intent,
resourceType,
parameters,
resourceId,
confidence: 0.8
};
}
private detectIntent(input: string): 'create' | 'delete' | 'list' | 'unknown' {
if (this.actionPatterns.create.test(input)) return 'create';
if (this.actionPatterns.delete.test(input)) return 'delete';
if (this.actionPatterns.list.test(input) && /\b(resources|all)\b/i.test(input)) return 'list';
return 'unknown';
}
private detectResourceType(input: string): ParsedCommand['resourceType'] | null {
for (const [type, pattern] of Object.entries(this.resourcePatterns)) {
if (pattern.test(input)) {
return type as ParsedCommand['resourceType'];
}
}
return null;
}
private extractParameters(input: string, resourceType: string): Record<string, any> {
const parameters: Record<string, any> = {};
switch (resourceType) {
case 's3':
parameters.bucketName = this.extractBucketName(input);
parameters.environment = this.extractEnvironment(input);
break;
case 'ec2':
parameters.instanceType = this.extractInstanceType(input) || 't2.micro';
parameters.name = this.extractResourceName(input, 'instance');
break;
case 'dynamodb':
parameters.tableName = this.extractTableName(input);
parameters.partitionKey = this.extractPartitionKey(input) || 'id';
break;
case 'lambda':
parameters.functionName = this.extractResourceName(input, 'function');
parameters.runtime = this.extractRuntime(input) || 'nodejs18.x';
parameters.roleArn = 'arn:aws:iam::123456789012:role/lambda-execution-role';
break;
case 'iam-role':
parameters.roleName = this.extractResourceName(input, 'role');
parameters.service = this.extractService(input) || 'lambda.amazonaws.com';
break;
}
if (!parameters.environment) {
parameters.environment = 'development';
}
return parameters;
}
private extractBucketName(input: string): string {
const patterns = [
/bucket\s+(?:named|called)?\s*["]?([a-z0-9-]+)["]?/i,
/s3\s+(?:named|called)?\s*["]?([a-z0-9-]+)["]?/i,
/(?:named|called)\s+["]?([a-z0-9-]+)["]?/i,
/name\s+["]?([a-z0-9-]+)["]?/i,
/["]([a-z0-9-]+)["]/,
];
for (const pattern of patterns) {
const match = input.match(pattern);
if (match && match[1]) {
return match[1];
}
}
return `bucket-${Date.now()}`;
}
private extractTableName(input: string): string {
const patterns = [
/table\s+(?:named|called)?\s*["]?([a-z0-9-_]+)["]?/i,
/for\s+([a-z0-9-_]+)/i,
/of\s+([a-z0-9-_]+)/i,
];
for (const pattern of patterns) {
const match = input.match(pattern);
if (match && match[1]) {
return match[1];
}
}
return `table-${Date.now()}`;
}
private extractResourceName(input: string, resourceTypeEn: string): string {
const patterns = [
new RegExp(`${resourceTypeEn}\\s+(?:named|called)?\\s*["]?([a-z0-9-_]+)["]?`, 'i'),
/(?:named|called)\s+["]?([a-z0-9-_]+)["]?/i,
/name\s+["]?([a-z0-9-_]+)["]?/i,
];
for (const pattern of patterns) {
const match = input.match(pattern);
if (match && match[1]) {
return match[1];
}
}
return `resource-${Date.now()}`;
}
private extractInstanceType(input: string): string | null {
for (const type of this.instanceTypes) {
if (input.includes(type)) {
return type;
}
}
if (/\b(small|micro)\b/i.test(input)) return 't2.micro';
if (/\b(medium)\b/i.test(input)) return 't2.medium';
if (/\b(large)\b/i.test(input)) return 't2.large';
return null;
}
private extractEnvironment(input: string): string {
if (/\b(prod|production)\b/i.test(input)) return 'production';
if (/\b(dev|development)\b/i.test(input)) return 'development';
if (/\b(test|testing)\b/i.test(input)) return 'testing';
if (/\b(staging)\b/i.test(input)) return 'staging';
return 'development';
}
private extractPartitionKey(input: string): string | null {
const patterns = [
/partition\s*key\s*["]?([a-z0-9_]+)["]?/i,
/key\s*["]?([a-z0-9_]+)["]?/i,
];
for (const pattern of patterns) {
const match = input.match(pattern);
if (match && match[1]) {
return match[1];
}
}
return null;
}
private extractRuntime(input: string): string | null {
const runtimes = [
'nodejs18.x', 'nodejs16.x', 'nodejs14.x',
'python3.11', 'python3.10', 'python3.9',
'java17', 'java11',
'dotnet6', 'dotnet7',
'go1.x',
'ruby3.2'
];
for (const runtime of runtimes) {
if (input.includes(runtime)) {
return runtime;
}
}
if (/\b(node|nodejs|javascript)\b/i.test(input)) return 'nodejs18.x';
if (/\b(python|py)\b/i.test(input)) return 'python3.11';
if (/\b(java)\b/i.test(input)) return 'java17';
if (/\b(dotnet|c#|csharp)\b/i.test(input)) return 'dotnet7';
if (/\b(go|golang)\b/i.test(input)) return 'go1.x';
if (/\b(ruby)\b/i.test(input)) return 'ruby3.2';
return null;
}
private extractService(input: string): string {
if (/\b(lambda|function)\b/i.test(input)) return 'lambda.amazonaws.com';
if (/\b(ec2|instance)\b/i.test(input)) return 'ec2.amazonaws.com';
if (/\b(s3|bucket)\b/i.test(input)) return 's3.amazonaws.com';
return 'lambda.amazonaws.com';
}
private extractResourceId(input: string): string | undefined {
const pattern = /\b([a-z]+-\d{10,})\b/i;
const match = input.match(pattern);
return match ? match[1] : undefined;
}
toResourceQuery(parsedCommand: ParsedCommand): ResourceQuery | null {
if (parsedCommand.intent === 'unknown' || !parsedCommand.resourceType) {
return null;
}
if (parsedCommand.intent === 'create') {
return {
resourceType: parsedCommand.resourceType,
action: 'create',
parameters: parsedCommand.parameters || {}
};
}
if (parsedCommand.intent === 'delete' && parsedCommand.resourceId) {
return {
resourceType: parsedCommand.resourceType,
action: 'delete',
parameters: {},
resourceId: parsedCommand.resourceId
};
}
return null;
}
generateHelpResponse(input: string): string {
const suggestions = [];
for (const [type, pattern] of Object.entries(this.resourcePatterns)) {
if (pattern.test(input)) {
suggestions.push(`• To create ${type === 's3' ? 'an S3 bucket' :
type === 'ec2' ? 'an EC2 instance' :
type === 'dynamodb' ? 'a DynamoDB table' :
type === 'lambda' ? 'a Lambda function' :
'an IAM role'}: "Create ${type === 's3' ? 'an S3 bucket named my-bucket' :
type === 'ec2' ? 'an EC2 instance t2.micro' :
type === 'dynamodb' ? 'a DynamoDB table for users' :
type === 'lambda' ? 'a Lambda function' :
'an IAM role for Lambda'}"`);
break;
}
}
if (suggestions.length === 0) {
suggestions.push(
'• To create resources: "Create an S3 bucket named my-project"',
'• To delete resources: "Delete the resource s3-1234567890"',
'• To list resources: "Show all resources"'
);
}
return `I could not fully understand your command. Here are some examples you can use:\n\n${suggestions.join('\n')}`;
}
}