cypher-v12-finalized / modules /cypher_tools.py
jescy525's picture
Upload folder using huggingface_hub
076a67c verified
Raw
History Blame Contribute Delete
16 kB
"""CYPHER V12 M11 — TOOLS_REGISTRY (central tool registry, NEXUS pattern).
Aggregates all CYPHER tools from M2-M10 modules into a single dict
TOOLS_REGISTRY. Lazy imports (try/except) so missing optional deps don't
break the bridge.
Public API:
- TOOLS_REGISTRY: dict[str, callable]
- call_tool(name, **kwargs): execute a tool by name
- list_available_tools(): introspection
- get_tool_signature(name): runtime signature peek
"""
from __future__ import annotations
import inspect
import logging
import os
import sys
from pathlib import Path
from typing import Any, Callable
logger = logging.getLogger(__name__)
# ──────────────────────────── HELPERS ───────────────────────────────
def _env(key: str, default: str | None = None) -> str | None:
return os.environ.get(key, default)
def _key_available(env_key: str) -> bool:
return bool(os.environ.get(env_key))
# ──────────────────────────── LAZY MODULES ──────────────────────────
_MODULE_DIR = Path(__file__).parent
if str(_MODULE_DIR) not in sys.path:
sys.path.insert(0, str(_MODULE_DIR))
def _lazy_import(module_name: str) -> Any:
try:
return __import__(module_name)
except ImportError as e:
logger.warning(f"_lazy_import {module_name} failed: {e}")
return None
# Cache singletons to avoid re-instantiation per call
_singletons: dict[str, Any] = {}
def _get_singleton(key: str, factory: Callable[[], Any]) -> Any:
if key not in _singletons:
try:
_singletons[key] = factory()
except Exception as e:
logger.error(f"singleton {key} init failed: {type(e).__name__}: {e}")
_singletons[key] = None
return _singletons[key]
# ──────────────────────────── TOOL ADAPTERS ─────────────────────────
# M2 familybus_client
def _t_family_pull_recent(n: int = 20, since_ts: int | None = None) -> list[dict]:
fb = _lazy_import("familybus_client")
if fb is None:
return [{"error": "familybus_client unavailable"}]
client = _get_singleton("familybus", lambda: fb.FamilyBusClient())
if client is None:
return [{"error": "FamilyBusClient init failed"}]
return client.pull_recent(n=n, since_ts=since_ts)
def _t_family_broadcast(lesson: str, category: str = "CYBERSEC",
trade_summary: dict | None = None) -> bool:
fb = _lazy_import("familybus_client")
if fb is None:
return False
client = _get_singleton("familybus", lambda: fb.FamilyBusClient())
if client is None:
return False
return client.broadcast(lesson=lesson, asi="CYPHER",
category=category, trade_summary=trade_summary)
def _t_family_stats() -> dict:
fb = _lazy_import("familybus_client")
if fb is None:
return {"error": "familybus_client unavailable"}
client = _get_singleton("familybus", lambda: fb.FamilyBusClient())
if client is None:
return {"error": "init failed"}
return client.get_stats()
# M3 cve_refresh_pipeline
def _t_cisa_kev_refresh(force: bool = False) -> dict:
cve_mod = _lazy_import("cve_refresh_pipeline")
if cve_mod is None:
return {"error": "cve_refresh_pipeline unavailable"}
kev = _get_singleton("kev", lambda: cve_mod.CISAKevRefresher())
if kev is None:
return {"error": "init failed"}
return {"count": kev.refresh(force=force)}
def _t_cisa_kev_lookup(cve_id: str) -> dict | None:
cve_mod = _lazy_import("cve_refresh_pipeline")
if cve_mod is None:
return None
kev = _get_singleton("kev", lambda: cve_mod.CISAKevRefresher())
if kev is None:
return None
return kev.lookup(cve_id)
def _t_nvd_cve_lookup(cve_id: str) -> dict | None:
cve_mod = _lazy_import("cve_refresh_pipeline")
if cve_mod is None:
return None
nvd = _get_singleton("nvd", lambda: cve_mod.NVDLookup())
if nvd is None:
return None
return nvd.lookup_cve(cve_id)
def _t_get_cve_context(cve_id: str) -> str:
cve_mod = _lazy_import("cve_refresh_pipeline")
if cve_mod is None:
return f"[CONTEXT: CVE={cve_id} (cve module unavailable)]"
kev = _get_singleton("kev", lambda: cve_mod.CISAKevRefresher())
nvd = _get_singleton("nvd", lambda: cve_mod.NVDLookup())
return cve_mod.get_cve_context(cve_id, kev=kev, nvd=nvd)
# M4 cypher_memory
def _t_store_memory(content: str, category: str,
metadata: dict | None = None) -> str | None:
mem_mod = _lazy_import("cypher_memory")
if mem_mod is None:
return None
mem = _get_singleton("memory", lambda: mem_mod.CypherMemory())
if mem is None:
return None
try:
return mem.store_memory(content, category, metadata)
except (ValueError, Exception) as e:
logger.error(f"store_memory: {type(e).__name__}: {e}")
return None
def _t_recall_memory(query: str, k: int = 3,
category: str | None = None) -> list[dict]:
mem_mod = _lazy_import("cypher_memory")
if mem_mod is None:
return []
mem = _get_singleton("memory", lambda: mem_mod.CypherMemory())
if mem is None:
return []
return mem.recall_memory(query, k=k, category=category)
def _t_list_memories(category: str | None = None, limit: int = 50) -> list[dict]:
mem_mod = _lazy_import("cypher_memory")
if mem_mod is None:
return []
mem = _get_singleton("memory", lambda: mem_mod.CypherMemory())
if mem is None:
return []
return mem.list_memories(category=category, limit=limit)
def _t_delete_memory(memory_id: str) -> bool:
mem_mod = _lazy_import("cypher_memory")
if mem_mod is None:
return False
mem = _get_singleton("memory", lambda: mem_mod.CypherMemory())
if mem is None:
return False
return mem.delete_memory(memory_id)
def _t_memory_stats() -> dict:
mem_mod = _lazy_import("cypher_memory")
if mem_mod is None:
return {"error": "memory module unavailable"}
mem = _get_singleton("memory", lambda: mem_mod.CypherMemory())
if mem is None:
return {"error": "init failed"}
return mem.memory_stats()
def _t_clear_category(category: str) -> int:
mem_mod = _lazy_import("cypher_memory")
if mem_mod is None:
return 0
mem = _get_singleton("memory", lambda: mem_mod.CypherMemory())
if mem is None:
return 0
return mem.clear_category(category)
# M5 cypher_rule_validator
def _t_yara_validate(rule_str: str) -> dict:
rv = _lazy_import("cypher_rule_validator")
if rv is None:
return {"valid": None, "error": "rule_validator unavailable", "rule_name": None}
return rv.YaraValidator().validate(rule_str)
def _t_sigma_validate(yaml_str: str) -> dict:
rv = _lazy_import("cypher_rule_validator")
if rv is None:
return {"valid": None, "error": "rule_validator unavailable", "title": None}
return rv.SigmaValidator().validate(yaml_str)
def _t_suricata_validate(rule_str: str) -> dict:
rv = _lazy_import("cypher_rule_validator")
if rv is None:
return {"valid": None, "error": "rule_validator unavailable", "sid": None}
return rv.SuricataValidator().validate(rule_str)
def _t_extract_rules_from_text(text: str) -> dict:
rv = _lazy_import("cypher_rule_validator")
if rv is None:
return {"yara": [], "sigma": [], "suricata": []}
return rv.extract_rules_from_text(text)
# M6 conversation_context_bank
def _t_conv_add_turn(user_msg: str, cypher_response: str,
category: str | None = None) -> bool:
cb = _lazy_import("conversation_context_bank")
if cb is None:
return False
bank = _get_singleton("conv", lambda: cb.ConversationContextBank())
if bank is None:
return False
bank.add_turn(user_msg, cypher_response, category=category)
return True
def _t_conv_get_context(format: str = "compact") -> str:
cb = _lazy_import("conversation_context_bank")
if cb is None:
return ""
bank = _get_singleton("conv", lambda: cb.ConversationContextBank())
if bank is None:
return ""
return bank.get_context(format=format)
def _t_conv_clear() -> bool:
cb = _lazy_import("conversation_context_bank")
if cb is None:
return False
bank = _get_singleton("conv", lambda: cb.ConversationContextBank())
if bank is None:
return False
bank.clear()
return True
# M10 multimodal_stub
def _t_describe_image(image_src: str, prompt: str = "Describe briefly.") -> str:
mm = _lazy_import("multimodal_stub")
if mm is None:
return "[multimodal_stub unavailable]"
backend = "anthropic" if _key_available("ANTHROPIC_API_KEY") else "mock"
stub = _get_singleton(f"mm_{backend}", lambda: mm.MultimodalStub(backend=backend))
if stub is None:
return "[init failed]"
return stub.describe_image(image_src, prompt=prompt)
def _t_is_image_request(prompt: str) -> bool:
mm = _lazy_import("multimodal_stub")
if mm is None:
return False
return mm.is_image_request(prompt)
# ──────────────────────────── REGISTRY ──────────────────────────────
TOOLS_REGISTRY: dict[str, dict] = {
# M2 FamilyBus
"family_pull_recent": {
"fn": _t_family_pull_recent,
"module": "M2",
"desc": "Pull recent broadcasts from aether-family-bus HF dataset",
},
"family_broadcast": {
"fn": _t_family_broadcast,
"module": "M2",
"desc": "Broadcast a CYPHER lesson to the family (async)",
},
"family_stats": {
"fn": _t_family_stats,
"module": "M2",
"desc": "Get FamilyBus cache stats (producers, latest)",
},
# M3 CVE
"cisa_kev_refresh": {
"fn": _t_cisa_kev_refresh,
"module": "M3",
"desc": "Refresh CISA KEV catalog from cisa.gov",
},
"cisa_kev_lookup": {
"fn": _t_cisa_kev_lookup,
"module": "M3",
"desc": "Lookup a CVE in CISA KEV",
},
"nvd_cve_lookup": {
"fn": _t_nvd_cve_lookup,
"module": "M3",
"desc": "Lookup a CVE in NVD API v2 (rate limited)",
},
"get_cve_context": {
"fn": _t_get_cve_context,
"module": "M3",
"desc": "Build [CONTEXT: ...] string for a CVE (KEV + NVD merged)",
},
# M4 Memory
"store_memory": {
"fn": _t_store_memory,
"module": "M4",
"desc": "Persist a memory in chromadb (categories: cve_analysis, trade_postmortem, ioc_confirmed, cybersec_insight, conversation, ecosystem_fact)",
},
"recall_memory": {
"fn": _t_recall_memory,
"module": "M4",
"desc": "Recall top-k memories by similarity (filter by category)",
},
"list_memories": {
"fn": _t_list_memories,
"module": "M4",
"desc": "List stored memories (filter by category)",
},
"delete_memory": {
"fn": _t_delete_memory,
"module": "M4",
"desc": "Delete a memory by ID",
},
"memory_stats": {
"fn": _t_memory_stats,
"module": "M4",
"desc": "Memory storage stats per category",
},
"clear_category": {
"fn": _t_clear_category,
"module": "M4",
"desc": "Clear all memories of a category",
},
# M5 Rule validator
"yara_validate": {
"fn": _t_yara_validate,
"module": "M5",
"desc": "Validate YARA rule via yara-python compile",
},
"sigma_validate": {
"fn": _t_sigma_validate,
"module": "M5",
"desc": "Validate Sigma rule YAML structure",
},
"suricata_validate": {
"fn": _t_suricata_validate,
"module": "M5",
"desc": "Validate Suricata/Snort rule syntax",
},
"extract_rules_from_text": {
"fn": _t_extract_rules_from_text,
"module": "M5",
"desc": "Detect YARA/Sigma/Suricata blocks in arbitrary text",
},
# M6 Conversation
"conv_add_turn": {
"fn": _t_conv_add_turn,
"module": "M6",
"desc": "Append a turn to the 3-window conversation context bank",
},
"conv_get_context": {
"fn": _t_conv_get_context,
"module": "M6",
"desc": "Get formatted conversation context for encoder injection",
},
"conv_clear": {
"fn": _t_conv_clear,
"module": "M6",
"desc": "Clear conversation context window",
},
# M10 Multimodal
"describe_image": {
"fn": _t_describe_image,
"module": "M10",
"desc": "Describe an image via Anthropic/OpenAI vision API",
},
"is_image_request": {
"fn": _t_is_image_request,
"module": "M10",
"desc": "Heuristic: does prompt require image analysis?",
},
}
def call_tool(name: str, **kwargs) -> Any:
"""Invoke a registered tool by name."""
if name not in TOOLS_REGISTRY:
raise KeyError(f"Tool not registered: {name!r}. Available: {sorted(TOOLS_REGISTRY)}")
fn = TOOLS_REGISTRY[name]["fn"]
try:
return fn(**kwargs)
except TypeError as e:
# Caller passed wrong kwargs
sig = inspect.signature(fn)
raise TypeError(
f"call_tool({name!r}, **kwargs) signature mismatch. "
f"Expected: {sig}. Got kwargs={list(kwargs)}. Error: {e}"
) from e
def list_available_tools() -> list[dict]:
"""Return list of tool descriptors."""
out: list[dict] = []
for name, info in TOOLS_REGISTRY.items():
try:
sig = str(inspect.signature(info["fn"]))
except (TypeError, ValueError):
sig = "(?)"
out.append({
"name": name,
"module": info["module"],
"desc": info["desc"],
"signature": sig,
})
return sorted(out, key=lambda x: (x["module"], x["name"]))
def get_tool_signature(name: str) -> str:
if name not in TOOLS_REGISTRY:
return ""
try:
return str(inspect.signature(TOOLS_REGISTRY[name]["fn"]))
except (TypeError, ValueError):
return "(?)"
__all__ = [
"TOOLS_REGISTRY",
"call_tool",
"list_available_tools",
"get_tool_signature",
]
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
print("=== M11 cypher_tools SMOKE TEST ===")
tools = list_available_tools()
print(f"Total tools registered: {len(tools)}")
by_mod: dict[str, list[str]] = {}
for t in tools:
by_mod.setdefault(t["module"], []).append(t["name"])
for mod in sorted(by_mod):
print(f" {mod} ({len(by_mod[mod])} tools): {by_mod[mod]}")
# Test a simple tool: extract_rules_from_text (no IO needed)
rules = call_tool("extract_rules_from_text",
text="rule Test { strings: $a = \"x\" condition: $a }")
print(f"\nextract_rules_from_text: yara={len(rules['yara'])} sigma={len(rules['sigma'])} suricata={len(rules['suricata'])}")
# Test family_stats (no network)
stats = call_tool("family_stats")
print(f"family_stats: {stats}")
# Test invalid kwargs error
try:
call_tool("yara_validate", wrong_kwarg="oops")
except TypeError as e:
print(f"Type error caught as expected: {str(e)[:200]}")
# Test unknown tool
try:
call_tool("nonexistent_tool")
except KeyError as e:
print(f"KeyError caught: {str(e)[:120]}")
# Test get_tool_signature
sig = get_tool_signature("yara_validate")
print(f"yara_validate signature: {sig}")
print("=== SMOKE PASS ===")