File size: 9,046 Bytes
cee0a62
 
14fdc5e
cee0a62
14fdc5e
cee0a62
14fdc5e
cee0a62
14fdc5e
 
cee0a62
 
14fdc5e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
---
title: Recruitment Copilot
emoji: 🎯
colorFrom: green
colorTo: blue
sdk: docker
app_port: 7860
pinned: false
license: mit
short_description: Agentic recruitment copilot for HR teams.
---

# Recruitment Copilot

An end-to-end agentic recruitment workspace built on **Google ADK** + **FastMCP**, with a Next.js chat UI rendered through the free **Crayon UI** components from the Thesys SDK.

It lets a recruiter:

- πŸ” Find candidates by natural-language queries (`"give me top AI engineers"`, `"anyone with NLP background"`).
- πŸ“„ Ingest scanned or text PDFs β€” Gemini OCR is used as a fallback when `pypdf` returns nothing.
- πŸ“‹ Pull all company HR policies in one click, rendered as a tabbed infographic with a donut chart.
- πŸ“ Draft a structured job posting from a chat brief β€” full markdown post + skill-weighting bar chart.
- πŸ“… Schedule interviews β€” auto-generates a Google Meet link, builds an `.ics`, and emails both attendees via SMTP.
- βœ‰οΈ Compose follow-up emails: the agent returns three tonal drafts (formal / casual / polite); pick one, give a recipient, and the email goes out via SMTP.

## Architecture

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Netlify    │───▢│  HF Spaces (Docker)│───▢│  Gemini API     β”‚
β”‚   Next.js    β”‚    β”‚  FastAPI + ADK +   β”‚    β”‚  (LLM + OCR)    β”‚
β”‚   frontend   β”‚    β”‚  MCP server        β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                              β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Άβ”‚  Qdrant Cloud   β”‚
                              β”‚              β”‚  (vector index) β”‚
                              β”‚              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                              β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Άβ”‚  Supabase       β”‚
                              β”‚              β”‚  Storage (PDFs) β”‚
                              β”‚              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                              └─────────────▢│  Gmail SMTP     β”‚
                                             β”‚  (app password) β”‚
                                             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

### Backend
- **FastAPI** serves the chat SSE stream and proxies tool calls.
- **Google ADK** agent (`gemini-2.5-pro`) orchestrates 10 MCP tools.
- **FastMCP** exposes those tools over `/mcp-server/sse`:

  | Tool | Purpose |
  |---|---|
  | `ingest_resume_pdf` | Parse a PDF (pypdf β†’ Gemini OCR fallback) and persist to SQLite + Qdrant |
  | `semantic_candidate_search` | Vector search via Qdrant Cloud |
  | `candidate_metadata_query` | Keyword search over local SQLite, role-aware filtering |
  | `compute_job_match_score` | Score a single candidate against a JD |
  | `get_policy_info` | Return company HR policies (donut + tabs UI) |
  | `manage_application_status` | Track candidate stages |
  | `manage_interview_records` | Schedule interviews + auto-email Google Meet invite |
  | `generate_job_posting` | Draft a job posting from natural-language fields |
  | `bulk_ingest_reference_resumes` | Bootstrap demo data |
  | `email_compose` | Draft 3 tone variants β†’ send via SMTP |

### Frontend
- **Next.js 14** (App Router) with a streaming chat UI.
- **Crayon UI** (`@crayonai/react-ui`) for cards, callouts, charts (bar/pie/donut/radar), tabs, tags, buttons.
- A small **chart auto-picker** chooses bar / donut / radar based on the shape of the data the backend returns, so visualisations adapt to the response.

## Local development

### Prerequisites
- Python 3.11+
- Node.js 18+
- A Gemini API key β€” https://aistudio.google.com/apikey

### One-shot bring-up (Windows / PowerShell)
```powershell
copy .env.example .env       # then fill in GEMINI_API_KEY at minimum
.\start-all.ps1
```
This installs dependencies, starts FastAPI on `http://127.0.0.1:7860` and Next.js on `http://127.0.0.1:3000`. Use `.\stop-all.ps1` to shut both down.

### Manual
```bash
# backend
cd backend
pip install -r requirements.txt
uvicorn main:app --host 0.0.0.0 --port 7860

# frontend (in another shell)
cd frontend
npm install
npm run dev
```

## Configuration

All runtime config is read from `.env`. See [`.env.example`](.env.example) for the full list with inline links to where each credential is generated.

