7ec2ba3c8f
Операторы получают веб-редактор: правят системный промпт и правила,
сохраняют как новую версию, активируют, откатываются. Активная версия
используется в «Песочнице» на каждый /chat.
Принципы, согласованные заранее:
- Сохранённые версии не редактируются — только создание новой. Честный
откат: v1 всегда та же, что была при создании.
- Правила на этом этапе — свободный markdown (textarea). Переход на
структурированные правила (pattern → instruction) — в бэклог.
- Файл prompts/system_prompt.md становится сид-источником: при первом
старте, если таблица agent_configs пустая, из него создаётся v1 и
активируется. Дальше правда идёт из БД, файл не трогаем.
- rules_text конкатенируется с system_prompt в один system-message
через compose_full_system_prompt: "{prompt}\n\nДополнительные
правила:\n{rules}".
- Активную версию удалить нельзя — сначала активируют другую.
Модель и миграция:
- db/models/AgentConfig: id, version (unique/indexed), name (nullable),
system_prompt, rules_text, is_active (indexed), created_at.
Без updated_at — версии неизменяемы.
- Миграция b4450e33664d_add_agent_configs_table.
Сервисы и роутеры:
- services/config_service: ensure_seed (seed v1 из файла),
list/get/get_active/create (version=max+1, при activate атомарно
сбрасывает is_active у остальных и ставит новой),
activate_config (та же схема), delete_config (возвращает причину
отказа: not_found / active), compose_full_system_prompt.
- services/chat_service.send_message: берёт active_cfg, собирает
system_prompt через compose_full_system_prompt, пишет
thread.agent_config_id при создании треда (колонка была nullable
ещё со Спринта 2 — пригодилась именно здесь).
- routers/configs: GET /configs, GET /configs/active, GET /configs/{id},
POST /configs (activate-флаг), POST /configs/{id}/activate,
DELETE /configs/{id} (404 / 400 если активная).
- Pydantic: AgentConfigCreateRequest, AgentConfigInfo, ListResponse,
DeleteResponse.
- main.py: ensure_seed в lifespan после инициализации БД/Chroma/LLM.
UI:
- static/settings.html — трёхблочная страница: имя версии, textarea
промпта, textarea правил, «Сохранить как новую» + галка
«Сразу активировать», «Загрузить активную в редактор». Справа —
список версий с бейджем «активная», действиями «Активировать» /
«Удалить» (disabled у активной) / «Загрузить в редактор». При
первом заходе активная версия автоматом подгружается в редактор.
- В nav на index.html и sandbox.html добавлена ссылка «Настройки».
- В шапке «Песочницы» — зелёный кликабельный бейдж «активная: vN · имя»
(ведёт на /settings.html), обновляется раз в 15 с.
E2E проверено: создана v2 с правилом «ВСЕГДА начинай со слов СПАСИБО
ЗА ВОПРОС», активирована; следующий /chat вернул ответ, начинающийся
ровно с этой фразы; assembled_prompt содержит блок «Дополнительные
правила». После отката на v1 тест-v2 удалена.
SPRINTS.md: Спринт 3 помечен закрытым.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
29 lines
1.3 KiB
Python
29 lines
1.3 KiB
Python
from pydantic import BaseModel, Field
|
|
|
|
|
|
class QueryRequest(BaseModel):
|
|
text: str = Field(..., description="Вопрос от лица пациента")
|
|
top_k: int = Field(5, ge=1, le=20, description="Количество чанков для retrieval")
|
|
document_ids: list[str] | None = Field(None, description="Ограничить поиск конкретными документами")
|
|
temperature: float | None = Field(None, ge=0.0, le=2.0)
|
|
max_tokens: int | None = Field(None, ge=100, le=8000)
|
|
|
|
|
|
class ChatRequest(BaseModel):
|
|
text: str = Field(..., description="Реплика пациента")
|
|
thread_id: int | None = Field(None, description="ID треда; если не передан — создаётся новый")
|
|
top_k: int = Field(5, ge=1, le=20)
|
|
temperature: float | None = Field(None, ge=0.0, le=2.0)
|
|
max_tokens: int | None = Field(None, ge=100, le=8000)
|
|
|
|
|
|
class ThreadRenameRequest(BaseModel):
|
|
name: str = Field(..., min_length=1, max_length=200)
|
|
|
|
|
|
class AgentConfigCreateRequest(BaseModel):
|
|
system_prompt: str = Field(..., min_length=1)
|
|
rules_text: str = Field("", description="Правила в свободной markdown-форме")
|
|
name: str | None = Field(None, max_length=200)
|
|
activate: bool = Field(False, description="Сразу сделать новую версию активной")
|