Skip to content

Commit e236fb8

Browse files
committed
First commit of hr vercel agent
1 parent d1eaf42 commit e236fb8

26 files changed

+2604
-0
lines changed

apps/vercel_sdk_hr_agent/.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
.env
3+
.env.local
4+
package-lock.json
5+
.next

apps/vercel_sdk_hr_agent/README.md

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# HR Team Matcher
2+
3+
A smart HR assistant that helps managers build optimal teams for projects using MongoDB Vector Search for semantic skills matching and the Vercel AI SDK for agentic capabilities.
4+
5+
## Features
6+
7+
- **Advanced Skill Matching**: Uses MongoDB Vector Search to find employees with the right skills, even when they're not an exact keyword match
8+
- **Multi-step Reasoning**: Uses Vercel AI SDK's agentic functionality to analyze projects, search for candidates, and evaluate team fit
9+
- **Team Analysis**: Evaluates skill coverage, team diversity, and past collaboration success
10+
- **Recommendations with Rationale**: Provides detailed justification for each team member suggestion
11+
- **Risk Assessment**: Identifies potential issues with proposed teams and suggests mitigation strategies
12+
13+
## Architecture
14+
15+
The HR Team Matcher is built with the following technologies:
16+
17+
- **Next.js**: Frontend and API routes
18+
- **MongoDB**: Database with Vector Search for semantic skill matching
19+
- **Vercel AI SDK**: Agentic AI capabilities with multi-step reasoning
20+
- **Voyage AI**: Generation of text embeddings for semantic search
21+
- **OpenAI**: Language model for team analysis and recommendations
22+
- **Tailwind CSS**: Styling
23+
24+
## How It Works
25+
26+
1. **Project Analysis**: The agent analyzes the project description to extract required skills, team size, and timeline constraints
27+
2. **Skill Matching**: Using MongoDB Vector Search, the system finds employees with matching skills
28+
3. **Team Formation**: The agent explores different combinations to form an optimal team
29+
4. **Team Evaluation**: Each potential team is evaluated for skill coverage, diversity, and collaboration history
30+
5. **Recommendation**: The best team is recommended with detailed justifications and risk assessments
31+
32+
## Getting Started
33+
34+
### Prerequisites
35+
36+
- Node.js 18.x or higher
37+
- MongoDB Atlas account (with Vector Search capability)
38+
- OpenAI API key
39+
- Voyage AI API key
40+
41+
### Environment Setup
42+
43+
1. Clone this repository
44+
2. Install dependencies:
45+
```
46+
npm install
47+
```
48+
3. Create a `.env.local` file with the following variables:
49+
```
50+
MONGODB_URI=your_mongodb_connection_string
51+
OPENAI_API_KEY=your_openai_api_key
52+
VOYAGE_API_KEY=your_voyage_api_key
53+
```
54+
55+
### Database Setup
56+
57+
1. Create a MongoDB Atlas cluster
58+
2. Create a database named `hr_database`
59+
3. Create the following collections:
60+
- `employees`
61+
- `teams`
62+
4. Set up the Vector Search index on the `employees` collection named `skill_vector_index`:
63+
```json
64+
{
65+
"fields": [
66+
{
67+
"type": "vector",
68+
"path": "embedding",
69+
"numDimensions": 1024,
70+
"similarity": "cosine"
71+
}
72+
]
73+
}
74+
```
75+
76+
### Running the Application
77+
78+
1. Start the development server:
79+
```
80+
npm run dev
81+
```
82+
2. Open your browser and navigate to `http://localhost:3000`
83+
84+
## Usage
85+
86+
1. Navigate to the "Build New Team" tab
87+
2. Enter a detailed project description, including required skills, timeline, and any special requirements
88+
3. Click "Build Team" and wait for the AI to generate a team recommendation
89+
4. Review the recommended team, including skill coverage, member details, and risk assessment
90+
5. Saved teams can be viewed and approved in the "Team History" tab
91+
92+
## Implementation Details
93+
94+
### MongoDB Vector Search
95+
96+
Skills are matched using semantic search, allowing the system to understand that "React experience" is related to "Frontend development" even without exact keyword matches.
97+
98+
### Voyage AI Embeddings
99+
100+
The Voyage AI model converts skill descriptions into vector embeddings that capture semantic meaning, enabling more intelligent matching.
101+
102+
### Vercel AI SDK Agent
103+
104+
The agent uses multiple tools in sequence to:
105+
1. Analyze projects with `analyzeProjectRequirements`
106+
2. Search for employees with `searchEmployeesBySkill`
107+
3. Analyze team compositions with `analyzeTeamComposition`
108+
4. Save recommended teams with `saveTeamToDatabase`
109+
5. Generate final recommendations with `generateTeamRecommendation`
110+
111+
The `maxSteps: 15` parameter enables the agent to perform multiple tool calls in sequence, making it a true agentic application rather than a simple API wrapper.
112+
113+
## License
114+
115+
MIT

