feat(voice): server-side LLM/STT — porting Python satellite into tablet
All checks were successful
Deploy / deploy (push) Successful in 5m44s
All checks were successful
Deploy / deploy (push) Successful in 5m44s
Шаг 1 миграции голосового стека из home-voice-assistant в сам tablet: - /api/voice/chat — Claude Haiku 4.5 с tool-loop (max 4 раунда), prompt caching на system + старой истории, история в /data/voice-history/. Эмитит command/response/error в voice-bus → орб моргает как раньше. - /api/voice/stt — Groq whisper-large-v3-turbo, multipart или raw audio. - lib/voice-text.ts — порт clean_for_speech (без pymorphy3, время в именительном падеже) и strip_fillers + RESET_PATTERNS. - lib/voice-executors.ts — tool executors через loopback fetch на существующие /api/voice/tools/* и /api/voice/timer. - Поддержка ANTHROPIC_PROXY/GROQ_PROXY (fallback на HTTPS_PROXY). После деплоя нужны GROQ_API_KEY и ANTHROPIC_API_KEY в tablet.env. Шаги 2 (push-to-talk в браузере) и 3 (wake-word) — отдельно.
This commit is contained in:
203
lib/voice-tool-schemas.ts
Normal file
203
lib/voice-tool-schemas.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* Tool schemas для Anthropic API. Порт TOOL_SCHEMAS из satellite/tools.py.
|
||||
* Формат — Anthropic native tools (name + description + input_schema).
|
||||
*/
|
||||
import type Anthropic from '@anthropic-ai/sdk'
|
||||
|
||||
export const TOOL_SCHEMAS: Anthropic.Tool[] = [
|
||||
{
|
||||
name: 'get_weather',
|
||||
description:
|
||||
'Получить текущую погоду и короткий прогноз для города. ' +
|
||||
'Для вопросов вроде «какая сегодня погода», «холодно ли на улице», «нужен ли зонт». ' +
|
||||
'По умолчанию — Санкт-Петербург.',
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
city: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Город на русском или шорткод (spb, msk, sochi, ekb, kzn, nsk, krd). ' +
|
||||
'По умолчанию Санкт-Петербург.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'get_transport',
|
||||
description:
|
||||
'Расписание ближайших трамваев на остановке Ул. Антонова-Овсеенко. ' +
|
||||
'Для вопросов «когда следующий 23-й», «что ближайшее в центр», «пора идти на остановку».',
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
direction: {
|
||||
type: 'string',
|
||||
enum: ['to_center', 'from_center', 'all'],
|
||||
description:
|
||||
'to_center = в центр (к Новочеркасской), ' +
|
||||
'from_center = от центра (к Большевиков), all = оба направления',
|
||||
},
|
||||
routes: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Фильтр маршрутов через запятую, например «23» или «23,27». Пусто = все маршруты.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'get_today_events',
|
||||
description:
|
||||
'События из календаря (Даниил + Света). Вернёт id события, title, start, end, ' +
|
||||
'owner («daniil» или «sveta»). ВАЖНО: для update_event / delete_event сначала ' +
|
||||
'вызывай этот tool чтобы получить event_id.',
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
range: {
|
||||
type: 'string',
|
||||
enum: ['today', 'week', 'month'],
|
||||
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',
|
||||
description:
|
||||
'Список заметок и списков покупок с планшета. Для «что мне купить», ' +
|
||||
'«что в списке», «какие записи».',
|
||||
input_schema: { type: 'object', properties: {} },
|
||||
},
|
||||
{
|
||||
name: 'set_timer',
|
||||
description:
|
||||
'Запустить таймер на планшете. Показывает обратный отсчёт с названием и звенит ' +
|
||||
'по окончании. Используй для «поставь таймер на 10 минут», «напомни через час», ' +
|
||||
'«засеки 5 минут для чайника».',
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
seconds: {
|
||||
type: 'integer',
|
||||
description: 'Длительность в секундах (1..86400)',
|
||||
minimum: 1,
|
||||
maximum: 86400,
|
||||
},
|
||||
label: {
|
||||
type: 'string',
|
||||
description: 'Короткое название таймера (например «Чайник», «Паста»)',
|
||||
},
|
||||
},
|
||||
required: ['seconds', 'label'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'cancel_timer',
|
||||
description:
|
||||
'Отменить активный таймер по его названию. Для «отмени таймер чайник», ' +
|
||||
'«убери таймер пасты», «останови отсчёт».',
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
label: {
|
||||
type: 'string',
|
||||
description: 'Название таймера (примерное совпадение — можно частично).',
|
||||
},
|
||||
},
|
||||
required: ['label'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'adjust_timer',
|
||||
description:
|
||||
'Изменить оставшееся время таймера. Для «добавь ещё 5 минут», «убавь на минуту», ' +
|
||||
'«накинь времени чайнику». Положительный delta_seconds = добавить, отрицательный = уменьшить.',
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
label: {
|
||||
type: 'string',
|
||||
description: 'Название таймера для которого меняем время.',
|
||||
},
|
||||
delta_seconds: {
|
||||
type: 'integer',
|
||||
description: 'Секунды (+ добавить, - уменьшить). Например 300 = +5 минут, -60 = -1 минута.',
|
||||
},
|
||||
},
|
||||
required: ['label', 'delta_seconds'],
|
||||
},
|
||||
},
|
||||
]
|
||||
Reference in New Issue
Block a user