Initial Commit

This commit is contained in:
2026-09-15 15:24:03 -04:00
commit 4dc7086e7e
605 changed files with 101380 additions and 0 deletions
+85
View File
@@ -0,0 +1,85 @@
"""Response cache.
Not a performance nicety. Stack Exchange allows 300 calls/day/IP without a
key and GitHub 60/hour, and those budgets are shared by every user of the
assistant at once. Without caching, two people comparing package versions
can spend the day's quota before lunch and every later answer degrades to
"I couldn't check" - or worse, to the model guessing.
Fails open: if valkey is unreachable the tools still work, they are just
uncached. A dead cache should never take the assistant's tools with it.
"""
from __future__ import annotations
import hashlib
import logging
import redis.asyncio as redis
from .settings import settings
log = logging.getLogger(__name__)
_pool: redis.Redis | None = None
# Chosen per-source from how fast the underlying thing actually changes.
TTL = {
"search": 900, # 15m - fresh enough for "latest", cheap to redo
"page": 3600, # 1h - article text rarely changes within a session
"wikipedia": 21600, # 6h
"stackexchange": 21600, # 6h - 300/day keyless makes this the tightest budget
"rfc": 604800, # 7d - a published RFC is immutable
"man": 604800, # 7d
"cve": 10800, # 3h - CVSS scores do get revised
"package": 3600, # 1h - the whole point is catching a new release
"rdap": 86400, # 24h
"dns": 300, # 5m - short on purpose; people debug live changes
"tls": 1800, # 30m
"papers": 86400, # 24h
"datasheet": 604800, # 7d
}
def _key(namespace: str, *parts: str) -> str:
digest = hashlib.sha256("\x1f".join(parts).encode()).hexdigest()[:32]
return f"owuitools:{namespace}:{digest}"
async def get_redis() -> redis.Redis | None:
global _pool
if _pool is None:
try:
_pool = redis.from_url(settings.valkey_url, decode_responses=True, socket_timeout=2)
await _pool.ping()
except Exception as exc:
log.warning("cache unavailable, continuing without it: %s", exc)
_pool = None
return _pool
async def close_redis() -> None:
global _pool
if _pool is not None:
await _pool.aclose()
_pool = None
async def cached(namespace: str, *parts: str) -> str | None:
client = await get_redis()
if client is None:
return None
try:
return await client.get(_key(namespace, *parts))
except Exception:
return None
async def store(namespace: str, value: str, *parts: str) -> None:
client = await get_redis()
if client is None:
return
try:
await client.setex(_key(namespace, *parts), TTL.get(namespace, 3600), value)
except Exception as exc:
log.debug("cache write failed for %s: %s", namespace, exc)