apps/vercel_sdk_hr_agent/app/.gitignore

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import { openai } from '@ai-sdk/openai';
3+
import { generateText, tool } from 'ai';
4+
import { z } from 'zod';
5+
import {
6+
analyzeProjectRequirements,
7+
searchEmployeesBySkill,
8+
analyzeTeamComposition,
9+
saveTeamToDatabase,
10+
generateTeamRecommendation
11+
} from '../../../utils/tools';
12+
13+
export async function POST(req: NextRequest) {
14+
try {
15+
const { projectDescription } = await req.json();
16+
17+
if (!projectDescription) {
18+
return NextResponse.json({ error: 'Project description is required' }, { status: 400 });
19+
}
20+
21+
const result = await buildTeam(projectDescription);
22+
23+
return NextResponse.json(result);
24+
} catch (error) {
25+
console.error('Error building team:', error);
26+
return NextResponse.json({ error: 'Failed to build team' }, { status: 500 });
27+
}
28+
}
29+
30+
async function buildTeam(projectDescription: string) {
31+
const { steps , toolCalls } = await generateText({
32+
model: openai('o3-mini', { structuredOutputs: true }),
33+
tools: {
34+
analyzeProjectRequirements,
35+
searchEmployeesBySkill,
36+
analyzeTeamComposition,
37+
saveTeamToDatabase,
38+
generateTeamRecommendation,
39+
},
40+
toolChoice: 'auto', // Force structured output using the generateTeamRecommendation tool
41+
maxSteps: 15, // This is the key to enabling agentic behavior!
42+
system: `You are an expert HR assistant that builds optimal teams for projects.
43+
Given a project description follow this set of steps:
44+
1. Analyze the requirements to identify needed skills (analyzeProjectRequirements)
45+
2. Search for employees with matching skills (searchEmployeesBySkill)
46+
3. Evaluate possible team compositions (analyzeTeamComposition)
47+
4. MUST Save the recommended team to the database (saveTeamToDatabase)
48+
5. Generate a final recommendation with justification (generateTeamRecommendation)
49+
50+
Consider skill coverage, team diversity, availability, and past collaboration success.
51+
Be thorough in your analysis but aim to build the smallest effective team possible. If unable to complete a team, please provide a reason why but do place people with potential.
52+
53+
After your final recommendation is complete, save the team to the database for record-keeping.`,
54+
prompt: projectDescription,
55+
});
56+
57+
// Find the last generateTeamRecommendation call for the final recommendation
58+
const recommendationCall = toolCalls.find(
59+
call => call.toolName === 'generateTeamRecommendation'
60+
);
61+
62+
// Find the database save call to get the team ID
63+
const databaseCall = toolCalls.find(
64+
call => call.toolName === 'saveTeamToDatabase'
65+
);
66+
67+
// Combine the recommendation with the database result
68+
if (recommendationCall && databaseCall) {
69+
return {
70+
recommendation: recommendationCall.args,
71+
databaseResult: databaseCall.args
72+
};
73+
}
74+
75+
// Fallback to just the recommendation if database save failed
76+
if (recommendationCall) {
77+
return { recommendation: recommendationCall.args };
78+
}
79+
80+
return { error: "Failed to generate team recommendation" };
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { NextResponse } from 'next/server';
2+
import { getCollections } from '../../../../../utils/db';
3+
import { ObjectId } from 'mongodb';
4+
5+
interface Params {
6+
id: string;
7+
}
8+
9+
export async function POST(
10+
request: Request,
11+
{ params }: { params: Params }
12+
) {
13+
try {
14+
const id = params.id;
15+
16+
if (!ObjectId.isValid(id)) {
17+
return NextResponse.json(
18+
{ error: "Invalid team ID" },
19+
{ status: 400 }
20+
);
21+
}
22+
23+
// In a real implementation, we would update the team in MongoDB
24+
// For this demo, we'll just return a success response
25+
26+
return NextResponse.json({
27+
success: true,
28+
message: `Team ${id} has been approved`,
29+
status: 'approved'
30+
});
31+
} catch (error) {
32+
console.error('Error approving team:', error);
33+
return NextResponse.json(
34+
{ error: "Failed to approve team" },
35+
{ status: 500 }
36+
);
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import { getCollections } from '../../../utils/db';
3+
4+
export async function GET(req: NextRequest) {
5+
try {
6+
const { teams } = await getCollections();
7+
8+
// Get all teams, sorted by creation date (newest first)
9+
const teamsList = await teams.find({}).sort({ createdAt: -1 }).toArray();
10+
11+
return NextResponse.json({ teams: teamsList });
12+
} catch (error) {
13+
console.error('Error retrieving teams:', error);
14+
return NextResponse.json({ error: 'Failed to retrieve teams' }, { status: 500 });
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React from 'react';
2+
3+
interface LoadingProps {
4+
message?: string;
5+
}
6+
7+
const Loading: React.FC<LoadingProps> = ({ message = "Building your team..." }) => {
8+
return (
9+
<div className="flex flex-col items-center justify-center py-8">
10+
<div className="relative w-16 h-16 mb-4">
11+
<div className="absolute top-0 left-0 w-full h-full border-4 border-blue-100 rounded-full"></div>
12+
<div className="absolute top-0 left-0 w-full h-full border-4 border-blue-500 rounded-full border-t-transparent animate-spin"></div>
13+
</div>
14+
<p className="text-gray-600">{message}</p>
15+
<p className="text-sm text-gray-500 mt-2">This might take a moment as we analyze the skills and build the perfect team</p>
16+
</div>
17+
);
18+
};
19+
20+
export default Loading;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React from 'react';
2+
3+
interface TeamMemberProps {
4+
name: string;
5+
role: string;
6+
keySkills: string[];
7+
justification: string;
8+
}
9+
10+
export const TeamMember: React.FC<TeamMemberProps> = ({
11+
name,
12+
role,
13+
keySkills,
14+
justification
15+
}) => {
16+
return (
17+
<div className="border p-4 rounded-lg shadow-sm bg-white hover:shadow-md transition-shadow">
18+
<div className="flex items-center mb-3">
19+
<div className="bg-blue-100 rounded-full h-10 w-10 flex items-center justify-center text-blue-500 font-bold mr-3">
20+
{name.split(' ').map(n => n[0]).join('')}
21+
</div>
22+
<div>
23+
<div className="font-bold">{name}</div>
24+
<div className="text-sm text-gray-600">{role}</div>
25+
</div>
26+
</div>
27+
28+
<div className="mt-3">
29+
<div className="text-sm font-semibold mb-1">Key Skills:</div>
30+
<div className="flex flex-wrap gap-1">
31+
{keySkills.map((skill, i) => (
32+
<span
33+
key={i}
34+
className="bg-blue-50 text-blue-700 text-xs px-2 py-1 rounded"
35+
>
36+
{skill}
37+
</span>
38+
))}
39+
</div>
40+
</div>
41+
42+
<div className="mt-3">
43+
<div className="text-sm font-semibold mb-1">Why this person?</div>
44+
<p className="text-sm text-gray-700">{justification}</p>
45+
</div>
46+
</div>
47+
);
48+
};
49+
50+
export default TeamMember;
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;
4+
5+
:root {
6+
--foreground-rgb: 0, 0, 0;
7+
--background-start-rgb: 214, 219, 220;
8+
--background-end-rgb: 255, 255, 255;
9+
}
10+
11+
body {
12+
color: rgb(var(--foreground-rgb));
13+
background: white;
14+
}
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { Metadata } from 'next';
2+
import { Inter } from 'next/font/google';
3+
import './globals.css';
4+
5+
const inter = Inter({ subsets: ['latin'] });
6+
7+
export const metadata: Metadata = {
8+
title: 'HR Team Matcher',
9+
description: 'Build optimal teams for projects with MongoDB Vector Search and AI',
10+
};
11+
12+
export default function RootLayout({
13+
children,
14+
}: {
15+
children: React.ReactNode;
16+
}) {
17+
return (
18+
<html lang="en">
19+
<body className={inter.className}>{children}</body>
20+
</html>
21+
);
22+
}

0 commit comments

Comments
 (0)