What is Abrac.ai?
Abrac.ai is an AI-powered learning platform that generates personalized learning roadmaps and acts as a learning coach. It analyzes knowledge gaps, creates custom curricula, recommends resources, and provides an AI tutor that adapts to how you learn.
The core value proposition: stop guessing what to learn next. Abrac creates a structured path with curated resources, interactive lessons, and an AI coach to guide you.
Tech Stack
Frontend & Framework
- Next.js 16 (App Router) with React 19
- TypeScript (strict mode)
- Tailwind CSS + shadcn/ui
- React Flow for roadmap visualization
Backend & Database
- Drizzle ORM with PostgreSQL
- Next.js Server Actions (mutations)
- Next.js API Routes (webhooks, public endpoints)
AI & LLM
- Vercel AI SDK (
aipackage) - OpenAI GPT-4o-mini (primary)
- OpenAI GPT-4o (quality-critical tasks)
- Centralized model routing
Authentication & Billing
- Clerk (auth + billing)
- Subscription management via Clerk Billing
Additional Services
- Brave Search API (resource URL resolution)
- Custom generation queue (background processing)
Architecture & Design Decisions
1. Multi-Step Generation Chains
Instead of single-shot LLM calls, Abrac uses multi-step chains that build context incrementally.
Curriculum Generation Chain (4 steps):
- Profile Extraction: Extract structured user profile from conversation
- Architecture: Generate top-level topic structure
- Enrichment: Parallel enrichment of each topic with subtopics
- Validation: Final validation and summary generation
Content Generation Chain (2 steps):
- Outline Generation: Create page structure and sections
- Content Generation: Generate full content with inline summary
Why this approach?
- Higher quality through incremental refinement
- Better error handling (failures isolated to steps)
- Progress tracking for users
- Resumable on failure
2. Repository-Service Pattern
The codebase uses a clean separation:
- Repositories: Direct database access (Drizzle queries)
- Services: Business logic and orchestration
- Actions: Server actions exposed to components
- API Routes: Public endpoints and webhooks
This separation enables:
- Testability
- Reusability
- Clear boundaries
3. On-Demand Resource Loading
Resources are not generated during curriculum creation. Instead:
- Resources load on-demand when a user opens a subtopic
- Caching prevents duplicate API calls
- Skeleton loaders show progress
- Background generation continues even after navigation
This trade-off prioritizes fast curriculum generation over eager resource generation.
AI Generation Strategies
1. Model Routing Strategy
All AI calls go through a centralized getModelForTask() function:
const MODEL_ROUTING: Record<GenerationTask, string> = {
// Cost-efficient tasks → GPT-4o-mini
PATH_ASSESSMENT_CHAT: GPT4O_MINI,
CURRICULUM_ARCHITECTURE: GPT4O_MINI,
// Quality-critical tasks → GPT-4o
TOPIC_STUDY_CHAT: GPT4O,
LESSON_CONTENT: GPT4O,
}
Benefits:
- Easy model switching
- Cost optimization
- Consistent quality
2. Priority-Based Generation Queue
A background queue processes generation jobs with three priority levels:
export const GENERATION_PRIORITY = {
HIGH: 0, // User-facing requests
MEDIUM: 1, // Background pre-generation
LOW: 2, // Optional optimizations
}
Features:
- Single job at a time (avoids API overload)
- Duplicate detection
- Job timeouts (5 minutes max)
- Stale generation detection (auto-recovery)
3. Parallel Section Generation
For lesson content, sections generate in parallel:
// Parallel generation for user-facing requests
const promises = sections.map((section) =>
generateSingleSection(section, sectionCtx)
)
return Promise.all(promises)
This reduces total generation time from sequential (60s+) to parallel (~20s).
4. Streaming Responses
Chat responses stream using Vercel AI SDK:
const result = await streamText({
model: openai(getModelForTask(GENERATION_TASK.TOPIC_STUDY_CHAT)),
messages: conversationMessages,
})
Database writes happen via after() to avoid blocking the stream.
5. Retry Logic with Exponential Backoff
All AI calls use retry logic:
const result = await withRetry(
() => streamText({ model, messages }),
3, // max attempts
1000 // backoff ms
)
Handles transient API failures gracefully.
Performance Optimizations
1. Resource Caching
Global cache prevents duplicate resource loads:
const resourceLoadCache = new Map<
string,
Promise<Resource[] | null>
>()
- Checks cache before API calls
- Prevents duplicate generations
- Auto-expires after 2-5 minutes
2. Stale Generation Detection
If a page is stuck in GENERATING status for >2 minutes, it’s considered stale and auto-retriggered:
if (isGenerationStale(existingPage.generationStartedAt)) {
// Re-trigger generation
await lessonRepository.update(existingPage.id, {
status: CONTENT_STATUS.GENERATING,
generationStartedAt: new Date(),
})
enqueueGeneration({ ...job, priority: HIGH })
}
Handles process restarts, lost jobs, and hung AI calls.
3. Progress Tracking in Database
Generation progress is stored in the database:
generationProgress: {
step: 'enrichment',
enrichedCount: 5,
totalTopics: 10,
updatedAt: '2024-01-01T00:00:00Z'
}
Frontend polls for updates, enabling:
- Resume on failure
- Real-time progress
- Better UX during long operations
4. Context Passing Optimization
Instead of re-sending full content, the system passes summaries:
type PageSummary = {
pageNumber: number
title: string
summary: string // 2-3 sentences
keyConcepts: string[] // Array of concepts
}
This reduces token usage while maintaining context.
Trade-offs Made
1. Cost vs Quality
- Decision: Use GPT-4o-mini for most tasks, GPT-4o only for quality-critical ones
- Trade-off: Lower cost, slightly lower quality on some tasks
- Result: ~80% cost reduction with acceptable quality
2. Speed vs Completeness
- Decision: Generate resources on-demand, not during curriculum creation
- Trade-off: Faster curriculum generation, resources load later
- Result: Curriculum appears in ~30s instead of 2-3 minutes
3. Simplicity vs Features
- Decision: Removed dedicated flashcards/quizzes pages, integrated into roadmap
- Trade-off: Fewer separate pages, more integrated experience
- Result: Cleaner UX, less navigation
4. Database vs Memory
- Decision: Store generation progress in database, not just memory
- Trade-off: More DB writes, but resumable on failure
- Result: Robust recovery from crashes/restarts
5. Parallel vs Sequential
- Decision: Parallel section generation for user-facing requests, sequential for background
- Trade-off: Higher API concurrency, but faster user experience
- Result: 3x faster content generation when users are waiting
Lessons Learned
1. Multi-Step Chains > Single Shots
Breaking complex tasks into steps improved quality and reliability. Each step can be optimized independently.
2. Progress Tracking is Essential
Users need visibility into long-running operations. Database-backed progress enables:
- Resume on failure
- Real-time updates
- Better error messages
3. Caching is Critical
Without caching, duplicate API calls caused:
- Higher costs
- Slower responses
- Poor UX
A simple Map-based cache solved this.
4. Stale Detection is Necessary
Process restarts, network failures, and hung AI calls happen. Stale detection with auto-recovery prevents stuck states.
5. Model Routing Centralization
Centralizing model selection made it easy to:
- Optimize costs
- A/B test models
- Switch providers
6. Type Safety Matters
Strict TypeScript caught many bugs early. Zod schemas provided runtime validation and type inference.
7. Server Actions > API Routes
For internal mutations, Server Actions are simpler:
- Type-safe end-to-end
- Direct component calls
- Automatic revalidation
API routes are reserved for webhooks and public endpoints.
Extensive Use of Cursor
This project was built extensively with Cursor, an AI-powered code editor. Cursor helped with:
- Architecture decisions: Discussing trade-offs and patterns
- Code generation: Generating boilerplate and complex logic
- Refactoring: Identifying and fixing technical debt
- Debugging: Tracing issues through the codebase
- Documentation: Writing clear comments and docs
Cursor’s context awareness was especially valuable for:
- Understanding the full codebase before making changes
- Maintaining consistency across files
- Generating code that fits existing patterns
- Catching edge cases during implementation
The ability to have multi-file conversations with Cursor accelerated development significantly.
Key Features Implemented
- Personalized Learning Paths: AI-generated curricula based on user goals
- Interactive Roadmap: Visual timeline showing progress
- AI Learning Coach: Context-aware chat for each topic
- On-Demand Resources: Curated learning resources with URL resolution
- Progress Tracking: Real-time updates on learning progress
- Assessment System: AI-generated quizzes with feedback
- Notes Integration: Organized notes accessible to AI coach
- Subscription Management: Free (3 paths, 50 messages/day) and Pro ($4.99/month, unlimited)
Conclusion
Building Abrac.ai required balancing:
- AI quality vs cost
- Speed vs completeness
- Simplicity vs features
- User experience vs technical complexity
The result is a platform that:
- Generates high-quality, personalized learning paths
- Provides an adaptive AI learning coach
- Handles failures gracefully
- Scales cost-effectively
The codebase demonstrates:
- Modern Next.js patterns (App Router, Server Actions)
- Efficient AI integration (streaming, caching, queues)
- Robust error handling (retries, stale detection, progress tracking)
- Clean architecture (repositories, services, actions)
For developers building AI-powered applications, the key takeaways are:
- Use multi-step chains for complex generation
- Implement caching aggressively
- Track progress in persistent storage
- Centralize model routing
- Build for failure (retries, timeouts, stale detection)
The full codebase is available for exploration, and I’m happy to discuss any specific implementation details.
Built with Next.js, TypeScript, Drizzle ORM, OpenAI, and extensive use of Cursor.