Add basic version: SemIf decision engine, urgency queue, skill tree, skill loop, decision log, dream pass, CLI
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
"""Real SemIf decision engine (no mocking).
|
||||
|
||||
Wraps the SemIf package (`semif_phase1`). The import and model load happen
|
||||
lazily so the rest of the agent is pure stdlib and testable without SemIf
|
||||
installed. On hardware that is neither CUDA nor Apple, use the shipped
|
||||
llama.cpp backend: a local GGUF scored on CPU (or Vulkan if your llama.cpp
|
||||
build enables it). This engine is only usable on a machine with SemIf and the
|
||||
pinned GGUF available; elsewhere calls raise EngineUnavailable.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from .decisions import DecisionRequest, DecisionResult
|
||||
|
||||
|
||||
class EngineUnavailable(RuntimeError):
|
||||
"""SemIf is not installed or the configured model is missing."""
|
||||
|
||||
|
||||
@dataclass
|
||||
class EngineConfig:
|
||||
backend: str = "llamacpp"
|
||||
source: str = "Qwen/Qwen3.5-4B"
|
||||
revision: str = ""
|
||||
gguf: str = ""
|
||||
context_tokens: int = 4096
|
||||
threads: int | None = None
|
||||
|
||||
|
||||
class SemIfEngine:
|
||||
"""One pinned SemIf model, loaded once and used for every decision."""
|
||||
|
||||
def __init__(self, config: EngineConfig):
|
||||
self.config = config
|
||||
self._model = None
|
||||
self._tokenizer = None
|
||||
self._metadata = None
|
||||
|
||||
def _ensure_loaded(self) -> tuple:
|
||||
if self._model is not None:
|
||||
return self._model, self._tokenizer, self._metadata
|
||||
if self.config.backend != "llamacpp":
|
||||
raise EngineUnavailable(
|
||||
f"Unsupported backend {self.config.backend!r}; use 'llamacpp'."
|
||||
)
|
||||
if not self.config.gguf or not Path(self.config.gguf).is_file():
|
||||
raise EngineUnavailable(
|
||||
f"GGUF not found: {self.config.gguf!r}. Set engine.gguf in config.json."
|
||||
)
|
||||
try:
|
||||
from semif_phase1 import llamacpp_backend as backend
|
||||
except ImportError as exc:
|
||||
raise EngineUnavailable(
|
||||
"SemIf is not installed here. Install it on the target box with "
|
||||
"`pip install -e '.[test,llamacpp]'`."
|
||||
) from exc
|
||||
try:
|
||||
model, tokenizer, metadata = backend.load_model(
|
||||
self.config.source,
|
||||
self.config.revision,
|
||||
self.config.gguf,
|
||||
threads=self.config.threads,
|
||||
context_tokens=self.config.context_tokens,
|
||||
)
|
||||
except Exception as exc:
|
||||
raise EngineUnavailable(f"Failed to load the SemIf model: {exc}") from exc
|
||||
self._model, self._tokenizer, self._metadata = model, tokenizer, metadata
|
||||
return model, tokenizer, metadata
|
||||
|
||||
@property
|
||||
def loaded(self) -> bool:
|
||||
return self._model is not None
|
||||
|
||||
def call(self, request: DecisionRequest) -> DecisionResult:
|
||||
model, tokenizer, metadata = self._ensure_loaded()
|
||||
from semif_phase1 import llamacpp_backend as backend
|
||||
|
||||
row = request.to_semif_row()
|
||||
result = backend.score(model, tokenizer, row, metadata)
|
||||
return DecisionResult(
|
||||
request=request,
|
||||
option_ids=list(result["option_ids"]),
|
||||
probabilities=list(result["probabilities"]),
|
||||
extra={
|
||||
"prompt_sha256": result.get("prompt_sha256"),
|
||||
"input_tokens": result.get("input_tokens"),
|
||||
"forward_seconds": result.get("forward_seconds"),
|
||||
"total_seconds": result.get("total_seconds"),
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user