Sau khi có danh sách Tier A/B, việc tốn thời gian tiếp theo là email qua lại tìm giờ hẹn. Bài này xây phần "Act" đầu tiên của agent: đặt lịch phỏng vấn tự động nhưng vẫn có chốt chặn con người.
Bài toán thực tế của đặt lịch
Đặt lịch không chỉ là "chọn 1 giờ". Bạn phải: lấy lịch trống của (các) interviewer, giao với khung giờ làm việc, tránh trùng buffer giữa 2 cuộc, tôn trọng múi giờ ứng viên, và xử lý khi ứng viên từ chối slot. Đây là nơi agent tỏa sáng vì logic lặp lại nhưng nhiều biến.
Kiến trúc luồng đặt lịch
1. Trigger: candidate.tier in [A, B] & recruiter đã duyệt
2. Query lịch interviewer (Google/Outlook Calendar API) → free/busy
3. Sinh danh sách slot khả dụng (áp dụng rule: giờ làm việc, buffer, độ dài)
4. Gửi ứng viên 3 slot đề xuất (email/link self-book)
5. Ứng viên chọn → tạo event + gửi lời mời (.ics) cho cả 2 bên
6. Ghi trạng thái vào ATS/sheet: scheduled
Bước 1: Lấy free/busy thay vì đọc chi tiết lịch
Dùng endpoint free/busy của Calendar API — bạn chỉ cần biết khoảng nào bận, không cần đọc nội dung cuộc họp (bảo mật hơn, đủ dùng):
# Google Calendar free/busy (giả lược)
body = {
"timeMin": "2026-07-14T00:00:00+07:00",
"timeMax": "2026-07-18T23:59:59+07:00",
"timeZone": "Asia/Ho_Chi_Minh",
"items": [{"id": "interviewer@company.com"}]
}
busy = calendar.freebusy().query(body=body).execute()
# → trả các khoảng busy; phần còn lại trong giờ làm là free
Bước 2: Sinh slot theo rule rõ ràng
RULES = {
"work_hours": (9, 17), # 9h-17h
"slot_minutes": 45,
"buffer_minutes": 15, # nghỉ giữa 2 cuộc
"skip_lunch": (12, 13),
"min_notice_hours": 24 # không đề xuất giờ <24h tới
}
def free_slots(busy_intervals, day, rules):
slots = []
# duyệt từng khe 45' trong giờ làm, loại nếu giao busy hoặc lunch
# loại nếu cách hiện tại < min_notice_hours
return slots
Rule tường minh giúp bạn giải thích được vì sao agent đề xuất giờ đó, và dễ chỉnh khi policy thay đổi.
Bước 3: Gửi đề xuất & self-booking
Gửi ứng viên 3 slot (không phải 1, để tăng tỉ lệ chốt; không phải 10, gây rối). Nội dung email do LLM soạn từ template + biến số:
Chào {name}, cảm ơn bạn đã ứng tuyển {role}.
Mời bạn chọn 1 trong các khung giờ phỏng vấn (GMT+7):
1) Thứ Ba 15/07, 10:00
2) Thứ Tư 16/07, 14:00
3) Thứ Năm 17/07, 09:30
Bấm để xác nhận: {booking_link}
Bước 4: Xử lý xung đột & race condition
Hai ứng viên có thể chọn cùng slot gần như đồng thời. Giải pháp:
- Tạm giữ (hold) slot 10 phút khi gửi đề xuất.
- Khi ứng viên xác nhận, kiểm tra lại free/busy một lần nữa trước khi tạo event.
- Nếu slot vừa bị chiếm → tự động đề xuất slot thay thế gần nhất, không báo lỗi cụt.
Khung "an toàn khi Act"
TRƯỚC KHI TẠO EVENT, agent phải:
[ ] Recruiter đã duyệt ứng viên này?
[ ] Slot còn trống (check lại lần cuối)?
[ ] Đúng múi giờ ứng viên?
[ ] Có link hủy/đổi lịch trong lời mời?
[ ] Ghi log ai/khi nào/slot nào
Checklist tính năng đặt lịch
- [ ] Dùng free/busy, không đọc nội dung lịch
- [ ] Rule sinh slot tường minh (giờ làm, buffer, min-notice)
- [ ] Gửi 3 lựa chọn, có link self-book
- [ ] Có cơ chế hold + recheck chống trùng
- [ ] Lời mời .ics kèm link đổi/hủy
- [ ] Cập nhật trạng thái về hệ thống nguồn
Sai lầm thường gặp
- Bỏ qua múi giờ. Ứng viên ở TP.HCM, interviewer set giờ theo server UTC → hẹn nhầm 7 tiếng. Luôn gắn timezone rõ ràng.
- Không recheck trước khi tạo event. Race condition khiến 2 người trùng giờ, quê với ứng viên.
- Gửi quá nhiều slot. 10 lựa chọn làm ứng viên tê liệt quyết định. Ba là con số tốt.
- Đề xuất giờ quá sát (trong 2 tiếng tới). Ứng viên không kịp chuẩn bị. Đặt min-notice.
- Không có đường hủy/đổi. Ứng viên kẹt lịch sẽ no-show thay vì đổi. Luôn kèm link tự phục vụ.