An AI-powered candidate screening system built with Node.js and OpenAI Agents SDK Typescript. This system helps HR professionals efficiently screen candidates using specialized AI agents for technical assessment, behavioral evaluation, and resume analysis.
- Multiple AI Agents: Specialized agents for different aspects of candidate evaluation
- Resume Analysis: Parse and analyze PDF, DOC, DOCX, and TXT files
- Real-time Streaming: Get live feedback during the screening process
- Comprehensive Scoring: Multi-factor scoring system with customizable weights
- Interview Questions: Generate tailored interview questions based on candidate profile
- Candidate Ranking: Compare and rank multiple candidates
- Secure API: Built with security best practices and input validation
| Agent | Description |
|---|---|
| Comprehensive | End-to-end candidate evaluation (recommended) |
| Triage | Main orchestrator that routes to specialized agents |
| Resume Analysis | Detailed resume analysis and job matching |
| Technical Screening | Technical competency assessment |
| Behavioral Assessment | Soft skills and cultural fit evaluation |
| Candidate Ranking | Comparative analysis and ranking |
| Interview Questions | Tailored interview question generation |
- Node.js 18+
- OpenAI API Key
- npm or yarn
-
Clone and setup:
git clone <repository-url> cd candidate-screening-agent npm install
-
Environment Configuration:
cp env.example .env
Edit
.envand add your OpenAI API key:OPENAI_API_KEY=sk-your-openai-api-key-here PORT=3000 NODE_ENV=development
-
Start the server:
# Development npm run dev # Production npm start
http://localhost:3000
POST /api/screening/screen
Content-Type: multipart/form-data
Form Data:
- resume: file (PDF, DOC, DOCX, TXT)
- jobTitle: string
- jobRequirements: string
- agentType: string (optional, default: "comprehensive")
- candidateName: string (optional)
- candidateEmail: string (optional)POST /api/screening/screen-text
Content-Type: application/json
{
"resumeText": "string",
"jobTitle": "string",
"jobRequirements": "string",
"agentType": "comprehensive",
"candidateName": "string",
"candidateEmail": "string"
}POST /api/screening/compare
Content-Type: application/json
{
"candidates": [
{
"name": "John Doe",
"resumeText": "...",
"email": "john@example.com"
}
],
"jobTitle": "string",
"jobRequirements": "string",
"jobPriorities": {
"technical": 0.5,
"behavioral": 0.3,
"experience": 0.2
}
}POST /api/screening/interview-questions
Content-Type: application/json
{
"candidateProfile": "string",
"jobRole": "string",
"questionType": "mixed", // technical|behavioral|situational|mixed
"experienceLevel": "mid-level", // junior|mid-level|senior|expert
"numberOfQuestions": 5
}All streaming endpoints use Server-Sent Events (SSE) and return text/event-stream:
POST /api/streaming/screen-stream- Stream candidate screening with file uploadPOST /api/streaming/screen-text-stream- Stream candidate screening with textPOST /api/streaming/interview-questions-stream- Stream interview question generation
status- Processing status updatescontent- AI analysis content chunksagent_update- Agent handoff notificationsrun_event- Internal processing eventscomplete- Analysis completionerror- Error notifications
const eventSource = new EventSource('http://localhost:3000/api/streaming/screen-text-stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
resumeText: "...",
jobTitle: "Software Engineer",
jobRequirements: "..."
})
});
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
switch(data.type) {
case 'content':
console.log('AI Response:', data.data);
break;
case 'status':
console.log('Status:', data.message);
break;
case 'complete':
console.log('Analysis complete!');
eventSource.close();
break;
case 'error':
console.error('Error:', data.message);
eventSource.close();
break;
}
};| Variable | Default | Description |
|---|---|---|
OPENAI_API_KEY |
- | Your OpenAI API key (required) |
PORT |
3000 | Server port |
NODE_ENV |
development | Environment mode |
SCREENING_LOOKBACK_DAYS |
7 | Days to look back for screening data |
MAX_RESUME_SIZE_MB |
5 | Maximum resume file size |
CANDIDATE_POOL_SIZE |
100 | Maximum candidates to process |
TECHNICAL_WEIGHT |
0.4 | Technical assessment weight (0-1) |
BEHAVIORAL_WEIGHT |
0.3 | Behavioral assessment weight (0-1) |
EXPERIENCE_WEIGHT |
0.3 | Experience assessment weight (0-1) |
MIN_PASSING_SCORE |
70 | Minimum score for recommendation |
The system uses a weighted scoring approach:
Overall Score = (Technical × 0.4) + (Behavioral × 0.3) + (Experience × 0.3)
Weights can be customized via environment variables.
src/
├── agents/ # AI agent definitions
├── config/ # Environment configuration
├── middleware/ # Express middleware
├── routes/ # API route handlers
├── tools/ # Agent tools and utilities
├── utils/ # Helper functions
└── server.js # Main server file
curl -X POST http://localhost:3000/api/screening/screen-text \
-H "Content-Type: application/json" \
-d '{
"resumeText": "John Doe - Software Engineer with 5 years experience in React, Node.js, and Python...",
"jobTitle": "Senior Frontend Developer",
"jobRequirements": "5+ years React experience, TypeScript, team leadership experience",
"candidateName": "John Doe"
}'curl -X POST http://localhost:3000/api/screening/screen \
-F "resume=@resume.pdf" \
-F "jobTitle=Data Scientist" \
-F "jobRequirements=Python, Machine Learning, SQL, 3+ years experience" \
-F "agentType=comprehensive"curl -X POST http://localhost:3000/api/screening/interview-questions \
-H "Content-Type: application/json" \
-d '{
"candidateProfile": "Senior developer with React and Node.js experience",
"jobRole": "Frontend Team Lead",
"questionType": "mixed",
"numberOfQuestions": 7
}'- Helmet.js: Security headers
- CORS: Configurable cross-origin requests
- Input Validation: Sanitization and validation
- File Upload Limits: Size and type restrictions
- Error Handling: Secure error responses
npm run devVisit http://localhost:3000/api/docs for interactive API documentation.
curl http://localhost:3000/All API responses follow this structure:
{
"success": true|false,
"data": {
// Response data
},
"error": "Error type",
"message": "Error description"
}import React, { useState } from 'react';
function CandidateScreening() {
const [result, setResult] = useState(null);
const [loading, setLoading] = useState(false);
const screenCandidate = async (formData) => {
setLoading(true);
try {
const response = await fetch('/api/screening/screen', {
method: 'POST',
body: formData
});
const data = await response.json();
setResult(data);
} catch (error) {
console.error('Screening failed:', error);
} finally {
setLoading(false);
}
};
return (
<div>
{/* Your screening form */}
{loading && <div>Analyzing candidate...</div>}
{result && <div>{result.data.screening.analysis}</div>}
</div>
);
}- Use Streaming: For better user experience with long-running analyses
- Choose Right Agent: Use specialized agents for focused tasks
- Batch Processing: Use comparison endpoint for multiple candidates
- Caching: Implement caching for frequently used job requirements
- File Optimization: Use PDF format for best parsing results
-
OpenAI API Key Error:
Error: Required environment variable OPENAI_API_KEY is not setSolution: Set your OpenAI API key in the
.envfile -
File Upload Failed:
- Check file size (max 5MB by default)
- Ensure file type is supported (PDF, DOC, DOCX, TXT)
-
Memory Issues:
- Reduce
MAX_RESUME_SIZE_MBif processing large files - Monitor Node.js memory usage
- Reduce
Set NODE_ENV=development for detailed error messages and stack traces.
MIT License - see LICENSE file for details.
This project uses the OpenAI Agents SDK. For more information:
Happy Screening! 🎯