| Group | Vars | Required for |
|---|---|---|
| Gemini | `GEMINI_API_KEY`, `GEMINI_MODEL`, `GEMINI_EMBEDDING_MODEL` | LLM, embeddings, scanned-PDF OCR |
| Qdrant | `QDRANT_URL`, `QDRANT_API_KEY`, `QDRANT_COLLECTION` | Semantic candidate search |
| Object storage | `S3_ENDPOINT`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, `S3_BUCKET`, `S3_REGION`, optional `S3_PUBLIC_BASE_URL` | Persistent resume PDF storage (Supabase Storage / Backblaze / S3 / MinIO) |
| SMTP | `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASSWORD`, `SMTP_FROM`, `SMTP_USE_TLS` | Auto-email interview invites + composed emails |
| Misc | `BACKEND_URL`, `CALENDLY_EVENT_URL`, `THESYS_API_KEY` | Frontend β†’ backend wiring (Netlify), optional integrations |

## Deployment (free tier)

```
GitHub repo  β†’  Netlify (frontend)  ──▢  HF Space Docker (backend)
                                         β”œβ”€β”€β–Ά Qdrant Cloud
                                         β”œβ”€β”€β–Ά Supabase Storage
                                         └──▢ Gmail SMTP
```

### 1. Backend on Hugging Face Spaces (Docker)
1. Create a new Space β†’ choose **Docker β†’ Blank**.
2. The repo's root [`Dockerfile`](Dockerfile) is picked up automatically.
3. In **Settings β†’ Variables and secrets**, add every key from `.env.example` (Gemini / Qdrant / S3 / SMTP). Set `CORS_ORIGINS=https://<your-netlify-site>.netlify.app`.
4. `git push` this repo to the Space remote β€” HF builds and exposes the API at `https://<user>-recruitment-copilot.hf.space`.

### 2. Frontend on Netlify
1. Import this repo in Netlify.
2. Set **Base directory** to `frontend`.
3. Add env var `BACKEND_URL=https://<user>-recruitment-copilot.hf.space`.
4. Deploy.

### 3. External services (all free, no card)
- **Qdrant Cloud** β€” https://cloud.qdrant.io β†’ 1 GB free cluster.
- **Supabase Storage** β€” https://supabase.com β†’ Free project β†’ create private `resumes` bucket β†’ Settings β†’ Storage β†’ S3 access keys.
- **Gemini API** β€” https://aistudio.google.com/apikey.
- **Gmail SMTP** β€” https://myaccount.google.com/apppasswords (requires 2-step verification).

## API surface

| Endpoint | Purpose |
|---|---|
| `GET /health` | Backend liveness |
| `GET /mcp-health` | List of registered MCP tools |
| `GET /api/tooling/status` | Reports which optional integrations are configured |
| `POST /api/upload?session_id=…` | Multipart PDF resume upload |
| `POST /api/chat` | SSE stream β€” chat with the agent |
| `GET  /api/candidates/{external_id}` | Full candidate profile (markdown + card + chart) |
| `POST /api/bootstrap/sync-reference-resumes` | Copy sample PDFs into the project storage dir |
| `POST /api/bootstrap/reference-resumes` | Bulk-ingest the synced PDFs |
| `GET  /mcp-server/sse` | MCP SSE stream endpoint |
| `POST /mcp-server/messages/?session_id=…` | MCP SSE message channel |

### SSE event contract (`/api/chat`)
| Event | Payload |
|---|---|
| `status` | `{ message }` |
| `token` | `{ delta }` (incremental text) |
| `genui` | `{ summary, markdown?, cards?, chart?, table?, tabs? }` |
| `done` | `{ message }` (final text) |
| `error` | `{ message }` |

## Project layout

```
.
β”œβ”€β”€ backend/
β”‚   β”œβ”€β”€ agent/                # Google ADK runtime + agent definition
β”‚   β”œβ”€β”€ core/                 # Settings, DB session, models, schemas, utils
β”‚   β”œβ”€β”€ mcp_server/           # FastMCP server + 10 tool modules
β”‚   β”‚   └── tools/
β”‚   β”œβ”€β”€ services/             # Candidate / search / ingestion / storage / notifications
β”‚   β”œβ”€β”€ scripts/              # Reference-resume sync utilities
β”‚   └── main.py               # FastAPI app + chat SSE stream
β”œβ”€β”€ frontend/
β”‚   └── src/
β”‚       β”œβ”€β”€ app/api/          # Next.js API proxy routes (chat, upload, candidates)
β”‚       └── components/       # Chat UI + Crayon-powered renderer
β”œβ”€β”€ Dockerfile                # HF Spaces backend image
β”œβ”€β”€ start-all.ps1 / stop-all.ps1
β”œβ”€β”€ .env.example
β”œβ”€β”€ LICENSE
└── README.md
```

## License

MIT β€” see [LICENSE](LICENSE).