diff --git a/LLMCompress/__init__.py b/LLMCompress/__init__.py new file mode 100644 index 0000000..72dc49f --- /dev/null +++ b/LLMCompress/__init__.py @@ -0,0 +1,23 @@ +"""LLMCompress + +Sandbox for symbolic compression of LLM behavior using: + +- GlyphOS Cognitive Kernel +- Supercharged Glyph Registry +- GlyphOS Event System +""" + +from .llm_adapter import LLMAdapter, LLMResponse +from .compression_report import CompressionReport +from .llm_compressor import ( + compress_interaction, + compress_session, +) + +__all__ = [ + "LLMAdapter", + "LLMResponse", + "CompressionReport", + "compress_interaction", + "compress_session", +] diff --git a/LLMCompress/__pycache__/__init__.cpython-314.pyc b/LLMCompress/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..66c3a13 Binary files /dev/null and b/LLMCompress/__pycache__/__init__.cpython-314.pyc differ diff --git a/LLMCompress/__pycache__/compression_report.cpython-314.pyc b/LLMCompress/__pycache__/compression_report.cpython-314.pyc new file mode 100644 index 0000000..d8ef70f Binary files /dev/null and b/LLMCompress/__pycache__/compression_report.cpython-314.pyc differ diff --git a/LLMCompress/__pycache__/llm_adapter.cpython-314.pyc b/LLMCompress/__pycache__/llm_adapter.cpython-314.pyc new file mode 100644 index 0000000..96d16ac Binary files /dev/null and b/LLMCompress/__pycache__/llm_adapter.cpython-314.pyc differ diff --git a/LLMCompress/__pycache__/llm_compressor.cpython-314.pyc b/LLMCompress/__pycache__/llm_compressor.cpython-314.pyc new file mode 100644 index 0000000..0386fa9 Binary files /dev/null and b/LLMCompress/__pycache__/llm_compressor.cpython-314.pyc differ diff --git a/LLMCompress/compression_report.py b/LLMCompress/compression_report.py new file mode 100644 index 0000000..6988be1 --- /dev/null +++ b/LLMCompress/compression_report.py @@ -0,0 +1,39 @@ +"""Compression report structures for LLMCompress.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any, Dict, List, Optional + + +@dataclass +class CompressionReport: + """Symbolic compression report for a single LLM interaction or session.""" + + # Raw interaction(s) + interactions: List[Dict[str, Any]] = field(default_factory=list) + + # Symbolic outputs from LAIN / GlyphOS + fused_symbol: Optional[Dict[str, Any]] = None + diagnostics: Optional[Dict[str, Any]] = None + cognition_trace: Optional[List[Dict[str, Any]]] = None + + # Glyph-related summaries + glyph_ids: List[str] = field(default_factory=list) + glyph_resonance: Optional[Dict[str, Any]] = None + + # Free-form notes / tags + tags: List[str] = field(default_factory=list) + metadata: Dict[str, Any] = field(default_factory=dict) + + def to_dict(self) -> Dict[str, Any]: + return { + "interactions": self.interactions, + "fused_symbol": self.fused_symbol, + "diagnostics": self.diagnostics, + "cognition_trace": self.cognition_trace, + "glyph_ids": self.glyph_ids, + "glyph_resonance": self.glyph_resonance, + "tags": self.tags, + "metadata": self.metadata, + } diff --git a/LLMCompress/llm_adapter.py b/LLMCompress/llm_adapter.py new file mode 100644 index 0000000..be3b6fa --- /dev/null +++ b/LLMCompress/llm_adapter.py @@ -0,0 +1,92 @@ +"""LLM Adapter + +Thin abstraction over a concrete LLM backend (local or remote). +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Callable, Dict, Optional + + +@dataclass +class LLMResponse: + """Container for a single LLM interaction.""" + + prompt: str + response: str + tokens_prompt: Optional[int] = None + tokens_response: Optional[int] = None + model_name: Optional[str] = None + metadata: Dict[str, Any] = None + + def to_dict(self) -> Dict[str, Any]: + return { + "prompt": self.prompt, + "response": self.response, + "tokens_prompt": self.tokens_prompt, + "tokens_response": self.tokens_response, + "model_name": self.model_name, + "metadata": self.metadata or {}, + } + + +class LLMAdapter: + """Adapter around a concrete LLM backend. + + backend: Callable that takes (prompt, **kwargs) and returns: + - str + - or dict with keys like: + - response + - tokens_prompt + - tokens_response + - model_name + """ + + def __init__( + self, + backend: Callable[..., Any], + model_name: Optional[str] = None, + ) -> None: + self._backend = backend + self._model_name = model_name or "unknown" + + def run(self, prompt: str, **kwargs: Any) -> LLMResponse: + """Run the underlying LLM on a prompt and normalize the result.""" + raw = self._backend(prompt, **kwargs) + + if isinstance(raw, str): + return LLMResponse( + prompt=prompt, + response=raw, + model_name=self._model_name, + metadata={}, + ) + + if isinstance(raw, dict): + return LLMResponse( + prompt=prompt, + response=str(raw.get("response", "")), + tokens_prompt=raw.get("tokens_prompt"), + tokens_response=raw.get("tokens_response"), + model_name=raw.get("model_name", self._model_name), + metadata={ + k: v + for k, v in raw.items() + if k + not in { + "response", + "tokens_prompt", + "tokens_response", + "model_name", + } + }, + ) + + # Fallback: best-effort stringification + return LLMResponse( + prompt=prompt, + response=str(raw), + model_name=self._model_name, + metadata={"raw_type": type(raw).__name__}, + ) diff --git a/LLMCompress/llm_compressor.py b/LLMCompress/llm_compressor.py new file mode 100644 index 0000000..d77570c --- /dev/null +++ b/LLMCompress/llm_compressor.py @@ -0,0 +1,186 @@ +"""LLM symbolic compressor. + +Feeds LLM interactions through the GlyphOS Cognitive Kernel and produces +a symbolic CompressionReport. +""" + +from __future__ import annotations + +import json +from typing import Any, Dict, List, Optional + +from glyphos.cognitive_kernel import get_kernel +from glyphos.events import emit + +from .llm_adapter import LLMAdapter, LLMResponse +from .compression_report import CompressionReport + + +def _interaction_to_payload(interaction: LLMResponse) -> bytes: + """Serialize a single interaction into a payload for symbolic analysis.""" + data = interaction.to_dict() + return json.dumps(data, ensure_ascii=False, sort_keys=True).encode("utf-8") + + +def compress_interaction( + adapter: LLMAdapter, + prompt: str, + *, + mode: str = "analyze", + context: Optional[Dict[str, Any]] = None, + **llm_kwargs: Any, +) -> CompressionReport: + """Run a single LLM interaction through the symbolic stack.""" + interaction = adapter.run(prompt, **llm_kwargs) + + emit( + "cognition.started", + { + "source": "LLMCompress", + "mode": mode, + "prompt_preview": prompt[:120], + }, + ) + + manifest: Dict[str, Any] = { + "type": "llm_interaction", + "source": "LLMCompress", + "model_name": interaction.model_name, + } + segments: List[Dict[str, Any]] = [] + payload: bytes = _interaction_to_payload(interaction) + + kernel = get_kernel() + exec_context: Dict[str, Any] = context.copy() if context else {} + exec_context.setdefault("source", "LLMCompress") + exec_context.setdefault("interaction_type", "single") + + result = kernel.execute_symbolic( + manifest=manifest, + segments=segments, + payload=payload, + mode=mode, + context=exec_context, + ) + + fused_symbol = result.get("fused_symbol", {}) + diagnostics = result.get("diagnostics", {}) + cognition_trace = result.get("cognition_trace", []) + + glyph_res = diagnostics.get("glyph_resonance") or {} + glyph_ids: List[str] = [] + if isinstance(glyph_res, dict): + gid = glyph_res.get("glyph_id") + if isinstance(gid, str): + glyph_ids.append(gid) + + report = CompressionReport( + interactions=[interaction.to_dict()], + fused_symbol=fused_symbol, + diagnostics=diagnostics, + cognition_trace=cognition_trace, + glyph_ids=glyph_ids, + glyph_resonance=glyph_res or None, + tags=["llm_compress", mode], + metadata={"model_name": interaction.model_name}, + ) + + emit( + "cognition.completed", + { + "source": "LLMCompress", + "mode": mode, + "model_name": interaction.model_name, + "glyph_resonance": glyph_res or None, + "summary": fused_symbol.get("summary"), + }, + ) + + return report + + +def compress_session( + adapter: LLMAdapter, + prompts: List[str], + *, + mode: str = "analyze", + context: Optional[Dict[str, Any]] = None, + **llm_kwargs: Any, +) -> CompressionReport: + """Compress a multi-turn LLM session into a single symbolic report.""" + interactions = [adapter.run(p, **llm_kwargs) for p in prompts] + + session_data = [i.to_dict() for i in interactions] + payload = json.dumps( + {"session": session_data}, + ensure_ascii=False, + sort_keys=True, + ).encode("utf-8") + + manifest: Dict[str, Any] = { + "type": "llm_session", + "source": "LLMCompress", + "turns": len(interactions), + "model_name": interactions[0].model_name if interactions else None, + } + segments: List[Dict[str, Any]] = [] + + kernel = get_kernel() + exec_context: Dict[str, Any] = context.copy() if context else {} + exec_context.setdefault("source", "LLMCompress") + exec_context.setdefault("interaction_type", "session") + + emit( + "cognition.started", + { + "source": "LLMCompress", + "mode": mode, + "turns": len(interactions), + }, + ) + + result = kernel.execute_symbolic( + manifest=manifest, + segments=segments, + payload=payload, + mode=mode, + context=exec_context, + ) + + fused_symbol = result.get("fused_symbol", {}) + diagnostics = result.get("diagnostics", {}) + cognition_trace = result.get("cognition_trace", []) + + glyph_res = diagnostics.get("glyph_resonance") or {} + glyph_ids: List[str] = [] + if isinstance(glyph_res, dict): + gid = glyph_res.get("glyph_id") + if isinstance(gid, str): + glyph_ids.append(gid) + + report = CompressionReport( + interactions=session_data, + fused_symbol=fused_symbol, + diagnostics=diagnostics, + cognition_trace=cognition_trace, + glyph_ids=glyph_ids, + glyph_resonance=glyph_res or None, + tags=["llm_compress", "session", mode], + metadata={ + "model_name": manifest.get("model_name"), + "turns": len(interactions), + }, + ) + + emit( + "cognition.completed", + { + "source": "LLMCompress", + "mode": mode, + "turns": len(interactions), + "glyph_resonance": glyph_res or None, + "summary": fused_symbol.get("summary"), + }, + ) + + return report diff --git a/LLMCompress/tests/__init__.py b/LLMCompress/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/LLMCompress/tests/__pycache__/__init__.cpython-314.pyc b/LLMCompress/tests/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..8fa14bb Binary files /dev/null and b/LLMCompress/tests/__pycache__/__init__.cpython-314.pyc differ diff --git a/LLMCompress/tests/__pycache__/test_llm_compress.cpython-314.pyc b/LLMCompress/tests/__pycache__/test_llm_compress.cpython-314.pyc new file mode 100644 index 0000000..71122f8 Binary files /dev/null and b/LLMCompress/tests/__pycache__/test_llm_compress.cpython-314.pyc differ diff --git a/LLMCompress/tests/test_llm_compress.py b/LLMCompress/tests/test_llm_compress.py new file mode 100644 index 0000000..a8f6363 --- /dev/null +++ b/LLMCompress/tests/test_llm_compress.py @@ -0,0 +1,133 @@ +"""Tests for LLMCompress subsystem. + +These tests verify: + +- LLMAdapter normalization +- compress_interaction() end-to-end flow +- compress_session() multi-turn flow +- Integration with GlyphOS Cognitive Kernel +- Event emission during compression + +This uses a fake LLM backend so tests run without external dependencies. +""" + +from typing import Any, Dict + +from LLMCompress import ( + LLMAdapter, + compress_interaction, + compress_session, + CompressionReport, +) + +from glyphos.events import get_event_bus +from glyphos.cognitive_kernel import get_kernel # noqa: F401 # imported to ensure availability + + +# --------------------------------------------------------------------------- +# Fake LLM backend +# --------------------------------------------------------------------------- + +def fake_llm_backend(prompt: str, **kwargs: Any) -> Dict[str, Any]: + """A deterministic fake LLM backend for testing.""" + return { + "response": f"FAKE_RESPONSE({prompt})", + "tokens_prompt": len(prompt.split()), + "tokens_response": 3, + "model_name": "fake-llm-test", + "extra_info": "ok", + } + + +# --------------------------------------------------------------------------- +# Tests +# --------------------------------------------------------------------------- + +def test_adapter_normalization(): + adapter = LLMAdapter(fake_llm_backend, model_name="fake-llm-test") + out = adapter.run("hello world") + + assert out.prompt == "hello world" + assert out.response.startswith("FAKE_RESPONSE") + assert out.tokens_prompt == 2 + assert out.tokens_response == 3 + assert out.model_name == "fake-llm-test" + assert isinstance(out.metadata, dict) + + +def test_compress_interaction_basic(): + adapter = LLMAdapter(fake_llm_backend, model_name="fake-llm-test") + + bus = get_event_bus() + bus.clear_history() + + report = compress_interaction(adapter, "hello test") + + # Report structure + assert isinstance(report, CompressionReport) + assert len(report.interactions) == 1 + assert "prompt" in report.interactions[0] + assert "response" in report.interactions[0] + + # Kernel output + assert isinstance(report.fused_symbol, dict) + assert isinstance(report.diagnostics, dict) + + # Events fired + history = bus.get_history(limit=10) + types = [e["type"] for e in history] + + assert "cognition.started" in types + assert "cognition.completed" in types + + +def test_compress_session_multi_turn(): + adapter = LLMAdapter(fake_llm_backend, model_name="fake-llm-test") + + bus = get_event_bus() + bus.clear_history() + + prompts = ["turn one", "turn two", "turn three"] + report = compress_session(adapter, prompts) + + assert isinstance(report, CompressionReport) + assert len(report.interactions) == 3 + + # Kernel output + assert isinstance(report.fused_symbol, dict) + assert isinstance(report.diagnostics, dict) + + # Events fired + history = bus.get_history(limit=10) + types = [e["type"] for e in history] + + assert "cognition.started" in types + assert "cognition.completed" in types + + +def test_payload_encoding_is_valid_json(): + adapter = LLMAdapter(fake_llm_backend) + report = compress_interaction(adapter, "encode me") + + # Ensure payload was JSON-serializable and processed by kernel + assert isinstance(report.fused_symbol, dict) + assert isinstance(report.diagnostics, dict) + + +def test_event_metadata_includes_source(): + adapter = LLMAdapter(fake_llm_backend) + + bus = get_event_bus() + bus.clear_history() + + compress_interaction(adapter, "metadata test") + + events = bus.get_history(limit=10) + found = False + for e in events: + if e["type"] == "cognition.started": + assert e["payload"]["source"] == "LLMCompress" + found = True + break + + assert found, "Expected cognition.started event with source=LLMCompress"