● Zion Boggan
repos/Darwin/cortex/adapters/rag.py
zionboggan.com ↗
91 lines · python
History for this file →
1
"""homelab-rag adapter - queries the Darwin RAG bridge (port 9875) directly.
2
 
3
The RAG bridge is a lightweight HTTP service on CT215 that queries the
4
homelab-rag SQLite FTS index directly, bypassing the Node.js MCP SSE shim
5
which takes 70-80s per call due to per-call Node.js process spawning + CIFS reads.
6
 
7
Bridge endpoint: GET http://REDACTED-IP:9875/search?q=QUERY&limit=N
8
Returns: JSON array in the same shape as homelab_search results.
9
 
10
Authority tier: recent-file (homelab-rag indexes files with mtime).
11
"""
12
from __future__ import annotations
13
import os
14
 
15
from datetime import datetime, timezone
16
 
17
import httpx
18
 
19
from models import Hit
20
from adapters.base import BaseAdapter
21
 
22
RAG_BRIDGE_URL = os.environ.get("RAG_BRIDGE_URL", "http://REDACTED-IP:9875")
23
RECENT_CUTOFF_DAYS = 30
24
 
25
 
26
def _parse_dt(val) -> datetime | None:
27
    if not val:
28
        return None
29
    try:
30
        if isinstance(val, (int, float)):
31
            ts = val / 1000.0 if val > 1e10 else float(val)
32
            return datetime.fromtimestamp(ts, tz=timezone.utc)
33
        return datetime.fromisoformat(str(val).replace("Z", "+00:00"))
34
    except Exception:
35
        return None
36
 
37
 
38
def _authority_from_mtime(dt: datetime | None) -> str:
39
    if dt is None:
40
        return "older-note"
41
    age = (datetime.now(timezone.utc) - dt).days
42
    return "recent-file" if age <= RECENT_CUTOFF_DAYS else "older-note"
43
 
44
 
45
def _parse_bridge_results(results: list[dict]) -> list[Hit]:
46
    """Convert bridge response (list of result dicts) to Hit objects."""
47
    hits = []
48
    for r in results:
49
        if not isinstance(r, dict):
50
            continue
51
        mtime = _parse_dt(r.get("mtimeMs") or r.get("mtime") or r.get("last_seen"))
52
        snippet = r.get("text") or r.get("snippet") or r.get("content", "")
53
        hits.append(Hit(
54
            engine="rag",
55
            title=r.get("title") or r.get("relPath", "")[:80],
56
            path=r.get("path") or r.get("relPath", ""),
57
            snippet=str(snippet)[:400],
58
            score=float(r.get("score") or 0.5),
59
            last_seen=mtime,
60
            confidence=0.8,
61
            authority_tier=_authority_from_mtime(mtime),
62
        ))
63
    return hits
64
 
65
 
66
class RagAdapter(BaseAdapter):
67
    engine = "rag"
68
    authority_tier = "recent-file"
69
 
70
    async def _fetch(self, query: str, k: int) -> list[Hit]:
71
        async with httpx.AsyncClient(timeout=5.0) as client:
72
            resp = await client.get(
73
                f"{RAG_BRIDGE_URL}/search",
74
                params={"q": query, "limit": k},
75
            )
76
            resp.raise_for_status()
77
            results = resp.json()
78
 
79
        if not isinstance(results, list):
80
            return []
81
 
82
        hits = _parse_bridge_results(results)
83
 
84
        seen: set[str] = set()
85
        deduped = []
86
        for h in hits:
87
            key = h.path or h.snippet[:50]
88
            if key not in seen:
89
                seen.add(key)
90
                deduped.append(h)
91
        return deduped[:k]