feat(tools): calendar CRUD tools — create_event, update_event, delete_event
- create_event(title, date, start_time?, end_time?, all_day?, owner)
owner обязателен (daniil | sveta). System prompt велит LLM уточнять
чей это календарь, если неясно.
- update_event(event_id, owner, ...fields) — меняет только переданные
поля. Сначала нужно вызвать get_today_events для получения event_id.
- delete_event(event_id, owner) — сначала get_today_events, найти
событие по названию, подтвердить если важное.
get_today_events теперь возвращает event_id и owner (daniil/sveta),
плюс принимает range=month. Description явно говорит LLM что это
первый tool для CRUD-сценариев.
System prompt (Cosmo и Люся) дополнен секцией 'Работа с календарём'
с правилами: даты YYYY-MM-DD, время HH:MM, «завтра» = +1 день,
вычислять от {today}.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -61,10 +61,20 @@ COSMO_SYSTEM_PROMPT = """Ты — Cosmo, домашний голосовой а
|
|||||||
результата. Не пересказывай сырые данные дословно — дай человеческую сводку.
|
результата. Не пересказывай сырые данные дословно — дай человеческую сводку.
|
||||||
5. Если подходящего tool нет — честно скажи «так я не умею», а не притворяйся.
|
5. Если подходящего tool нет — честно скажи «так я не умею», а не притворяйся.
|
||||||
|
|
||||||
Доступные tools: get_weather, get_transport, get_today_events, get_notes,
|
Доступные tools: get_weather, get_transport, get_today_events, create_event,
|
||||||
set_timer, cancel_timer, adjust_timer.
|
update_event, delete_event, get_notes, set_timer, cancel_timer, adjust_timer.
|
||||||
|
|
||||||
Контекст: Даниил — разработчик, живёт в СПб с женой Светой. Сегодня {today}."""
|
Работа с календарём:
|
||||||
|
- У Даниила и Светы разные календари. Параметр owner обязательный.
|
||||||
|
- Если пользователь не уточнил чей календарь — СПРОСИ прежде чем вызывать
|
||||||
|
create_event. Не угадывай даже если контекст намекает.
|
||||||
|
- Для изменения или удаления события сначала вызови get_today_events
|
||||||
|
(можно с range=week/month), найди нужное событие по названию и времени,
|
||||||
|
потом действуй с его event_id и owner.
|
||||||
|
- Даты в формате YYYY-MM-DD (2026-04-24), времена HH:MM (14:30).
|
||||||
|
«завтра» = сегодня+1 по дате, «послезавтра» = +2. Сегодня {today}.
|
||||||
|
|
||||||
|
Контекст: Даниил — разработчик, живёт в СПб с женой Светой."""
|
||||||
|
|
||||||
LUSYA_SYSTEM_PROMPT = """Ты — Люся, домашний голосовой ассистент Светы (Санкт-Петербург).
|
LUSYA_SYSTEM_PROMPT = """Ты — Люся, домашний голосовой ассистент Светы (Санкт-Петербург).
|
||||||
|
|
||||||
@@ -75,12 +85,17 @@ LUSYA_SYSTEM_PROMPT = """Ты — Люся, домашний голосовой
|
|||||||
- Если не знаешь — скажи коротко.
|
- Если не знаешь — скажи коротко.
|
||||||
|
|
||||||
ЖЁСТКИЕ ПРАВИЛА про tools:
|
ЖЁСТКИЕ ПРАВИЛА про tools:
|
||||||
1. Действия (таймер, etc.) — только через вызов tool. Без tool действие не произошло.
|
1. Действия (таймер, события) — только через вызов tool. Без tool действие не произошло.
|
||||||
2. Не говори «поставила/отменила/изменила», если ты не вызвала соответствующий tool.
|
2. Не говори «поставила/отменила/изменила», если ты не вызвала соответствующий tool.
|
||||||
3. Информацию (погода, транспорт, события) — всегда через tool, не выдумывай.
|
3. Информацию (погода, транспорт, события) — всегда через tool, не выдумывай.
|
||||||
4. Tool → результат → короткий ответ человеческим языком.
|
4. Tool → результат → короткий ответ человеческим языком.
|
||||||
|
|
||||||
Сегодня {today}."""
|
Календарь:
|
||||||
|
- Свой = Светин, ещё есть календарь Данила. Для create_event уточняй
|
||||||
|
в какой календарь, если неясно.
|
||||||
|
- Для update_event / delete_event: сначала get_today_events, найди по
|
||||||
|
названию, потом действуй.
|
||||||
|
- Даты YYYY-MM-DD, время HH:MM. Сегодня {today}."""
|
||||||
|
|
||||||
_client: "anthropic.Anthropic | None" = None
|
_client: "anthropic.Anthropic | None" = None
|
||||||
|
|
||||||
|
|||||||
@@ -84,20 +84,100 @@ TOOL_SCHEMAS: list[dict] = [
|
|||||||
{
|
{
|
||||||
"name": "get_today_events",
|
"name": "get_today_events",
|
||||||
"description": (
|
"description": (
|
||||||
"События из календаря на сегодня или на неделю. "
|
"События из календаря (Даниил + Света). Вернёт id события, "
|
||||||
"Для вопросов 'что сегодня', 'какие планы', 'во сколько встреча'."
|
"title, start, end, owner ('daniil' или 'sveta'). "
|
||||||
|
"ВАЖНО: для update_event / delete_event сначала вызывай этот tool "
|
||||||
|
"чтобы получить event_id."
|
||||||
),
|
),
|
||||||
"input_schema": {
|
"input_schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"range": {
|
"range": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["today", "week"],
|
"enum": ["today", "week", "month"],
|
||||||
"description": "today (по умолчанию) или week (7 дней)",
|
"description": "today (по умолчанию), week (7 дней) или month (текущий месяц)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "create_event",
|
||||||
|
"description": (
|
||||||
|
"Создать событие в Google Calendar. "
|
||||||
|
"ВАЖНО: параметр owner обязателен. Если пользователь не сказал "
|
||||||
|
"чей это календарь — СПРОСИ у него ('в твой календарь или в Светин?') "
|
||||||
|
"и только потом вызывай tool. Не угадывай."
|
||||||
|
),
|
||||||
|
"input_schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": {"type": "string", "description": "Название события"},
|
||||||
|
"date": {"type": "string", "description": "Дата в формате YYYY-MM-DD"},
|
||||||
|
"start_time": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Время начала в формате HH:MM (24-часовой). Обязательно если all_day=false.",
|
||||||
|
},
|
||||||
|
"end_time": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Время окончания в формате HH:MM. По умолчанию start_time + 1 час.",
|
||||||
|
},
|
||||||
|
"all_day": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Событие на весь день без времени. По умолчанию false.",
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["daniil", "sveta"],
|
||||||
|
"description": "Чей это календарь — Даниила или Светы",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["title", "date", "owner"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "update_event",
|
||||||
|
"description": (
|
||||||
|
"Изменить существующее событие. Сначала обязательно вызови get_today_events "
|
||||||
|
"чтобы получить event_id и owner нужного события. Передавай только те поля "
|
||||||
|
"которые меняешь."
|
||||||
|
),
|
||||||
|
"input_schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"event_id": {"type": "string"},
|
||||||
|
"owner": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["daniil", "sveta"],
|
||||||
|
"description": "Чей календарь (из get_today_events)",
|
||||||
|
},
|
||||||
|
"title": {"type": "string"},
|
||||||
|
"date": {"type": "string", "description": "YYYY-MM-DD"},
|
||||||
|
"start_time": {"type": "string", "description": "HH:MM"},
|
||||||
|
"end_time": {"type": "string", "description": "HH:MM"},
|
||||||
|
"all_day": {"type": "boolean"},
|
||||||
|
},
|
||||||
|
"required": ["event_id", "owner"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "delete_event",
|
||||||
|
"description": (
|
||||||
|
"Удалить событие из календаря. Сначала вызови get_today_events чтобы "
|
||||||
|
"найти event_id и определить owner. Подтверди удаление с пользователем "
|
||||||
|
"если событие важное (встреча, врач, работа)."
|
||||||
|
),
|
||||||
|
"input_schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"event_id": {"type": "string"},
|
||||||
|
"owner": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["daniil", "sveta"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["event_id", "owner"],
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "get_notes",
|
"name": "get_notes",
|
||||||
"description": (
|
"description": (
|
||||||
@@ -196,6 +276,53 @@ def _exec_get_today_events(params: dict, agent_id: str) -> Any:
|
|||||||
return _tablet_get("/api/voice/tools/events", params={"range": range_})
|
return _tablet_get("/api/voice/tools/events", params={"range": range_})
|
||||||
|
|
||||||
|
|
||||||
|
def _exec_create_event(params: dict, agent_id: str) -> Any:
|
||||||
|
payload = {
|
||||||
|
"title": params.get("title", "").strip(),
|
||||||
|
"date": params.get("date", "").strip(),
|
||||||
|
"owner": params.get("owner", "daniil"),
|
||||||
|
"all_day": bool(params.get("all_day", False)),
|
||||||
|
}
|
||||||
|
if not payload["title"] or not payload["date"]:
|
||||||
|
return {"error": "title and date required"}
|
||||||
|
if not payload["all_day"]:
|
||||||
|
payload["start_time"] = params.get("start_time", "")
|
||||||
|
if "end_time" in params:
|
||||||
|
payload["end_time"] = params.get("end_time", "")
|
||||||
|
return _tablet_post("/api/voice/tools/events", payload)
|
||||||
|
|
||||||
|
|
||||||
|
def _exec_update_event(params: dict, agent_id: str) -> Any:
|
||||||
|
event_id = params.get("event_id", "").strip()
|
||||||
|
owner = params.get("owner", "").strip()
|
||||||
|
if not event_id or not owner:
|
||||||
|
return {"error": "event_id and owner required"}
|
||||||
|
payload = {"event_id": event_id, "owner": owner}
|
||||||
|
for k in ("title", "date", "start_time", "end_time", "all_day"):
|
||||||
|
if k in params:
|
||||||
|
payload[k] = params[k]
|
||||||
|
url = f"{TABLET_URL}/api/voice/tools/events"
|
||||||
|
r = _session.put(url, headers={**_headers(), "Content-Type": "application/json"}, json=payload, timeout=8)
|
||||||
|
r.raise_for_status()
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
|
||||||
|
def _exec_delete_event(params: dict, agent_id: str) -> Any:
|
||||||
|
event_id = params.get("event_id", "").strip()
|
||||||
|
owner = params.get("owner", "daniil").strip()
|
||||||
|
if not event_id:
|
||||||
|
return {"error": "event_id required"}
|
||||||
|
url = f"{TABLET_URL}/api/voice/tools/events"
|
||||||
|
r = _session.delete(
|
||||||
|
url,
|
||||||
|
headers=_headers(),
|
||||||
|
params={"event_id": event_id, "owner": owner},
|
||||||
|
timeout=8,
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
|
||||||
def _exec_get_notes(params: dict, agent_id: str) -> Any:
|
def _exec_get_notes(params: dict, agent_id: str) -> Any:
|
||||||
return _tablet_get("/api/voice/tools/notes")
|
return _tablet_get("/api/voice/tools/notes")
|
||||||
|
|
||||||
@@ -235,6 +362,9 @@ EXECUTORS = {
|
|||||||
"get_weather": _exec_get_weather,
|
"get_weather": _exec_get_weather,
|
||||||
"get_transport": _exec_get_transport,
|
"get_transport": _exec_get_transport,
|
||||||
"get_today_events": _exec_get_today_events,
|
"get_today_events": _exec_get_today_events,
|
||||||
|
"create_event": _exec_create_event,
|
||||||
|
"update_event": _exec_update_event,
|
||||||
|
"delete_event": _exec_delete_event,
|
||||||
"get_notes": _exec_get_notes,
|
"get_notes": _exec_get_notes,
|
||||||
"set_timer": _exec_set_timer,
|
"set_timer": _exec_set_timer,
|
||||||
"cancel_timer": _exec_cancel_timer,
|
"cancel_timer": _exec_cancel_timer,
|
||||||
|
|||||||
Reference in New Issue
Block a user