The AI layer in Kids Learn isn’t a novelty feature bolted on for marketing. It’s the core differentiator. Every child learns at a different pace. A 6-year-old who breezes through basic addition but struggles with word problems needs a fundamentally different lesson path than one who understands the concepts but makes arithmetic errors. Static curriculum can’t adapt. AI can.

In this part, we integrate three AWS AI services: Amazon Bedrock for on-demand content generation using foundation models, Amazon SageMaker for training and deploying our custom difficulty-prediction model, and Amazon Personalize for learning path recommendations that improve with every interaction.

This is Part 6 of the AWS Full-Stack Mastery series. The adaptive engine running on ECS Fargate from Part 4 is where these AI services are orchestrated.

AI/ML architecture — Bedrock, SageMaker, and Personalize powering adaptive learning

Amazon Bedrock — Content Generation

Why Bedrock Over Self-Hosted Models

We evaluated three approaches:

  1. Self-hosted LLM on SageMaker endpoints. Full control, but managing GPU instances, handling scaling, and optimizing inference pipelines is a dedicated team’s job. For a team of four, this is too much operational overhead.

  2. Third-party APIs (OpenAI, Anthropic direct). Works great, but adds vendor dependency outside AWS. Network latency between our VPC and external APIs adds 50-100ms. And we can’t use VPC endpoints for traffic privacy.

  3. Amazon Bedrock. AWS-managed inference, accessed through AWS SDK within our VPC. Models available: Claude 3.5 Sonnet, Titan Text, Llama 3, Mistral, and others. Pay-per-token, no infrastructure. Integrates natively with IAM, CloudWatch, and X-Ray.

We chose Bedrock. The cost delta versus self-hosting is acceptable for our volume, and the operational simplification is worth every penny.

Content Generation Implementation

// src/fargate/adaptive-engine/src/services/content-generator.ts
import { 
  BedrockRuntimeClient, 
  InvokeModelCommand,
  InvokeModelWithResponseStreamCommand 
} from '@aws-sdk/client-bedrock-runtime';
import { Logger } from '@aws-lambda-powertools/logger';

const bedrock = new BedrockRuntimeClient({ region: process.env.BEDROCK_REGION });
const logger = new Logger({ serviceName: 'content-generator' });

interface LessonGenerationRequest {
  subject: string;
  topic: string;
  ageGroup: string;
  difficultyLevel: number;
  childContext: {
    recentScores: number[];
    knownConcepts: string[];
    struggledConcepts: string[];
  };
}

interface GeneratedLesson {
  title: string;
  introduction: string;
  questions: Array<{
    question: string;
    options: string[];
    correctIndex: number;
    explanation: string;
    hint: string;
  }>;
  summary: string;
}

export async function generateLesson(
  request: LessonGenerationRequest
): Promise<GeneratedLesson> {
  const prompt = buildLessonPrompt(request);
  
  const response = await bedrock.send(new InvokeModelCommand({
    modelId: 'anthropic.claude-3-5-sonnet-20241022-v2:0',
    contentType: 'application/json',
    accept: 'application/json',
    body: JSON.stringify({
      anthropic_version: 'bedrock-2023-05-31',
      max_tokens: 4096,
      temperature: 0.7,
      messages: [
        {
          role: 'user',
          content: prompt,
        },
      ],
    }),
  }));

  const result = JSON.parse(new TextDecoder().decode(response.body));
  const lessonContent = JSON.parse(result.content[0].text);
  
  logger.info('Lesson generated', {
    subject: request.subject,
    topic: request.topic,
    tokenCount: result.usage?.output_tokens,
  });
  
  return lessonContent;
}

function buildLessonPrompt(request: LessonGenerationRequest): string {
  return `You are an expert children's education content creator. Generate a lesson for:

Subject: ${request.subject}
Topic: ${request.topic}
Age Group: ${request.ageGroup}
Difficulty Level: ${request.difficultyLevel}/10

Child Context:
- Recent scores: ${request.childContext.recentScores.join(', ')}
- Known concepts: ${request.childContext.knownConcepts.join(', ')}
- Areas to improve: ${request.childContext.struggledConcepts.join(', ')}

Requirements:
1. The language must be appropriate for the age group
2. Questions should mix difficulty — some reinforcing known concepts, some introducing new ones
3. Every wrong answer option should be a plausible misconception, not an obviously wrong answer
4. Hints should guide thinking without giving away the answer
5. The explanation should teach, not just state the correct answer

Respond in valid JSON with this structure:
{
  "title": "string",
  "introduction": "string (2-3 child-friendly sentences)",
  "questions": [
    {
      "question": "string",
      "options": ["string", "string", "string", "string"],
      "correctIndex": number (0-3),
      "explanation": "string",
      "hint": "string"
    }
  ],
  "summary": "string (1-2 sentences about what was learned)"
}

Generate exactly 5 questions.`;
}

