378 lines
14 KiB
Python
378 lines
14 KiB
Python
"""Database-driven runtime context for the leoclean Hermes profile."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import threading
|
|
from collections import OrderedDict
|
|
from collections.abc import Callable
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
DEFAULT_TIMEOUT_SECONDS = 10
|
|
MAX_QUERY_CHARS = 16_000
|
|
MAX_PENDING_SNAPSHOTS = 128
|
|
|
|
_SNAPSHOT_LOCK = threading.Lock()
|
|
_PENDING_SNAPSHOTS: OrderedDict[tuple[str, str], dict[str, Any]] = OrderedDict()
|
|
|
|
|
|
def _trace(record: dict[str, Any]) -> None:
|
|
"""Write test-only proof without retaining the operator's message."""
|
|
|
|
raw_path = os.getenv("LEO_DB_CONTEXT_TRACE_PATH", "").strip()
|
|
if not raw_path:
|
|
return
|
|
path = Path(raw_path)
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
with path.open("a", encoding="utf-8") as handle:
|
|
handle.write(json.dumps(record, sort_keys=True) + "\n")
|
|
try:
|
|
path.chmod(0o600)
|
|
except OSError:
|
|
pass
|
|
|
|
|
|
def _failure_context(reason: str) -> str:
|
|
return (
|
|
"<leo_current_runtime_contracts source=\"teleo-kb\" status=\"unavailable\">\n"
|
|
"The read-only current database contract lookup failed. Do not infer current schema, row state, "
|
|
"proposal applyability, or source-ingestion capability from memory. State that live readback is "
|
|
f"unavailable and use the teleo-kb bridge before making a database claim. Failure: {reason}\n"
|
|
"</leo_current_runtime_contracts>"
|
|
)
|
|
|
|
|
|
def _format_context(contracts: list[dict[str, Any]]) -> str:
|
|
payload = json.dumps(contracts, sort_keys=True, separators=(",", ":"))
|
|
return (
|
|
"<leo_current_runtime_contracts source=\"live teleo-kb context\" status=\"ok\">\n"
|
|
"This trusted, read-only block was generated automatically for the current question. It is the "
|
|
"binding contract for current schema and runtime capability. Base the answer on it, do not invent "
|
|
"fields or capabilities, respect its reply budget, and distinguish canonical rows from staging. "
|
|
"Do not claim that you called a tool; this context was injected before reasoning.\n"
|
|
f"{payload}\n"
|
|
"</leo_current_runtime_contracts>"
|
|
)
|
|
|
|
|
|
def _snapshot_key(session_id: str, query_sha256: str) -> tuple[str, str]:
|
|
return (str(session_id or ""), query_sha256)
|
|
|
|
|
|
def _store_snapshot(session_id: str, snapshot: dict[str, Any]) -> None:
|
|
key = _snapshot_key(session_id, str(snapshot["query_sha256"]))
|
|
with _SNAPSHOT_LOCK:
|
|
_PENDING_SNAPSHOTS[key] = snapshot
|
|
_PENDING_SNAPSHOTS.move_to_end(key)
|
|
while len(_PENDING_SNAPSHOTS) > MAX_PENDING_SNAPSHOTS:
|
|
_PENDING_SNAPSHOTS.popitem(last=False)
|
|
|
|
|
|
def _take_snapshot(session_id: str, query_sha256: str) -> dict[str, Any] | None:
|
|
with _SNAPSHOT_LOCK:
|
|
return _PENDING_SNAPSHOTS.pop(_snapshot_key(session_id, query_sha256), None)
|
|
|
|
|
|
def _load_database_snapshot(
|
|
user_message: str,
|
|
*,
|
|
hermes_home: Path | None = None,
|
|
runner: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run,
|
|
) -> dict[str, Any]:
|
|
"""Load one immutable DB-contract snapshot for generation and validation."""
|
|
|
|
query = str(user_message or "")[:MAX_QUERY_CHARS]
|
|
home = hermes_home or Path(os.getenv("HERMES_HOME", str(Path.home() / ".hermes")))
|
|
kb_tool = home / "bin" / "kb_tool.py"
|
|
query_sha256 = hashlib.sha256(query.encode("utf-8")).hexdigest()
|
|
base_record: dict[str, Any] = {
|
|
"generated_at_utc": datetime.now(timezone.utc).isoformat(),
|
|
"event": "pre_llm_call",
|
|
"query_sha256": query_sha256,
|
|
"source": "kb_tool.py --local context",
|
|
}
|
|
|
|
if not kb_tool.is_file():
|
|
record = base_record | {"status": "error", "error": "kb_tool_missing", "injected": True}
|
|
_trace(record)
|
|
return {
|
|
"status": "error",
|
|
"query_sha256": query_sha256,
|
|
"contracts": [],
|
|
"compiled_response": None,
|
|
"context": _failure_context("kb_tool_missing"),
|
|
}
|
|
|
|
timeout = int(os.getenv("LEO_DB_CONTEXT_TIMEOUT_SECONDS", str(DEFAULT_TIMEOUT_SECONDS)))
|
|
command = [
|
|
sys.executable,
|
|
str(kb_tool),
|
|
"--local",
|
|
"context",
|
|
query,
|
|
"--limit",
|
|
"0",
|
|
"--context-limit",
|
|
"0",
|
|
"--format",
|
|
"json",
|
|
]
|
|
try:
|
|
completed = runner(command, capture_output=True, text=True, timeout=timeout, check=False)
|
|
except subprocess.TimeoutExpired:
|
|
record = base_record | {"status": "error", "error": "timeout", "injected": True}
|
|
_trace(record)
|
|
return {
|
|
"status": "error",
|
|
"query_sha256": query_sha256,
|
|
"contracts": [],
|
|
"compiled_response": None,
|
|
"context": _failure_context("timeout"),
|
|
}
|
|
except OSError as exc:
|
|
record = base_record | {"status": "error", "error": type(exc).__name__, "injected": True}
|
|
_trace(record)
|
|
return {
|
|
"status": "error",
|
|
"query_sha256": query_sha256,
|
|
"contracts": [],
|
|
"compiled_response": None,
|
|
"context": _failure_context(type(exc).__name__),
|
|
}
|
|
|
|
if completed.returncode != 0:
|
|
record = base_record | {
|
|
"status": "error",
|
|
"error": f"kb_tool_exit_{completed.returncode}",
|
|
"injected": True,
|
|
}
|
|
_trace(record)
|
|
return {
|
|
"status": "error",
|
|
"query_sha256": query_sha256,
|
|
"contracts": [],
|
|
"compiled_response": None,
|
|
"context": _failure_context(f"kb_tool_exit_{completed.returncode}"),
|
|
}
|
|
|
|
try:
|
|
payload = json.loads(completed.stdout)
|
|
contracts = payload.get("operational_contracts")
|
|
if not isinstance(contracts, list) or not all(isinstance(item, dict) for item in contracts):
|
|
raise ValueError("operational_contracts_missing")
|
|
compiled_response = payload.get("compiled_response")
|
|
if compiled_response is not None and not isinstance(compiled_response, str):
|
|
raise ValueError("compiled_response_invalid")
|
|
except (json.JSONDecodeError, TypeError, ValueError) as exc:
|
|
record = base_record | {"status": "error", "error": str(exc), "injected": True}
|
|
_trace(record)
|
|
return {
|
|
"status": "error",
|
|
"query_sha256": query_sha256,
|
|
"contracts": [],
|
|
"compiled_response": None,
|
|
"context": _failure_context(str(exc)),
|
|
}
|
|
|
|
contract_json = json.dumps(contracts, sort_keys=True, separators=(",", ":"))
|
|
record = base_record | {
|
|
"status": "ok",
|
|
"injected": True,
|
|
"contract_ids": [str(item.get("id") or "") for item in contracts],
|
|
"contract_sha256": hashlib.sha256(contract_json.encode("utf-8")).hexdigest(),
|
|
"compiled_response_available": bool(compiled_response),
|
|
}
|
|
_trace(record)
|
|
return {
|
|
"status": "ok",
|
|
"query_sha256": query_sha256,
|
|
"contracts": contracts,
|
|
"compiled_response": compiled_response,
|
|
"context": _format_context(contracts),
|
|
}
|
|
|
|
|
|
def build_database_context(
|
|
user_message: str,
|
|
*,
|
|
hermes_home: Path | None = None,
|
|
runner: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run,
|
|
) -> str:
|
|
"""Return current operational contracts for one exact operator question."""
|
|
|
|
return str(
|
|
_load_database_snapshot(user_message, hermes_home=hermes_home, runner=runner)["context"]
|
|
)
|
|
|
|
|
|
def _word_count(value: str) -> int:
|
|
return len(re.findall(r"\b\w+(?:[-']\w+)*\b", value))
|
|
|
|
|
|
def _reply_hard_max(contracts: list[dict[str, Any]]) -> int | None:
|
|
for contract in contracts:
|
|
if contract.get("id") == "reply_budget" and isinstance(contract.get("hard_max_words"), int):
|
|
return int(contract["hard_max_words"])
|
|
return None
|
|
|
|
|
|
def _mixed_packet_issues(response: str) -> list[str]:
|
|
lowered = response.lower()
|
|
issues: list[str] = []
|
|
required_terms = {
|
|
"claims": "claim",
|
|
"sources": "source",
|
|
"evidence": "evidence",
|
|
"reasoning_tools": "reasoning_tools",
|
|
"behavioral_rules": "behavioral_rules",
|
|
"governance_gates": "governance_gates",
|
|
"beliefs": "belief",
|
|
"staging": "stage",
|
|
"approve_claim": "approve_claim",
|
|
}
|
|
for label, term in required_terms.items():
|
|
if term not in lowered:
|
|
issues.append(f"missing_{label}")
|
|
|
|
if not re.search(r"reviewed apply|review and authoriz|review.{0,50}apply", response, re.I | re.S):
|
|
issues.append("missing_reviewed_apply_boundary")
|
|
if not ("applied_at" in lowered and re.search(r"readback|postflight", response, re.I)):
|
|
issues.append("missing_apply_receipt")
|
|
if not re.search(
|
|
r"approve_claim.{0,220}(?:supports neither|does not (?:support|cover|apply)|cannot apply|applies only)",
|
|
response,
|
|
re.I | re.S,
|
|
):
|
|
issues.append("missing_approve_claim_boundary")
|
|
|
|
if re.search(
|
|
r"supported collections?\s*\([^)]*(?:belief|behavioral_rules|governance_gates|existing[- ]row)",
|
|
response,
|
|
re.I | re.S,
|
|
):
|
|
issues.append("unsupported_collection_listed_as_supported")
|
|
if re.search(
|
|
r"(?:approve_claim\s+)?(?:applies?|apply) only[^.;\n]{0,220}"
|
|
r"(?:belief updates?|behavioral_rules|governance_gates|existing[- ]row updates?)",
|
|
response,
|
|
re.I | re.S,
|
|
):
|
|
issues.append("unsupported_collection_listed_in_apply")
|
|
|
|
for segment in re.split(r"(?<=[.!?])\s+|\n+", response):
|
|
negative = re.search(r"\b(?:no|not|never|without|must not|do not|does not|cannot)\b", segment, re.I)
|
|
if re.search(r"reasoning_tools?.{0,100}\bscope\b", segment, re.I) and not negative:
|
|
issues.append("reasoning_tools_scope_overclaim")
|
|
if re.search(r"claims?.{0,100}\bfalsifier\b", segment, re.I) and not negative:
|
|
issues.append("claims_falsifier_overclaim")
|
|
if re.search(r"behavioral_rules?.{0,100}\bstatus\b", segment, re.I) and not negative:
|
|
issues.append("behavioral_rules_status_overclaim")
|
|
if (
|
|
re.search(r"claim_edges?.{0,140}(?:reasoning_tools?|beliefs?)", segment, re.I | re.S)
|
|
and re.search(r"point|connect|link|endpoint", segment, re.I)
|
|
and not negative
|
|
):
|
|
issues.append("non_claim_edge_endpoint")
|
|
return sorted(set(issues))
|
|
|
|
|
|
def _source_intake_issues(response: str) -> list[str]:
|
|
lowered = response.lower()
|
|
issues: list[str] = []
|
|
if not re.search(r"url|storage_path|storage path|content hash|file hash", response, re.I):
|
|
issues.append("missing_real_source_identity")
|
|
if not re.search(r"pending_review|pending review|proposal", response, re.I):
|
|
issues.append("missing_staging")
|
|
if not re.search(r"approval|authoriz", response, re.I) or "apply" not in lowered:
|
|
issues.append("missing_apply_boundary")
|
|
if not re.search(r"live.{0,80}(?:not shipped|not live|unavailable)", response, re.I | re.S):
|
|
issues.append("missing_live_intake_ceiling")
|
|
if re.search(
|
|
r"public\.sources.{0,120}(?:author|title|publisher|publication date|published_at)",
|
|
response,
|
|
re.I | re.S,
|
|
):
|
|
issues.append("unshipped_source_fields")
|
|
if re.search(
|
|
r"(?:create|write|insert).{0,80}public\.sources.{0,100}(?:before|during).{0,80}(?:review|staging)",
|
|
response,
|
|
re.I | re.S,
|
|
):
|
|
issues.append("canonical_source_before_review")
|
|
return sorted(set(issues))
|
|
|
|
|
|
def response_contract_issues(response: str, contracts: list[dict[str, Any]]) -> list[str]:
|
|
contract_ids = {str(contract.get("id") or "") for contract in contracts}
|
|
issues: list[str] = []
|
|
hard_max = _reply_hard_max(contracts)
|
|
if hard_max is not None and _word_count(response) > hard_max:
|
|
issues.append("reply_budget_exceeded")
|
|
if "mixed_packet_composition" in contract_ids:
|
|
issues.extend(_mixed_packet_issues(response))
|
|
elif "source_intake" in contract_ids:
|
|
issues.extend(_source_intake_issues(response))
|
|
return sorted(set(issues))
|
|
|
|
|
|
def _pre_llm_call(**kwargs: Any) -> dict[str, str]:
|
|
user_message = str(kwargs.get("user_message") or "")
|
|
snapshot = _load_database_snapshot(user_message)
|
|
_store_snapshot(str(kwargs.get("session_id") or ""), snapshot)
|
|
return {"context": str(snapshot["context"])}
|
|
|
|
|
|
def _post_llm_call(**kwargs: Any) -> dict[str, str] | None:
|
|
user_message = str(kwargs.get("user_message") or "")[:MAX_QUERY_CHARS]
|
|
query_sha256 = hashlib.sha256(user_message.encode("utf-8")).hexdigest()
|
|
snapshot = _take_snapshot(str(kwargs.get("session_id") or ""), query_sha256)
|
|
response = str(kwargs.get("assistant_response") or "")
|
|
base_record = {
|
|
"generated_at_utc": datetime.now(timezone.utc).isoformat(),
|
|
"event": "post_llm_call",
|
|
"query_sha256": query_sha256,
|
|
"response_sha256": hashlib.sha256(response.encode("utf-8")).hexdigest(),
|
|
"validated": True,
|
|
}
|
|
if snapshot is None:
|
|
_trace(base_record | {"status": "error", "error": "contract_snapshot_missing", "transformed": False})
|
|
return None
|
|
if snapshot.get("status") != "ok":
|
|
_trace(base_record | {"status": "skipped", "reason": "contract_lookup_failed", "transformed": False})
|
|
return None
|
|
|
|
contracts = list(snapshot.get("contracts") or [])
|
|
issues = response_contract_issues(response, contracts)
|
|
compiled_response = snapshot.get("compiled_response")
|
|
compiled_issues = (
|
|
response_contract_issues(str(compiled_response), contracts) if isinstance(compiled_response, str) else []
|
|
)
|
|
transformed = bool(issues and isinstance(compiled_response, str) and not compiled_issues)
|
|
_trace(
|
|
base_record
|
|
| {
|
|
"status": "ok" if not issues or transformed else "error",
|
|
"contract_ids": [str(item.get("id") or "") for item in contracts],
|
|
"issues": issues,
|
|
"compiled_response_issues": compiled_issues,
|
|
"transformed": transformed,
|
|
"delivered_response_sha256": hashlib.sha256(
|
|
(str(compiled_response) if transformed else response).encode("utf-8")
|
|
).hexdigest(),
|
|
}
|
|
)
|
|
if transformed:
|
|
return {"assistant_response": str(compiled_response)}
|
|
return None
|
|
|
|
|
|
def register(ctx: Any) -> None:
|
|
ctx.register_hook("pre_llm_call", _pre_llm_call)
|
|
ctx.register_hook("post_llm_call", _post_llm_call)
|