core: Registry, Skills, Router, Executor, Verifier, Runner mit Tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
"""Kleine Hilfsfunktionen ohne Fachlogik."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
|
||||
_DURATION = re.compile(r"^\s*(\d+(?:\.\d+)?)\s*([smhd]?)\s*$")
|
||||
_FACTORS = {"": 1, "s": 1, "m": 60, "h": 3600, "d": 86400}
|
||||
_PLACEHOLDER = re.compile(r"\{([a-zA-Z0-9_]+)\}")
|
||||
|
||||
|
||||
def now() -> float:
|
||||
return time.time()
|
||||
|
||||
|
||||
def load_yaml(path: str | Path) -> Any:
|
||||
with open(path, encoding="utf-8") as fh:
|
||||
return yaml.safe_load(fh) or {}
|
||||
|
||||
|
||||
def parse_duration(value: str | int | float | None) -> float:
|
||||
"""'5m' -> 300.0; Zahlen gelten als Sekunden."""
|
||||
if value is None:
|
||||
return 0.0
|
||||
if isinstance(value, int | float):
|
||||
return float(value)
|
||||
match = _DURATION.match(value)
|
||||
if not match:
|
||||
raise ValueError(f"Ungültige Dauer: {value!r}")
|
||||
return float(match.group(1)) * _FACTORS[match.group(2)]
|
||||
|
||||
|
||||
class MissingLabel(KeyError):
|
||||
pass
|
||||
|
||||
|
||||
def fill_template(value: Any, labels: dict[str, str]) -> Any:
|
||||
"""Ersetzt {label} rekursiv; fehlende Labels lösen MissingLabel aus."""
|
||||
if isinstance(value, str):
|
||||
def repl(m: re.Match[str]) -> str:
|
||||
key = m.group(1)
|
||||
if key not in labels:
|
||||
raise MissingLabel(key)
|
||||
return str(labels[key])
|
||||
|
||||
return _PLACEHOLDER.sub(repl, value)
|
||||
if isinstance(value, dict):
|
||||
return {k: fill_template(v, labels) for k, v in value.items()}
|
||||
if isinstance(value, list):
|
||||
return [fill_template(v, labels) for v in value]
|
||||
return value
|
||||
Reference in New Issue
Block a user