AI Рекрутер — цифровий працівник для найму
AI Рекрутер автоматизує повний цикл підбору персоналу: генерування опису вакансії, публікацію на платформах, скринінг вхідних резюме, розписування інтерв'ю, персоналізовану комунікацію з кандидатами, ведення воронки в ATS. Дозволяє одному рекрутеру керувати значно більшим обсягом вакансій без втрати якості.
Генератор опису вакансії
from openai import AsyncOpenAI
from pydantic import BaseModel
client = AsyncOpenAI()
class JobDescription(BaseModel):
title: str
department: str
level: str
responsibilities: list[str]
required_skills: list[str]
preferred_skills: list[str]
about_team: str
compensation_range: str
async def generate_job_description(
hiring_manager_brief: str,
similar_jd_examples: list[str] = None,
) -> str:
"""Генерує опис вакансії з короткого брифу менеджера з найму"""
examples_context = ""
if similar_jd_examples:
examples_context = f"\nПриклади подібних вакансій для стилю:\n{similar_jd_examples[0][:500]}"
response = await client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "system",
"content": f"""Ти — досвідчений HR. Пиши описи вакансій, які привертають сильних кандидатів.
Уникай: списків з 20 пунктів, нереалістичних вимог, корпоративного жаргону.
Фокус: що буде робити людина, чому це цікаво, хто нам потрібен.{examples_context}"""
}, {
"role": "user",
"content": f"Бриф менеджера з найму:\n{hiring_manager_brief}",
}],
temperature=0.4,
)
return response.choices[0].message.content
Мультиплатформена публікація
class JobBoardPublisher:
async def publish_to_all_boards(self, jd: JobDescription) -> dict:
"""Публікує вакансію на всіх платформах одночасно"""
results = await asyncio.gather(
self.publish_hh(jd),
self.publish_avito_rabota(jd),
self.publish_linkedin(jd),
self.publish_superjob(jd),
return_exceptions=True,
)
return {
"hh.ru": results[0] if not isinstance(results[0], Exception) else str(results[0]),
"avito": results[1] if not isinstance(results[1], Exception) else str(results[1]),
"linkedin": results[2] if not isinstance(results[2], Exception) else str(results[2]),
"superjob": results[3] if not isinstance(results[3], Exception) else str(results[3]),
}
async def publish_hh(self, jd: JobDescription) -> str:
response = await hh_api.post(
endpoint="/vacancies",
data={
"name": jd.title,
"area": {"id": "1"}, # Москва
"description": format_for_hh(jd),
"key_skills": [{"name": s} for s in jd.required_skills[:10]],
"employment": {"id": "full"},
"schedule": {"id": "fullDay"},
}
)
return response["id"]
Скринінг та ранжування кандидатів
class CandidateScreener:
async def screen_batch(
self,
candidates: list[dict],
job_description: JobDescription,
required_skills: list[str],
) -> list[dict]:
"""Паралельний скринінг кандидатів"""
semaphore = asyncio.Semaphore(10)
async def screen_one(candidate: dict) -> dict:
async with semaphore:
return await self._screen_single(
candidate, job_description, required_skills
)
results = await asyncio.gather(*[screen_one(c) for c in candidates])
return sorted(results, key=lambda x: -x["score"])
async def _screen_single(
self,
candidate: dict,
jd: JobDescription,
required_skills: list[str],
) -> dict:
from pydantic import BaseModel
from typing import Literal
class ScreeningResult(BaseModel):
score: int # 0-100
recommendation: Literal["strong_yes", "yes", "maybe", "no"]
required_skills_match: int # процент збігу
experience_match: str
red_flags: list[str]
green_flags: list[str]
personalized_question: str # Запитання для інтерв'ю
result = await client.beta.chat.completions.parse(
model="gpt-4o",
messages=[{
"role": "system",
"content": f"""Оцініть кандидата об'єктивно. Необхідні навички: {required_skills}.
НЕ припускайте приховані навички. Враховуйте ТІЛЬКИ явно вказаний досвід."""
}, {
"role": "user",
"content": f"Позиція:\n{jd.title}\n\nРезюме:\n{candidate['resume_text']}"
}],
response_format=ScreeningResult,
temperature=0,
)
return {
"candidate_id": candidate["id"],
"name": candidate["name"],
"email": candidate["email"],
**result.choices[0].message.parsed.model_dump(),
}
Автоматизація комунікації
class CandidateCommunicator:
async def send_invite(self, candidate: dict, vacancy: dict) -> None:
invite_text = await client.chat.completions.create(
model="gpt-4o-mini",
messages=[{
"role": "system",
"content": "Напиши запрошення на інтерв'ю. Тон: повага, конкретність. 3-4 речення."
}, {
"role": "user",
"content": f"Кандидат: {candidate['name']}, позиція: {vacancy['title']}, компанія: {vacancy['company']}"
}],
)
await email_service.send(
to=candidate["email"],
subject=f"Запрошення на інтерв'ю — {vacancy['title']} у {vacancy['company']}",
body=invite_text.choices[0].message.content + f"\n\nДля запису: {CALENDLY_URL}",
)
async def send_rejection(self, candidate: dict, reason: str) -> None:
"""Персоналізований відмова з конкретною причиною"""
rejection = await client.chat.completions.create(
model="gpt-4o-mini",
messages=[{
"role": "system",
"content": "Напиши ввічливу відмову. Без кліше. Зазначте конкретну причину (з повагою). 2-3 речення."
}, {
"role": "user",
"content": f"Кандидат: {candidate['name']}, причина відмови: {reason}"
}],
)
await email_service.send(
to=candidate["email"],
subject="Статус вашої заявки",
body=rejection.choices[0].message.content,
)
Практичний кейс: IT-компанія, 30 відкритих вакансій
Ситуація: 3 рекрутери, 30 активних вакансій, 200+ резюме/день, час до найму 52 дні.
Автоматизовано AI Рекрутером:
- Парсинг резюме з платформ кожні 2 години
- Скринінг та оцінка (первинна фільтрація 70%)
- Автоматичні відмови нерелевантним кандидатам
- Запрошення для топ-30% з посиланням Calendly
- Нагадування кандидатам, які не відповіли на запрос інтерв'ю
Результати:
- Час до найму: 52 → 31 день
- Час відповіді кандидату: 3.2 дня → 40 хвилин
- Рекрутери зосереджуються на: інтерв'ю, пропозиціях, онбордингу
- Узгодженість (AI vs рекрутер на 300 спільно оцінених): 87%
Часова шкала
- Генератор JD та публікація: 1–2 тижні
- Скринінг та ранжування: 2–3 тижні
- Шаблони комунікації та інтеграція email: 1 тиждень
- Інтеграція ATS (HH, Huntflow та інші): 1–2 тижні
- Разом: 5–8 тижнів







