Skip to content

Commit 38f0b99

Browse files
author
staradigm
committed
Add utility smoke coverage and response cache
1 parent 230c452 commit 38f0b99

9 files changed

Lines changed: 255 additions & 9 deletions

File tree

config/includes.chroot/usr/local/bin/neuros-draw

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,6 @@ def main():
349349

350350
sub = parser.add_subparsers(dest="subcommand", help="Subcommands")
351351

352-
sub.add_parser("gallery", aliases=["list", "ls"], help="Browse generated images")
353352
p_gal = sub.add_parser("gallery", aliases=["list", "ls"], help="Browse generated images")
354353
p_gal.add_argument("--open", "-o", action="store_true", help="Open latest image")
355354

config/includes.chroot/usr/local/bin/neuros-edit

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ def main():
378378
parser.add_argument("--diff", action="store_true", help="Show diff from backup")
379379
parser.add_argument("--batch", help="Batch edit with glob pattern")
380380
parser.add_argument("--history", "-H", action="store_true", help="Show edit history")
381-
parser.add_argument("-n", type=int, default=10, help="Number of history entries (default: 10)")
381+
parser.add_argument("--number", type=int, default=10, help="Number of history entries (default: 10)")
382382

383383
args = parser.parse_args()
384384

config/includes.chroot/usr/local/bin/neuros-firstboot

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55

66
set -e
77

