Files
HadTavern/agentui/pipeline/storage.py

170 lines
5.6 KiB
Python

from __future__ import annotations
from pathlib import Path
from typing import Any, Dict, List
import json
from agentui.pipeline.defaults import default_pipeline
PIPELINE_FILE = Path("pipeline.json")
PRESETS_DIR = Path("presets")
VARS_DIR = Path(".agentui") / "vars"
# DRY нормализация meta/пайплайна: единый источник дефолтов и типов
def normalize_pipeline(pipeline: Dict[str, Any]) -> Dict[str, Any]:
"""
Приводит верхнеуровневые ключи пайплайна к согласованному виду, заполняет дефолты.
Безопасно к отсутствующим ключам и неверным типам.
"""
if not isinstance(pipeline, dict):
pipeline = {}
out: Dict[str, Any] = dict(pipeline)
def _to_int(v, d):
try:
n = int(v)
return n if n > 0 else d
except Exception:
return d
def _to_float(v, d):
try:
n = float(v)
return n if n > 0 else d
except Exception:
return d
# Базовые поля
out["id"] = str(out.get("id") or "pipeline_editor")
out["name"] = str(out.get("name") or "Edited Pipeline")
out["parallel_limit"] = _to_int(out.get("parallel_limit"), 8)
out["loop_mode"] = str(out.get("loop_mode") or "dag")
out["loop_max_iters"] = _to_int(out.get("loop_max_iters"), 1000)
out["loop_time_budget_ms"] = _to_int(out.get("loop_time_budget_ms"), 10000)
out["clear_var_store"] = bool(out.get("clear_var_store", True))
out["http_timeout_sec"] = _to_float(out.get("http_timeout_sec"), 60)
# Глобальные опции извлечения текста для [[OUTx]]
out["text_extract_strategy"] = str(out.get("text_extract_strategy") or "auto")
out["text_extract_json_path"] = str(out.get("text_extract_json_path") or "")
# Поддержка разных написаний text_join_sep
join_sep = out.get("text_join_sep")
if join_sep is None:
for k in list(out.keys()):
if isinstance(k, str) and k.lower() == "text_join_sep":
join_sep = out.get(k)
break
out["text_join_sep"] = str(join_sep or "\n")
# Пресеты парсинга
presets = out.get("text_extract_presets")
norm_presets: List[Dict[str, Any]] = []
if isinstance(presets, list):
for i, it in enumerate(presets):
if not isinstance(it, dict):
continue
norm_presets.append({
"id": str(it.get("id") or f"p{i}"),
"name": str(it.get("name") or it.get("json_path") or "Preset"),
"strategy": str(it.get("strategy") or "auto"),
"json_path": str(it.get("json_path") or ""),
"join_sep": str(it.get("join_sep") or "\n"),
})
out["text_extract_presets"] = norm_presets
# Узлы — список
try:
nodes = out.get("nodes") or []
if not isinstance(nodes, list):
nodes = []
out["nodes"] = nodes
except Exception:
out["nodes"] = []
return out
def load_pipeline() -> Dict[str, Any]:
if PIPELINE_FILE.exists():
try:
data = json.loads(PIPELINE_FILE.read_text(encoding="utf-8"))
return normalize_pipeline(data)
except Exception:
pass
return normalize_pipeline(default_pipeline())
def save_pipeline(pipeline: Dict[str, Any]) -> None:
norm = normalize_pipeline(pipeline or {})
PIPELINE_FILE.write_text(json.dumps(norm, ensure_ascii=False, indent=2), encoding="utf-8")
def list_presets() -> List[str]:
PRESETS_DIR.mkdir(parents=True, exist_ok=True)
return sorted([p.stem for p in PRESETS_DIR.glob("*.json")])
def load_preset(name: str) -> Dict[str, Any]:
PRESETS_DIR.mkdir(parents=True, exist_ok=True)
path = PRESETS_DIR / f"{name}.json"
if not path.exists():
raise FileNotFoundError(name)
return json.loads(path.read_text(encoding="utf-8"))
def save_preset(name: str, pipeline: Dict[str, Any]) -> None:
PRESETS_DIR.mkdir(parents=True, exist_ok=True)
path = PRESETS_DIR / f"{name}.json"
path.write_text(json.dumps(pipeline, ensure_ascii=False, indent=2), encoding="utf-8")
# ---------------- Variable Store (per-pipeline) ----------------
def _var_store_path(pipeline_id: str) -> Path:
pid = pipeline_id or "pipeline_editor"
VARS_DIR.mkdir(parents=True, exist_ok=True)
# normalize to safe filename
safe = "".join(ch if ch.isalnum() or ch in ("-", "_", ".") else "_" for ch in str(pid))
return VARS_DIR / f"{safe}.json"
def load_var_store(pipeline_id: str) -> Dict[str, Any]:
"""
Load variable store dictionary for given pipeline id.
Returns {} if not exists or invalid.
"""
path = _var_store_path(pipeline_id)
if not path.exists():
return {}
try:
data = json.loads(path.read_text(encoding="utf-8"))
return data if isinstance(data, dict) else {}
except Exception:
return {}
def save_var_store(pipeline_id: str, data: Dict[str, Any]) -> None:
"""
Save variable store dictionary for given pipeline id.
"""
path = _var_store_path(pipeline_id)
try:
VARS_DIR.mkdir(parents=True, exist_ok=True)
except Exception:
pass
path.write_text(json.dumps(data or {}, ensure_ascii=False, indent=2), encoding="utf-8")
def clear_var_store(pipeline_id: str) -> None:
"""
Delete/reset variable store for given pipeline id.
"""
path = _var_store_path(pipeline_id)
try:
if path.exists():
path.unlink()
except Exception:
# ignore failures
pass