Knowledge Bases for Bedrock — RAG Pipeline

Our curriculum is structured in a knowledge base that Bedrock can search to ground its content generation in real educational standards:

// src/fargate/adaptive-engine/src/services/knowledge-base.ts
import {
  BedrockAgentRuntimeClient,
  RetrieveCommand,
  RetrieveAndGenerateCommand,
} from '@aws-sdk/client-bedrock-agent-runtime';

const bedrockAgent = new BedrockAgentRuntimeClient({
  region: process.env.BEDROCK_REGION,
});

export async function searchCurriculum(
  query: string,
  gradeLevel: number
): Promise<Array<{
  content: string;
  score: number;
  source: string;
}>> {
  const response = await bedrockAgent.send(new RetrieveCommand({
    knowledgeBaseId: process.env.CURRICULUM_KB_ID!,
    retrievalQuery: {
      text: query,
    },
    retrievalConfiguration: {
      vectorSearchConfiguration: {
        numberOfResults: 5,
        filter: {
          equals: {
            key: 'gradeLevel',
            value: gradeLevel,
          },
        },
      },
    },
  }));

  return (response.retrievalResults || []).map(result => ({
    content: result.content?.text || '',
    score: result.score || 0,
    source: result.location?.s3Location?.uri || 'unknown',
  }));
}

export async function generateWithCurriculum(
  prompt: string,
  gradeLevel: number
): Promise<string> {
  const response = await bedrockAgent.send(new RetrieveAndGenerateCommand({
    input: { text: prompt },
    retrieveAndGenerateConfiguration: {
      type: 'KNOWLEDGE_BASE',
      knowledgeBaseConfiguration: {
        knowledgeBaseId: process.env.CURRICULUM_KB_ID!,
        modelArn: `arn:aws:bedrock:${process.env.BEDROCK_REGION}::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0`,
        retrievalConfiguration: {
          vectorSearchConfiguration: {
            numberOfResults: 5,
          },
        },
      },
    },
  }));

  return response.output?.text || '';
}

CDK Configuration for Bedrock Knowledge Base

// lib/stacks/ai-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as bedrock from 'aws-cdk-lib/aws-bedrock';
import { Construct } from 'constructs';

export class AIStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: cdk.StackProps) {
    super(scope, id, props);

    // S3 bucket for curriculum documents
    const curriculumBucket = new s3.Bucket(this, 'CurriculumBucket', {
      bucketName: `kidslearn-curriculum-${this.account}`,
      encryption: s3.BucketEncryption.S3_MANAGED,
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
    });

    // Bedrock Knowledge Base
    const kbRole = new iam.Role(this, 'KBRole', {
      assumedBy: new iam.ServicePrincipal('bedrock.amazonaws.com'),
    });
    
    curriculumBucket.grantRead(kbRole);

    // Knowledge Base using Titan Embeddings
    const knowledgeBase = new bedrock.CfnKnowledgeBase(this, 'CurriculumKB', {
      name: 'kidslearn-curriculum',
      description: 'Kids Learn curriculum aligned to educational standards',
      roleArn: kbRole.roleArn,
      knowledgeBaseConfiguration: {
        type: 'VECTOR',
        vectorKnowledgeBaseConfiguration: {
          embeddingModelArn: `arn:aws:bedrock:${this.region}::foundation-model/amazon.titan-embed-text-v2:0`,
        },
      },
      storageConfiguration: {
        type: 'OPENSEARCH_SERVERLESS',
        opensearchServerlessConfiguration: {
          collectionArn: 'arn:aws:aoss:...',  // OpenSearch Serverless collection
          vectorIndexName: 'curriculum-index',
          fieldMapping: {
            vectorField: 'embedding',
            textField: 'content',
            metadataField: 'metadata',
          },
        },
      },
    });

