core: Policy, Pflege-Agent (HITL), Healer, Lifecycle-Pipeline, Graduierung + Konfiguration
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
"""Verdrahtung aller Komponenten (einmal pro Prozess, in Tests mit Fakes)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from .classifier import EmbeddingClassifier
|
||||
from .embeddings import Embedder, LlamaCppReranker, OpenAIEmbedder, Reranker
|
||||
from .executor import Executor
|
||||
from .healer.drivers import CompositeInfra, InfraDriver, Notifier, PortainerDriver, ProxmoxDriver, WebhookNotifier
|
||||
from .healer.engine import Healer, HealerConfig
|
||||
from .lifecycle import JobRunner, Lifecycle, LifecycleConfig, LoggingJobRunner, WebhookJobRunner
|
||||
from .llm import ChatClient, OpenAIChatClient
|
||||
from .maintenance.actions import ActionExecutor
|
||||
from .maintenance.agent import IntentMapper, LLMIntentMapper, MaintenanceAgent, MaintenanceConfig
|
||||
from .metrics import Metrics
|
||||
from .policy import Policy
|
||||
from .registry import Registry
|
||||
from .router import Router
|
||||
from .runner import Runner
|
||||
from .settings import Settings
|
||||
from .skills import SkillLibrary
|
||||
from .store import Store
|
||||
from .util import now
|
||||
from .verifier import Verifier
|
||||
|
||||
|
||||
@dataclass
|
||||
class System:
|
||||
settings: Settings
|
||||
store: Store
|
||||
registry: Registry
|
||||
library: SkillLibrary
|
||||
embedder: Embedder
|
||||
chat: ChatClient
|
||||
router: Router
|
||||
executor: Executor
|
||||
verifier: Verifier
|
||||
runner: Runner
|
||||
policy: Policy
|
||||
lifecycle: Lifecycle
|
||||
actions: ActionExecutor
|
||||
maintenance: MaintenanceAgent
|
||||
healer: Healer
|
||||
metrics: Metrics
|
||||
|
||||
|
||||
def _load_classifiers(directory: Path) -> dict[str, EmbeddingClassifier]:
|
||||
if not directory.is_dir():
|
||||
return {}
|
||||
return {p.stem: EmbeddingClassifier.load(p) for p in sorted(directory.glob("*.json"))}
|
||||
|
||||
|
||||
def _optional(path: Path, loader: Callable, default): # type: ignore[no-untyped-def]
|
||||
return loader(path) if path.exists() else default
|
||||
|
||||
|
||||
def build_system(
|
||||
settings: Settings,
|
||||
*,
|
||||
embedder: Embedder | None = None,
|
||||
chat: ChatClient | None = None,
|
||||
reranker: Reranker | None = None,
|
||||
infra: InfraDriver | None = None,
|
||||
notifier: Notifier | None = None,
|
||||
jobs: JobRunner | None = None,
|
||||
intent_mapper: IntentMapper | None = None,
|
||||
clock: Callable[[], float] = now,
|
||||
wallclock: Callable[[], datetime] = datetime.now,
|
||||
) -> System:
|
||||
if settings.db_url.startswith("sqlite:///") and not settings.db_url.startswith("sqlite:///:"):
|
||||
Path(settings.db_url.removeprefix("sqlite:///")).parent.mkdir(parents=True, exist_ok=True)
|
||||
store = Store(settings.db_url)
|
||||
registry = Registry.from_file(settings.site_dir / "registry.yaml", store)
|
||||
library = SkillLibrary.from_dir(settings.skills_dir, store)
|
||||
|
||||
chat = chat or OpenAIChatClient(settings.litellm_base_url, settings.litellm_api_key, settings.request_timeout)
|
||||
embedder = embedder or OpenAIEmbedder(settings.litellm_base_url, settings.embed_model, settings.litellm_api_key)
|
||||
if reranker is None and settings.rerank_url:
|
||||
reranker = LlamaCppReranker(settings.rerank_url, settings.rerank_model)
|
||||
|
||||
cfg = settings.config_dir
|
||||
policy = _optional(cfg / "policy.yaml", Policy.from_file, Policy())
|
||||
healer_cfg = _optional(cfg / "healer-rules.yaml", HealerConfig.from_file, HealerConfig())
|
||||
maint_cfg = _optional(cfg / "maintenance.yaml", MaintenanceConfig.from_file, MaintenanceConfig())
|
||||
life_cfg = _optional(cfg / "lifecycle.yaml", LifecycleConfig.from_file, LifecycleConfig())
|
||||
|
||||
metrics = Metrics()
|
||||
router = Router(registry, library, embedder, reranker)
|
||||
executor = Executor(library, chat, embedder, _load_classifiers(cfg / "classifiers"))
|
||||
verifier = Verifier(registry, chat)
|
||||
runner = Runner(router, executor, verifier, library, store, metrics)
|
||||
|
||||
if jobs is None:
|
||||
jobs = WebhookJobRunner(settings.job_webhook_url) if settings.job_webhook_url and not settings.dry_run \
|
||||
else LoggingJobRunner()
|
||||
lifecycle = Lifecycle(store, registry, jobs, life_cfg)
|
||||
|
||||
if infra is None and not settings.dry_run:
|
||||
portainer = PortainerDriver(settings.portainer_host, settings.portainer_key) \
|
||||
if settings.portainer_host and settings.portainer_key else None
|
||||
proxmox = ProxmoxDriver(settings.proxmox_url, settings.proxmox_token_id, settings.proxmox_token_secret) \
|
||||
if settings.proxmox_url and settings.proxmox_token_id else None
|
||||
infra = CompositeInfra(portainer, proxmox)
|
||||
if notifier is None and settings.notify_webhook_url:
|
||||
notifier = WebhookNotifier(settings.notify_webhook_url)
|
||||
|
||||
actions = ActionExecutor(store=store, registry=registry, library=library, lifecycle=lifecycle, infra=infra,
|
||||
notifier=notifier, dry_run=settings.dry_run,
|
||||
enabled_infra_actions=set(healer_cfg.enabled_actions), on_skills_changed=router.refresh)
|
||||
if intent_mapper is None and registry.has_slot("tier1-general"):
|
||||
intent_mapper = LLMIntentMapper(chat, registry.slot("tier1-general").spec.litellm_model)
|
||||
maintenance = MaintenanceAgent(store=store, registry=registry, library=library, index=router.index,
|
||||
policy=policy, actions=actions, lifecycle=lifecycle, config=maint_cfg,
|
||||
intent_mapper=intent_mapper, clock=clock, wallclock=wallclock)
|
||||
healer = Healer(healer_cfg, store, actions, policy, propose=maintenance.propose, clock=clock)
|
||||
return System(settings, store, registry, library, embedder, chat, router, executor, verifier, runner, policy,
|
||||
lifecycle, actions, maintenance, healer, metrics)
|
||||
Reference in New Issue
Block a user