Что получится в результате
Соберем ИИ-агента, который помогает рекрутеру разбирать входящие резюме, сверять их с требованиями вакансии, готовить карточку кандидата, вопросы для интервью, внутреннюю заметку в ATS и черновик письма кандидату.
В результате будет рабочий прототип:
- вакансия хранится в таблице `job_profiles`;
- входящее резюме попадает в `candidate_inbox`;
- агент извлекает факты в `candidate_profiles`;
- требования вакансии проверяются по `screening_criteria`;
- результаты scoring пишутся в `screening_results`;
- риски и запрещенные признаки пишутся в `fairness_checks`;
- вопросы для интервью попадают в `interview_questions`;
- письмо кандидату создается как draft в `message_drafts`;
- рекрутер принимает решение в `review_queue`;
- после approval агент создает заметку или задачу в ATS;
- каждое действие фиксируется в `audit_log`.
Агент не должен автоматически отказывать кандидату, принимать решение о найме, оценивать человека по возрасту, полу, семейному положению, национальности, здоровью, фотографии, имени или другим нерелевантным признакам.
Что понадобится
Минимальный набор:
- Google Sheets как база прототипа.
- n8n для workflow.
- Папка с резюме: PDF, DOCX или ссылки.
- LLM API для извлечения фактов и summary.
- ATS: Greenhouse, Lever, Workable, Huntflow, Potok, Хантфлоу, hh.ru API или ручной CSV.
- Google Calendar или Microsoft Calendar для слотов интервью.
- Gmail, Outlook или Telegram для черновиков уведомлений.
- Рекрутер или hiring manager, который подтвердит критерии вакансии.
- Политика компании по персональным данным и хранению резюме.
Для первого запуска хватит Google Sheets, n8n, папки с 10 тестовыми резюме и одной вакансии.
Шаг 1. Выберите одну вакансию для MVP
Не запускайте агента сразу на все вакансии.
Возьмите вакансию, где:
- требования уже понятны;
- есть 10-30 реальных или обезличенных резюме;
- рекрутер может быстро проверить результат;
- нет спорных юридических требований;
- критерии можно подтвердить фактами из резюме.
Хороший пример:
Backend Python developer
Middle+
FastAPI, PostgreSQL, Docker, REST API
Опыт от 3 лет
Формат: remote
Плохой пример:
Нам нужен сильный человек с правильным вайбом.
Проверка: вакансию можно описать в `job_profiles` без абстрактных фраз.
Шаг 2. Запретите опасные решения
Создайте системное правило для HR-агента.
Запретите агенту:
- автоматически отказывать кандидату;
- автоматически переводить кандидата в hired;
- удалять кандидата из ATS;
- менять зарплатные ожидания кандидата;
- делать выводы по возрасту;
- учитывать пол, национальность, семейное положение, здоровье, фото, имя, религию;
- делать психологический диагноз;
- обещать оффер;
- отправлять письмо кандидату без approval;
- скрывать низкую уверенность или отсутствующие данные.
Prompt:
Ты помощник рекрутера.
Ты извлекаешь факты из резюме и сравниваешь их с критериями вакансии.
Ты не принимаешь решение о найме или отказе.
Ты не используешь защищенные и нерелевантные признаки.
Если данных не хватает, пиши missing_data.
Любой отказ, оффер, письмо кандидату и изменение ATS требует human approval.
Проверка: даже если резюме явно не подходит, агент ставит `needs_recruiter_review`, а не `rejected`.
Шаг 3. Создайте Google Sheet проекта
Создайте таблицу `hr_recruiting_agent_mvp`.
Добавьте листы:
hr_settings
job_profiles
screening_criteria
candidate_inbox
candidate_profiles
screening_results
fairness_checks
interview_questions
message_drafts
review_queue
interview_slots
ats_updates
report_snapshots
audit_log
error_log
Проверка: первая строка каждого листа занята заголовками, доступ к таблице есть только у HR-команды и владельца вакансии.
Шаг 4. Заполните hr_settings
Лист `hr_settings` хранит пороги и правила.
Колонки:
key
value
description
updated_by
updated_at
Стартовые строки:
resume_parse_min_confidence | 0.82 | минимальная уверенность извлечения резюме
criteria_match_min_score | 0.70 | порог для review как потенциально релевантного
auto_reject_allowed | no | автоотказ запрещен
candidate_message_requires_approval | yes | письма только после approval
ats_write_requires_approval | yes | запись в ATS только после approval
protected_attributes_allowed | no | защищенные признаки не используются
retention_days | 180 | срок хранения резюме в прототипе
calendar_invite_requires_approval | yes | инвайт только после approval
Проверка: workflow читает `auto_reject_allowed = no` и не имеет ветки автоматического отказа.
Шаг 5. Создайте job_profiles
Лист `job_profiles` описывает вакансии.
Колонки:
job_id
job_title
department
level
location
work_format
salary_range
must_have
nice_to_have
responsibilities
screening_questions
hiring_manager
recruiter
status
created_at
Пример:
job_backend_001 | Backend Python Developer | Product | Middle+ | Россия | remote | 250-350k gross | Python 3, FastAPI, PostgreSQL, Docker, REST API, 3+ years | Kafka, Kubernetes, CI/CD | backend services, integrations, code review | опыт FastAPI? опыт PostgreSQL? | Иван | Мария | active | 2026-05-23
Проверка: у вакансии есть `must_have`, `nice_to_have`, рекрутер и hiring manager.
Шаг 6. Создайте screening_criteria
Лист `screening_criteria` превращает требования в проверяемые критерии.
Колонки:
criterion_id
job_id
criterion
type
weight
evidence_required
knockout
comment
Типы:
skill
experience
domain
language
location
work_format
salary
portfolio
Пример:
cr_001 | job_backend_001 | Python 3 | skill | 20 | упоминание Python в опыте или проектах | yes | базовый must-have
cr_002 | job_backend_001 | FastAPI | skill | 15 | опыт в коммерческом проекте | no | можно уточнить
cr_003 | job_backend_001 | PostgreSQL | skill | 15 | опыт БД, SQL, оптимизация | yes | must-have
cr_004 | job_backend_001 | 3+ years backend | experience | 20 | годы опыта или даты проектов | yes | must-have
cr_005 | job_backend_001 | remote | work_format | 10 | готовность к удаленной работе | no | уточнить, если не найдено
Проверка: каждый критерий можно подтвердить цитатой или фактом из резюме.
Шаг 7. Создайте candidate_inbox
Лист `candidate_inbox` хранит входящие резюме и заявки.
Колонки:
candidate_id
received_at
source
job_id
file_name
file_url
candidate_name_raw
email
phone
ats_candidate_id
status
parse_confidence
assigned_to
error_code
comment
Статусы:
new
parsed
screened
needs_review
ready_for_recruiter
approved_for_message
message_sent
interview_requested
archived
Проверка: каждое резюме получает `candidate_id` до вызова LLM.
Шаг 8. Настройте загрузку резюме
В n8n создайте workflow `hr_candidate_intake`.
Узлы:
- `Google Drive Trigger` или webhook ATS;
- `Set candidate_id`;
- `Get file metadata`;
- `Extract text from PDF/DOCX`;
- `Google Sheets Append candidate_inbox`;
- `Audit append file_received`.
Пример `candidate_id`:
cand_20260523_0001
Проверка: после загрузки резюме появляется строка в `candidate_inbox` со статусом `new`.
Шаг 9. Извлеките факты из резюме
Создайте лист `candidate_profiles`.
Колонки:
candidate_id
job_id
full_name
email
phone
location
work_format
current_role
total_years_experience
skills_json
companies_json
education_json
languages_json
salary_expectation
notice_period
portfolio_links
facts_json
missing_data_json
confidence_json
parsed_at
Проверка: агент извлекает только факты, которые есть в резюме, и не додумывает опыт.
Шаг 10. Настройте prompt для парсинга резюме
Prompt:
Извлеки факты из резюме.
Верни только JSON без Markdown.
Не оценивай человека.
Не используй возраст, пол, фото, семейное положение, национальность, здоровье, религию и другие нерелевантные признаки.
Если данных нет, верни null или добавь поле в missing_data.
Схема:
{
"full_name": "string|null",
"email": "string|null",
"phone": "string|null",
"location": "string|null",
"work_format": "remote|office|hybrid|unknown",
"current_role": "string|null",
"total_years_experience": 0,
"skills": ["string"],
"companies": [
{
"name": "string",
"role": "string",
"start": "YYYY-MM|null",
"end": "YYYY-MM|null",
"facts": ["string"]
}
],
"education": ["string"],
"languages": ["string"],
"salary_expectation": "string|null",
"notice_period": "string|null",
"portfolio_links": ["string"],
"facts": ["short factual bullet"],
"missing_data": ["string"],
"confidence": {
"contacts": 0,
"experience": 0,
"skills": 0,
"salary": 0
}
}
Проверка: JSON валиден, а спорные поля попадают в `missing_data`.
Шаг 11. Очистите защищенные признаки
Создайте лист `fairness_checks`.
Колонки:
check_id
candidate_id
job_id
protected_attribute_detected
attribute_type
action
safe_summary
checked_at
Запрещенные типы:
age
gender
marital_status
children
nationality
religion
health
photo
political_views
Правило:
Если защищенный признак встретился в резюме, не использовать его в score, summary, вопросах и решении.
Проверка: если резюме содержит дату рождения, она не появляется в `screening_results`.
Шаг 12. Сравните кандидата с вакансией
Создайте лист `screening_results`.
Колонки:
screening_id
candidate_id
job_id
total_score
must_have_status
nice_to_have_score
criteria_json
confirmed_evidence_json
missing_requirements_json
risk_flags_json
recommendation
status
screened_at
Статусы:
ready_for_recruiter
needs_review
missing_data
not_enough_evidence
Рекомендации:
review_first
review_normal
ask_clarifying_questions
not_enough_evidence
Проверка: нет значения `reject`, потому что отказ делает человек.
Шаг 13. Настройте prompt для screening
Prompt:
Сравни факты кандидата с критериями вакансии.
Используй только candidate_profiles и screening_criteria.
Не используй защищенные признаки.
Для каждого критерия укажи evidence или missing.
Не принимай решение о найме или отказе.
Верни JSON:
{
"total_score": 0,
"must_have_status": "met|partially_met|not_enough_evidence|not_met",
"criteria": [
{
"criterion_id": "string",
"status": "met|partial|missing|not_enough_evidence",
"score": 0,
"evidence": "short quote or fact",
"question_to_clarify": "string|null"
}
],
"missing_requirements": ["string"],
"risk_flags": ["string"],
"recommendation": "review_first|review_normal|ask_clarifying_questions|not_enough_evidence"
}
Проверка: каждый балл объяснен фактом, а не общей фразой “кандидат сильный”.
Шаг 14. Создайте карточку кандидата
Карточка для рекрутера должна быть короткой.
Сохраните в `screening_results.confirmed_evidence_json`:
{
"summary": "Backend-разработчик с 5 годами Python, FastAPI и PostgreSQL",
"strong_matches": ["Python 3", "FastAPI", "PostgreSQL", "Docker"],
"missing_or_unclear": ["salary_expectation", "notice_period", "Kafka"],
"questions": ["Уточнить коммерческий опыт с Kafka", "Уточнить ожидания по доходу"],
"do_not_use": ["age", "photo"]
}
Проверка: карточку можно прочитать за 30 секунд и понять, что проверять дальше.
Шаг 15. Создайте interview_questions
Лист `interview_questions` хранит вопросы для первичного скрининга.
Колонки:
question_id
candidate_id
job_id
criterion_id
question_text
purpose
priority
status
created_at
Типовые вопросы:
Уточните, на каких проектах вы использовали FastAPI?
Сколько лет коммерческого опыта с PostgreSQL?
Был ли опыт оптимизации SQL-запросов?
Какой формат работы вам подходит?
Какой срок выхода на новое место?
Проверка: вопросы связаны с критериями вакансии, а не с личными признаками.
Шаг 16. Создайте message_drafts
Лист `message_drafts` хранит черновики писем и сообщений.
Колонки:
draft_id
candidate_id
job_id
message_type
channel
subject
body
status
approved_by
approved_at
sent_at
created_at
Типы:
clarifying_questions
interview_invite
thank_you
manual_reject_draft
Важно: даже reject draft не отправляется автоматически. Рекрутер проверяет причину, тон и соответствие правилам компании.
Проверка: новый draft получает `status = waiting_approval`.
Шаг 17. Настройте prompt для письма кандидату
Prompt:
Составь вежливый черновик письма кандидату.
Не обещай оффер.
Не сообщай автоматический отказ.
Не упоминай внутренний score.
Не используй защищенные признаки.
Если нужно уточнить данные, задай до 5 конкретных вопросов.
Письмо должно быть готово для проверки рекрутером.
Пример результата:
Здравствуйте, Анна.
Спасибо за отклик на вакансию Backend Python Developer.
Хотим уточнить несколько деталей перед следующим шагом:
1. На каких проектах вы использовали FastAPI?
2. Есть ли коммерческий опыт с PostgreSQL и оптимизацией SQL?
3. Какой формат работы вам подходит?
4. Какой срок выхода на новое место?
После ответа рекрутер вернется с дальнейшими шагами.
Проверка: письмо не содержит оценки “вы нам не подходите” без решения человека.
Шаг 18. Создайте review_queue
Лист `review_queue` нужен для решений рекрутера.
Колонки:
review_id
candidate_id
job_id
review_type
status
assigned_to
decision
decision_comment
created_at
decided_at
Типы review:
candidate_screening
message_approval
ats_note_approval
interview_invite_approval
manual_reject_review
Решения:
approved
needs_fix
reject_by_recruiter
move_to_interview
archive
Проверка: любые внешние сообщения и ATS write-actions проходят через `review_queue`.
Шаг 19. Подключите ATS в read-only режиме
Для Greenhouse, Lever или Workable сначала используйте чтение.
Разрешите:
- читать вакансии;
- читать кандидатов;
- читать applications;
- читать stages;
- читать interview slots;
- читать notes, если это разрешено политикой компании.
Запретите без approval:
- changing stage;
- reject candidate;
- create offer;
- update salary;
- send email;
- delete candidate;
- merge profiles.
Проверка: API credentials не имеют права удалить или отклонить кандидата.
Шаг 20. Создайте ats_updates
Лист `ats_updates` хранит то, что можно записать в ATS после approval.
Колонки:
update_id
candidate_id
job_id
target_system
action_type
payload_json
status
approved_by
approved_at
external_id
error_message
created_at
Разрешенные `action_type`:
create_internal_note
create_task
add_tag_needs_review
add_interview_questions_note
Запрещенные:
reject_candidate
hire_candidate
send_offer
delete_candidate
change_salary
Проверка: после approval создается внутренняя заметка, а не меняется статус кандидата.
Шаг 21. Подготовьте internal note для ATS
Internal note должна быть объяснимой.
Шаблон:
AI summary for recruiter review
Candidate: {{full_name}}
Job: {{job_title}}
Recommendation: {{recommendation}}
Confirmed evidence:
- {{criterion}}: {{evidence}}
Missing or unclear:
- {{missing_requirement}}
Suggested interview questions:
- {{question}}
Important: this is not a hiring decision. Recruiter review required.
Проверка: заметка не содержит protected attributes и не выглядит как финальное решение.
Шаг 22. Создайте interview_slots
Лист `interview_slots` хранит возможное время интервью.
Колонки:
slot_id
candidate_id
job_id
interviewer
start_at
end_at
timezone
calendar_event_id
status
approved_by
created_at
Статусы:
suggested
approved_to_send
sent_to_candidate
booked
cancelled
Проверка: агент предлагает слоты, но не отправляет calendar invite без approval.
Шаг 23. Настройте подбор слотов интервью
Workflow `hr_interview_scheduling`:
- получить кандидата со статусом `move_to_interview`;
- прочитать календарь рекрутера и интервьюера;
- найти 3-5 свободных слотов;
- записать их в `interview_slots`;
- создать draft письма в `message_drafts`;
- поставить `review_queue.review_type = interview_invite_approval`.
Правила:
- не ставить интервью вне рабочего времени;
- учитывать timezone кандидата, если она известна;
- не отправлять ссылку на встречу до approval;
- не раскрывать внутренние комментарии кандидату.
Проверка: рекрутер видит список слотов и утверждает письмо.
Шаг 24. Добавьте обработку missing_data
Если данных не хватает, агент должен спрашивать, а не додумывать.
Типовые missing_data:
salary_expectation
notice_period
work_format
english_level
commercial_experience_years
specific_skill_evidence
portfolio_link
Если `missing_data` критичны:
- создайте `message_drafts.message_type = clarifying_questions`;
- поставьте `review_queue.status = waiting`;
- не ставьте высокий score по отсутствующим данным;
- не меняйте stage в ATS.
Проверка: если в резюме нет зарплатных ожиданий, агент спрашивает кандидата, а не придумывает сумму.
Шаг 25. Настройте error_log
Лист `error_log` хранит технические и процессные ошибки.
Колонки:
error_id
created_at
candidate_id
job_id
step
severity
error_code
message
raw_payload_url
resolved
resolved_by
resolved_at
Типовые `error_code`:
resume_parse_failed
llm_json_invalid
missing_job_profile
missing_criteria
protected_attribute_leak
ats_api_failed
calendar_api_failed
message_draft_failed
low_confidence
Проверка: если LLM вернула невалидный JSON, кандидат не теряется, а получает ошибку `llm_json_invalid`.
Шаг 26. Настройте audit_log
Лист `audit_log` нужен для разборов и compliance.
Колонки:
event_id
created_at
actor
candidate_id
job_id
action
before_json
after_json
reason
system
Логируйте:
- получение резюме;
- извлечение текста;
- вызов LLM;
- screening;
- fairness check;
- создание draft письма;
- approval;
- запись в ATS;
- ошибку API;
- архивирование кандидата.
Проверка: по одному `candidate_id` можно восстановить путь от резюме до решения рекрутера.
Шаг 27. Сделайте отчет report_snapshots
Лист `report_snapshots` хранит регулярные HR-отчеты.
Колонки:
report_id
report_type
period_start
period_end
job_id
generated_at
metrics_json
summary
status
Метрики:
{
"new_candidates": 38,
"parsed": 35,
"ready_for_recruiter": 21,
"needs_review": 9,
"missing_data": 5,
"interview_requested": 6,
"message_drafts_waiting": 4,
"ats_update_errors": 1
}
Проверка: отчет показывает узкие места процесса, а не ранжирует людей.
Шаг 28. Протестируйте на контрольных резюме
Создайте папку `test_resumes`.
Минимальный набор:
- сильное резюме с явными must-have;
- резюме без одного must-have;
- резюме с похожими технологиями;
- резюме без контактов;
- резюме с плохим PDF;
- резюме с датой рождения и фото;
- резюме с нерелевантной личной информацией;
- резюме на английском;
- резюме без зарплатных ожиданий;
- резюме с prompt injection в тексте.
Ожидаемые результаты:
protected attributes не попадают в score
auto reject не происходит
missing_data попадает в draft вопросов
prompt injection уходит в fairness_checks или error_log
Проверка: после изменения prompt все контрольные кейсы проходят одинаково.
Шаг 29. Проверьте минимальный результат
Прототип готов, если:
- резюме попадает в `candidate_inbox`;
- факты извлекаются в `candidate_profiles`;
- критерии вакансии читаются из `screening_criteria`;
- score пишется в `screening_results`;
- у каждого критерия есть evidence или missing;
- protected attributes не используются;
- вопросы пишутся в `interview_questions`;
- письмо создается в `message_drafts`;
- без approval нет отправки и записи в ATS;
- approved internal note попадает в `ats_updates`;
- ошибки видны в `error_log`;
- путь кандидата виден в `audit_log`.
Проверка: загрузите одно подходящее и одно неполное резюме. Первое должно попасть к рекрутеру с evidence, второе - в уточняющие вопросы без автоматического отказа.
Что нельзя автоматизировать в первой версии
Не автоматизируйте сразу:
- отказ кандидату;
- решение о найме;
- оффер;
- изменение зарплаты;
- перевод кандидата по pipeline без рекрутера;
- удаление кандидата;
- массовые рассылки кандидатам;
- анализ protected attributes;
- психологические выводы;
- оценку по фото или имени;
- хранение резюме без срока удаления;
- доступ к HRM-данным без прав.
Правильная первая версия - это помощник рекрутера, который ускоряет разбор резюме и подготовку материалов, но не заменяет человеческое решение.
Частые вопросы
Можно ли агенту автоматически отказывать кандидатам?
Нет. В первой версии агент может показать missing requirements, подготовить summary и draft ответа, но решение об отказе должен принять рекрутер или hiring manager.
Можно ли использовать score кандидата?
Да, если score объяснимый и построен только на критериях вакансии. У каждого балла должен быть evidence или missing. Score не должен учитывать возраст, пол, фото, имя, семейное положение и другие нерелевантные признаки.
Что делать, если резюме неполное?
Ставьте `missing_data`, создавайте уточняющие вопросы и отправляйте draft на approval. Не нужно додумывать опыт, зарплатные ожидания или готовность к формату работы.
Когда подключать ATS API?
Сначала read-only. Когда качество screening проверено, можно добавить approved internal note или задачу рекрутеру. Отказы, офферы и смена stage должны оставаться под контролем человека.
Какой минимум нужен для запуска?
Таблицы `job_profiles`, `screening_criteria`, `candidate_inbox`, `candidate_profiles`, `screening_results`, `fairness_checks`, `message_drafts`, `review_queue`, n8n workflow, парсер резюме, LLM JSON extraction и обязательный `audit_log`.