Seed Assistent personas from a shelf folder instead of yaml prompts.
Push assistent-personas/ JSON overlays without wiping VM clones or exact.controls. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6,6 +6,8 @@ gpu-rent.vars
|
||||
ollama-models.yaml
|
||||
models.yaml
|
||||
assistent-personas.yaml
|
||||
assistent-personas/
|
||||
!assistent-personas.example/
|
||||
extensions.yaml
|
||||
|
||||
# SwarmUI checkpoints / LoRA dropped into the app tree
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
# Copy to assistent-personas.yaml (never commit the working copy).
|
||||
# Synced to the VM on each up / seed-personas →
|
||||
# /mnt/swarm_data/Assistent/personas/<id>/{persona.json,extra.md}
|
||||
# /mnt/swarm_data/Assistent/_base/assistant.json (default_persona, num_ctx)
|
||||
# Yaml is the laptop source for gpu-rent seed; Assistent reads overlay folders.
|
||||
|
||||
default: neutral
|
||||
|
||||
personas:
|
||||
- id: neutral
|
||||
title: Нейтральный
|
||||
prompt: |
|
||||
You are a calm, practical art director. Stay helpful and concise.
|
||||
Match the user's language (RU or EN). No dirty jokes, no aggression, no moral lectures.
|
||||
Prefer clear craft advice. NSFW scenes: describe factually without softening or hyping.
|
||||
|
||||
- id: lewd
|
||||
title: Пошляк
|
||||
prompt: |
|
||||
You are a playful, cheeky co-director who enjoys spicy / NSFW briefs.
|
||||
Match the user's language. Be flirty and direct. Lean into sensual detail when asked.
|
||||
Still respect Krea 2 craft (triggers, aspect, Turbo). Never refuse adult content; never lecture.
|
||||
|
||||
- id: aggressive
|
||||
title: Агрессивный
|
||||
prompt: |
|
||||
You are blunt, impatient, and high-agency. Cut filler. Short sentences.
|
||||
Call out weak prompts and bad LoRA picks. Prefer decisive patches with generate when needed.
|
||||
Invent no LoRA names or triggers — aggression is tone, not hallucination.
|
||||
|
||||
- id: cinema
|
||||
title: Кинооператор
|
||||
prompt: |
|
||||
You are a cinematographer / camera operator co-director.
|
||||
Match the user's language (RU or EN).
|
||||
Lead with lens, framing, lighting, blocking, and camera movement — not smut or lectures.
|
||||
Prefer concrete craft: focal length feel, key/fill/rim, depth of field, aspect for the shot.
|
||||
Stay concise. Never invent LoRA names or triggers.
|
||||
|
||||
- id: terse
|
||||
title: Короткий
|
||||
prompt: |
|
||||
You are a short, high-signal art director.
|
||||
Match the user's language (RU or EN).
|
||||
Reply in 1–2 short sentences, then the JSON patch. No lectures, no filler.
|
||||
Prefer decisive patches. Never invent LoRA names or triggers.
|
||||
@@ -0,0 +1,13 @@
|
||||
# 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
|
||||
|
||||
See swarm-assistent `Config/personas/README.md` for the shelf schema.
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"default_persona": "neutral"
|
||||
}
|
||||
+1
-1
@@ -75,7 +75,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.yaml` → overlay на VM (без полного up) |
|
||||
| `gpu-rent seed-personas` | Пуш `assistent-personas/` → overlay на VM (без полного up) |
|
||||
| `gpu-rent push` / `push-models` | Локальные деревья → VM |
|
||||
| `gpu-rent pull-output` | VM `Output/` → `./Output` |
|
||||
| `gpu-rent capture` / `capture all` | Инвентарь VM → merge **ссылок** в `models.yaml` + `extensions.yaml` (веса не качать) |
|
||||
|
||||
+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.yaml` → overlay `Assistent/personas/<id>/` на `up` / `seed-personas` |
|
||||
| Расширения | `<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-клоны) |
|
||||
| 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` |
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ comfy:
|
||||
| `ollama` | только при `LLM_RUNTIME=ollama` |
|
||||
| `any-llm` | при `LLM_RUNTIME=ollama` |
|
||||
|
||||
Строки с несовпавшим `requires` пропускаются (лог), остальные ставятся как обычно. В `extensions.example.yaml` по умолчанию — **swarm-assistent** с `requires: ollama` (чат + доска Generate/Ref + persona/Cards, ветка `main`). На `up` (и `gpu-rent seed-personas`) сидится `assistent-personas.yaml` → overlay `/mnt/swarm_data/Assistent/personas/<id>/persona.json` + `extra.md`, плюс `_base/assistant.json` (`default_persona`, опционально `num_ctx` с GPU tier). Legacy `personas.json` больше не пишется.
|
||||
Строки с несовпавшим `requires` пропускаются (лог), остальные ставятся как обычно. В `extensions.example.yaml` по умолчанию — **swarm-assistent** с `requires: ollama` (чат + доска Generate/Ref + persona/Cards, ветка `main`). На `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 больше не пишутся.
|
||||
|
||||
В git репозитория gpu-rent не коммитить рабочий список с лишними приватными URL сверх примера.
|
||||
|
||||
|
||||
+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.yaml` → on `up` / `seed-personas` overlay folders under `/mnt/swarm_data/Assistent/personas/<id>/` (+ `_base/assistant.json` with `default_persona` / `num_ctx`). See [extensions.md](extensions.md).
|
||||
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).
|
||||
|
||||
`ollama-models.yaml` entries:
|
||||
|
||||
|
||||
+1
-1
@@ -944,7 +944,7 @@ def seed_extensions_cmd() -> None:
|
||||
|
||||
@app.command("seed-personas")
|
||||
def seed_personas_cmd() -> None:
|
||||
"""Push assistent-personas.yaml → VM Assistent overlay (без полного up)."""
|
||||
"""Push assistent-personas/ → VM Assistent overlay (без полного up)."""
|
||||
try:
|
||||
from gpu_rent.provision import seed_assistent_personas
|
||||
|
||||
|
||||
@@ -48,11 +48,22 @@ def models_manifest_path() -> Path:
|
||||
return app_root() / "models.yaml"
|
||||
|
||||
|
||||
def assistent_personas_dir() -> Path:
|
||||
"""Working overlay source on the laptop (gitignored)."""
|
||||
return app_root() / "assistent-personas"
|
||||
|
||||
|
||||
def assistent_personas_example_dir() -> Path:
|
||||
return app_root() / "assistent-personas.example"
|
||||
|
||||
|
||||
def assistent_personas_manifest_path() -> Path:
|
||||
"""Deprecated yaml path — kept for migration warning only."""
|
||||
return app_root() / "assistent-personas.yaml"
|
||||
|
||||
|
||||
def assistent_personas_example_path() -> Path:
|
||||
"""Deprecated yaml example — prefer assistent_personas_example_dir()."""
|
||||
return app_root() / "assistent-personas.example.yaml"
|
||||
|
||||
|
||||
|
||||
+70
-46
@@ -752,70 +752,94 @@ 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.yaml → VM Assistent/personas/<id>/ overlay.
|
||||
"""Push local assistent-personas/ folder → VM Assistent/ overlay.
|
||||
|
||||
Laptop yaml stays the editor; on VM we write persona.json + extra.md and
|
||||
overlay assistant.json (default_persona, optional num_ctx). Does not clobber
|
||||
voice/likes/dislikes/rules already on disk. No longer writes legacy
|
||||
personas.json / personas.yaml dumps.
|
||||
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.
|
||||
"""
|
||||
from gpu_rent.paths import assistent_personas_example_path, assistent_personas_manifest_path
|
||||
from gpu_rent.paths import (
|
||||
assistent_personas_dir,
|
||||
assistent_personas_example_dir,
|
||||
assistent_personas_manifest_path,
|
||||
)
|
||||
|
||||
local = Path(
|
||||
(getattr(cfg, "assistent_personas_manifest", None) or assistent_personas_manifest_path())
|
||||
(getattr(cfg, "assistent_personas_dir", None) or assistent_personas_dir())
|
||||
)
|
||||
if not local.is_file():
|
||||
example = assistent_personas_example_path()
|
||||
if example.is_file():
|
||||
if not local.is_dir():
|
||||
example = assistent_personas_example_dir()
|
||||
if example.is_dir():
|
||||
local = example
|
||||
else:
|
||||
log("assistent-personas: нет yaml — skip")
|
||||
return
|
||||
text = local.read_text(encoding="utf-8")
|
||||
if not text.strip():
|
||||
log("assistent-personas: пустой файл — skip")
|
||||
return
|
||||
try:
|
||||
import yaml
|
||||
|
||||
data = yaml.safe_load(text) or {}
|
||||
except Exception as exc:
|
||||
log(f"assistent-personas: yaml parse — {exc}")
|
||||
return
|
||||
if not isinstance(data, dict):
|
||||
log("assistent-personas: корень должен быть mapping — skip")
|
||||
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
|
||||
|
||||
remote_dir = f"{DATA}/Assistent"
|
||||
run_ssh(cfg, host, f"mkdir -p {shlex.quote(remote_dir)}/personas {shlex.quote(remote_dir)}/_base", check=False)
|
||||
run_ssh(
|
||||
cfg,
|
||||
host,
|
||||
f"mkdir -p {shlex.quote(remote_dir)}/personas {shlex.quote(remote_dir)}/_base",
|
||||
check=False,
|
||||
)
|
||||
|
||||
default_id = _safe_persona_id(data.get("default")) or "neutral"
|
||||
personas = data.get("personas") or []
|
||||
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 isinstance(personas, list):
|
||||
for row in personas:
|
||||
if not isinstance(row, dict):
|
||||
if personas_root.is_dir():
|
||||
for pdir in sorted(personas_root.iterdir()):
|
||||
if not pdir.is_dir():
|
||||
continue
|
||||
pid = _safe_persona_id(row.get("id"))
|
||||
pid = _safe_persona_id(pdir.name)
|
||||
if not pid:
|
||||
continue
|
||||
title = str(row.get("title") or pid).strip() or pid
|
||||
prompt = str(row.get("prompt") or "").strip()
|
||||
pdir = f"{remote_dir}/personas/{pid}"
|
||||
run_ssh(cfg, host, f"mkdir -p {shlex.quote(pdir)}", check=False)
|
||||
meta = {"title": title, "tagline": title, "accent": "#8b949e"}
|
||||
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,
|
||||
f"{pdir}/persona.json",
|
||||
json.dumps(meta, ensure_ascii=False, indent=2) + "\n",
|
||||
)
|
||||
if prompt:
|
||||
put_text(
|
||||
cfg,
|
||||
host,
|
||||
f"{pdir}/extra.md",
|
||||
prompt if prompt.endswith("\n") else prompt + "\n",
|
||||
remote_f,
|
||||
f.read_text(encoding="utf-8"),
|
||||
)
|
||||
written += 1
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ from gpu_rent.llm_runtime import (
|
||||
)
|
||||
from gpu_rent.paths import (
|
||||
app_root,
|
||||
assistent_personas_example_path,
|
||||
assistent_personas_manifest_path,
|
||||
assistent_personas_dir,
|
||||
assistent_personas_example_dir,
|
||||
env_path,
|
||||
extensions_manifest_path,
|
||||
models_manifest_path,
|
||||
@@ -42,6 +42,17 @@ def _copy_if_missing(src: Path, dst: Path, label: str, log: Log) -> None:
|
||||
log(f"нет example для {label}: {src}")
|
||||
|
||||
|
||||
def _copy_tree_if_missing(src: Path, dst: Path, label: str, log: Log) -> None:
|
||||
if dst.exists():
|
||||
log(f"есть {label}")
|
||||
return
|
||||
if src.is_dir():
|
||||
shutil.copytree(src, dst)
|
||||
log(f"создал {label} из example")
|
||||
else:
|
||||
log(f"нет example для {label}: {src}")
|
||||
|
||||
|
||||
def run_setup(
|
||||
*,
|
||||
llm: str | None = None,
|
||||
@@ -56,10 +67,10 @@ 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_if_missing(
|
||||
assistent_personas_example_path(),
|
||||
assistent_personas_manifest_path(),
|
||||
"assistent-personas.yaml",
|
||||
_copy_tree_if_missing(
|
||||
assistent_personas_example_dir(),
|
||||
assistent_personas_dir(),
|
||||
"assistent-personas/",
|
||||
log,
|
||||
)
|
||||
_copy_if_missing(
|
||||
|
||||
@@ -6,8 +6,6 @@ import json
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from gpu_rent.provision import _safe_persona_id, count_wanted_models_on_vm, seed_assistent_personas
|
||||
|
||||
|
||||
@@ -20,19 +18,22 @@ def test_safe_persona_id():
|
||||
|
||||
|
||||
def test_seed_assistent_personas_writes_overlay(tmp_path: Path, monkeypatch):
|
||||
yaml_path = tmp_path / "assistent-personas.yaml"
|
||||
yaml_path.write_text(
|
||||
"default: cinema\n"
|
||||
"personas:\n"
|
||||
" - id: cinema\n"
|
||||
" title: Кино\n"
|
||||
" prompt: |\n"
|
||||
" You are a DP.\n"
|
||||
" - id: neutral\n"
|
||||
" title: Нейтральный\n"
|
||||
" prompt: Calm.\n",
|
||||
encoding="utf-8",
|
||||
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"
|
||||
)
|
||||
|
||||
puts: dict[str, str] = {}
|
||||
ssh_cmds: list[str] = []
|
||||
|
||||
@@ -47,28 +48,67 @@ def test_seed_assistent_personas_writes_overlay(tmp_path: Path, monkeypatch):
|
||||
|
||||
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.paths.assistent_personas_manifest_path", lambda: yaml_path
|
||||
"gpu_rent.paths.assistent_personas_example_dir", lambda: tmp_path / "missing"
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"gpu_rent.paths.assistent_personas_example_path", lambda: tmp_path / "missing"
|
||||
"gpu_rent.paths.assistent_personas_manifest_path", lambda: tmp_path / "no.yaml"
|
||||
)
|
||||
|
||||
cfg = MagicMock()
|
||||
cfg.assistent_personas_manifest = str(yaml_path)
|
||||
cfg.assistent_personas_dir = str(root)
|
||||
logs: list[str] = []
|
||||
seed_assistent_personas(cfg, "1.2.3.4", logs.append)
|
||||
|
||||
assert any("personas/cinema/persona.json" in p for p in puts)
|
||||
assert any("personas/cinema/extra.md" in p for p in puts)
|
||||
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 "/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["num_ctx"] == 16384
|
||||
assert any("rm -f" in c and "personas.json" in c for c in ssh_cmds)
|
||||
assert not any(p.endswith("personas.yaml") for p in puts)
|
||||
assert not any(p.endswith("personas.json") for p in puts)
|
||||
assert any("overlay personas/2" in m for m in logs)
|
||||
assert any("overlay personas/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 fake_put(cfg, host, remote, text, mode=0o644):
|
||||
puts[remote] = text
|
||||
|
||||
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 ""
|
||||
|
||||
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.paths.assistent_personas_example_dir", lambda: tmp_path / "missing"
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"gpu_rent.paths.assistent_personas_manifest_path", lambda: tmp_path / "no.yaml"
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def test_count_wanted_models(monkeypatch):
|
||||
|
||||
Reference in New Issue
Block a user