upd promts; fix reports
This commit is contained in:
parent
8d449af338
commit
9128bb8881
@ -65,9 +65,8 @@ class InterviewAgent:
|
||||
self.interview_finalized = False # Флаг завершения интервью
|
||||
|
||||
# Трекинг времени интервью
|
||||
import time
|
||||
|
||||
self.interview_start_time = time.time()
|
||||
self.interview_start_time = None # Устанавливается при фактическом старте
|
||||
self.interview_end_time = None # Устанавливается при завершении
|
||||
self.duration_minutes = interview_plan.get("interview_structure", {}).get(
|
||||
"duration_minutes", 10
|
||||
)
|
||||
@ -77,10 +76,6 @@ class InterviewAgent:
|
||||
)
|
||||
self.total_sections = len(self.sections)
|
||||
|
||||
logger.info(
|
||||
f"[TIME] Interview started at {time.strftime('%H:%M:%S')}, duration: {self.duration_minutes} min"
|
||||
)
|
||||
|
||||
def get_current_section(self) -> dict:
|
||||
"""Получить текущую секцию интервью"""
|
||||
if self.current_section < len(self.sections):
|
||||
@ -126,11 +121,10 @@ class InterviewAgent:
|
||||
key_evaluation_points = self.interview_plan.get("key_evaluation_points", [])
|
||||
|
||||
# Вычисляем текущее время интервью
|
||||
import time
|
||||
|
||||
elapsed_minutes = (time.time() - self.interview_start_time) / 60
|
||||
remaining_minutes = max(0, self.duration_minutes - elapsed_minutes)
|
||||
time_percentage = min(100, (elapsed_minutes / self.duration_minutes) * 100)
|
||||
time_info = self.get_time_info()
|
||||
elapsed_minutes = time_info["elapsed_minutes"]
|
||||
remaining_minutes = time_info["remaining_minutes"]
|
||||
time_percentage = time_info["time_percentage"]
|
||||
|
||||
# Формируем план интервью для агента
|
||||
sections_info = "\n".join(
|
||||
@ -193,7 +187,7 @@ class InterviewAgent:
|
||||
- Контактное лицо: {self.vacancy_data.get('contacts_name') or 'Не указано'}"""
|
||||
|
||||
return f"""
|
||||
Ты опытный HR-интервьюер, который проводит адаптивное голосовое собеседование. Представься контактным именем из вакансии (если оно есть)
|
||||
Ты опытный HR-интервьюер Стефани, который проводит адаптивное голосовое собеседование. Представься контактным именем из вакансии (если оно есть)
|
||||
|
||||
ИНФОРМАЦИЯ О ВАКАНСИИ:
|
||||
|
||||
@ -233,6 +227,10 @@ class InterviewAgent:
|
||||
Проблемные / кейсы (20%) — проверить мышление и подход к решению.
|
||||
Пример: "У нас есть система, которая падает раз в неделю. Как бы ты подошёл к диагностике проблемы?"
|
||||
|
||||
Задавай вопросы кратко и понятно. Не вываливай кучу информации на человека.
|
||||
Не перечисляй человеку все пункты и вопросы из секции. Предлагай один общий вопрос или задавай уточняющие по по очереди.
|
||||
Ты должна спрашивать вопросы максимум в 3 предложения
|
||||
|
||||
ВРЕМЯ ИНТЕРВЬЮ:
|
||||
- Запланированная длительность: {self.duration_minutes} минут
|
||||
- Прошло времени: {elapsed_minutes:.1f} минут ({time_percentage:.0f}%)
|
||||
@ -286,9 +284,17 @@ class InterviewAgent:
|
||||
"""Получает информацию о времени интервью"""
|
||||
import time
|
||||
|
||||
elapsed_minutes = (time.time() - self.interview_start_time) / 60
|
||||
remaining_minutes = max(0.0, self.duration_minutes - elapsed_minutes)
|
||||
time_percentage = min(100.0, (elapsed_minutes / self.duration_minutes) * 100)
|
||||
if self.interview_start_time is None:
|
||||
# Интервью еще не началось
|
||||
elapsed_minutes = 0.0
|
||||
remaining_minutes = float(self.duration_minutes)
|
||||
time_percentage = 0.0
|
||||
else:
|
||||
# Интервью идет
|
||||
current_time = self.interview_end_time or time.time()
|
||||
elapsed_minutes = (current_time - self.interview_start_time) / 60
|
||||
remaining_minutes = max(0.0, self.duration_minutes - elapsed_minutes)
|
||||
time_percentage = min(100.0, (elapsed_minutes / self.duration_minutes) * 100)
|
||||
|
||||
return {
|
||||
"elapsed_minutes": elapsed_minutes,
|
||||
@ -421,8 +427,9 @@ async def entrypoint(ctx: JobContext):
|
||||
# TTS
|
||||
tts = (
|
||||
openai.TTS(
|
||||
model="gpt-4o-mini-tts",
|
||||
model="tts-1-hd",
|
||||
api_key=settings.openai_api_key,
|
||||
voice='coral'
|
||||
)
|
||||
if settings.openai_api_key
|
||||
else silero.TTS(language="ru", model="v4_ru")
|
||||
@ -474,6 +481,18 @@ async def entrypoint(ctx: JobContext):
|
||||
|
||||
interviewer_instance.interview_finalized = True
|
||||
|
||||
# Устанавливаем время завершения интервью
|
||||
import time
|
||||
interviewer_instance.interview_end_time = time.time()
|
||||
|
||||
if interviewer_instance.interview_start_time:
|
||||
total_minutes = (interviewer_instance.interview_end_time - interviewer_instance.interview_start_time) / 60
|
||||
logger.info(
|
||||
f"[TIME] Interview ended at {time.strftime('%H:%M:%S')}, total duration: {total_minutes:.1f} min"
|
||||
)
|
||||
else:
|
||||
logger.info(f"[TIME] Interview ended at {time.strftime('%H:%M:%S')} (no start time recorded)")
|
||||
|
||||
try:
|
||||
logger.info(
|
||||
f"[FINALIZE] Starting interview finalization for room: {room_name}"
|
||||
@ -543,11 +562,13 @@ async def entrypoint(ctx: JobContext):
|
||||
|
||||
# --- Мониторинг команд завершения ---
|
||||
async def monitor_end_commands():
|
||||
"""Мониторит команды завершения сессии"""
|
||||
"""Мониторит команды завершения сессии и лимит времени"""
|
||||
command_file = "agent_commands.json"
|
||||
TIME_LIMIT_MINUTES = 60 # Жесткий лимит времени интервью
|
||||
|
||||
while not interviewer.interview_finalized:
|
||||
try:
|
||||
# Проверяем команды завершения
|
||||
if os.path.exists(command_file):
|
||||
with open(command_file, encoding="utf-8") as f:
|
||||
command = json.load(f)
|
||||
@ -566,71 +587,50 @@ async def entrypoint(ctx: JobContext):
|
||||
)
|
||||
break
|
||||
|
||||
await asyncio.sleep(1) # Проверяем каждые 1 секунды
|
||||
# Проверяем превышение лимита времени
|
||||
if interviewer.interview_start_time is not None:
|
||||
time_info = interviewer.get_time_info()
|
||||
if time_info["elapsed_minutes"] >= TIME_LIMIT_MINUTES:
|
||||
logger.warning(
|
||||
f"[TIME_LIMIT] Interview exceeded {TIME_LIMIT_MINUTES} minutes "
|
||||
f"({time_info['elapsed_minutes']:.1f} min), forcing completion"
|
||||
)
|
||||
|
||||
if not interviewer.interview_finalized:
|
||||
await complete_interview_sequence(
|
||||
ctx.room.name, interviewer
|
||||
)
|
||||
break
|
||||
|
||||
await asyncio.sleep(2) # Проверяем каждые 5 секунд
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[COMMAND] Error monitoring commands: {str(e)}")
|
||||
await asyncio.sleep(5)
|
||||
await asyncio.sleep(2)
|
||||
|
||||
# Запускаем мониторинг команд в фоне
|
||||
asyncio.create_task(monitor_end_commands())
|
||||
|
||||
# --- Обработчик состояния пользователя (замена мониторинга тишины) ---
|
||||
disconnect_timer: asyncio.Task | None = None
|
||||
|
||||
@session.on("user_state_changed")
|
||||
def on_user_state_changed(event):
|
||||
"""Обработчик изменения состояния пользователя (активен/неактивен)"""
|
||||
|
||||
async def on_change():
|
||||
nonlocal disconnect_timer
|
||||
|
||||
logger.info(f"[USER_STATE] User state changed to: {event.new_state}")
|
||||
|
||||
# === Пользователь начал говорить ===
|
||||
if event.new_state == "speaking":
|
||||
# Если есть таймер на 30 секунд — отменяем его
|
||||
if disconnect_timer is not None:
|
||||
logger.info("[USER_STATE] Cancelling disconnect timer due to speaking")
|
||||
disconnect_timer.cancel()
|
||||
disconnect_timer = None
|
||||
|
||||
# === Пользователь молчит более 10 секунд (state == away) ===
|
||||
elif event.new_state == "away" and interviewer.intro_done:
|
||||
if event.new_state == "away" and interviewer.intro_done:
|
||||
logger.info("[USER_STATE] User away detected, sending check-in message...")
|
||||
|
||||
# 1) Первое сообщение — проверка связи
|
||||
handle = await session.generate_reply(
|
||||
# сообщение — проверка связи
|
||||
await session.generate_reply(
|
||||
instructions=(
|
||||
"Клиент молчит уже больше 10 секунд. "
|
||||
"Проверь связь фразой вроде 'Приём! Ты меня слышишь?' "
|
||||
"или 'Связь не пропала?'"
|
||||
)
|
||||
)
|
||||
await handle # ждем завершения первой реплики
|
||||
|
||||
# 2) Таймер на 30 секунд
|
||||
async def disconnect_timeout():
|
||||
try:
|
||||
await asyncio.sleep(30)
|
||||
logger.info("[DISCONNECT_TIMER] 30 seconds passed, sending disconnect message")
|
||||
|
||||
# Второе сообщение — считаем, что клиент отключился
|
||||
await session.generate_reply(
|
||||
instructions="Похоже клиент отключился"
|
||||
)
|
||||
|
||||
logger.info("[DISCONNECT_TIMER] Disconnect message sent successfully")
|
||||
except asyncio.CancelledError:
|
||||
logger.info("[DISCONNECT_TIMER] Timer cancelled before completion")
|
||||
except Exception as e:
|
||||
logger.error(f"[DISCONNECT_TIMER] Error in disconnect timeout: {e}")
|
||||
|
||||
# 3) Если уже есть активный таймер — отменяем его перед запуском нового
|
||||
if disconnect_timer is not None:
|
||||
disconnect_timer.cancel()
|
||||
|
||||
disconnect_timer = asyncio.create_task(disconnect_timeout())
|
||||
|
||||
asyncio.create_task(on_change())
|
||||
|
||||
@ -705,6 +705,12 @@ async def entrypoint(ctx: JobContext):
|
||||
# Обновляем прогресс интервью
|
||||
if not interviewer.intro_done:
|
||||
interviewer.intro_done = True
|
||||
# Устанавливаем время начала интервью при первом сообщении
|
||||
import time
|
||||
interviewer.interview_start_time = time.time()
|
||||
logger.info(
|
||||
f"[TIME] Interview started at {time.strftime('%H:%M:%S')}, duration: {interviewer.duration_minutes} min"
|
||||
)
|
||||
|
||||
# Обновляем счетчик сообщений и треким время
|
||||
interviewer.questions_asked_total += 1
|
||||
|
@ -233,7 +233,7 @@ class InterviewRoomService:
|
||||
# Если плана нет, создаем базовый план на основе имеющихся данных
|
||||
fallback_plan = {
|
||||
"interview_structure": {
|
||||
"duration_minutes": 30,
|
||||
"duration_minutes": 45,
|
||||
"greeting": f"Привет, {resume.applicant_name}! Готов к интервью?",
|
||||
"sections": [
|
||||
{
|
||||
|
@ -28,6 +28,21 @@ class PDFReportService:
|
||||
self.styles = getSampleStyleSheet()
|
||||
self._setup_custom_styles()
|
||||
|
||||
def _format_list_field(self, field_value) -> str:
|
||||
"""Форматирует поле со списком для отображения в PDF"""
|
||||
if not field_value:
|
||||
return "—"
|
||||
|
||||
if isinstance(field_value, list):
|
||||
# Если это список, объединяем элементы
|
||||
return "\n• ".join([""] + field_value)
|
||||
elif isinstance(field_value, str):
|
||||
# Если это строка, возвращаем как есть
|
||||
return field_value
|
||||
else:
|
||||
# Для других типов конвертируем в строку
|
||||
return str(field_value)
|
||||
|
||||
def _register_fonts(self):
|
||||
"""Регистрация шрифтов для поддержки кириллицы"""
|
||||
try:
|
||||
@ -212,31 +227,31 @@ class PDFReportService:
|
||||
Paragraph("Технические навыки", table_text_style),
|
||||
Paragraph(f"{report.technical_skills_score}/100", table_text_style),
|
||||
Paragraph(report.technical_skills_justification or "—", table_text_style),
|
||||
Paragraph(report.technical_skills_concerns or "—", table_text_style),
|
||||
Paragraph(self._format_list_field(report.technical_skills_concerns), table_text_style),
|
||||
],
|
||||
[
|
||||
Paragraph("Релевантность опыта", table_text_style),
|
||||
Paragraph(f"{report.experience_relevance_score}/100", table_text_style),
|
||||
Paragraph(report.experience_relevance_justification or "—", table_text_style),
|
||||
Paragraph(report.experience_relevance_concerns or "—", table_text_style),
|
||||
Paragraph(self._format_list_field(report.experience_relevance_concerns), table_text_style),
|
||||
],
|
||||
[
|
||||
Paragraph("Коммуникация", table_text_style),
|
||||
Paragraph(f"{report.communication_score}/100", table_text_style),
|
||||
Paragraph(report.communication_justification or "—", table_text_style),
|
||||
Paragraph(report.communication_concerns or "—", table_text_style),
|
||||
Paragraph(self._format_list_field(report.communication_concerns), table_text_style),
|
||||
],
|
||||
[
|
||||
Paragraph("Решение задач", table_text_style),
|
||||
Paragraph(f"{report.problem_solving_score}/100", table_text_style),
|
||||
Paragraph(report.problem_solving_justification or "—", table_text_style),
|
||||
Paragraph(report.problem_solving_concerns or "—", table_text_style),
|
||||
Paragraph(self._format_list_field(report.problem_solving_concerns), table_text_style),
|
||||
],
|
||||
[
|
||||
Paragraph("Культурное соответствие", table_text_style),
|
||||
Paragraph(f"{report.cultural_fit_score}/100", table_text_style),
|
||||
Paragraph(report.cultural_fit_justification or "—", table_text_style),
|
||||
Paragraph(report.cultural_fit_concerns or "—", table_text_style),
|
||||
Paragraph(self._format_list_field(report.cultural_fit_concerns), table_text_style),
|
||||
],
|
||||
]
|
||||
|
||||
@ -343,15 +358,15 @@ class PDFReportService:
|
||||
)
|
||||
for weakness in report.weaknesses:
|
||||
story.append(Paragraph(f"• {weakness}", self.styles["CustomBodyText"]))
|
||||
story.append(Spacer(1, 10))
|
||||
story.append(Spacer(1, 15))
|
||||
|
||||
# Красные флаги
|
||||
if report.red_flags:
|
||||
story.append(Paragraph("Важные риски:", self.styles["SubHeader"]))
|
||||
story.append(Paragraph("Красные флаги:", self.styles["SectionHeader"]))
|
||||
for red_flag in report.red_flags:
|
||||
story.append(
|
||||
Paragraph(
|
||||
f"⚠ {red_flag}",
|
||||
f"{red_flag}",
|
||||
ParagraphStyle(
|
||||
name="RedFlag",
|
||||
parent=self.styles["CustomBodyText"],
|
||||
|
@ -385,19 +385,21 @@ def _call_openai_for_evaluation(context: str) -> dict | None:
|
||||
|
||||
И топ 3 сильные/слабые стороны.
|
||||
|
||||
И red_flags (если есть): расхождение в стаже и опыте резюме и собеседования, шаблонные ответы, уклонение от вопросов
|
||||
|
||||
ОТВЕТЬ СТРОГО В JSON ФОРМАТЕ с обязательными полями:
|
||||
- scores: объект с 5 критериями, каждый содержит score, justification, concerns
|
||||
- overall_score: число от 0 до 100 (среднее арифметическое всех scores)
|
||||
- recommendation: одно из 4 значений выше
|
||||
- strengths: массив из 3 сильных сторон
|
||||
- weaknesses: массив из 3 слабых сторон
|
||||
- red_flags: массив из красных флагов (если есть)
|
||||
"""
|
||||
|
||||
response = openai.chat.completions.create(
|
||||
model="gpt-4o-mini",
|
||||
model="gpt-5-mini",
|
||||
messages=[{"role": "user", "content": evaluation_prompt}],
|
||||
response_format={"type": "json_object"},
|
||||
temperature=0.3,
|
||||
)
|
||||
|
||||
evaluation = json.loads(response.choices[0].message.content)
|
||||
@ -617,6 +619,20 @@ async def _generate_and_upload_pdf_report(
|
||||
logger.error(f"[PDF_GENERATION] Error generating PDF report: {str(e)}")
|
||||
|
||||
|
||||
def _format_concerns_field(concerns_data) -> str:
|
||||
"""Форматирует поле concerns для сохранения как строку"""
|
||||
if not concerns_data:
|
||||
return ""
|
||||
|
||||
if isinstance(concerns_data, list):
|
||||
# Если это массив, объединяем элементы через запятую с переносом строки
|
||||
return "; ".join(concerns_data)
|
||||
elif isinstance(concerns_data, str):
|
||||
return concerns_data
|
||||
else:
|
||||
return str(concerns_data)
|
||||
|
||||
|
||||
def _create_report_from_dict(
|
||||
interview_session_id: int, report: dict
|
||||
) -> "InterviewReport":
|
||||
@ -633,8 +649,8 @@ def _create_report_from_dict(
|
||||
technical_skills_justification=scores.get("technical_skills", {}).get(
|
||||
"justification", ""
|
||||
),
|
||||
technical_skills_concerns=scores.get("technical_skills", {}).get(
|
||||
"concerns", ""
|
||||
technical_skills_concerns=_format_concerns_field(
|
||||
scores.get("technical_skills", {}).get("concerns", "")
|
||||
),
|
||||
experience_relevance_score=scores.get("experience_relevance", {}).get(
|
||||
"score", 0
|
||||
@ -642,24 +658,30 @@ def _create_report_from_dict(
|
||||
experience_relevance_justification=scores.get("experience_relevance", {}).get(
|
||||
"justification", ""
|
||||
),
|
||||
experience_relevance_concerns=scores.get("experience_relevance", {}).get(
|
||||
"concerns", ""
|
||||
experience_relevance_concerns=_format_concerns_field(
|
||||
scores.get("experience_relevance", {}).get("concerns", "")
|
||||
),
|
||||
communication_score=scores.get("communication", {}).get("score", 0),
|
||||
communication_justification=scores.get("communication", {}).get(
|
||||
"justification", ""
|
||||
),
|
||||
communication_concerns=scores.get("communication", {}).get("concerns", ""),
|
||||
communication_concerns=_format_concerns_field(
|
||||
scores.get("communication", {}).get("concerns", "")
|
||||
),
|
||||
problem_solving_score=scores.get("problem_solving", {}).get("score", 0),
|
||||
problem_solving_justification=scores.get("problem_solving", {}).get(
|
||||
"justification", ""
|
||||
),
|
||||
problem_solving_concerns=scores.get("problem_solving", {}).get("concerns", ""),
|
||||
problem_solving_concerns=_format_concerns_field(
|
||||
scores.get("problem_solving", {}).get("concerns", "")
|
||||
),
|
||||
cultural_fit_score=scores.get("cultural_fit", {}).get("score", 0),
|
||||
cultural_fit_justification=scores.get("cultural_fit", {}).get(
|
||||
"justification", ""
|
||||
),
|
||||
cultural_fit_concerns=scores.get("cultural_fit", {}).get("concerns", ""),
|
||||
cultural_fit_concerns=_format_concerns_field(
|
||||
scores.get("cultural_fit", {}).get("concerns", "")
|
||||
),
|
||||
# Агрегированные поля
|
||||
overall_score=report.get("overall_score", 0),
|
||||
recommendation=RecommendationType(report.get("recommendation", "reject")),
|
||||
@ -693,8 +715,8 @@ def _update_report_from_dict(existing_report, report: dict):
|
||||
existing_report.technical_skills_justification = scores["technical_skills"].get(
|
||||
"justification", ""
|
||||
)
|
||||
existing_report.technical_skills_concerns = scores["technical_skills"].get(
|
||||
"concerns", ""
|
||||
existing_report.technical_skills_concerns = _format_concerns_field(
|
||||
scores["technical_skills"].get("concerns", "")
|
||||
)
|
||||
|
||||
if "experience_relevance" in scores:
|
||||
@ -704,17 +726,17 @@ def _update_report_from_dict(existing_report, report: dict):
|
||||
existing_report.experience_relevance_justification = scores[
|
||||
"experience_relevance"
|
||||
].get("justification", "")
|
||||
existing_report.experience_relevance_concerns = scores[
|
||||
"experience_relevance"
|
||||
].get("concerns", "")
|
||||
existing_report.experience_relevance_concerns = _format_concerns_field(
|
||||
scores["experience_relevance"].get("concerns", "")
|
||||
)
|
||||
|
||||
if "communication" in scores:
|
||||
existing_report.communication_score = scores["communication"].get("score", 0)
|
||||
existing_report.communication_justification = scores["communication"].get(
|
||||
"justification", ""
|
||||
)
|
||||
existing_report.communication_concerns = scores["communication"].get(
|
||||
"concerns", ""
|
||||
existing_report.communication_concerns = _format_concerns_field(
|
||||
scores["communication"].get("concerns", "")
|
||||
)
|
||||
|
||||
if "problem_solving" in scores:
|
||||
@ -724,8 +746,8 @@ def _update_report_from_dict(existing_report, report: dict):
|
||||
existing_report.problem_solving_justification = scores["problem_solving"].get(
|
||||
"justification", ""
|
||||
)
|
||||
existing_report.problem_solving_concerns = scores["problem_solving"].get(
|
||||
"concerns", ""
|
||||
existing_report.problem_solving_concerns = _format_concerns_field(
|
||||
scores["problem_solving"].get("concerns", "")
|
||||
)
|
||||
|
||||
if "cultural_fit" in scores:
|
||||
@ -733,8 +755,8 @@ def _update_report_from_dict(existing_report, report: dict):
|
||||
existing_report.cultural_fit_justification = scores["cultural_fit"].get(
|
||||
"justification", ""
|
||||
)
|
||||
existing_report.cultural_fit_concerns = scores["cultural_fit"].get(
|
||||
"concerns", ""
|
||||
existing_report.cultural_fit_concerns = _format_concerns_field(
|
||||
scores["cultural_fit"].get("concerns", "")
|
||||
)
|
||||
|
||||
# Агрегированные поля
|
||||
|
@ -62,15 +62,15 @@ def generate_interview_plan(
|
||||
compatibility_prompt = f"""
|
||||
Проанализируй соответствие кандидата вакансии и определи, стоит ли проводить интервью.
|
||||
|
||||
КЛЮЧЕВОЙ И ЕДИНСТВЕННЫй КРИТЕРИЙ ОТКЛОНЕНИЯ:
|
||||
1. Профессиональная область кандидата: Полное несоответствие сферы деятельности вакансии (иначе 100 за критерий)
|
||||
ДОПУСТИМЫЕ КРИТЕРИИ:
|
||||
2. Остальные показатели кандидата хотя бы примерно соответствуют вакансии: скиллы кандидата похожи или смежны вакансионным, опыт не сильно отдален
|
||||
от указанного
|
||||
3. Учитывай опыт с аналогичными, похожими, смежными технологиями
|
||||
4. Когда смотришь на вакансию и кандидата не учитывай строгие слова, такие как "Требования", "Ключевые" и тп. Это лишь маркеры,
|
||||
КЛЮЧЕВЫЕ КРИТЕРИИ ОТКЛОНЕНИЯ:
|
||||
1. Несоответствие профессиональной сферы — опыт и навыки кандидата не относятся к области деятельности, связанной с вакансией.
|
||||
2. Несоответствие уровня и фокуса позиции — текущая или предыдущая должность кандидата существенно отличается по направлению или уровню ответственности от требований вакансии. Допускаются смежные переходы (например, переход из fullstack в frontend или переход кандидата уровня senior на позицию middle/junior).
|
||||
КЛЮЧЕВЫЕ КРИТЕРИИ ДОПУСКА:
|
||||
3. Остальные показатели кандидата примерно соответствуют вакансии: скиллы кандидата похожи или смежны вакансионным, опыт попадает в указанных промежуток
|
||||
4. Учитывай опыт с аналогичными, похожими, смежными технологиями
|
||||
5. Когда смотришь на вакансию и кандидата не учитывай строгие слова, такие как "Требования", "Ключевые" и тп. Это лишь маркеры,
|
||||
но не оценочные указатели
|
||||
5. Если есть спорные вопросы соответствия, лучше допустить к собеседованию и уточнить их там
|
||||
6. Если есть спорные вопросы соответствия, лучше допустить к собеседованию и уточнить их там
|
||||
|
||||
КАНДИДАТ:
|
||||
- Имя: {combined_data.get("name", "Не указано")}
|
||||
@ -134,7 +134,7 @@ def generate_interview_plan(
|
||||
|
||||
# Если кандидат подходит - генерируем план интервью
|
||||
plan_prompt = f"""
|
||||
Создай детальный план интервью для кандидата на основе его резюме и требований вакансии.
|
||||
Создай детальный план интервью для кандидата на основе его резюме и требований вакансии на 45 МИНУТ.
|
||||
|
||||
РЕЗЮМЕ КАНДИДАТА:
|
||||
- Имя: {combined_data.get("name", "Не указано")}
|
||||
@ -182,7 +182,7 @@ def generate_interview_plan(
|
||||
"Опыт командной работы",
|
||||
"Мотивация к изучению нового"
|
||||
],
|
||||
"red_flags_to_check": [],
|
||||
"red_flags_to_check": [Шаблонные ответы, уклонения от вопросов, расхождение в стаже],
|
||||
"personalization_notes": "Кандидат имеет хороший технический опыт"
|
||||
}}
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user