8+
if [ "$#" -gt 0 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; then
9+
sed -n '2,/^$/p' "$0"
10+
exit 0
11+
fi
12+
813
MARKER="$HOME/.config/neuros/.firstboot-done"
914
CONFIG_DIR="$HOME/.config/neuros"
1015
SKEL_CONTINUE="/etc/skel/.continue/config.json"

config/includes.chroot/usr/local/bin/neuros-tray

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,21 @@ Shows model status, RAM usage, and provides quick AI access from the GNOME panel
66
Requires: python3-gi, gir1.2-gtk-3.0, gir1.2-appindicator3-0.1
77
"""
88

9-
import gi
10-
gi.require_version('Gtk', '3.0')
11-
gi.require_version('AppIndicator3', '0.1')
12-
from gi.repository import Gtk, AppIndicator3, GLib
139
import subprocess
1410
import json
1511
import os
12+
import sys
1613
import threading
1714

15+
if any(arg in ("-h", "--help") for arg in sys.argv[1:]):
16+
print(__doc__.strip())
17+
sys.exit(0)
18+
19+
import gi
20+
gi.require_version('Gtk', '3.0')
21+
gi.require_version('AppIndicator3', '0.1')
22+
from gi.repository import Gtk, AppIndicator3, GLib
23+
1824
APP_ID = "neuros-tray"
1925
CONFIG_PATH = os.path.expanduser("~/.config/neuros/llm.conf")
2026
OLLAMA_URL = "http://localhost:11434"

config/includes.chroot/usr/local/bin/neuros-welcome

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import sys
88
import os
99
import subprocess
1010

11+
if any(arg in ("-h", "--help") for arg in sys.argv[1:]):
12+
print(__doc__.strip())
13+
sys.exit(0)
14+
1115
try:
1216
import gi
1317
gi.require_version('Gtk', '3.0')

config/includes.chroot/usr/local/bin/neuroslib.py

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,17 @@
1717
import shlex
1818
import subprocess
1919
import sys
20+
import tempfile
2021
import time
2122
from datetime import datetime
2223

2324
# === Paths ===
2425
CONFIG_DIR = os.path.expanduser("~/.config/neuros")
2526
LLM_CONF = os.path.join(CONFIG_DIR, "llm.conf")
27+
RESPONSE_CACHE_PATH = os.path.expanduser(
28+
os.getenv("NEUROS_RESPONSE_CACHE", "~/.cache/neuros/responses.json")
29+
)
30+
RESPONSE_CACHE_LIMIT = 256
2631

2732

2833
# ═══════════════════════════════════════════════════════════════════════
@@ -50,24 +55,74 @@ def load_config():
5055
# ═══════════════════════════════════════════════════════════════════════
5156

5257
def query_llm(prompt, system="You are a helpful AI assistant.", timeout=120):
53-
"""Query the local Ollama instance. Returns response string or None."""
58+
"""Query Ollama, reusing successful responses for the same model/input."""
5459
config = load_config()
60+
model = config["model"]
61+
cache_key = _response_cache_key(model, prompt, system)
62+
cached = _read_response_cache().get(cache_key)
63+
if cached is not None:
64+
return cached
5565
try:
5666
import urllib.request
5767
url = f"http://{config['host']}:{config['port']}/api/generate"
5868
data = json.dumps({
59-
"model": config["model"],
69+
"model": model,
6070
"prompt": prompt,
6171
"system": system,
6272
"stream": False,
6373
}).encode()
6474
req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"})
6575
with urllib.request.urlopen(req, timeout=timeout) as resp:
66-
return json.loads(resp.read()).get("response", "").strip()
76+
response = json.loads(resp.read()).get("response", "").strip()
77+
if response:
78+
_write_response_cache(cache_key, response)
79+
return response
6780
except Exception:
6881
return None
6982

7083

84+
def _response_cache_key(model, prompt, system):
85+
"""Return a stable key; system is included to prevent prompt collisions."""
86+
payload = json.dumps(
87+
{"model": model, "prompt": prompt, "system": system},
88+
sort_keys=True,
89+
ensure_ascii=False,
90+
).encode("utf-8")
91+
return hashlib.sha256(payload).hexdigest()
92+
93+
94+
def _read_response_cache():
95+
try:
96+
with open(RESPONSE_CACHE_PATH, encoding="utf-8") as f:
97+
data = json.load(f)
98+
return data if isinstance(data, dict) else {}
99+
except (OSError, json.JSONDecodeError, TypeError):
100+
return {}
101+
102+
103+
def _write_response_cache(key, response):
104+
cache = _read_response_cache()
105+
cache[key] = response
106+
if len(cache) > RESPONSE_CACHE_LIMIT:
107+
cache = dict(list(cache.items())[-RESPONSE_CACHE_LIMIT:])
108+
try:
109+
parent = os.path.dirname(RESPONSE_CACHE_PATH) or "."
110+
os.makedirs(parent, exist_ok=True)
111+
fd, tmp_path = tempfile.mkstemp(prefix="responses-", dir=parent, text=True)
112+
try:
113+
with os.fdopen(fd, "w", encoding="utf-8") as f:
114+
json.dump(cache, f, ensure_ascii=False)
115+
os.replace(tmp_path, RESPONSE_CACHE_PATH)
116+
except Exception:
117+
try:
118+
os.unlink(tmp_path)
119+
except OSError:
120+
pass
121+
raise
122+
except OSError:
123+
pass
124+
125+
71126
def query_llm_stream(prompt, system="You are a helpful AI assistant.", timeout=120):
72127
"""Query Ollama with streaming response. Yields tokens."""
73128
config = load_config()

tests/benchmark_cache.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""Reproducible in-process benchmark for the response cache."""
2+
3+
import importlib.util
4+
import os
5+
import pathlib
6+
import tempfile
7+
import time
8+
from importlib.machinery import SourceFileLoader
9+
from unittest.mock import patch
10+
11+
12+
ROOT = pathlib.Path(__file__).resolve().parents[1]
13+
LIBRARY = ROOT / "config/includes.chroot/usr/local/bin/neuroslib.py"
14+
15+
16+
class Response:
17+
def __enter__(self):
18+
return self
19+
20+
def __exit__(self, *args):
21+
return False
22+
23+
def read(self):
24+
return b'{"response":"benchmark response"}'
25+
26+
27+
def main():
28+
loader = SourceFileLoader("neuroslib_benchmark", str(LIBRARY))
29+
spec = importlib.util.spec_from_loader(loader.name, loader)
30+
module = importlib.util.module_from_spec(spec)
31+
loader.exec_module(module)
32+
with tempfile.TemporaryDirectory() as tmp:
33+
module.RESPONSE_CACHE_PATH = os.path.join(tmp, "responses.json")
34+
config = {"model": "mistral", "host": "localhost", "port": "11434"}
35+
with patch.object(module, "load_config", return_value=config), \
36+
patch("urllib.request.urlopen", return_value=Response()) as request:
37+
start = time.perf_counter()
38+
module.query_llm("benchmark prompt")
39+
cold = time.perf_counter() - start
40+
start = time.perf_counter()
41+
module.query_llm("benchmark prompt")
42+
warm = time.perf_counter() - start
43+
print(f"cold_seconds={cold:.9f}")
44+
print(f"cached_seconds={warm:.9f}")
45+
print(f"network_calls={request.call_count}")
46+
print(f"speedup={cold / warm:.2f}x")
47+
48+
49+
if __name__ == "__main__":
50+
main()

tests/test_cache.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""Tests for the shared NeurOS response cache."""
2+
3+
import importlib.util
4+
import json
5+
import os
6+
import pathlib
7+
import tempfile
8+
import unittest
9+
from importlib.machinery import SourceFileLoader
10+
from unittest.mock import patch
11+
12+
13+
LIBRARY_PATH = pathlib.Path(__file__).resolve().parents[1] / "config/includes.chroot/usr/local/bin/neuroslib.py"
14+
15+
16+
def load_library():
17+
loader = SourceFileLoader("neuroslib_cache_test", str(LIBRARY_PATH))
18+
spec = importlib.util.spec_from_loader(loader.name, loader)
19+
module = importlib.util.module_from_spec(spec)
20+
loader.exec_module(module)
21+
return module
22+
23+
24+
class FakeResponse:
25+
def __init__(self, payload):
26+
self.payload = json.dumps(payload).encode()
27+
28+
def __enter__(self):
29+
return self
30+
31+
def __exit__(self, *args):
32+
return False
33+
34+
def read(self):
35+
return self.payload
36+
37+
38+
class ResponseCacheTests(unittest.TestCase):
39+
def test_same_prompt_and_model_uses_cached_response(self):
40+
module = load_library()
41+
with tempfile.TemporaryDirectory() as tmp:
42+
module.RESPONSE_CACHE_PATH = os.path.join(tmp, "responses.json")
43+
with patch.object(module, "load_config", return_value={"model": "mistral", "host": "localhost", "port": "11434"}), \
44+
patch("urllib.request.urlopen", return_value=FakeResponse({"response": "cached"})) as request:
45+
self.assertEqual(module.query_llm("same"), "cached")
46+
self.assertEqual(module.query_llm("same"), "cached")
47+
self.assertEqual(request.call_count, 1)
48+
49+
def test_model_is_part_of_cache_key(self):
50+
module = load_library()
51+
with tempfile.TemporaryDirectory() as tmp:
52+
module.RESPONSE_CACHE_PATH = os.path.join(tmp, "responses.json")
53+
configs = iter([
54+
{"model": "mistral", "host": "localhost", "port": "11434"},
55+
{"model": "llama3", "host": "localhost", "port": "11434"},
56+
])
57+
with patch.object(module, "load_config", side_effect=lambda: next(configs)), \
58+
patch("urllib.request.urlopen", side_effect=[FakeResponse({"response": "one"}), FakeResponse({"response": "two"})]) as request:
59+
self.assertEqual(module.query_llm("same"), "one")
60+
self.assertEqual(module.query_llm("same"), "two")
61+
self.assertEqual(request.call_count, 2)
62+
63+
def test_cache_write_is_json_and_failure_is_not_cached(self):
64+
module = load_library()
65+
with tempfile.TemporaryDirectory() as tmp:
66+
module.RESPONSE_CACHE_PATH = os.path.join(tmp, "responses.json")
67+
with patch.object(module, "load_config", return_value={"model": "mistral", "host": "localhost", "port": "11434"}), \
68+
patch("urllib.request.urlopen", return_value=FakeResponse({"response": ""})) as request:
69+
self.assertEqual(module.query_llm("empty"), "")
70+
self.assertEqual(module.query_llm("empty"), "")
71+
self.assertEqual(request.call_count, 2)
72+
self.assertFalse(os.path.exists(module.RESPONSE_CACHE_PATH))
73+
74+
75+
if __name__ == "__main__":
76+
unittest.main()

tests/test_utilities.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""Smoke-test every executable installed by the NeurOS tools hook."""
2+
3+
import ast
4+
import os
5+
import pathlib
6+
import subprocess
7+
import unittest
8+
9+
10+
ROOT = pathlib.Path(__file__).resolve().parents[1]
11+
TOOLS = ROOT / "config" / "includes.chroot" / "usr" / "local" / "bin"
12+
ENTRYPOINTS = sorted(
13+
path for path in TOOLS.iterdir()
14+
if path.is_file() and os.access(path, os.X_OK)
15+
)
16+
17+
18+
class UtilitySmokeTests(unittest.TestCase):
19+
def test_entrypoint_inventory_is_complete(self):
20+
self.assertEqual(len(ENTRYPOINTS), 80)
21+
22+
def test_python_and_shell_sources_parse(self):
23+
for path in ENTRYPOINTS:
24+
with self.subTest(path=path.name):
25+
first_line = path.read_bytes().splitlines()[0]
26+
if b"python" in first_line:
27+
ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
28+
elif b"sh" in first_line or b"bash" in first_line:
29+
result = subprocess.run(["bash", "-n", str(path)], capture_output=True, text=True)
30+
self.assertEqual(result.returncode, 0, result.stderr)
31+
else:
32+
self.fail(f"missing recognized shebang: {path.name}")
33+
34+
def test_help_for_every_entrypoint(self):
35+
failures = []
36+
for path in ENTRYPOINTS:
37+
with self.subTest(path=path.name):
38+
result = subprocess.run(
39+
[str(path), "--help"],
40+
cwd=ROOT,
41+
capture_output=True,
42+
text=True,
43+
timeout=10,
44+
)
45+
if result.returncode != 0:
46+
failures.append(f"{path.name}: rc={result.returncode}: {(result.stderr or result.stdout).strip()}")
47+
self.assertFalse(failures, "\n".join(failures))
48+
49+
50+
if __name__ == "__main__":
51+
unittest.main()

0 commit comments

Comments
 (0)