Add assistent-personas support and enhance model merging functionality
- Introduced `assistent-personas.yaml` to the project and updated the `.gitignore` accordingly. - Implemented functions to seed and merge assistent personas from local files to the VM. - Enhanced the model capture process to include merging of wanted models from the VM into `models.yaml`. - Updated documentation to reflect changes in the assistent personas and model management processes. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
gpu-rent.vars
|
gpu-rent.vars
|
||||||
ollama-models.yaml
|
ollama-models.yaml
|
||||||
models.yaml
|
models.yaml
|
||||||
|
assistent-personas.yaml
|
||||||
extensions.yaml
|
extensions.yaml
|
||||||
|
|
||||||
# SwarmUI checkpoints / LoRA dropped into the app tree
|
# SwarmUI checkpoints / LoRA dropped into the app tree
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Copy to assistent-personas.yaml (never commit the working copy).
|
||||||
|
# Synced to the VM on each up → /mnt/swarm_data/Assistent/personas.yaml
|
||||||
|
# Overlay wins over bundled Personas/*.md in swarm-assistent for the same id.
|
||||||
|
|
||||||
|
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.
|
||||||
+1
-1
@@ -53,7 +53,7 @@ comfy:
|
|||||||
| `ollama` | только при `LLM_RUNTIME=ollama` |
|
| `ollama` | только при `LLM_RUNTIME=ollama` |
|
||||||
| `any-llm` | при `LLM_RUNTIME=ollama` |
|
| `any-llm` | при `LLM_RUNTIME=ollama` |
|
||||||
|
|
||||||
Строки с несовпавшим `requires` пропускаются (лог), остальные ставятся как обычно. В `extensions.example.yaml` по умолчанию — **swarm-assistent** с `requires: ollama` (чат + доска Generate/Ref + vision под Krea 2, ветка `main`).
|
Строки с несовпавшим `requires` пропускаются (лог), остальные ставятся как обычно. В `extensions.example.yaml` по умолчанию — **swarm-assistent** с `requires: ollama` (чат + доска Generate/Ref + persona/Cards, ветка `main`). На `up` также сидится `assistent-personas.yaml` → `/mnt/swarm_data/Assistent/personas.yaml`.
|
||||||
|
|
||||||
В git репозитория gpu-rent не коммитить рабочий список с лишними приватными URL сверх примера.
|
В git репозитория gpu-rent не коммитить рабочий список с лишними приватными URL сверх примера.
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -27,7 +27,7 @@ copy models.example.yaml models.yaml
|
|||||||
.\gpu-rent.ps1 capture all
|
.\gpu-rent.ps1 capture all
|
||||||
```
|
```
|
||||||
|
|
||||||
Берёт `{stem}.civitai.json` или SHA256 → Civitai `by-hash`. Дубли `modelVersionId` не дублируются. Перед записью — `models.yaml.bak`. Неизвестные файлы (нет в Civitai) — только в отчёте.
|
Берёт `{stem}.civitai.json` или SHA256 → Civitai `by-hash`. Также мержит очередь Assistent `/mnt/swarm_data/.gpu-rent-wanted-models.yaml` (модели, добавленные из вкладки Cards). Дубли `modelVersionId` не дублируются. Перед записью — `models.yaml.bak`. Неизвестные файлы (нет в Civitai) — только в отчёте.
|
||||||
|
|
||||||
Локальный SwarmUI на `7801` не зеркалируем. Только дерево `./Models` приложения.
|
Локальный SwarmUI на `7801` не зеркалируем. Только дерево `./Models` приложения.
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ copy models.example.yaml models.yaml
|
|||||||
| Веса уже на VM с тем же SHA256 | Пропуск (нет «обновления») |
|
| Веса уже на VM с тем же SHA256 | Пропуск (нет «обновления») |
|
||||||
| Веса изменились | Залить веса заново и актуальную метадату |
|
| Веса изменились | Залить веса заново и актуальную метадату |
|
||||||
|
|
||||||
Метадата — файлы рядом с тем же stem: `.json`, `.civitai.json`, `.swarm.json`, превью (`.preview.png` / `.png` / `.webp`), если лежат в той же папке. Веса без sidecar тоже можно залить (предупреждение в лог: «метадаты нет»). Метадату без весов **не** шлём отдельно.
|
Метадата — файлы рядом с тем же stem: `.json`, `.civitai.json`, `.swarm.json`, `.assistent.json` (карточки рекомендаций Assistent), превью (`.preview.png` / `.png` / `.webp`), если лежат в той же папке. Веса без sidecar тоже можно залить (предупреждение в лог: «метадаты нет»). Метадату без весов **не** шлём отдельно.
|
||||||
|
|
||||||
С диска в облаке **ничего не удаляем**. Civitai-seed и то, чего нет в `./Models`, остаётся.
|
С диска в облаке **ничего не удаляем**. Civitai-seed и то, чего нет в `./Models`, остаётся.
|
||||||
|
|
||||||
|
|||||||
@@ -405,6 +405,64 @@ def merge_extensions_yaml(
|
|||||||
return added, updated, skipped
|
return added, updated, skipped
|
||||||
|
|
||||||
|
|
||||||
|
def merge_wanted_models_from_vm(cfg: Config, host: str, log: Log) -> int:
|
||||||
|
"""Pull VM Assistent wanted queue into local models.yaml. Returns new url count."""
|
||||||
|
from gpu_rent.manifests import MODEL_DIRS
|
||||||
|
from gpu_rent.provision import DATA
|
||||||
|
|
||||||
|
remote = f"{DATA}/.gpu-rent-wanted-models.yaml"
|
||||||
|
raw = run_ssh(
|
||||||
|
cfg,
|
||||||
|
host,
|
||||||
|
f"test -f {remote!r} && cat {remote!r} || true".replace("'", '"')
|
||||||
|
if False
|
||||||
|
else f'test -f {remote} && cat {remote} || true',
|
||||||
|
check=False,
|
||||||
|
timeout=20,
|
||||||
|
).strip()
|
||||||
|
if not raw:
|
||||||
|
return 0
|
||||||
|
try:
|
||||||
|
data = yaml.safe_load(raw) or {}
|
||||||
|
except Exception as exc:
|
||||||
|
log(f"wanted-models: bad yaml — {exc}")
|
||||||
|
return 0
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
return 0
|
||||||
|
items: list[ModelCaptureItem] = []
|
||||||
|
for kind, rows in data.items():
|
||||||
|
if kind not in MODEL_DIRS or not isinstance(rows, list):
|
||||||
|
continue
|
||||||
|
for row in rows:
|
||||||
|
if not isinstance(row, dict):
|
||||||
|
continue
|
||||||
|
url = str(row.get("url") or "").strip()
|
||||||
|
if not url:
|
||||||
|
continue
|
||||||
|
vid = row.get("version_id")
|
||||||
|
try:
|
||||||
|
vid_i = int(vid) if vid not in (None, "", 0, "0") else extract_version_id(url)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
vid_i = extract_version_id(url)
|
||||||
|
items.append(
|
||||||
|
ModelCaptureItem(
|
||||||
|
kind=kind,
|
||||||
|
url=url,
|
||||||
|
title=str(row.get("title") or Path(url).name),
|
||||||
|
version_id=vid_i,
|
||||||
|
source="wanted",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if not items:
|
||||||
|
return 0
|
||||||
|
added, skipped = merge_models_yaml(Path(cfg.models_manifest), items, dry_run=False)
|
||||||
|
if added:
|
||||||
|
log(f"wanted→models.yaml: +{len(added)} (skip {len(skipped)})")
|
||||||
|
elif skipped:
|
||||||
|
log(f"wanted→models.yaml: уже есть ({len(skipped)})")
|
||||||
|
return len(added)
|
||||||
|
|
||||||
|
|
||||||
def capture_models(
|
def capture_models(
|
||||||
cfg: Config,
|
cfg: Config,
|
||||||
host: str,
|
host: str,
|
||||||
@@ -551,6 +609,18 @@ def capture_models(
|
|||||||
if hf_fallback:
|
if hf_fallback:
|
||||||
log(f"capture: Hugging Face fallback — {hf_fallback} файл(ов)")
|
log(f"capture: Hugging Face fallback — {hf_fallback} файл(ов)")
|
||||||
|
|
||||||
|
# Also merge Assistent wanted-queue (models not yet on disk / seed list).
|
||||||
|
try:
|
||||||
|
from gpu_rent.provision import merge_wanted_models_from_vm
|
||||||
|
|
||||||
|
# merge writes directly; still collect for report when dry_run=False
|
||||||
|
if not dry_run:
|
||||||
|
n = merge_wanted_models_from_vm(cfg, host, log)
|
||||||
|
if n:
|
||||||
|
log(f"capture: wanted queue → models.yaml (+{n})")
|
||||||
|
except Exception as exc:
|
||||||
|
log(f"capture wanted: {exc}")
|
||||||
|
|
||||||
added, skipped = merge_models_yaml(
|
added, skipped = merge_models_yaml(
|
||||||
cfg.models_manifest, resolved, dry_run=dry_run
|
cfg.models_manifest, resolved, dry_run=dry_run
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -48,6 +48,14 @@ def models_manifest_path() -> Path:
|
|||||||
return app_root() / "models.yaml"
|
return app_root() / "models.yaml"
|
||||||
|
|
||||||
|
|
||||||
|
def assistent_personas_manifest_path() -> Path:
|
||||||
|
return app_root() / "assistent-personas.yaml"
|
||||||
|
|
||||||
|
|
||||||
|
def assistent_personas_example_path() -> Path:
|
||||||
|
return app_root() / "assistent-personas.example.yaml"
|
||||||
|
|
||||||
|
|
||||||
def ollama_models_manifest_path() -> Path:
|
def ollama_models_manifest_path() -> Path:
|
||||||
return app_root() / "ollama-models.yaml"
|
return app_root() / "ollama-models.yaml"
|
||||||
|
|
||||||
|
|||||||
@@ -710,9 +710,139 @@ def seed_swarmui_api_keys(cfg: Config, host: str, log: Log) -> None:
|
|||||||
run_ssh(cfg, host, "rm -f /tmp/gpu-rent-swarm-api-keys.json", check=False)
|
run_ssh(cfg, host, "rm -f /tmp/gpu-rent-swarm-api-keys.json", check=False)
|
||||||
|
|
||||||
|
|
||||||
|
def seed_assistent_personas(cfg: Config, host: str, log: Log) -> None:
|
||||||
|
"""Push local assistent-personas.yaml → VM Assistent/personas.yaml + personas.json."""
|
||||||
|
from gpu_rent.paths import assistent_personas_example_path, assistent_personas_manifest_path
|
||||||
|
|
||||||
|
local = Path(
|
||||||
|
(getattr(cfg, "assistent_personas_manifest", None) or assistent_personas_manifest_path())
|
||||||
|
)
|
||||||
|
if not local.is_file():
|
||||||
|
example = assistent_personas_example_path()
|
||||||
|
if example.is_file():
|
||||||
|
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
|
||||||
|
remote_dir = f"{DATA}/Assistent"
|
||||||
|
remote_yaml = f"{remote_dir}/personas.yaml"
|
||||||
|
remote_json = f"{remote_dir}/personas.json"
|
||||||
|
run_ssh(cfg, host, f"mkdir -p {shlex.quote(remote_dir)}", check=False)
|
||||||
|
put_text(cfg, host, remote_yaml, text if text.endswith("\n") else text + "\n")
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
data = yaml.safe_load(text) or {}
|
||||||
|
put_text(
|
||||||
|
cfg,
|
||||||
|
host,
|
||||||
|
remote_json,
|
||||||
|
json.dumps(data, ensure_ascii=False, indent=2) + "\n",
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
log(f"assistent-personas: json convert — {exc}")
|
||||||
|
log(f"assistent-personas → {remote_yaml}")
|
||||||
|
|
||||||
|
|
||||||
|
def merge_wanted_models_from_vm(cfg: Config, host: str, log: Log) -> int:
|
||||||
|
"""Pull VM wanted queue into local models.yaml. Returns count of new urls merged."""
|
||||||
|
from gpu_rent.capture import ModelCaptureItem, merge_models_yaml
|
||||||
|
|
||||||
|
remote = f"{DATA}/.gpu-rent-wanted-models.yaml"
|
||||||
|
raw = run_ssh(
|
||||||
|
cfg,
|
||||||
|
host,
|
||||||
|
f"test -f {shlex.quote(remote)} && cat {shlex.quote(remote)} || true",
|
||||||
|
check=False,
|
||||||
|
timeout=20,
|
||||||
|
).strip()
|
||||||
|
if not raw:
|
||||||
|
return 0
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
data = yaml.safe_load(raw) or {}
|
||||||
|
except Exception as exc:
|
||||||
|
log(f"wanted-models: bad yaml — {exc}")
|
||||||
|
return 0
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
return 0
|
||||||
|
items: list[ModelCaptureItem] = []
|
||||||
|
for kind, rows in data.items():
|
||||||
|
if kind not in MODEL_DIRS or not isinstance(rows, list):
|
||||||
|
continue
|
||||||
|
for row in rows:
|
||||||
|
if not isinstance(row, dict):
|
||||||
|
continue
|
||||||
|
url = str(row.get("url") or "").strip()
|
||||||
|
if not url:
|
||||||
|
continue
|
||||||
|
vid = row.get("version_id")
|
||||||
|
try:
|
||||||
|
vid_i = int(vid) if vid not in (None, "", 0, "0") else extract_version_id(url)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
vid_i = extract_version_id(url)
|
||||||
|
items.append(
|
||||||
|
ModelCaptureItem(
|
||||||
|
kind=kind,
|
||||||
|
url=url,
|
||||||
|
title=str(row.get("title") or Path(url).name),
|
||||||
|
version_id=vid_i,
|
||||||
|
source="wanted",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if not items:
|
||||||
|
return 0
|
||||||
|
added, skipped = merge_models_yaml(Path(cfg.models_manifest), items, dry_run=False)
|
||||||
|
if added:
|
||||||
|
log(f"wanted→models.yaml: +{len(added)} (skip {len(skipped)})")
|
||||||
|
elif skipped:
|
||||||
|
log(f"wanted→models.yaml: уже есть ({len(skipped)})")
|
||||||
|
return len(added)
|
||||||
|
|
||||||
|
|
||||||
|
def _place_wanted_cards(cfg: Config, host: str, jobs: list[dict], log: Log) -> None:
|
||||||
|
"""Copy drafted .assistent.json from wanted-cards next to freshly seeded weights."""
|
||||||
|
cards_dir = f"{DATA}/.gpu-rent-wanted-cards"
|
||||||
|
exists = run_ssh(
|
||||||
|
cfg, host, f"test -d {shlex.quote(cards_dir)} && echo YES || true", check=False
|
||||||
|
).strip()
|
||||||
|
if "YES" not in exists:
|
||||||
|
return
|
||||||
|
placed = 0
|
||||||
|
for job in jobs:
|
||||||
|
dest = str(job.get("dest") or "")
|
||||||
|
vid = job.get("version_id")
|
||||||
|
if not dest or vid in (None, "", 0, "0"):
|
||||||
|
continue
|
||||||
|
stem = Path(dest).stem
|
||||||
|
card_src = f"{cards_dir}/{vid}.assistent.json"
|
||||||
|
card_dst = str(Path(dest).with_name(f"{stem}.assistent.json"))
|
||||||
|
out = run_ssh(
|
||||||
|
cfg,
|
||||||
|
host,
|
||||||
|
f"if test -f {shlex.quote(card_src)} && test -f {shlex.quote(dest)}; then "
|
||||||
|
f"cp -n {shlex.quote(card_src)} {shlex.quote(card_dst)} && echo PLACED; fi",
|
||||||
|
check=False,
|
||||||
|
).strip()
|
||||||
|
if "PLACED" in out:
|
||||||
|
placed += 1
|
||||||
|
if placed:
|
||||||
|
log(f"wanted-cards: положил {placed} .assistent.json рядом с весами")
|
||||||
|
|
||||||
|
|
||||||
def seed_civitai(cfg: Config, host: str, log: Log) -> None:
|
def seed_civitai(cfg: Config, host: str, log: Log) -> None:
|
||||||
from gpu_rent.huggingface import is_huggingface_url
|
from gpu_rent.huggingface import is_huggingface_url
|
||||||
|
|
||||||
|
try:
|
||||||
|
merge_wanted_models_from_vm(cfg, host, log)
|
||||||
|
except Exception as exc:
|
||||||
|
log(f"wanted-models merge: {exc}")
|
||||||
|
|
||||||
entries = parse_models(cfg.models_manifest)
|
entries = parse_models(cfg.models_manifest)
|
||||||
if not entries:
|
if not entries:
|
||||||
log("model-seed пропущен: манифест пуст — дефолт SwarmUI")
|
log("model-seed пропущен: манифест пуст — дефолт SwarmUI")
|
||||||
@@ -775,6 +905,7 @@ def seed_civitai(cfg: Config, host: str, log: Log) -> None:
|
|||||||
"url": url,
|
"url": url,
|
||||||
"sha256": "",
|
"sha256": "",
|
||||||
"auth": "hf",
|
"auth": "hf",
|
||||||
|
"version_id": None,
|
||||||
"sidecars": {
|
"sidecars": {
|
||||||
f"{stem}.swarm.json": json.dumps(swarm, ensure_ascii=False, indent=2),
|
f"{stem}.swarm.json": json.dumps(swarm, ensure_ascii=False, indent=2),
|
||||||
},
|
},
|
||||||
@@ -825,6 +956,7 @@ def seed_civitai(cfg: Config, host: str, log: Log) -> None:
|
|||||||
"url": _download_url(api_host, vid, info),
|
"url": _download_url(api_host, vid, info),
|
||||||
"sha256": sha,
|
"sha256": sha,
|
||||||
"auth": "civitai",
|
"auth": "civitai",
|
||||||
|
"version_id": int(vid),
|
||||||
"sidecars": {
|
"sidecars": {
|
||||||
f"{stem}.civitai.json": civitai_json,
|
f"{stem}.civitai.json": civitai_json,
|
||||||
f"{stem}.swarm.json": json.dumps(swarm, ensure_ascii=False, indent=2),
|
f"{stem}.swarm.json": json.dumps(swarm, ensure_ascii=False, indent=2),
|
||||||
@@ -903,6 +1035,10 @@ def seed_civitai(cfg: Config, host: str, log: Log) -> None:
|
|||||||
timeout=7200,
|
timeout=7200,
|
||||||
log=log,
|
log=log,
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
|
_place_wanted_cards(cfg, host, jobs, log)
|
||||||
|
except Exception as exc:
|
||||||
|
log(f"wanted-cards: {exc}")
|
||||||
put_text(
|
put_text(
|
||||||
cfg,
|
cfg,
|
||||||
host,
|
host,
|
||||||
@@ -1159,6 +1295,10 @@ def provision_vm(
|
|||||||
except GpuRentError as exc:
|
except GpuRentError as exc:
|
||||||
log(f"autocomplete: {exc}")
|
log(f"autocomplete: {exc}")
|
||||||
seed_civitai(cfg, host, log)
|
seed_civitai(cfg, host, log)
|
||||||
|
try:
|
||||||
|
seed_assistent_personas(cfg, host, log)
|
||||||
|
except Exception as exc:
|
||||||
|
log(f"assistent-personas: {exc}")
|
||||||
push_tree(cfg, host, cfg.local_models_dir, f"{DATA}/Models", log, models=True)
|
push_tree(cfg, host, cfg.local_models_dir, f"{DATA}/Models", log, models=True)
|
||||||
push_tree(
|
push_tree(
|
||||||
cfg, host, cfg.local_wildcards_dir, f"{DATA}/Data/Wildcards", log, models=False
|
cfg, host, cfg.local_wildcards_dir, f"{DATA}/Data/Wildcards", log, models=False
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ from gpu_rent.llm_runtime import (
|
|||||||
)
|
)
|
||||||
from gpu_rent.paths import (
|
from gpu_rent.paths import (
|
||||||
app_root,
|
app_root,
|
||||||
|
assistent_personas_example_path,
|
||||||
|
assistent_personas_manifest_path,
|
||||||
env_path,
|
env_path,
|
||||||
extensions_manifest_path,
|
extensions_manifest_path,
|
||||||
models_manifest_path,
|
models_manifest_path,
|
||||||
@@ -54,6 +56,12 @@ def run_setup(
|
|||||||
|
|
||||||
_copy_if_missing(root / "env.example", env_path(), ".env", log)
|
_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(root / "models.example.yaml", models_manifest_path(), "models.yaml", log)
|
||||||
|
_copy_if_missing(
|
||||||
|
assistent_personas_example_path(),
|
||||||
|
assistent_personas_manifest_path(),
|
||||||
|
"assistent-personas.yaml",
|
||||||
|
log,
|
||||||
|
)
|
||||||
_copy_if_missing(
|
_copy_if_missing(
|
||||||
root / "extensions.example.yaml", extensions_manifest_path(), "extensions.yaml", log
|
root / "extensions.example.yaml", extensions_manifest_path(), "extensions.yaml", log
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user