Merge pull request 'feat(ai-chat): agent roles (admin persona + optional model)' (#11) from feat/ai-agent-roles into develop
This commit was merged in pull request #11.
This commit is contained in:
@@ -1,362 +0,0 @@
|
||||
# Роли агента (Agent Roles) — проектный план
|
||||
|
||||
> Статус: проработанная фича, **не реализована**. Контекст: gitmost — форк Docmost.
|
||||
> Идея: дать возможность создавать переиспользуемые **роли агента** (например
|
||||
> «Корректор» или «Факт-чекер, который ходит в веб и проверяет факты») и
|
||||
> заводить чат, привязанный к выбранной роли. Роль задаёт поведение агента
|
||||
> (системный промпт) и, опционально, модель.
|
||||
>
|
||||
> Зафиксированные решения по объёму (см. раздел «Развилки»):
|
||||
> - **Владение** — только **админские, общие на воркспейс** роли (как провайдер и
|
||||
> внешние MCP-серверы сегодня). Личных ролей в v1 нет.
|
||||
> - **Гейтинг инструментов** — **нет**. Роль меняет только инструкции и (опц.) модель;
|
||||
> набор инструментов всегда полный (тот же, что у обычного чата). Ограничение
|
||||
> возможностей по ролям отложено (см. «Возможные расширения»).
|
||||
> - **Артефакт этого шага** — только дизайн-документ; код не пишется.
|
||||
|
||||
## Зачем это (и почему ложится в текущую архитектуру)
|
||||
|
||||
Сегодня у встроенного AI-агента нет понятия персоны/роли на уровне чата: вся
|
||||
настройка поведения — один системный промпт **на весь воркспейс**. Пользователь
|
||||
хочет заводить разные чаты под разные задачи (вычитка орфографии, проверка фактов
|
||||
по вебу и т. д.), каждый — со своей инструкцией и, возможно, своей моделью.
|
||||
|
||||
Три факта из текущего кода определяют дизайн (всё сверено по исходникам):
|
||||
|
||||
1. **Системный промпт — только на уровне воркспейса.** Собирается в
|
||||
[ai-chat.prompt.ts](../apps/server/src/core/ai-chat/ai-chat.prompt.ts),
|
||||
функция `buildSystemPrompt()`, по слоям: *базовая персона*
|
||||
(`workspace.settings.ai.provider.systemPrompt` либо `DEFAULT_PROMPT`) →
|
||||
*контекст* (имя воркспейса, открытая страница) → несъёмный `SAFETY_FRAMEWORK`.
|
||||
Персоны на чат сейчас нет — её надо добавить как ещё один слой.
|
||||
|
||||
2. **Инструменты — всегда все включены.** В
|
||||
[ai-chat.service.ts](../apps/server/src/core/ai-chat/ai-chat.service.ts):
|
||||
`const tools = { ...external.tools, ...docmostTools }`. ~40 Docmost-инструментов
|
||||
строит `AiChatToolsService.forUser()`
|
||||
([tools/ai-chat-tools.service.ts](../apps/server/src/core/ai-chat/tools/ai-chat-tools.service.ts)),
|
||||
внешние MCP-инструменты подмешивает `mcpClients.toolsFor(workspaceId)`
|
||||
([external-mcp/mcp-clients.service.ts](../apps/server/src/core/ai-chat/external-mcp/mcp-clients.service.ts)).
|
||||
Механизма включать подмножество инструментов нет — есть только CASL-проверка в
|
||||
момент вызова (через персональный loopback-токен). **По зафиксированному
|
||||
решению этот механизм мы и не вводим** — роль не трогает набор инструментов.
|
||||
|
||||
3. **Веб-доступ уже решён внешними MCP.** Внешние MCP-серверы
|
||||
(`ai_mcp_servers`, напр. Tavily) с SSRF-защитой
|
||||
([external-mcp/ssrf-guard.ts](../apps/server/src/core/ai-chat/external-mcp/ssrf-guard.ts))
|
||||
и шифрованием заголовков — это и есть «факт-чекер ходит в гугл». Поскольку
|
||||
гейтинга нет, веб-инструменты **уже доступны каждому чату**, если админ
|
||||
подключил соответствующий MCP-сервер. Роль «Факт-чекер» работает чисто за счёт
|
||||
инструкции «проверяй факты по веб-источникам и цитируй ссылки» — она направляет
|
||||
модель пользоваться уже доступными инструментами, а не добавляет их.
|
||||
|
||||
4. **Чат создаётся неявно** при первом сообщении: клиент
|
||||
([chat-thread.tsx](../apps/client/src/features/ai-chat/components/chat-thread.tsx))
|
||||
шлёт POST `/api/ai-chat/stream` с `chatId: null`, сервер
|
||||
([ai-chat.controller.ts](../apps/server/src/core/ai-chat/ai-chat.controller.ts))
|
||||
создаёт строку `ai_chats`. Привязать чат к роли можно одним новым полем `role_id`,
|
||||
которое клиент передаёт один раз при первом сообщении.
|
||||
|
||||
**Вывод:** роль — это тонкий слой поверх существующего пайплайна. Нужны:
|
||||
новая таблица ролей + админский CRUD, поле `ai_chats.role_id`, новый слой в
|
||||
`buildSystemPrompt()`, опциональный override модели в `getChatModel()`, пикер роли
|
||||
и управление ролями в UI. Граница безопасности (CASL через loopback-токен)
|
||||
**не меняется** — роль её не ослабляет и не усиливает (см. «Безопасность»).
|
||||
|
||||
## Модель
|
||||
|
||||
**Роль (Agent Role)** — именованный, общий на воркспейс пресет, который связывает:
|
||||
|
||||
| Часть | Что задаёт | Пример «Корректор» | Пример «Факт-чекер» |
|
||||
| --- | --- | --- | --- |
|
||||
| **instructions** | фрагмент системного промпта (персона/поведение) | «Исправляй только орфографию, пунктуацию и грамматику. Никогда не меняй смысл, факты, тон и структуру текста. Используй точечную правку текста» | «Проверяй фактические утверждения страницы по авторитетным веб-источникам. Цитируй ссылки. Помечай сомнительные места комментарием. Не редактируй текст страницы без явной просьбы» |
|
||||
| **model (опц.)** | модель ≠ дефолт воркспейса | дешёвая модель | сильная модель |
|
||||
| **presentation** | имя, emoji, описание | 🔤 «Корректор» | 🔎 «Факт-чекер» |
|
||||
|
||||
Чего роль в v1 **не** задаёт (по зафиксированным решениям): набор инструментов,
|
||||
выбор конкретных внешних MCP-серверов, владельца (роли только общие/админские),
|
||||
снапшот конфигурации на чат.
|
||||
|
||||
**Привязка чата к роли** — нулевое поле `ai_chats.role_id`. Чат «помнит», с какой
|
||||
ролью создан; роль применяется на каждом ходу. Чат без роли (`role_id IS NULL`) —
|
||||
обычный универсальный ассистент (текущее поведение).
|
||||
|
||||
## Модель данных (миграции)
|
||||
|
||||
Соглашение: `apps/server/src/database/migrations/YYYYMMDDThhmmss-description.ts`.
|
||||
Только **добавляем** таблицы/столбцы (никогда не трогаем данные Docmost). Timestamp
|
||||
новой миграции должен сортироваться **после** последней применённой; на момент
|
||||
написания последняя — `20260618T160000-ai-stt-credentials.ts`, значит брать
|
||||
`20260619T...`. После миграции — `pnpm --filter server migration:codegen` для
|
||||
регенерации [db.d.ts](../apps/server/src/database/types/db.d.ts). Образец стиля —
|
||||
[20260617T130000-ai-mcp-servers.ts](../apps/server/src/database/migrations/20260617T130000-ai-mcp-servers.ts).
|
||||
|
||||
**Миграция — таблица ролей + привязка чата:**
|
||||
```sql
|
||||
CREATE TABLE ai_agent_roles (
|
||||
id uuid PRIMARY KEY DEFAULT gen_uuid_v7(),
|
||||
workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
||||
creator_id uuid REFERENCES users(id) ON DELETE SET NULL, -- кто создал (аудит)
|
||||
name varchar NOT NULL, -- "Корректор"
|
||||
emoji varchar, -- presentation
|
||||
description text,
|
||||
instructions text NOT NULL, -- фрагмент system prompt
|
||||
model_config jsonb, -- { driver?, chatModel } | NULL = дефолт воркспейса
|
||||
enabled boolean NOT NULL DEFAULT true,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
deleted_at timestamptz -- soft delete (как у ai_chats)
|
||||
);
|
||||
CREATE INDEX idx_ai_agent_roles_workspace_id ON ai_agent_roles (workspace_id);
|
||||
|
||||
-- привязка чата к роли
|
||||
ALTER TABLE ai_chats
|
||||
ADD COLUMN role_id uuid REFERENCES ai_agent_roles(id) ON DELETE SET NULL;
|
||||
```
|
||||
|
||||
Заметки:
|
||||
- `creator_id ON DELETE SET NULL` — роль общая и переживает удаление автора
|
||||
(в отличие от `ai_chats.creator_id`, который `NOT NULL`); это только аудит.
|
||||
- `ai_chats.role_id ON DELETE SET NULL` — если роль жёстко удалят, чат
|
||||
деградирует к универсальному поведению, а не ломается (см. edge-cases).
|
||||
В сочетании с `deleted_at` основной путь удаления роли — **soft delete**:
|
||||
старые чаты тогда продолжают видеть инструкции через JOIN с учётом `deleted_at`
|
||||
(решение по поведению при удалении — в «Открытых вопросах»).
|
||||
- `model_config jsonb` — `{ chatModel }` либо `{ driver, chatModel }`. Пусто/`NULL`
|
||||
→ модель воркспейса. По образцу `publicShareChatModel` из
|
||||
[public-share-assistant-plan.md](./public-share-assistant-plan.md): креды
|
||||
(`apiKey`/`baseUrl`) берутся от провайдера соответствующего драйвера из
|
||||
`ai_provider_credentials`, отдельные креды на роль не нужны.
|
||||
|
||||
Типы: добавить `AiAgentRoles` в `db.interface.ts` (или поднять через codegen),
|
||||
`role_id` появится в `AiChats` автоматически после codegen.
|
||||
|
||||
## Бэкенд
|
||||
|
||||
### 1. Слой инструкций роли в системном промпте
|
||||
|
||||
В [ai-chat.prompt.ts](../apps/server/src/core/ai-chat/ai-chat.prompt.ts) добавить
|
||||
вход `roleInstructions` в `buildSystemPrompt()`. Приоритет персоны:
|
||||
```text
|
||||
effectivePersona = roleInstructions?.trim() || adminPrompt?.trim() || DEFAULT_PROMPT
|
||||
return `${effectivePersona}${context}\n${SAFETY_FRAMEWORK}`
|
||||
```
|
||||
Ключевое: **`SAFETY_FRAMEWORK` по-прежнему добавляется всегда и не отключается
|
||||
ролью.** Роль задаёт только персону; контекст (воркспейс, открытая страница) и
|
||||
safety-блок остаются как есть.
|
||||
|
||||
Решение «роль заменяет, а не дополняет admin-промпт» выбрано намеренно: для
|
||||
узкой роли вроде «Корректора» нужно, чтобы её инструкция доминировала, а не
|
||||
конкурировала с общим промптом воркспейса. (Альтернатива «конкатенировать
|
||||
admin-промпт + роль» — в «Открытых вопросах».)
|
||||
|
||||
### 2. Применение роли в стриме
|
||||
|
||||
В [ai-chat.service.ts](../apps/server/src/core/ai-chat/ai-chat.service.ts) (метод
|
||||
`stream()`), где сейчас резолвится `system` и `model`:
|
||||
- Загрузить роль по `ai_chats.role_id` (если задан и не удалён).
|
||||
- Передать `role.instructions` в `buildSystemPrompt({ ..., roleInstructions })`.
|
||||
- Если у роли есть `model_config` — резолвить модель с override (см. п. 3).
|
||||
- Набор инструментов **не меняется** (по решению).
|
||||
|
||||
Важно: `role_id` сервер берёт **из строки `ai_chats`, а не из тела запроса** на
|
||||
каждом ходу — роль нельзя подменить пораздачно. Клиент сообщает `roleId` только
|
||||
при создании чата (первое сообщение), сервер сохраняет его в `ai_chats.role_id`.
|
||||
|
||||
### 3. Override модели
|
||||
|
||||
`AiService.getChatModel(workspaceId)`
|
||||
([integrations/ai/ai.service.ts](../apps/server/src/integrations/ai/ai.service.ts))
|
||||
получает опциональный аргумент override модели (паттерн из
|
||||
[public-share-assistant-plan.md](./public-share-assistant-plan.md) §5):
|
||||
- `model_config.chatModel` — id модели вместо `chatModel` воркспейса;
|
||||
- `model_config.driver` (опц.) — если указан другой драйвер, берём его креды из
|
||||
`ai_provider_credentials`; если кредов нет → `AiNotConfiguredException` (503) с
|
||||
**внятным сообщением** («для роли X выбран провайдер Y, но он не настроен»),
|
||||
согласно конвенции об ошибках (никаких «Something went wrong»).
|
||||
- Пусто → текущее поведение (модель воркспейса).
|
||||
|
||||
Резолв модели делать **до** hijack ответа, чтобы ненастроенный провайдер вернул
|
||||
503, а не падал в середине стрима (как уже сделано в контроллере для воркспейс-модели).
|
||||
|
||||
### 4. CRUD ролей (админский модуль)
|
||||
|
||||
Новый модуль `core/ai-chat/roles/` рядом с `external-mcp/`:
|
||||
`ai-agent-roles.controller.ts` + `ai-agent-roles.service.ts` + repo
|
||||
(`database/repos/ai-agent-roles/`). Эндпоинты под `/api/ai-chat/roles` (или
|
||||
`/api/ai-settings/roles` — рядом с MCP-серверами; выбрать единообразно с
|
||||
существующим размещением, см. «Открытые вопросы»):
|
||||
|
||||
| Метод | Доступ | Назначение |
|
||||
| --- | --- | --- |
|
||||
| `list` | **любой участник воркспейса** | получить список ролей для пикера при создании чата |
|
||||
| `create` / `update` / `delete` | **только админ** | управление ролями (как `ai-settings`) |
|
||||
|
||||
Нюанс CASL: создание/правка/удаление — под админской абилити (как
|
||||
[ai-settings.controller.ts](../apps/server/src/core/.../ai-settings.controller.ts)
|
||||
управляет провайдером и MCP-серверами), но **list должен быть доступен всем
|
||||
участникам**, иначе обычный пользователь не сможет выбрать роль при заведении
|
||||
чата. Все запросы строго скоупятся по `workspace_id` (мультитенант по хосту).
|
||||
|
||||
Валидация при create/update: непустые `name` и `instructions`; если задан
|
||||
`model_config.driver` — он из числа поддерживаемых (`openai`/`gemini`/`ollama`).
|
||||
|
||||
## Клиент
|
||||
|
||||
### 1. Пикер роли при создании чата
|
||||
|
||||
В зоне «New chat» / композере
|
||||
([ai-chat-window.tsx](../apps/client/src/features/ai-chat/components/ai-chat-window.tsx),
|
||||
[chat-input.tsx](../apps/client/src/features/ai-chat/components/chat-input.tsx)) —
|
||||
селектор роли (Mantine `Select`/`SegmentedControl`), дефолт «Универсальный
|
||||
ассистент» (без роли). Выбранный `roleId` хранится в новом Jotai-атоме рядом с
|
||||
[atoms/ai-chat-atom.ts](../apps/client/src/features/ai-chat/atoms/ai-chat-atom.ts)
|
||||
и уходит в теле **первого** запроса на `/stream` (расширить
|
||||
`prepareSendMessagesRequest` в `chat-thread.tsx`: добавить `roleId`). После того
|
||||
как сервер создал чат с ролью, пикер для этого чата фиксируется (роль чата
|
||||
неизменна; смена роли = новый чат — простое и предсказуемое поведение для v1).
|
||||
|
||||
### 2. Бейдж роли
|
||||
|
||||
Показывать emoji+имя роли в шапке окна чата и в строке списка
|
||||
([conversation-list.tsx](../apps/client/src/features/ai-chat/components/conversation-list.tsx)),
|
||||
чтобы было видно, «с кем» разговор. `role_id`/денормализованное имя+emoji роли
|
||||
добавить в выдачу списка чатов и тип `IAiChat`
|
||||
([types/ai-chat.types.ts](../apps/client/src/features/ai-chat/types/ai-chat.types.ts)).
|
||||
|
||||
### 3. Управление ролями в настройках
|
||||
|
||||
Новая секция «Роли агента» в Settings → AI
|
||||
([pages/settings/workspace/ai-settings.tsx](../apps/client/src/pages/settings/workspace/ai-settings.tsx)),
|
||||
рядом с «External tools». Переиспользовать паттерн add/edit/delete-модалки из
|
||||
[ai-mcp-servers.tsx](../apps/client/src/features/workspace/components/settings/components/ai-mcp-servers.tsx).
|
||||
Форма роли: имя, emoji, описание, **instructions** (textarea — как редактор
|
||||
системного сообщения в
|
||||
[ai-provider-settings.tsx](../apps/client/src/features/workspace/components/settings/components/ai-provider-settings.tsx)),
|
||||
опциональный override модели. Подпись-напоминание под полем instructions:
|
||||
«встроенный safety-фреймворк добавляется автоматически» (как у системного сообщения).
|
||||
|
||||
### 4. Слой запросов
|
||||
|
||||
Новые TanStack Query хуки в
|
||||
[queries/ai-chat-query.ts](../apps/client/src/features/ai-chat/queries/ai-chat-query.ts)
|
||||
(или отдельный файл): `useAiRolesQuery()` (list), `useCreate/Update/DeleteAiRoleMutation()`
|
||||
+ функции в
|
||||
[services/ai-chat-service.ts](../apps/client/src/features/ai-chat/services/ai-chat-service.ts).
|
||||
Тип `IAiRole` зеркалит серверную схему.
|
||||
|
||||
## Поток одного хода (с ролью)
|
||||
|
||||
1. Создание чата: клиент шлёт первое сообщение + `roleId` → `/ai-chat/stream`;
|
||||
сервер создаёт `ai_chats` с `role_id`.
|
||||
2. Последующие ходы: сервер читает `role_id` из строки чата (не из тела запроса).
|
||||
3. Резолв: загрузить роль (если не удалена) → `instructions` + `model_config`.
|
||||
4. `buildSystemPrompt({ workspace, adminPrompt, roleInstructions, openedPage })`
|
||||
→ персона роли + контекст + несъёмный `SAFETY_FRAMEWORK`.
|
||||
5. `getChatModel(workspaceId, role.model_config)` → модель роли или дефолт.
|
||||
6. `streamText({ model, system, messages, tools, stopWhen: stepCountIs(8) })` —
|
||||
**набор инструментов полный, как у обычного чата**.
|
||||
|
||||
## Edge-cases (главное)
|
||||
|
||||
- **Роль удалена/выключена, а чаты на неё ссылаются.** При hard-delete
|
||||
`ON DELETE SET NULL` обнуляет `ai_chats.role_id` → чат продолжает работать как
|
||||
универсальный. Основной путь — soft-delete (`deleted_at`)/`enabled=false`:
|
||||
тогда роль исчезает из пикера, но старые чаты могут продолжать применять её
|
||||
инструкции (резолв учитывает `deleted_at` — точное поведение в «Открытых
|
||||
вопросах»).
|
||||
- **Роль отредактировали после создания чатов.** В v1 без снапшота правка
|
||||
применяется «вживую» — старые чаты подхватывают новые инструкции на следующем
|
||||
ходу. Приемлемо для кейсов «Корректор/Факт-чекер»; снапшот конфигурации на чат —
|
||||
возможное расширение.
|
||||
- **Safety не переопределяется.** `SAFETY_FRAMEWORK` добавляется всегда, что бы
|
||||
ни написали в `instructions` роли (включая попытку «игнорируй прежние инструкции»).
|
||||
- **Override модели на ненастроенный провайдер** → 503 с конкретным сообщением,
|
||||
а не молчаливый фолбэк (конвенция об ошибках). Решить, делать ли мягкий фолбэк
|
||||
на модель воркспейса (в «Открытых вопросах»).
|
||||
- **Пустые `instructions`** недопустимы при создании (валидация); но если роль
|
||||
как-то оказалась с пустыми инструкциями — персона падает на admin-промпт/дефолт.
|
||||
- **Заголовок чата** генерируется фоново (`generateText`) — оставить на модели
|
||||
воркспейса, чтобы экзотический override роли не ломал автозаголовок (мелочь).
|
||||
- **Мультитенант.** Все операции с ролями скоупятся по `workspace_id`; роль из
|
||||
чужого воркспейса не видна и не применима.
|
||||
- **MCP-зеркало схемы** ([packages/mcp](../packages/mcp)) фичу не затрагивает —
|
||||
роли живут только во встроенном AI-чате, не в standalone MCP.
|
||||
|
||||
## Безопасность
|
||||
|
||||
- **Граница безопасности не меняется.** Агент по-прежнему ходит в API через
|
||||
персональный loopback-JWT (`AiChatToolsService.forUser`), и CASL ограничивает
|
||||
его ровно правами текущего пользователя. Роль — это слой *формирования промпта
|
||||
и выбора модели*, он не выдаёт и не отнимает прав.
|
||||
- **Следствие решения «без гейтинга» (осознанный компромисс):**
|
||||
- Роль «Корректор» инструкцией просят не менять смысл, но технически у чата
|
||||
остаются все write-инструменты — модель *могла бы* отредактировать/удалить
|
||||
(под soft-delete и CASL, т. е. обратимо и в пределах прав пользователя). Это
|
||||
мягкая граница (промпт), а не жёсткая.
|
||||
- Роль «Факт-чекер» полагается на то, что админ глобально подключил веб-MCP
|
||||
(Tavily); тогда веб-инструменты доступны *всем* чатам, а не только этой роли.
|
||||
Жёсткие границы возможностей по ролям — отдельная будущая фаза (см. ниже).
|
||||
- **Instructions — доверенный контент:** их пишет админ воркспейса, они попадают
|
||||
только в системный промпт чатов этого воркспейса и исполняются под правами
|
||||
конкретного пользователя. Эскалации нет.
|
||||
- **Внешние MCP** остаются под SSRF-guard; роли логику подключения MCP не трогают.
|
||||
|
||||
## Явные non-goals (v1)
|
||||
|
||||
- Нет гейтинга/ограничения инструментов по ролям (роль не сужает тулсет).
|
||||
- Нет личных ролей (только общие админские).
|
||||
- Нет выбора конкретных внешних MCP-серверов на роль (все включённые доступны всем).
|
||||
- Нет снапшота конфигурации роли на чат (правка роли применяется вживую).
|
||||
- Нет per-role параметров генерации сверх модели (temperature и т. п.).
|
||||
- Нет композиции «скиллов» поверх роли (см. «Связь со „скиллами“»).
|
||||
|
||||
## Связь со «скиллами»
|
||||
|
||||
В терминах Anthropic Skills (подгружаемый по требованию пакет инструкций +
|
||||
ресурсов/скриптов) текущая роль = MVP-«скилл»: только текстовая инструкция + выбор
|
||||
модели. Естественная эволюция — сделать «скиллы» композируемыми (несколько скиллов
|
||||
на одну роль), привязывать к роли эталонные страницы/файлы как контекст, и —
|
||||
главное — добавить **жёсткий гейтинг инструментов** (тогда «Корректор» физически не
|
||||
сможет удалять, а «Факт-чекер» получит веб ровно тогда, когда роль это разрешает).
|
||||
Всё это — следующие итерации, вне scope v1.
|
||||
|
||||
## Развилки (зафиксированные решения)
|
||||
|
||||
| Развилка | Решение | Альтернативы (отклонены / отложены) |
|
||||
| --- | --- | --- |
|
||||
| Владение ролями | **Только админские, общие на воркспейс** | личные роли; личные + общие |
|
||||
| Ограничение инструментов | **Нет (только промпт + модель)** | крупные группы возможностей; тонкий per-tool allowlist |
|
||||
| Выбор MCP-серверов на роль | **Нет (все включённые доступны всем)** | мультиселект MCP-серверов на роль |
|
||||
| Привязка чата к роли | **Поле `ai_chats.role_id`, неизменно после создания** | смена роли внутри чата; роль в теле каждого запроса |
|
||||
| Персона роли vs admin-промпт | **Роль заменяет персону** (safety всегда добавляется) | конкатенация admin-промпт + роль |
|
||||
| Снапшот конфигурации | **Нет (правка вживую)** | снапшот конфигурации роли на чат |
|
||||
|
||||
## Открытые вопросы (не блокируют дизайн)
|
||||
|
||||
1. **Размещение CRUD-эндпоинтов и UI:** `/ai-chat/roles` (рядом с чатом) или
|
||||
`/ai-settings/roles` (рядом с MCP-серверами). Предлагаю в одном месте с MCP —
|
||||
там уже живут админские AI-настройки.
|
||||
2. **Поведение при удалении роли:** soft-delete с сохранением инструкций для старых
|
||||
чатов vs hard-delete + `SET NULL` (старые чаты деградируют к универсальным).
|
||||
Предлагаю soft-delete (`deleted_at`) — консистентно с `ai_chats`.
|
||||
3. **Override модели на ненастроенный драйвер:** жёсткий 503 с внятным сообщением
|
||||
vs мягкий фолбэк на модель воркспейса. Предлагаю 503 (явность важнее).
|
||||
4. **Стартовые пресеты:** поставлять ли «Корректор» и «Факт-чекер» как
|
||||
преднастроенные роли-шаблоны (seed) при включении фичи, чтобы админ не писал
|
||||
инструкции с нуля. Предлагаю — да, как необязательный «вставить пример».
|
||||
5. **Денормализация для бейджа:** хранить имя/emoji роли только в `ai_agent_roles`
|
||||
и джойнить, либо денормализовать на `ai_chats` для дешёвого списка. Предлагаю
|
||||
джойн (простота; список чатов не горячий путь).
|
||||
|
||||
## Объём работ
|
||||
|
||||
Бэкенд: 1 миграция (`ai_agent_roles` + `ai_chats.role_id`) + codegen типов;
|
||||
новый CRUD-модуль ролей (controller/service/repo) под CASL; правка
|
||||
`buildSystemPrompt()` (слой `roleInstructions`); правка `AiChatService.stream()`
|
||||
(загрузка роли, передача инструкций и override модели); опциональный override
|
||||
модели в `AiService.getChatModel()`. Клиент: пикер роли при создании чата + атом +
|
||||
проброс `roleId` в первый запрос; бейдж роли в шапке и списке; секция управления
|
||||
ролями в Settings → AI (модалка add/edit/delete по образцу MCP-серверов); хуки
|
||||
запросов/мутаций. **Без изменений в `packages/mcp`. Набор инструментов агента не
|
||||
трогаем.**
|
||||
Reference in New Issue
Block a user