Научите AI-агента автоматически планировать встречи, находить оптимальные временные слоты и разрешать конфликты в расписании. Практическое руководство с интеграцией Google Calendar API, алгоритмами поиска окон и NLP-интерфейсом на естественном языке.
Агент для работы с расписаниями — это специализированный AI-ассистент, который понимает запросы на естественном языке и преобразует их в операции с календарём. Ключевая особенность: агент должен не просто выполнять CRUD операции, но и принимать интеллектуальные решения — находить свободные слоты, учитывать предпочтения пользователя, разрешать конфликты и предлагать альтернативы. Архитектура состоит из трёх слоёв: NLP-интерфейс (понимание намерений), Scheduling Engine (алгоритмы работы со слотами), Calendar Adapter (интеграция с API календаря).
# Структура инструментов агента-планировщика SCHEDULING_TOOLS = [ { "type": "function", "function": { "name": "find_free_slots", "description": "Найти свободные временные слоты в указанном диапазоне дат", "parameters": { "type": "object", "properties": { "start_date": {"type": "string", "description": "Начальная дата ISO"}, "end_date": {"type": "string"}, "duration_minutes": {"type": "integer"}, "preferred_hours": {"type": "array", "items": {"type": "integer"}} }, "required": ["start_date", "end_date", "duration_minutes"] } } }, { "type": "function", "function": { "name": "create_event", "description": "Создать событие в календаре", "parameters": { "type": "object", "properties": { "title": {"type": "string"}, "start_time": {"type": "string"}, "end_time": {"type": "string"}, "attendees": {"type": "array", "items": {"type": "string"}}, "description": {"type": "string"} }, "required": ["title", "start_time", "end_time"] } } }, { "type": "function", "function": { "name": "list_events", "description": "Получить список событий за период", "parameters": { "type": "object", "properties": { "time_min": {"type": "string"}, "time_max": {"type": "string"} }, "required": ["time_min"] } } } ]
Google Calendar — наиболее распространённый календарный сервис, и его API отлично подходит для программного управления расписаниями. Для работы потребуется создать проект в Google Cloud Console, включить Calendar API, настроить OAuth 2.0 (для персональных календарей) или Service Account (для корпоративных/доменных). Полученные credentials сохраняются в переменные окружения и используются для инициализации клиента.
# google_calendar_adapter.py — адаптер для Google Calendar API import os from datetime import datetime, timedelta from google.oauth2.service_account import Credentials from googleapiclient.discovery import build class GoogleCalendarAdapter: def __init__(self, calendar_id: str = "primary"): credentials = Credentials.from_service_account_file( os.getenv("GOOGLE_CREDENTIALS_PATH"), scopes=["https://www.googleapis.com/auth/calendar"] ) self.service = build("calendar", "v3", credentials=credentials) self.calendar_id = calendar_id def list_events(self, time_min: datetime, time_max: datetime = None): if not time_max: time_max = time_min + timedelta(days=7) result = self.service.events().list( calendarId=self.calendar_id, timeMin=time_min.isoformat() + "Z", timeMax=time_max.isoformat() + "Z", singleEvents=True, orderBy="startTime" ).execute() return result.get("items", []) def create_event(self, title: str, start: datetime, end: datetime, attendees: list = None, description: str = ""): event = { "summary": title, "start": {"dateTime": start.isoformat(), "timeZone": "Europe/Moscow"}, "end": {"dateTime": end.isoformat(), "timeZone": "Europe/Moscow"}, "description": description, "attendees": [{"email": a} for a in (attendees or [])], "reminders": {"useDefault": True} } created = self.service.events().insert( calendarId=self.calendar_id, body=event, sendUpdates="all" ).execute() return created def get_freebusy(self, time_min: datetime, time_max: datetime): body = { "timeMin": time_min.isoformat() + "Z", "timeMax": time_max.isoformat() + "Z", "items": [{"id": self.calendar_id}] } result = self.service.freebusy().query(body=body).execute() return result["calendars"][self.calendar_id].get("busy", [])
Ключевой алгоритм агента-планировщика — поиск свободных окон в заданном диапазоне с учётом занятых интервалов. Алгоритм собирает все занятые промежутки из freebusy API, сортирует их, а затем ищет «дыры» достаточной длительности между ними. Дополнительно учитываются рабочие часы, минимальное время на подготовку и буфер между встречами.
# slot_finder.py — алгоритм поиска свободных временных окон from datetime import datetime, timedelta from typing import List, Tuple def find_free_slots( busy_intervals: List[Tuple[datetime, datetime]], start: datetime, end: datetime, duration_min: int, working_hours: Tuple[int, int] = (9, 18), buffer_minutes: int = 15 ) -> List[Tuple[datetime, datetime]]: # Нормализуем занятые интервалы — сортируем и объединяем пересекающиеся if not busy_intervals: busy_intervals = [] sorted_busy = sorted(busy_intervals) merged = [] for b_start, b_end in sorted_busy: if merged and b_start <= merged[-1][1] + timedelta(minutes=buffer_minutes): merged[-1] = (merged[-1][0], max(merged[-1][1], b_end)) else: merged.append((b_start, b_end)) free_slots = [] cursor = start for b_start, b_end in merged: if cursor < b_start: slot_end = min(cursor + timedelta(minutes=duration_min), b_start) if (slot_end - cursor).total_seconds() / 60 >= duration_min: if _is_within_working_hours(cursor, slot_end, working_hours): free_slots.append((cursor, slot_end)) cursor = max(cursor, b_end) # Проверяем хвост после последнего занятого интервала if cursor < end: slot_end = min(cursor + timedelta(minutes=duration_min), end) if (slot_end - cursor).total_seconds() / 60 >= duration_min: if _is_within_working_hours(cursor, slot_end, working_hours): free_slots.append((cursor, slot_end)) return free_slots def _is_within_working_hours(slot_start, slot_end, hours): # Проверяем, что слот попадает в рабочие часы (с гранулярностью в день) sh, eh = hours return (sh <= slot_start.hour < eh) and (sh <= slot_end.hour <= eh)
Пользователь взаимодействует с агентом через естественный язык: «Найди время для встречи с командой на следующей неделе», «Перенеси стендап на час позже», «Какие у меня планы на четверг?». LLM (GPT-4, Claude) парсит запрос, извлекает намерение и параметры, а затем вызывает соответствующую функцию. Системный промпт должен чётко описывать контекст — имя пользователя, часовой пояс, рабочие часы, стандартную длительность встреч.
# scheduling_agent.py — основной цикл агента-планировщика import json from datetime import datetime from openai import OpenAI client = OpenAI() calendar = GoogleCalendarAdapter() SYSTEM_PROMPT = """Ты AI-ассистент по планированию. Пользователь: Алексей. Часовой пояс: Europe/Moscow (UTC+3). Рабочие часы: 9:00–18:00, стандартная встреча — 30 минут. Перед созданием встречи ВСЕГДА вызывай find_free_slots. Если подходящего слота нет — предложи ближайшие альтернативы.""" def execute_tool(tool_name: str, args: dict) -> dict: if tool_name == "find_free_slots": start = datetime.fromisoformat(args["start_date"]) end = datetime.fromisoformat(args["end_date"]) busy_raw = calendar.get_freebusy(start, end) busy = [(datetime.fromisoformat(b["start"].replace("Z","")), datetime.fromisoformat(b["end"].replace("Z",""))) for b in busy_raw] free = find_free_slots(busy, start, end, args["duration_minutes"]) return {"free_slots": [(s.isoformat(), e.isoformat()) for s, e in free]} elif tool_name == "create_event": event = calendar.create_event( title=args["title"], start=datetime.fromisoformat(args["start_time"]), end=datetime.fromisoformat(args["end_time"]), attendees=args.get("attendees"), description=args.get("description", "") ) return {"event_id": event["id"], "link": event.get("htmlLink")} elif tool_name == "list_events": tmin = datetime.fromisoformat(args["time_min"]) tmax = datetime.fromisoformat(args.get("time_max")) if args.get("time_max") else None events = calendar.list_events(tmin, tmax) return {"events": [{"summary": e.get("summary"), "start": e["start"].get("dateTime")} for e in events]} def chat(user_message: str) -> str: messages = [{"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_message}] for _ in range(5): # максимум 5 tool-call раундов response = client.chat.completions.create( model="gpt-4", messages=messages, tools=SCHEDULING_TOOLS, tool_choice="auto" ) msg = response.choices[0].message if msg.tool_calls: messages.append(msg) for tc in msg.tool_calls: result = execute_tool(tc.function.name, json.loads(tc.function.arguments)) messages.append({"role": "tool", "tool_call_id": tc.id, "content": json.dumps(result, ensure_ascii=False)}) else: return msg.content return "Не удалось выполнить запрос за отведённое число шагов."
В реальных сценариях пользователь может запросить встречу на уже занятое время. Агент должен не просто отказать, а предложить альтернативы: ближайшие свободные слоты до и после запрошенного времени, возможность сдвинуть существующие встречи (с согласия участников), или сократить длительность. Реализуем алгоритм ранжирования альтернатив: слоты оцениваются по близости к запрошенному времени, попаданию в предпочтительные часы и отсутствию конфликтов с высокоприоритетными событиями.
# conflict_resolver.py — ранжирование альтернативных слотов from datetime import datetime, timedelta def rank_alternatives( requested_time: datetime, free_slots: list, preferred_hours: list = None ) -> list: """Ранжирует свободные слоты: чем ближе к запрошенному времени, тем выше.""" scored = [] for slot_start, slot_end in free_slots: score = 0 # Близость к запрошенному времени (чем ближе — тем выше) distance = abs((slot_start - requested_time).total_seconds() / 3600) score += max(0, 100 - distance * 10) # Попадание в предпочтительные часы (бонус +25) if preferred_hours and slot_start.hour in preferred_hours: score += 25 # Утренние слоты — приоритетнее вечерних if 9 <= slot_start.hour <= 12: score += 15 elif 16 <= slot_start.hour <= 18: score += 5 scored.append((score, slot_start, slot_end)) scored.sort(key=lambda x: x[0], reverse=True) return [(s, e) for _, s, e in scored[:5]] # топ-5 альтернатив
Реальный агент-планировщик должен работать с несколькими календарями одновременно: личный, рабочий, календари коллег. Google Calendar API поддерживает запрос freebusy для нескольких calendarId одновременно, что позволяет находить слоты, свободные у всех участников. Для повторяющихся событий (recurring events) API возвращает только первое вхождение с правилом recurrence (RRULE, EXDATE) — агент должен разворачивать такие события на нужный диапазон дат.
# multi_calendar.py — поиск слотов у нескольких участников def find_group_free_slots( calendar_ids: list, time_min: datetime, time_max: datetime, duration_min: int ): """Находит слоты, свободные у ВСЕХ участников.""" body = { "timeMin": time_min.isoformat() + "Z", "timeMax": time_max.isoformat() + "Z", "items": [{"id": cid} for cid in calendar_ids] } result = calendar.service.freebusy().query(body=body).execute() # Объединяем занятости всех участников all_busy = [] for cid in calendar_ids: busy_list = result["calendars"].get(cid, {}).get("busy", []) for b in busy_list: all_busy.append(( datetime.fromisoformat(b["start"].replace("Z", "")), datetime.fromisoformat(b["end"].replace("Z", "")) )) return find_free_slots(all_busy, time_min, time_max, duration_min) # Пример использования: найти окно для встречи команды из 3 человек team_calendars = ["alex@company.com", "maria@company.com", "ivan@company.com"] slots = find_group_free_slots( team_calendars, datetime(2026, 6, 22), # понедельник datetime(2026, 6, 26), # пятница duration_min=45 ) print(f"Найдено общих слотов: {len(slots)}") for s, e in slots: print(f" {s.strftime('%d.%m %H:%M')} - {e.strftime('%H:%M')}")
Мы создали полноценного AI-агента для управления расписаниями: интеграция с Google Calendar API, алгоритм поиска свободных слотов, NLP-интерфейс через function calling, разрешение конфликтов и мульти-календарная оркестрация. Агент понимает запросы на естественном языке и самостоятельно находит оптимальное время для встреч. Следующие шаги: добавление интеграции с Microsoft Outlook/Exchange, поддержка видеоконференций (автоматическое создание Zoom/Google Meet), машинное обучение для предсказания оптимальной длительности встреч на основе типа события и состава участников.