Support Assistent persona packs via git and local sync.
Replace assistent-personas overlay seed with assistent-extensions SFTP and an assistent: section in extensions.yaml so personalities install like other extensions without private URLs in the public repo. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -8,6 +8,8 @@ models.yaml
|
||||
assistent-personas.yaml
|
||||
assistent-personas/
|
||||
!assistent-personas.example/
|
||||
assistent-extensions/
|
||||
!assistent-extensions.example/
|
||||
extensions.yaml
|
||||
|
||||
# SwarmUI checkpoints / LoRA dropped into the app tree
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Assistent persona packs (laptop → VM)
|
||||
|
||||
Clone git persona packs into this folder (one subdirectory per pack).
|
||||
|
||||
On `gpu-rent up` / `seed-personas` / `push` each pack is SFTP'd to
|
||||
`/mnt/swarm_data/Assistent/extensions/<dirname>/` (``.git`` is not uploaded).
|
||||
|
||||
Pack layout (repo root):
|
||||
|
||||
```
|
||||
assistent-pack.yaml # kind: persona / id: my_persona
|
||||
persona.json
|
||||
voice.json
|
||||
…
|
||||
```
|
||||
|
||||
Or multi-pack: `personas/<id>/persona.json` under the clone.
|
||||
|
||||
Bundled personas ship with swarm-assistent (`neutral`, `aggressive`, `dreamer`).
|
||||
Private packs stay only here — do not put their URLs in `extensions.yaml` if you
|
||||
do not want them captured/shared.
|
||||
|
||||
Overlay clones created in the Assistent UI are **not** deleted by seed.
|
||||
@@ -1,16 +0,0 @@
|
||||
# Assistent personas (laptop → VM overlay)
|
||||
|
||||
Copy this folder to `assistent-personas/` (gitignored) and edit shelves.
|
||||
|
||||
On `gpu-rent up` / `seed-personas` files under `personas/<id>/` are pushed to
|
||||
`/mnt/swarm_data/Assistent/personas/<id>/`. Bundled personas (including **leonid**)
|
||||
ship with the swarm-assistent extension — you do not need to duplicate them here.
|
||||
|
||||
- `_base/assistant.json` — only `default_persona` (and gpu-rent adds `num_ctx`)
|
||||
- `personas/<id>/*.json` — sparse identity shelves (bio, voice, humor, …)
|
||||
- Overlay personas created in the Assistent UI are **not** deleted by seed
|
||||
|
||||
This is a **seed overlay**, not `gpu-rent push` (see [docs/local-folders.md](../docs/local-folders.md)).
|
||||
Debug overlay vs laptop: `GET http://127.0.0.1:17821/assistent/overlay`.
|
||||
|
||||
See swarm-assistent `Config/personas/README.md` for the shelf schema.
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"default_persona": "neutral"
|
||||
}
|
||||
+3
-3
@@ -79,7 +79,7 @@ gpu-rent up --yes --ollama
|
||||
| `gpu-rent ssh` | Оболочка на VM |
|
||||
| `gpu-rent seed-models` | Докачать новые строки Civitai-манифеста на живой диск |
|
||||
| `gpu-rent seed-extensions` | Доклонировать/обновить git-репы; restart swarmui |
|
||||
| `gpu-rent seed-personas` | Пуш `assistent-personas/` → overlay на VM (без полного up); также `search.jsonl` → FTS, если есть |
|
||||
| `gpu-rent seed-personas` | Пуш `assistent-extensions/` → `Assistent/extensions/` (+ `_base`); также `search.jsonl` → FTS, если есть |
|
||||
| `gpu-rent push` / `push-models` | Локальные деревья → VM |
|
||||
| `gpu-rent pull-output` | VM `Output/` → `./Output` |
|
||||
| `gpu-rent capture` / `capture all` | Инвентарь VM → merge **ссылок** в `models.yaml` + `extensions.yaml` (веса не качать) |
|
||||
@@ -151,7 +151,7 @@ Exit 0 → можно `up`. Exit 1 → причина в таблице / кра
|
||||
| `/assistent` | Сводка: extension + overlay + roles + memory + live API + hints + playbook |
|
||||
| `/assistent/diagnose` | То же + journal по умолчанию + session store meta |
|
||||
| `/assistent/extension` | DLL, Sqlite deps, Tab/Assets, git HEAD |
|
||||
| `/assistent/overlay` | personas на VM vs локальный `assistent-personas/` |
|
||||
| `/assistent/overlay` | personas/packs на VM vs локальный `assistent-extensions/` |
|
||||
| `/assistent/roles` | `ollama-roles.json` ↔ `/api/tags` (`default_chat`) |
|
||||
| `/assistent/memory` | `assistent.sqlite` + counts |
|
||||
| `/assistent/api` | `AssistentListPersonas/Models/Memory/Chats` через туннель (или SSH) |
|
||||
@@ -198,7 +198,7 @@ curl -sS -X DELETE "$BASE/assistent/session/$SID"
|
||||
# One-shot convenience (session + one chat + delete)
|
||||
curl -sS -X POST "$BASE/assistent/chat-eval" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"message":"какие steps/cfg для turbo?","persona":"leonid","timeout":120}' \
|
||||
-d '{"message":"какие steps/cfg для turbo?","persona":"neutral","timeout":120}' \
|
||||
| jq '{ok,reply,patch,trace}'
|
||||
```
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
| Civitai хост | Дефолт API `civitai.red` (полный каталог). `.com` — SFW-витрина, NSFW с неё часто 404. Ссылки `.com`/`.red`/`.green` в манифесте принимаем. 404 → один retry на второй хост. Один токен на оба домена |
|
||||
| Пул GPU | Перед `up`/`flavors` сканируем `SCAN_POOLS` (дефолт `ru-6,ru-7`) по compute URL. Сегменты `ru-6a/b/c` берём из Nova `availability_zones`, не отдельными пулами. Пресет размера `FLAVOR_SIZE_PRESET` (дефолт `cheap` = 1×GPU, min vCPU/RAM). Диск уже есть — AZ диска, не дешёвый чужой сегмент. Первый запуск — дешёвый SKU в сегменте, где есть карта из `FLAVOR_PREFERENCE`. `.env` автоматом не пишем |
|
||||
| Манифест моделей | `<repo>/models.yaml`, типы: checkpoint / lora / vae / embedding / controlnet / upscaler. В git только `models.example.yaml` |
|
||||
| Расширения | `<repo>/extensions.yaml`: git-репы `swarmui` → `src/Extensions`, `comfy` → DLNodes. Поле `requires: none\|ollama\|any-llm` фильтрует по `LLM_RUNTIME`. Пример: swarm-assistent с `requires: ollama`. Personas: `assistent-personas/` → overlay `Assistent/personas/<id>/` на `up` / `seed-personas` (не сносит UI-клоны) |
|
||||
| Расширения | `<repo>/extensions.yaml`: git-репы `swarmui` → `src/Extensions`, `comfy` → DLNodes, `assistent` → `Assistent/extensions`. Поле `requires: none\|ollama\|any-llm` фильтрует по `LLM_RUNTIME`. Пример: swarm-assistent с `requires: ollama`. Persona packs: также `assistent-extensions/` → SFTP на `up` / `seed-personas` (не сносит UI-клоны) |
|
||||
| Autocomplete | До первого старта: скачать word-list в `Data/Autocompletions`, прописать `DefaultUser.AutoComplete.Source`. На каждом `up` сверить GitHub blob sha и обновить файл, если изменился. Дефолт: `tags/danbooru.csv` из a1111-sd-webui-tagcomplete (как в доке SwarmUI) |
|
||||
| Доступ | Браузер на туннеле; MCP переключается на облако, пока оно живо; HTTP API SwarmUI через тот же туннель |
|
||||
| Preemptible | По умолчанию всегда. Обычный сервер — только `--no-spot` |
|
||||
|
||||
+16
-1
@@ -26,6 +26,7 @@ copy extensions.example.yaml extensions.yaml
|
||||
| --- | --- | --- |
|
||||
| `swarmui` | `/mnt/swarm_data/Extensions/<dir>` → `/opt/swarmui/src/Extensions/<dir>` | C# extension SwarmUI (класс `: Extension`). После рестарта подхватывается сам |
|
||||
| `comfy` | `/mnt/swarm_data/DLNodes/<dir>` → `…/ComfyUIBackend/DLNodes/<dir>` | Custom nodes ComfyUI (Python). `requirements.txt` ставит SwarmUI/Comfy при первом старте backend |
|
||||
| `assistent` | `/mnt/swarm_data/Assistent/extensions/<dir>/` | Persona pack (полки JSON + `assistent-pack.yaml`). Без рестарта C# — Assistent читает с диска |
|
||||
|
||||
Пустой манифест или его нет — ничего не клонируем, SwarmUI как из коробки. Это не связано с Civitai-токеном.
|
||||
|
||||
@@ -42,6 +43,11 @@ swarmui:
|
||||
- url: https://github.com/example/SwarmUI-SomeExt.git
|
||||
ref: main
|
||||
dir: SomeExt
|
||||
assistent: []
|
||||
# - url: https://example.com/org/some-persona-pack.git
|
||||
# ref: main
|
||||
# dir: some-persona-pack
|
||||
# requires: ollama
|
||||
comfy:
|
||||
- url: https://github.com/example/ComfyUI-Something.git
|
||||
ref: v1.2.0
|
||||
@@ -53,7 +59,16 @@ comfy:
|
||||
| `ollama` | только при `LLM_RUNTIME=ollama` |
|
||||
| `any-llm` | при `LLM_RUNTIME=ollama` (сейчас единственный LLM-рантайм; llama.cpp снят) |
|
||||
|
||||
Строки с несовпавшим `requires` пропускаются (лог), остальные ставятся как обычно. В `extensions.example.yaml` по умолчанию — **swarm-assistent** с `requires: ollama` (чат + доска Generate/Ref + persona/Cards, ветка `main`). После `seed-extensions` + рестарта SwarmUI подтягивается текущий `main` (с **0.13.1+**: `CopyLocalLockFileAssemblies` копирует `Microsoft.Data.Sqlite` + `SQLitePCLRaw*` рядом с extension DLL — иначе ListMemory/ListChats/SaveChat падают; с **0.10.11**: default chat = `default_chat` / старший тег, warm той же моделью). На `up` (и `gpu-rent seed-personas`) сидится папка `assistent-personas/` → overlay `/mnt/swarm_data/Assistent/personas/<id>/*.json`, плюс `_base/assistant.json` (`default_persona`, опционально `num_ctx` с GPU tier). Seed **не** удаляет overlay-личности, созданные в UI. Legacy `personas.json` / yaml-prompt больше не пишутся.
|
||||
Строки с несовпавшим `requires` пропускаются (лог), остальные ставятся как обычно. В `extensions.example.yaml` по умолчанию — **swarm-assistent** с `requires: ollama` (чат + доска Generate/Ref + persona/Cards, ветка `main`). После `seed-extensions` + рестарта SwarmUI подтягивается текущий `main` (с **0.13.1+**: `CopyLocalLockFileAssemblies` копирует `Microsoft.Data.Sqlite` + `SQLitePCLRaw*` рядом с extension DLL — иначе ListMemory/ListChats/SaveChat падают; с **0.10.11**: default chat = `default_chat` / старший тег, warm той же моделью).
|
||||
|
||||
### Persona packs
|
||||
|
||||
Bundled в swarm-assistent: `neutral` (Нормальный), `aggressive`, `dreamer`. Дополнительные личности — git-паки:
|
||||
|
||||
1. **Конфиг** — секция `assistent:` в `extensions.yaml` (clone на VM в `Assistent/extensions/<dir>/`).
|
||||
2. **Локально** — клоны в `assistent-extensions/<dir>/` (gitignored). На `up` / `seed-personas` / `push` уезжают SFTP **без** `.git` в тот же dest. Приватные паки можно держать только так — без URL в yaml (capture их не увидит).
|
||||
|
||||
Формат пака: `assistent-pack.yaml` (`kind: persona`, `id: …`) + полки JSON в корне (или `personas/<id>/` для мульти-пака). На `up` также пишется `_base/assistant.json` (`default_persona: neutral`, опционально `num_ctx` с GPU tier). Seed **не** удаляет overlay-личности, созданные в UI. Legacy `assistent-personas/` / `personas.json` больше не сидятся.
|
||||
|
||||
Если есть `datasets/civitai/search.jsonl`, на том же шаге пушится FTS-индекс → `/mnt/swarm_data/Assistent/civitai-examples.jsonl` ([datasets/README](../datasets/README.md)). Диагностика Assistent: Debug API `/assistent/*` ([cli.md](cli.md)).
|
||||
|
||||
|
||||
+1
-1
@@ -113,7 +113,7 @@ Unit `gpu-rent-ollama` читает `/mnt/swarm_data/.gpu-rent-gpu.json`:
|
||||
|
||||
\*Flash на Ampere+ (compute ≥ 8.0). `NUM_PARALLEL=2`, `MAX_LOADED_MODELS=2` (chat VL + memory embed). Memory models use `use: memory` and a CPU Modelfile (`num_gpu 0`) so embed does not steal VRAM from the chat model. Ollama default `num_ctx` is 4096; we set `OLLAMA_CONTEXT_LENGTH` so Assistent + vision fits. Mid/high keep-alive is **5m** because Assistent parks the chat model before Generate (`AssistentParkLlm`) and warms after. Env: `/mnt/swarm_data/.gpu-rent-ollama.env`.
|
||||
|
||||
Personas: laptop `assistent-personas/` → on `up` / `seed-personas` overlay folders under `/mnt/swarm_data/Assistent/personas/<id>/` (+ `_base/assistant.json` with `default_persona` / `num_ctx`). Shelves are short JSON files (not a long prompt). See [extensions.md](extensions.md). Optional FTS examples: [datasets/README](../datasets/README.md) (`search.jsonl` → `Assistent/civitai-examples.jsonl`). Journal: `gpu-rent logs -u ollama`.
|
||||
Personas: bundled `neutral` / `aggressive` / `dreamer` in swarm-assistent; extra packs via `extensions.yaml` `assistent:` or laptop `assistent-extensions/` → `Assistent/extensions/` on `up` / `seed-personas` / `push` (+ `_base/assistant.json` with `default_persona` / `num_ctx`). See [extensions.md](extensions.md). Optional FTS examples: [datasets/README](../datasets/README.md) (`search.jsonl` → `Assistent/civitai-examples.jsonl`). Journal: `gpu-rent logs -u ollama`.
|
||||
|
||||
`ollama-models.yaml` entries:
|
||||
|
||||
|
||||
@@ -19,18 +19,18 @@
|
||||
|
||||
Веса, картинки, воркфлоу в git не коммитить (`.gitignore` + `.gitkeep`).
|
||||
|
||||
## Seed overlay (не `gpu-rent push`)
|
||||
## Seed overlay (не только `gpu-rent push`)
|
||||
|
||||
Другой механизм — не SHA256-синк четырёх деревьев выше:
|
||||
Другой механизм — не SHA256-синк Models/Wildcards/workflows:
|
||||
|
||||
| Локально | На VM | Когда |
|
||||
| --- | --- | --- |
|
||||
| `assistent-personas/` | `/mnt/swarm_data/Assistent/personas/<id>/` | `up` / `seed-personas`; см. [extensions.md](extensions.md) |
|
||||
| `assistent-extensions/<pack>/` | `/mnt/swarm_data/Assistent/extensions/<pack>/` | `up` / `seed-personas` / `push` (без `.git`); см. [extensions.md](extensions.md) |
|
||||
| `datasets/civitai/search.jsonl` | `Assistent/civitai-examples.jsonl` | `up` / `seed-personas`, если файл есть; см. [datasets/README](../datasets/README.md) |
|
||||
|
||||
## Push (Models / Wildcards / CustomWorkflows)
|
||||
## Push (Models / Wildcards / CustomWorkflows / packs)
|
||||
|
||||
Как у моделей: нет содержимого кроме README/`.gitkeep` — skip. Новый или изменённый файл — SFTP. Метадата рядом с весами едет парой. Команда на все три дерева: `gpu-rent push`.
|
||||
Как у моделей: нет содержимого кроме README/`.gitkeep` — skip. Новый или изменённый файл — SFTP. Метадата рядом с весами едет парой. Команда: `gpu-rent push` (также сидит persona-паки).
|
||||
|
||||
Wildcards: `.txt` (одна строка — один вариант), как в доке SwarmUI `Data/Wildcards`.
|
||||
|
||||
|
||||
+1
-1
@@ -181,7 +181,7 @@ copy models.example.yaml models.yaml
|
||||
Опционально позже (не нужно для первой картинки):
|
||||
|
||||
- `SELECTEL_API_TOKEN` (X-Token панели) + `gpu-rent watchdog install` — toast по балансу ₽; см. [cli.md](cli.md) / [decisions.md](decisions.md).
|
||||
- `copy assistent-personas.example assistent-personas` — overlay personas; см. [extensions.md](extensions.md).
|
||||
- `copy assistent-extensions.example assistent-extensions` — persona packs; см. [extensions.md](extensions.md).
|
||||
- `.\civitai-dataset.bat` — scrape метаданных Krea2 → Assistent FTS; см. [datasets/README](../datasets/README.md).
|
||||
|
||||
---
|
||||
|
||||
+10
-1
@@ -2,6 +2,7 @@
|
||||
# Empty/missing file → no extra extensions, stock SwarmUI.
|
||||
# swarmui = C# repos cloned to src/Extensions
|
||||
# comfy = Python custom nodes cloned to ComfyUI DLNodes
|
||||
# assistent = persona packs cloned to Assistent/extensions (requires ollama)
|
||||
# requires: none (default) | ollama | any-llm
|
||||
# — clone only when LLM_RUNTIME matches (ollama / either LLM)
|
||||
|
||||
@@ -14,6 +15,14 @@ swarmui:
|
||||
# ref: main
|
||||
# dir: MyExt
|
||||
|
||||
# Persona packs (git): optional. Or clone packs into assistent-extensions/ locally
|
||||
# (SFTP on up / seed-personas / push — no URL needed in this file).
|
||||
assistent: []
|
||||
# - url: https://example.com/org/some-persona-pack.git
|
||||
# ref: main
|
||||
# dir: some-persona-pack
|
||||
# requires: ollama
|
||||
|
||||
comfy: []
|
||||
# - url: https://github.com/org/ComfyUI-CustomNodes.git
|
||||
# - url: https://github.com/example/ComfyUI-CustomNodes.git
|
||||
# ref: v1.0.0
|
||||
|
||||
@@ -331,11 +331,11 @@ def merge_extensions_yaml(
|
||||
dry_run: bool,
|
||||
) -> tuple[list[ExtCaptureItem], list[ExtCaptureItem], list[str]]:
|
||||
"""Return (added, updated, skipped). Same dir + different URL → update url/ref."""
|
||||
data: dict[str, list[dict[str, Any]]] = {"swarmui": [], "comfy": []}
|
||||
data: dict[str, list[dict[str, Any]]] = {"swarmui": [], "comfy": [], "assistent": []}
|
||||
if path.is_file():
|
||||
raw = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
||||
if isinstance(raw, dict):
|
||||
for kind in ("swarmui", "comfy"):
|
||||
for kind in ("swarmui", "comfy", "assistent"):
|
||||
items = raw.get(kind) or []
|
||||
if isinstance(items, list):
|
||||
for it in items:
|
||||
@@ -701,7 +701,7 @@ def capture_extensions(
|
||||
report.ext_unknown.append(f"{raw.get('kind')}/{name} ({reason})")
|
||||
continue
|
||||
kind = str(raw.get("kind") or "")
|
||||
if kind not in ("swarmui", "comfy"):
|
||||
if kind not in ("swarmui", "comfy", "assistent"):
|
||||
continue
|
||||
resolved.append(
|
||||
ExtCaptureItem(
|
||||
|
||||
+7
-2
@@ -923,13 +923,18 @@ def seed_models() -> None:
|
||||
|
||||
@app.command("push")
|
||||
def push_all() -> None:
|
||||
"""SFTP Models + Wildcards + CustomWorkflows."""
|
||||
"""SFTP Models + Wildcards + CustomWorkflows + assistent-extensions."""
|
||||
try:
|
||||
from gpu_rent.paths import assistent_extensions_dir
|
||||
from gpu_rent.provision import seed_assistent_personas
|
||||
|
||||
cfg, host = _live()
|
||||
|
||||
push_tree(cfg, host, cfg.local_models_dir, "/mnt/swarm_data/Models", log, models=True)
|
||||
push_tree(cfg, host, cfg.local_wildcards_dir, "/mnt/swarm_data/Data/Wildcards", log, models=False)
|
||||
push_tree(cfg, host, cfg.local_workflows_dir, "/mnt/swarm_data/CustomWorkflows", log, models=False)
|
||||
if assistent_extensions_dir().is_dir():
|
||||
seed_assistent_personas(cfg, host, log)
|
||||
except GpuRentError as exc:
|
||||
_die(exc)
|
||||
|
||||
@@ -985,7 +990,7 @@ def seed_extensions_cmd() -> None:
|
||||
|
||||
@app.command("seed-personas")
|
||||
def seed_personas_cmd() -> None:
|
||||
"""Push assistent-personas/ → VM Assistent overlay (без полного up)."""
|
||||
"""Push assistent-extensions/ → VM Assistent/extensions (+ _base)."""
|
||||
try:
|
||||
from gpu_rent.provision import seed_assistent_personas
|
||||
|
||||
|
||||
@@ -118,6 +118,12 @@ if personas_root.is_dir():
|
||||
for d in sorted(personas_root.iterdir()):
|
||||
if d.is_dir():
|
||||
persona_ids.append(d.name)
|
||||
pack_root = overlay / "extensions"
|
||||
pack_dirs = []
|
||||
if pack_root.is_dir():
|
||||
for d in sorted(pack_root.iterdir()):
|
||||
if d.is_dir():
|
||||
pack_dirs.append(d.name)
|
||||
base_json = {}
|
||||
base_path = overlay / "_base" / "assistant.json"
|
||||
if base_path.is_file():
|
||||
@@ -186,6 +192,7 @@ print(json.dumps({
|
||||
"exists": overlay.is_dir(),
|
||||
"entries": sorted(x.name for x in overlay.iterdir())[:50] if overlay.is_dir() else [],
|
||||
"persona_ids": persona_ids,
|
||||
"pack_dirs": pack_dirs,
|
||||
"default_persona": base_json.get("default_persona") if isinstance(base_json, dict) else None,
|
||||
"num_ctx": base_json.get("num_ctx") if isinstance(base_json, dict) else None,
|
||||
"embed_model": base_json.get("embed_model") if isinstance(base_json, dict) else None,
|
||||
@@ -610,30 +617,31 @@ def collect_assistent_overlay(cfg: Config, *, fs: dict[str, Any] | None = None)
|
||||
if not fs.get("ok"):
|
||||
return fs
|
||||
overlay = fs.get("overlay") or {}
|
||||
local_ids: list[str] = []
|
||||
local_packs: list[str] = []
|
||||
try:
|
||||
from gpu_rent.paths import assistent_personas_dir
|
||||
from gpu_rent.paths import assistent_extensions_dir
|
||||
|
||||
pdir = assistent_personas_dir()
|
||||
pdir = assistent_extensions_dir()
|
||||
if pdir.is_dir():
|
||||
local_ids = sorted(
|
||||
x.name for x in pdir.iterdir() if x.is_dir() and not x.name.startswith("_")
|
||||
local_packs = sorted(
|
||||
x.name for x in pdir.iterdir() if x.is_dir() and not x.name.startswith(".")
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
remote_ids = list(overlay.get("persona_ids") or [])
|
||||
missing_on_vm = sorted(set(local_ids) - set(remote_ids))
|
||||
remote_packs = list(overlay.get("pack_dirs") or [])
|
||||
missing_on_vm = sorted(set(local_packs) - set(remote_packs))
|
||||
hints: list[str] = []
|
||||
if local_ids and missing_on_vm:
|
||||
if local_packs and missing_on_vm:
|
||||
hints.append(
|
||||
f"локальные personas не на VM: {', '.join(missing_on_vm[:8])} — gpu-rent seed-personas"
|
||||
f"локальные packs не на VM: {', '.join(missing_on_vm[:8])} — gpu-rent seed-personas"
|
||||
)
|
||||
if not overlay.get("exists"):
|
||||
hints.append("нет /mnt/swarm_data/Assistent — seed ещё не писал overlay (bundled personas ок)")
|
||||
return {
|
||||
"ok": True,
|
||||
"overlay": overlay,
|
||||
"local_persona_ids": local_ids,
|
||||
"local_pack_dirs": local_packs,
|
||||
"local_persona_ids": local_packs, # back-compat for older clients
|
||||
"missing_on_vm": missing_on_vm,
|
||||
"hints": hints,
|
||||
}
|
||||
@@ -1034,16 +1042,17 @@ def _ollama_chat_smoke_ssh(cfg: Config, *, model: str) -> dict[str, Any]:
|
||||
def _local_assistent_bits(cfg: Config) -> dict[str, Any]:
|
||||
local: dict[str, Any] = {}
|
||||
try:
|
||||
from gpu_rent.paths import assistent_personas_dir
|
||||
from gpu_rent.paths import assistent_extensions_dir
|
||||
|
||||
pdir = assistent_personas_dir()
|
||||
local["personas_dir"] = {
|
||||
pdir = assistent_extensions_dir()
|
||||
local["extensions_dir"] = {
|
||||
"path": str(pdir),
|
||||
"exists": pdir.is_dir(),
|
||||
"entries": sorted(x.name for x in pdir.iterdir())[:40] if pdir.is_dir() else [],
|
||||
}
|
||||
local["personas_dir"] = local["extensions_dir"] # back-compat
|
||||
except Exception as exc:
|
||||
local["personas_dir"] = {"error": str(exc)[:120]}
|
||||
local["extensions_dir"] = {"error": str(exc)[:120]}
|
||||
try:
|
||||
from gpu_rent.manifests import parse_extensions, repo_dirname
|
||||
|
||||
@@ -1051,9 +1060,11 @@ def _local_assistent_bits(cfg: Config) -> dict[str, Any]:
|
||||
has = any(
|
||||
"assistent" in repo_dirname(r).lower() or "assistent" in (r.url or "").lower()
|
||||
for r in repos
|
||||
if r.kind == "swarmui"
|
||||
)
|
||||
local["extensions_yaml"] = {
|
||||
"has_swarm_assistent": has,
|
||||
"assistent_packs": sum(1 for r in repos if r.kind == "assistent"),
|
||||
"manifest": str(cfg.extensions_manifest),
|
||||
}
|
||||
except Exception as exc:
|
||||
|
||||
@@ -127,9 +127,9 @@ def parse_extensions(path: Path) -> list[GitRepo]:
|
||||
if not data:
|
||||
return []
|
||||
if not isinstance(data, dict):
|
||||
raise ConfigError(f"{path}: корень swarmui: / comfy:")
|
||||
raise ConfigError(f"{path}: корень swarmui: / comfy: / assistent:")
|
||||
repos: list[GitRepo] = []
|
||||
for kind in ("swarmui", "comfy"):
|
||||
for kind in ("swarmui", "comfy", "assistent"):
|
||||
items = data.get(kind) or []
|
||||
if not items:
|
||||
continue
|
||||
@@ -184,5 +184,10 @@ def is_commit_sha(ref: str) -> bool:
|
||||
|
||||
|
||||
def remote_root_for(repo: GitRepo) -> str:
|
||||
base = "/mnt/swarm_data/Extensions" if repo.kind == "swarmui" else "/mnt/swarm_data/DLNodes"
|
||||
if repo.kind == "swarmui":
|
||||
base = "/mnt/swarm_data/Extensions"
|
||||
elif repo.kind == "assistent":
|
||||
base = "/mnt/swarm_data/Assistent/extensions"
|
||||
else:
|
||||
base = "/mnt/swarm_data/DLNodes"
|
||||
return f"{base}/{repo_dirname(repo)}"
|
||||
|
||||
+12
-2
@@ -48,12 +48,22 @@ def models_manifest_path() -> Path:
|
||||
return app_root() / "models.yaml"
|
||||
|
||||
|
||||
def assistent_extensions_dir() -> Path:
|
||||
"""Working persona-pack clones on the laptop (gitignored)."""
|
||||
return app_root() / "assistent-extensions"
|
||||
|
||||
|
||||
def assistent_extensions_example_dir() -> Path:
|
||||
return app_root() / "assistent-extensions.example"
|
||||
|
||||
|
||||
def assistent_personas_dir() -> Path:
|
||||
"""Working overlay source on the laptop (gitignored)."""
|
||||
"""Deprecated overlay-shelf folder — kept for migration warning only."""
|
||||
return app_root() / "assistent-personas"
|
||||
|
||||
|
||||
def assistent_personas_example_dir() -> Path:
|
||||
"""Deprecated example — prefer assistent_extensions_example_dir()."""
|
||||
return app_root() / "assistent-personas.example"
|
||||
|
||||
|
||||
@@ -63,7 +73,7 @@ def assistent_personas_manifest_path() -> Path:
|
||||
|
||||
|
||||
def assistent_personas_example_path() -> Path:
|
||||
"""Deprecated yaml example — prefer assistent_personas_example_dir()."""
|
||||
"""Deprecated yaml example — prefer assistent_extensions_example_dir()."""
|
||||
return app_root() / "assistent-personas.example.yaml"
|
||||
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@ def is_skipped(path: Path) -> bool:
|
||||
name = path.name
|
||||
if name in SKIP_NAMES or name.startswith("."):
|
||||
return True
|
||||
# Never sync git metadata (persona packs push shelves only).
|
||||
if any(part == ".git" for part in path.parts):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
|
||||
+48
-78
@@ -895,97 +895,64 @@ def _overlay_num_ctx(cfg: Config, host: str) -> int | None:
|
||||
|
||||
|
||||
def seed_assistent_personas(cfg: Config, host: str, log: Log) -> None:
|
||||
"""Push local assistent-personas/ folder → VM Assistent/ overlay.
|
||||
"""Push local assistent-extensions/ packs + _base/assistant.json to VM Assistent/.
|
||||
|
||||
Copies personas/<id>/*.json (+ optional extra.md) and sparse _base/assistant.json
|
||||
(default_persona + optional num_ctx). Does not delete overlay personas missing
|
||||
from the laptop (VM-created clones stay). Does not clobber exact.controls.
|
||||
Legacy assistent-personas.yaml is no longer read.
|
||||
Each subdirectory of assistent-extensions/ is SFTP'd to
|
||||
Assistent/extensions/<dirname>/ (``.git`` skipped). Overlay UI clones under
|
||||
Assistent/personas/ are never deleted. Legacy assistent-personas/ shelf seed
|
||||
is no longer read.
|
||||
"""
|
||||
from gpu_rent.paths import (
|
||||
assistent_extensions_dir,
|
||||
assistent_personas_dir,
|
||||
assistent_personas_example_dir,
|
||||
assistent_personas_manifest_path,
|
||||
)
|
||||
|
||||
local = Path(
|
||||
(getattr(cfg, "assistent_personas_dir", None) or assistent_personas_dir())
|
||||
)
|
||||
if not local.is_dir():
|
||||
example = assistent_personas_example_dir()
|
||||
if example.is_dir():
|
||||
local = example
|
||||
else:
|
||||
legacy = assistent_personas_manifest_path()
|
||||
if legacy.is_file():
|
||||
log(
|
||||
"assistent-personas: yaml больше не читается — "
|
||||
"скопируй полки в assistent-personas/ (см. assistent-personas.example/)"
|
||||
)
|
||||
else:
|
||||
log("assistent-personas: нет папки — skip (bundled из расширения)")
|
||||
return
|
||||
from gpu_rent.sync_files import push_tree
|
||||
|
||||
remote_dir = f"{DATA}/Assistent"
|
||||
run_ssh(
|
||||
cfg,
|
||||
host,
|
||||
f"mkdir -p {shlex.quote(remote_dir)}/personas {shlex.quote(remote_dir)}/_base",
|
||||
f"mkdir -p {shlex.quote(remote_dir)}/extensions {shlex.quote(remote_dir)}/_base",
|
||||
check=False,
|
||||
)
|
||||
|
||||
legacy = Path(
|
||||
(getattr(cfg, "assistent_personas_dir", None) or assistent_personas_dir())
|
||||
)
|
||||
if legacy.is_dir() and any(legacy.iterdir()):
|
||||
log(
|
||||
"assistent-personas/: больше не сидится — "
|
||||
"клонируй паки в assistent-extensions/ (см. assistent-extensions.example/)"
|
||||
)
|
||||
legacy_yaml = assistent_personas_manifest_path()
|
||||
if legacy_yaml.is_file():
|
||||
log(
|
||||
"assistent-personas.yaml больше не читается — "
|
||||
"используй assistent: в extensions.yaml или assistent-extensions/"
|
||||
)
|
||||
|
||||
local_ext = Path(
|
||||
getattr(cfg, "assistent_extensions_dir", None) or assistent_extensions_dir()
|
||||
)
|
||||
pushed = 0
|
||||
if local_ext.is_dir():
|
||||
for child in sorted(local_ext.iterdir()):
|
||||
if not child.is_dir() or child.name.startswith("."):
|
||||
continue
|
||||
# Require a pack manifest or persona.json so empty clone dirs are skipped.
|
||||
if not (
|
||||
(child / "assistent-pack.yaml").is_file()
|
||||
or (child / "persona.json").is_file()
|
||||
or (child / "personas").is_dir()
|
||||
):
|
||||
log(f"assistent-extensions/{child.name}: нет assistent-pack.yaml — skip")
|
||||
continue
|
||||
remote = f"{remote_dir}/extensions/{child.name}"
|
||||
push_tree(cfg, host, child, remote, log, models=False)
|
||||
pushed += 1
|
||||
|
||||
default_id = "neutral"
|
||||
base_asst = local / "_base" / "assistant.json"
|
||||
if base_asst.is_file():
|
||||
try:
|
||||
data = json.loads(base_asst.read_text(encoding="utf-8"))
|
||||
if isinstance(data, dict):
|
||||
default_id = _safe_persona_id(data.get("default_persona")) or default_id
|
||||
except (json.JSONDecodeError, OSError) as exc:
|
||||
log(f"assistent-personas: _base/assistant.json — {exc}")
|
||||
|
||||
personas_root = local / "personas"
|
||||
written = 0
|
||||
if personas_root.is_dir():
|
||||
for pdir in sorted(personas_root.iterdir()):
|
||||
if not pdir.is_dir():
|
||||
continue
|
||||
pid = _safe_persona_id(pdir.name)
|
||||
if not pid:
|
||||
continue
|
||||
remote_p = f"{remote_dir}/personas/{pid}"
|
||||
run_ssh(cfg, host, f"mkdir -p {shlex.quote(remote_p)}", check=False)
|
||||
for f in sorted(pdir.iterdir()):
|
||||
if not f.is_file():
|
||||
continue
|
||||
name = f.name
|
||||
if name.endswith(".json") or name == "extra.md":
|
||||
if ".." in name or "/" in name or "\\" in name:
|
||||
continue
|
||||
# Never overwrite exact.json controls from laptop if remote has
|
||||
# user-tuned values — merge only when seeding a missing exact,
|
||||
# or push non-controls shelves always. For simplicity: push all
|
||||
# shelves except skip exact.json when remote already has it
|
||||
# (preserves preference_bias). First-time still seeds exact.
|
||||
remote_f = f"{remote_p}/{name}"
|
||||
if name == "exact.json":
|
||||
exists = run_ssh(
|
||||
cfg,
|
||||
host,
|
||||
f"test -f {shlex.quote(remote_f)} && echo yes || true",
|
||||
check=False,
|
||||
timeout=15,
|
||||
).strip()
|
||||
if exists == "yes":
|
||||
continue
|
||||
put_text(
|
||||
cfg,
|
||||
host,
|
||||
remote_f,
|
||||
f.read_text(encoding="utf-8"),
|
||||
)
|
||||
written += 1
|
||||
|
||||
assistant_overlay: dict = {"default_persona": default_id}
|
||||
num_ctx = _overlay_num_ctx(cfg, host)
|
||||
if num_ctx:
|
||||
@@ -1005,7 +972,10 @@ def seed_assistent_personas(cfg: Config, host: str, log: Log) -> None:
|
||||
check=False,
|
||||
)
|
||||
ctx_note = f", num_ctx={num_ctx}" if num_ctx else ""
|
||||
log(f"assistent-personas → overlay personas/{written} (default={default_id}{ctx_note})")
|
||||
log(
|
||||
f"assistent-extensions → packs/{pushed} "
|
||||
f"(default={default_id}{ctx_note})"
|
||||
)
|
||||
seed_civitai_examples(cfg, host, log)
|
||||
|
||||
|
||||
@@ -1623,7 +1593,7 @@ def provision_vm(
|
||||
try:
|
||||
seed_assistent_personas(cfg, host, log)
|
||||
except Exception as exc:
|
||||
log(f"assistent-personas: {exc}")
|
||||
log(f"assistent packs: {exc}")
|
||||
push_tree(cfg, host, cfg.local_models_dir, f"{DATA}/Models", log, models=True)
|
||||
push_tree(
|
||||
cfg, host, cfg.local_wildcards_dir, f"{DATA}/Data/Wildcards", log, models=False
|
||||
|
||||
@@ -16,6 +16,7 @@ MARKER = Path("/mnt/swarm_data/.gpu-rent-extensions-seeded")
|
||||
EXTRA_ROOTS = (
|
||||
Path("/mnt/swarm_data/Extensions"),
|
||||
Path("/mnt/swarm_data/DLNodes"),
|
||||
Path("/mnt/swarm_data/Assistent/extensions"),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ FOLDER_TO_KIND = {
|
||||
EXT_ROOTS = (
|
||||
("swarmui", DATA / "Extensions"),
|
||||
("comfy", DATA / "DLNodes"),
|
||||
("assistent", DATA / "Assistent" / "extensions"),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ from gpu_rent.llm_runtime import (
|
||||
)
|
||||
from gpu_rent.paths import (
|
||||
app_root,
|
||||
assistent_personas_dir,
|
||||
assistent_personas_example_dir,
|
||||
assistent_extensions_dir,
|
||||
assistent_extensions_example_dir,
|
||||
env_path,
|
||||
extensions_manifest_path,
|
||||
models_manifest_path,
|
||||
@@ -68,9 +68,9 @@ def run_setup(
|
||||
_copy_if_missing(root / "env.example", env_path(), ".env", log)
|
||||
_copy_if_missing(root / "models.example.yaml", models_manifest_path(), "models.yaml", log)
|
||||
_copy_tree_if_missing(
|
||||
assistent_personas_example_dir(),
|
||||
assistent_personas_dir(),
|
||||
"assistent-personas/",
|
||||
assistent_extensions_example_dir(),
|
||||
assistent_extensions_dir(),
|
||||
"assistent-extensions/",
|
||||
log,
|
||||
)
|
||||
_copy_if_missing(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Unit tests for assistent personas overlay seed helpers."""
|
||||
"""Unit tests for assistent persona-pack seed helpers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -6,35 +6,29 @@ import json
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from gpu_rent.provision import _safe_persona_id, count_wanted_models_on_vm, seed_assistent_personas
|
||||
from gpu_rent.provision import _safe_persona_id, seed_assistent_personas
|
||||
|
||||
|
||||
def test_safe_persona_id():
|
||||
assert _safe_persona_id("neutral") == "neutral"
|
||||
assert _safe_persona_id("lewd_v2") == "lewd_v2"
|
||||
assert _safe_persona_id("dreamer_v2") == "dreamer_v2"
|
||||
assert _safe_persona_id("../x") is None
|
||||
assert _safe_persona_id("a/b") is None
|
||||
assert _safe_persona_id("") is None
|
||||
|
||||
|
||||
def test_seed_assistent_personas_writes_overlay(tmp_path: Path, monkeypatch):
|
||||
root = tmp_path / "assistent-personas"
|
||||
(root / "_base").mkdir(parents=True)
|
||||
(root / "personas" / "cinema").mkdir(parents=True)
|
||||
(root / "_base" / "assistant.json").write_text(
|
||||
json.dumps({"default_persona": "cinema"}), encoding="utf-8"
|
||||
)
|
||||
(root / "personas" / "cinema" / "persona.json").write_text(
|
||||
json.dumps({"title": "Кино", "accent": "#7aa2f7"}), encoding="utf-8"
|
||||
)
|
||||
(root / "personas" / "cinema" / "bio.json").write_text(
|
||||
json.dumps({"role": "DP"}), encoding="utf-8"
|
||||
)
|
||||
(root / "personas" / "cinema" / "humor.json").write_text(
|
||||
json.dumps({"frequency": "rare"}), encoding="utf-8"
|
||||
def test_seed_assistent_personas_pushes_packs(tmp_path: Path, monkeypatch):
|
||||
root = tmp_path / "assistent-extensions"
|
||||
pack = root / "sample-pack"
|
||||
pack.mkdir(parents=True)
|
||||
(pack / "assistent-pack.yaml").write_text("kind: persona\nid: sample\n", encoding="utf-8")
|
||||
(pack / "persona.json").write_text(
|
||||
json.dumps({"title": "Sample", "accent": "#7aa2f7"}), encoding="utf-8"
|
||||
)
|
||||
(pack / "voice.json").write_text(json.dumps({"tone": ["calm"]}), encoding="utf-8")
|
||||
|
||||
puts: dict[str, str] = {}
|
||||
pushed: list[tuple[str, str]] = []
|
||||
ssh_cmds: list[str] = []
|
||||
|
||||
def fake_put(cfg, host, remote, text, mode=0o644):
|
||||
@@ -46,76 +40,72 @@ def test_seed_assistent_personas_writes_overlay(tmp_path: Path, monkeypatch):
|
||||
return json.dumps({"vram_mib": 24576, "name": "4090", "compute_cap": "8.9"})
|
||||
return ""
|
||||
|
||||
def fake_push(cfg, host, local_root, remote_root, log, *, models):
|
||||
pushed.append((str(local_root), remote_root))
|
||||
log(f"push {Path(local_root).name}")
|
||||
return 2
|
||||
|
||||
monkeypatch.setattr("gpu_rent.provision.put_text", fake_put)
|
||||
monkeypatch.setattr("gpu_rent.provision.run_ssh", fake_ssh)
|
||||
monkeypatch.setattr("gpu_rent.paths.assistent_personas_dir", lambda: root)
|
||||
monkeypatch.setattr("gpu_rent.sync_files.push_tree", fake_push)
|
||||
monkeypatch.setattr("gpu_rent.paths.assistent_extensions_dir", lambda: root)
|
||||
monkeypatch.setattr(
|
||||
"gpu_rent.paths.assistent_personas_example_dir", lambda: tmp_path / "missing"
|
||||
"gpu_rent.paths.assistent_personas_dir", lambda: tmp_path / "no-personas"
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"gpu_rent.paths.assistent_personas_manifest_path", lambda: tmp_path / "no.yaml"
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"gpu_rent.provision.seed_civitai_examples", lambda *a, **k: None
|
||||
)
|
||||
|
||||
cfg = MagicMock()
|
||||
cfg.assistent_personas_dir = str(root)
|
||||
cfg.assistent_extensions_dir = str(root)
|
||||
logs: list[str] = []
|
||||
seed_assistent_personas(cfg, "1.2.3.4", logs.append)
|
||||
|
||||
assert any(p.endswith("personas/cinema/persona.json") for p in puts)
|
||||
assert any(p.endswith("personas/cinema/bio.json") for p in puts)
|
||||
assert any(p.endswith("personas/cinema/humor.json") for p in puts)
|
||||
assert not any(p.endswith("extra.md") for p in puts)
|
||||
assert len(pushed) == 1
|
||||
assert pushed[0][1].endswith("/Assistent/extensions/sample-pack")
|
||||
assert "/mnt/swarm_data/Assistent/_base/assistant.json" in puts
|
||||
asst = json.loads(puts["/mnt/swarm_data/Assistent/_base/assistant.json"])
|
||||
assert asst["default_persona"] == "cinema"
|
||||
assert asst["default_persona"] == "neutral"
|
||||
assert asst["num_ctx"] == 16384
|
||||
assert any("rm -f" in c and "personas.json" in c for c in ssh_cmds)
|
||||
assert any("overlay personas/1" in m for m in logs)
|
||||
assert any("packs/1" in m for m in logs)
|
||||
|
||||
|
||||
def test_seed_skips_exact_if_remote_exists(tmp_path: Path, monkeypatch):
|
||||
root = tmp_path / "assistent-personas"
|
||||
(root / "personas" / "leonid").mkdir(parents=True)
|
||||
(root / "personas" / "leonid" / "persona.json").write_text(
|
||||
'{"title":"L"}', encoding="utf-8"
|
||||
)
|
||||
(root / "personas" / "leonid" / "exact.json").write_text(
|
||||
'{"controls":{"preference_bias":0.9}}', encoding="utf-8"
|
||||
)
|
||||
puts: dict[str, str] = {}
|
||||
def test_seed_skips_empty_pack_dir(tmp_path: Path, monkeypatch):
|
||||
root = tmp_path / "assistent-extensions"
|
||||
(root / "empty").mkdir(parents=True)
|
||||
|
||||
def fake_put(cfg, host, remote, text, mode=0o644):
|
||||
puts[remote] = text
|
||||
pushed: list[str] = []
|
||||
|
||||
def fake_ssh(cfg, host, cmd, check=False, timeout=60):
|
||||
if "exact.json" in cmd and "test -f" in cmd:
|
||||
return "yes"
|
||||
if ".gpu-rent-gpu.json" in cmd:
|
||||
return "{}"
|
||||
return ""
|
||||
def fake_push(cfg, host, local_root, remote_root, log, *, models):
|
||||
pushed.append(remote_root)
|
||||
return 0
|
||||
|
||||
monkeypatch.setattr("gpu_rent.provision.put_text", fake_put)
|
||||
monkeypatch.setattr("gpu_rent.provision.run_ssh", fake_ssh)
|
||||
monkeypatch.setattr("gpu_rent.paths.assistent_personas_dir", lambda: root)
|
||||
monkeypatch.setattr("gpu_rent.provision.put_text", lambda *a, **k: None)
|
||||
monkeypatch.setattr(
|
||||
"gpu_rent.paths.assistent_personas_example_dir", lambda: tmp_path / "missing"
|
||||
"gpu_rent.provision.run_ssh",
|
||||
lambda *a, **k: json.dumps({"vram_mib": 24576}) if ".gpu-rent-gpu" in str(a) else "",
|
||||
)
|
||||
monkeypatch.setattr("gpu_rent.sync_files.push_tree", fake_push)
|
||||
monkeypatch.setattr("gpu_rent.paths.assistent_extensions_dir", lambda: root)
|
||||
monkeypatch.setattr(
|
||||
"gpu_rent.paths.assistent_personas_dir", lambda: tmp_path / "no-personas"
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"gpu_rent.paths.assistent_personas_manifest_path", lambda: tmp_path / "no.yaml"
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"gpu_rent.provision.seed_civitai_examples", lambda *a, **k: None
|
||||
)
|
||||
|
||||
cfg = MagicMock()
|
||||
cfg.assistent_personas_dir = str(root)
|
||||
seed_assistent_personas(cfg, "h", lambda *_: None)
|
||||
assert not any(p.endswith("exact.json") for p in puts)
|
||||
assert any(p.endswith("persona.json") for p in puts)
|
||||
cfg.assistent_extensions_dir = str(root)
|
||||
logs: list[str] = []
|
||||
seed_assistent_personas(cfg, "1.2.3.4", logs.append)
|
||||
|
||||
|
||||
def test_count_wanted_models(monkeypatch):
|
||||
raw = "lora:\n - url: https://civitai.red/x?modelVersionId=1\n title: A\n"
|
||||
|
||||
monkeypatch.setattr(
|
||||
"gpu_rent.provision.run_ssh",
|
||||
lambda *a, **k: raw,
|
||||
)
|
||||
assert count_wanted_models_on_vm(MagicMock(), "h") == 1
|
||||
assert pushed == []
|
||||
assert any("нет assistent-pack.yaml" in m for m in logs)
|
||||
assert any("packs/0" in m for m in logs)
|
||||
|
||||
@@ -221,7 +221,7 @@ def test_chat_eval_mocked_http(monkeypatch, tmp_path):
|
||||
if url.endswith("/API/AssistentGetConfig"):
|
||||
return True, {
|
||||
"success": True,
|
||||
"persona": "leonid",
|
||||
"persona": "neutral",
|
||||
"exact": {
|
||||
"profiles": {"turbo": {"steps": 8, "cfg": 1, "sigma_shift": 1.15}}
|
||||
},
|
||||
@@ -245,14 +245,14 @@ def test_chat_eval_mocked_http(monkeypatch, tmp_path):
|
||||
out = debug_assistent.run_assistent_chat_eval(
|
||||
cfg,
|
||||
message="какой checkpoint?",
|
||||
persona="leonid",
|
||||
persona="neutral",
|
||||
timeout=60,
|
||||
)
|
||||
assert out["ok"] is True
|
||||
assert out["via"] == "local"
|
||||
assert out["model"] == "qwen3-vl:8b"
|
||||
assert out["preferred"] == "qwen3-vl:8b"
|
||||
assert out["persona"] == "leonid"
|
||||
assert out["persona"] == "neutral"
|
||||
assert out["patch"]["steps"] == 4
|
||||
assert out["patch"]["cfg"] == 1
|
||||
assert out["patch"]["aspect"] == "3:4"
|
||||
@@ -317,7 +317,7 @@ def test_assistent_compact_and_roles_local(monkeypatch, tmp_path):
|
||||
],
|
||||
"overlay": {
|
||||
"exists": True,
|
||||
"persona_ids": ["leonid"],
|
||||
"persona_ids": ["neutral"],
|
||||
"roles_present": True,
|
||||
"roles": {
|
||||
"chat": ["qwen3-vl:8b"],
|
||||
|
||||
+27
-1
@@ -3,7 +3,12 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
from gpu_rent.errors import ConfigError
|
||||
from gpu_rent.manifests import parse_extensions, parse_models, repo_matches_runtime
|
||||
from gpu_rent.manifests import (
|
||||
parse_extensions,
|
||||
parse_models,
|
||||
remote_root_for,
|
||||
repo_matches_runtime,
|
||||
)
|
||||
|
||||
|
||||
def test_models_skips_version_zero(tmp_path: Path):
|
||||
@@ -77,3 +82,24 @@ def test_extensions_requires_invalid(tmp_path: Path):
|
||||
)
|
||||
with pytest.raises(ConfigError, match="requires"):
|
||||
parse_extensions(path)
|
||||
|
||||
|
||||
def test_extensions_assistent_kind(tmp_path: Path):
|
||||
path = tmp_path / "extensions.yaml"
|
||||
path.write_text(
|
||||
"assistent:\n"
|
||||
" - url: https://example.com/org/sample-pack.git\n"
|
||||
" ref: main\n"
|
||||
" dir: sample-pack\n"
|
||||
" requires: ollama\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
repos = parse_extensions(path)
|
||||
assert len(repos) == 1
|
||||
assert repos[0].kind == "assistent"
|
||||
assert repos[0].directory == "sample-pack"
|
||||
assert remote_root_for(repos[0]) == (
|
||||
"/mnt/swarm_data/Assistent/extensions/sample-pack"
|
||||
)
|
||||
assert repo_matches_runtime(repos[0], "ollama")
|
||||
assert not repo_matches_runtime(repos[0], "none")
|
||||
|
||||
Reference in New Issue
Block a user