7527cf8848
- LlmClient интерфейс; RemoteLlmClient (OkHttp, llm.binom.pw) и LocalLlmClient (LiteRT-LM, Gemma 4 E2B) - LocalLlm: Gemma4E2B (SHA-256, GPU/CPU-бэкенды), buildLocalLlmPrompt (SYSTEM→systemInstruction, TOOL→user), GemmaModelDownloader (стриминг, прогресс, фолбэк источников), спекулятивное декодирование - LlmPrefs (персистентность выбора), PhoneApp: фабрика по llmChoice, пересоздание ассистента, скачивание/отмена - UI: GlassesInfoScreen — выбор сервер/локальная + прогресс; ChatScreen — индикатор активной модели - Тесты: +12 (LlmLocalTest) — phone 154 зелёных
56 lines
5.0 KiB
Markdown
56 lines
5.0 KiB
Markdown
# Задача: реализовать механику скиллов и тулсетов агента «Порфирий»
|
|
|
|
Прочитай ТЗ: TASK-agent-tools.md (в корне репо). Реализуй ТОЛЬКО механику, БЕЗ привязки к медиа-данным (медиа-тулы будут позже, отдельно).
|
|
|
|
## Объём работ
|
|
|
|
### 1. Механика скиллов
|
|
- Абстракция `Skill`: name, description (триггер, одна строка), body (текст)
|
|
- `SkillRepository`: читает скиллы из папки `files/skills/` (YAML frontmatter: `---\nname: ...\ndescription: ...\n---\n` + markdown body)
|
|
- `SkillRegistry`: индекс (имя + триггер) для промпта; `readSkill(name)` возвращает полное содержимое
|
|
- Скиллов реальных пока НЕТ (механика работает на пустом каталоге или тестовых скиллах в юнит-тестах)
|
|
|
|
### 2. Механика тулсетов
|
|
- `Toolset`: name, описание набора (для каталога), список тул (имя с префиксом набора + описание + сигнатура аргументов)
|
|
- `ToolsetRegistry`: активность НА СЕССИЮ (Map сессия→(набор→deadline)), хипп, таймаут 10 минут с последнего вызова тулы набора, проверка при сборке промпта (никаких фоновых таймеров)
|
|
- `enable_toolset|имя`: идемпотентно (повторный = продлить), неактивный вызов НЕ пишется в историю (системная тула-невидимка)
|
|
- `disable_toolset|имя`: снять, идемпотентно («нет активных наборов» — не ошибка)
|
|
- Неизвестное имя набора — «набора X нет, доступны: ...»
|
|
|
|
### 3. Сборщик промпта (три состояния)
|
|
- Состояние 1 (набор не активен): ядро + каталог тулсетов + индекс скиллов
|
|
- Состояние 2 (активация): enable вызывает пересборку, вызов в историю НЕ пишется
|
|
- Состояние 3 (активен): ядро + описания тул набора + «Активные тулсеты: имя» + скилл, связанный с набором, УБРАН из промпта
|
|
- Промпт пересобирается на каждый запрос
|
|
|
|
### 4. Бесшовная подмена вызова
|
|
- Вызов тулы набора при НЕактивном наборе (например media_search): НЕ выполнять, подменить как будто вызван enable_toolset|media, ответить «Тулсет media активирован. Реестр: ... Поиск ещё НЕ выполнялся. Для поиска вызови media_search с аргументом query.»
|
|
- Если аргументы вызова совпадают со схемой из реестра — можно выполнить сразу
|
|
|
|
### 5. Обработчик вызовов
|
|
- Префиксная маршрутизация: имя тулы с префиксом набора (media_*) → набор
|
|
- Ядро-тулы: get_current_time (есть), show_text (есть ShowText), read_skill, enable_toolset, disable_toolset
|
|
- Все тулы идемпотентные, прощающие, с понятными ошибками
|
|
- Валидация аргументов по реестру
|
|
|
|
### 6. Юнит-тесты (JVM, без Android) — ОБЯЗАТЕЛЬНО
|
|
Покрыть механику полностью:
|
|
- сборка промпта: 3 состояния
|
|
- подмена вызова при неактивном наборе (включая «поиск ещё не выполнен»)
|
|
- идемпотентность enable/disable
|
|
- таймаут (инжектируемые часы — не ждать реальные 10 минут)
|
|
- активность на сессию (переключение сессий)
|
|
- префиксная маршрутизация, валидация аргументов
|
|
- read_skill: чтение из files/skills, парсинг frontmatter
|
|
|
|
## Ограничения
|
|
- Код только Kotlin (как в проекте)
|
|
- НЕ трогать существующую логику ассистента без необходимости — встраиваться аккуратно
|
|
- НЕ делать реальные медиа-тулы (search по каталогу и т.п.) — это отдельный этап
|
|
- Юнит-тесты должны быть зелёными: ./gradlew :app-phone:testDebugUnitTest
|
|
- Не коммитить без подтверждения
|
|
|
|
## Проверка
|
|
1. ./gradlew :app-phone:testDebugUnitTest — зелёные
|
|
2. ./gradlew :app-phone:assembleDebug — собирается
|