karthik.dev
Back to blogAI Engineering

CareerByte AI: Building an Open-Source AI Career Copilot with Gemini and Prisma

2026-07-05 · 4 min read

Why Another Career Platform?

The job search process is broken into too many disconnected tools: LinkedIn for jobs, resume.io for resume building, Google Sheets for tracking, YouTube for interview prep. Every tool requires you to re-enter the same context. Nothing is connected.

CareerByte AI is a single platform that connects all four stages of the job search into one workflow.

The Four-Stage Pipeline

Job Discovery → ATS Optimization → Application Tracking → Interview Prep
      ↓                ↓                    ↓                  ↓
   Gemini AI      Gemini AI           PostgreSQL           Gemini AI
   (relevance     (ATS scoring,       (application         (questions,
    scoring)       bullet fixes)       pipeline)            answers)

Job Discovery with AI Relevance Scoring

Instead of just listing jobs, CareerByte AI scores each job's relevance to the candidate's profile:

async function scoreJobRelevance(
  job: JobListing,
  candidateProfile: CandidateProfile,
): Promise<JobScore> {
  const prompt = `
    Analyze the match between this candidate and job.
    
    Candidate Profile:
    - Skills: ${candidateProfile.skills.join(", ")}
    - Experience: ${candidateProfile.yearsExperience} years as ${candidateProfile.currentRole}
    - Target roles: ${candidateProfile.targetRoles.join(", ")}
    
    Job Description:
    Title: ${job.title}
    Company: ${job.company}
    Requirements: ${job.requirements}
    
    Return JSON: {
      overallScore: 0-100,
      skillMatchScore: 0-100,
      experienceMatchScore: 0-100,
      matchingSkills: string[],
      gapSkills: string[],
      fitSummary: string
    }
  `;
  
  const response = await gemini.generateContent({
    contents: [{ role: "user", parts: [{ text: prompt }] }],
    generationConfig: { responseMimeType: "application/json" },
  });
  
  return JSON.parse(response.response.text());
}

The responseMimeType: "application/json" forces Gemini to return valid JSON — critical for reliable parsing.

ATS Resume Optimization

The ATS optimizer takes a resume and a job description and returns specific, actionable improvements:

async function optimizeResumeForATS(
  resumeText: string,
  jobDescription: string,
): Promise<ATSOptimizationResult> {
  const prompt = `
    You are an expert ATS (Applicant Tracking System) specialist.
    
    Job Description:
    ${jobDescription}
    
    Current Resume:
    ${resumeText}
    
    Analyze the resume for ATS compatibility against this job. Return JSON:
    {
      atsScore: 0-100,
      missingKeywords: string[],  // keywords in JD not in resume
      bulletImprovements: [       // specific bullet point rewrites
        { original: string, improved: string, reason: string }
      ],
      formatIssues: string[],     // ATS-unfriendly formatting
      summaryRewrite: string      // improved professional summary
    }
  `;
  
  const response = await gemini.generateContent({
    contents: [{ role: "user", parts: [{ text: prompt }] }],
    generationConfig: { responseMimeType: "application/json" },
  });
  
  return JSON.parse(response.response.text());
}

Prisma Schema for the Application Pipeline

model Candidate {
  id        String   @id @default(cuid())
  email     String   @unique
  profile   Json     // skills, experience, targetRoles
  resume    String?  // current resume text
  createdAt DateTime @default(now())
  
  applications Application[]
  savedJobs    SavedJob[]
}

model Application {
  id           String      @id @default(cuid())
  candidateId  String
  candidate    Candidate   @relation(fields: [candidateId], references: [id])
  
  jobTitle     String
  company      String
  jobUrl       String?
  status       AppStatus   @default(SAVED)
  atsScore     Int?
  matchScore   Int?
  appliedAt    DateTime?
  nextAction   String?
  notes        String?
  
  createdAt    DateTime    @default(now())
  updatedAt    DateTime    @updatedAt
  
  @@index([candidateId, status])
}

enum AppStatus {
  SAVED
  APPLIED
  PHONE_SCREEN
  INTERVIEW
  OFFER
  REJECTED
  WITHDRAWN
}

The status field tracks the full application pipeline. The atsScore and matchScore are populated when the candidate optimizes their resume for that specific job.

Interview Preparation Track

async function generateInterviewPrep(
  job: JobListing,
  resume: string,
): Promise<InterviewPrep> {
  const prompt = `
    Generate targeted interview preparation for:
    Job: ${job.title} at ${job.company}
    Resume highlights: ${extractHighlights(resume)}
    
    Create:
    1. 5 technical questions specific to this role and company
    2. 3 system design questions for this level
    3. 5 behavioral questions (STAR format) based on resume experiences
    4. 2 questions to ask the interviewer
    
    For each question: provide model answer, common mistakes, follow-up questions.
    
    Return as JSON.
  `;
  // ...
}

Why Gemini Over OpenAI?

For this project I used Gemini for three reasons:

  1. Generous free tier — good for an open-source project where contributors run it locally
  2. Large context window — job descriptions + full resume fit easily in one prompt
  3. JSON mode — reliable structured output with responseMimeType: "application/json"

Open Source

CareerByte AI is Apache 2.0 licensed — production-ready for forks, extensions, and enterprise deployment.

GitHub: github.com/karthikrshet/CareerByte-AI