    // Data source connected to curriculum S3 bucket
    new bedrock.CfnDataSource(this, 'CurriculumDataSource', {
      knowledgeBaseId: knowledgeBase.attrKnowledgeBaseId,
      name: 'curriculum-documents',
      dataSourceConfiguration: {
        type: 'S3',
        s3Configuration: {
          bucketArn: curriculumBucket.bucketArn,
          inclusionPrefixes: ['standards/', 'lessons/', 'assessments/'],
        },
      },
    });
  }
}

Amazon Personalize — Learning Path Recommendations

Amazon Personalize uses machine learning to generate real-time recommendations. For Kids Learn, it recommends the optimal sequence of lessons for each child.

Setting Up the Dataset

// scripts/setup-personalize.ts
import {
  PersonalizeClient,
  CreateSchemaCommand,
  CreateDatasetGroupCommand,
  CreateDatasetCommand,
  CreateSolutionCommand,
  CreateSolutionVersionCommand,
  CreateCampaignCommand,
} from '@aws-sdk/client-personalize';

const personalize = new PersonalizeClient({});

async function setupPersonalize() {
  // 1. Create Schema for interactions
  const interactionsSchema = {
    type: 'record',
    name: 'Interactions',
    namespace: 'com.kidslearn',
    fields: [
      { name: 'USER_ID', type: 'string' },   // childId
      { name: 'ITEM_ID', type: 'string' },   // lessonId
      { name: 'TIMESTAMP', type: 'long' },
      { name: 'EVENT_TYPE', type: 'string' }, // 'completed', 'started'
      { name: 'EVENT_VALUE', type: ['null', 'float'] }, // mastery level
    ],
    version: '1.0',
  };

  await personalize.send(new CreateSchemaCommand({
    name: 'kidslearn-interactions',
    schema: JSON.stringify(interactionsSchema),
  }));

  // 2. Create Dataset Group
  const datasetGroup = await personalize.send(new CreateDatasetGroupCommand({
    name: 'kidslearn-recommendations',
  }));

  // 3. Create Solution with USER_PERSONALIZATION recipe
  const solution = await personalize.send(new CreateSolutionCommand({
    name: 'kidslearn-next-lesson',
    datasetGroupArn: datasetGroup.datasetGroupArn!,
    recipeArn: 'arn:aws:personalize:::recipe/aws-user-personalization',
  }));

  console.log('Personalize setup complete');
}

Getting Recommendations

// src/fargate/adaptive-engine/src/services/recommender.ts
import {
  PersonalizeRuntimeClient,
  GetRecommendationsCommand,
} from '@aws-sdk/client-personalize-runtime';

const personalizeRuntime = new PersonalizeRuntimeClient({});

export async function getPersonalizedLessons(
  childId: string,
  numResults: number = 10
): Promise<string[]> {
  const response = await personalizeRuntime.send(new GetRecommendationsCommand({
    campaignArn: process.env.PERSONALIZE_CAMPAIGN_ARN!,
    userId: childId,
    numResults,
  }));

  return (response.itemList || []).map(item => item.itemId!);
}

The Bottom Line

The AI layer transforms Kids Learn from a static lesson library into an adaptive learning platform. Key architectural decisions:

  • Bedrock for content generation eliminates model hosting complexity
  • Knowledge Bases for Bedrock grounds AI content in real curriculum standards
  • SageMaker for custom models handles specialized predictions that foundation models can’t
  • Personalize for recommendations improves with every child interaction

In Part 7, we build the CI/CD pipeline with CodePipeline, CodeBuild, and ECR.

See you in Part 7.


This is Part 6 of a 10-part series: AWS Full-Stack Mastery for Technical Leads.

Series outline:

  1. Why AWS & Getting Started (Part 1)
  2. Infrastructure as Code (CDK) (Part 2)
  3. Frontend (Amplify + CloudFront) (Part 3)
  4. Backend (API Gateway + Lambda + Fargate) (Part 4)
  5. Database (Aurora + DynamoDB + ElastiCache) (Part 5)
  6. AI/ML (Bedrock + SageMaker) (this post)
  7. DevOps (CodePipeline + CodeBuild) (Part 7)
  8. Security (IAM + Cognito + WAF) (Part 8)
  9. Observability (CloudWatch + X-Ray) (Part 9)
  10. Production (Multi-Region + DR) (Part 10)

References

Export for reading

Comments