Developer-Architecture.md: - Module layout table (eviction.py, amqp.py, routers/) - External services table with ports + purposes - Config discovery / env var mapping - Chat pipeline includes upload_context injection + logprobs/perplexity - RAG eviction engine design (score formula, hysteresis, pinned sources) - AMQP cluster architecture overview (jc.admin / jc.system exchanges) - SSE protocol reference - Full test coverage table (132 tests across 18 files) - Hardware self-assessment section current-wip.md: - Replace stale backlog with active Roadmap N table (Tasks 8-15) - Add post-Roadmap-N backlog (B1-B3)
10 KiB
Developer Architecture Guide
This document explains how JarvisChat is structured, the external services it integrates with, and the key architectural changes made during development.
1. System Overview
JarvisChat is a single-process FastAPI service with a Jinja2 frontend and SQLite persistence. It connects to an external llama-server for inference and optionally to SearXNG (web search), Qdrant (vector search), and RabbitMQ (AMQP cluster messaging).
1.1 Module Layout
Refactored from single-file (app.py) into modules under project root:
| File | Role |
|---|---|
app.py |
FastAPI app, middleware, router registration, lifespan |
config.py |
Constants, env vars, rate/payload limits, built-in skills registry, upload limits, RAG eviction config |
db.py |
SQLite schema, connection factory, settings helpers, upload_context CRUD |
auth.py |
PIN-based guest/admin sessions, auth routes |
security.py |
Rate limiting, origin checks, IP allowlist, audit/incident logging |
memory.py |
FTS5 memory CRUD, remember/forget command parsing |
search.py |
SearXNG integration, perplexity scoring, refusal detection |
rag.py |
Qdrant vector search, system prompt assembly, chunk_text() helper, collection stats |
eviction.py |
Score-based RAG eviction engine (extracted from rag.py) |
gpu.py |
AMD GPU stats via rocm-smi |
amqp.py |
(WIP) aio-pika connection manager for RabbitMQ |
routers/ |
One module per endpoint group |
1.2 External Services
| Service | Required | Port | Purpose |
|---|---|---|---|
| llama-server (ultron) | Yes | 8081 | LLM inference (OpenAI-compat), RPC offload to jarvis:50052 |
| SearXNG | No | 8888 | Privacy-respecting web search |
| Qdrant (ultron) | No | 6333 | Vector database for RAG |
| Ollama (jarvis) | No | 11434 | Embeddings for RAG chunk vectors |
| RabbitMQ (ultron) | No | 5672 | AMQP broker for cluster messaging |
| rocm-smi | No | — | AMD GPU stats (host-level) |
1.3 Config Discovery
Key base URLs are configured via environment variables with sensible defaults:
| Variable | Default | Service |
|---|---|---|
LLAMA_SERVER_BASE |
http://192.168.50.108:8081 |
llama-server on ultron |
OLLAMA_BASE |
http://localhost:11434 |
Legacy — all inference goes through LLAMA_SERVER_BASE |
SEARXNG_BASE |
http://localhost:8888 |
SearXNG |
QDRANT_URL |
http://192.168.50.108:6333 |
Qdrant on ultron |
JARVISCHAT_AMQP_URL |
amqp://jarvischat:password@localhost:5672/jarvischat |
RabbitMQ |
2. Request/Response Architecture
2.1 Chat Pipeline (/api/chat)
- Validate session, role, origin, rate, and payload limits in middleware
- Intercept "remember that..." / "forget about..." commands → process_remember_command()
- Persist user message and conversation metadata
- Build system prompt: profile + FTS5 memory + Qdrant RAG results + preset + active skills + uploaded document (if upload_context_id)
- Stream from llama-server with
logprobs: truefor perplexity scoring - If perplexity > 15.0 OR refusal patterns match → re-query with SearXNG results
- Persist final assistant message and emit terminal SSE event
2.2 Explicit Search Pipeline (/api/search)
- Persist search-as-message into conversation
- Emit
searchingSSE event - Pull web results from SearXNG
- Summarize via llama-server SSE stream
- Persist summary and emit
doneevent
2.3 RAG Ingest Pipeline (/api/ingest)
- Bearer token auth (same key as completions API)
- Chunk text via shared
chunk_text()helper (512-token chunks, 128-token overlap) - Embed via Ollama
/api/embeddings - Upsert to Qdrant collection
jarvis_rag - Trigger
maybe_evict()if collection exceeds high-water mark
2.4 Upload Pipeline (/api/upload)
- Admin required, multipart file upload
- Validate MIME type + size against config limits
- PDF text extraction via pypdf; plain text for all other types
- Three modes:
context(SQLite with 1hr expiry),ingest(RAG/Qdrant),both - Trigger
maybe_evict()if ingest mode
3. Data Model (SQLite)
Key tables:
conversations— headers, timestamps, attachment_countmessages— ordered chat history per conversationprofile— singleton row for injected profile promptsettings— runtime toggles and selected defaultssystem_presets— named reusable system promptsskills— per-skill enabled state and timestampmemories(FTS5 virtual table) — full-text searchable user memory factsupload_context— auto-expiring document storage for context injection
Design notes:
- Startup is idempotent: tables created if missing, defaults seeded only when absent
- No connection pool: each request opens and closes a short-lived SQLite connection
init_db()called in FastAPI lifespan
4. Security Implementations
4.1 Auth Model
- Guest session by default (POST /api/auth/guest)
- Admin unlock via 4-digit PIN (POST /api/auth/login)
- Admin required for PUT/DELETE/PATCH + all POST except allowlist (/api/chat, /api/search, /api/auth/*)
- /api/ingest is exempt from session auth — self-authenticates via Bearer token
- Session heartbeat/timeout (90s default) and explicit logout
4.2 PIN Hardening
- Admin PIN hashed with PBKDF2-HMAC-SHA256 + salt
- Failed PIN attempts tracked per client IP (max 5, 300s lockout)
- Default PIN allowed only if JARVISCHAT_ALLOW_DEFAULT_PIN=true
4.3 Browser and API Abuse Controls
- Origin checks on all /api/ requests (rejects absent Origin AND Referer)
- Rate limiting per endpoint category and identity (IP/session)
- Payload size limits per route class (64KB default, 128KB chat, 20MB upload)
- Settings key allowlist (5 keys: profile_enabled, default_model, etc.)
- IP allowlist/CIDR gate with trusted proxy forwarding mode
4.4 Output and Error Safety
- Search result URLs sanitized to http/https only
- Client-safe error envelopes with incident key correlation
- Full stack traces logged server-side only
4.5 Operational Auditability
- Structured audit events for auth actions, admin ops, guardrail denials
- Incident logs with event type, key, path/method, and runtime metadata
5. RAG Architecture
5.1 Vector Search
- Qdrant collection
jarvis_ragon ultron:6333 - Embeddings via Ollama on jarvis:11434 (
/api/embeddings) - Shared
chunk_text(text, chunk_size=512, overlap=128)helper in rag.py - Upload and ingest endpoints share the same chunk+embed+upsert pipeline
5.2 Score-Based Eviction
When RAG_MAX_VECTORS is exceeded, eviction fires with hysteresis:
- High-water mark: 80% of max → trigger eviction
- Low-water mark: 20% of max → stop eviction
- Batch size: 1000 vectors per cycle
- Score formula:
score = (access_weight * retrieval_count) + (age_weight * hours_since_ingested) - Lower score evicted first (least useful)
- Tiebreaker: oldest last_accessed ASC
- Excluded sources:
upload,profile(pinned) - Grace period: 1 hour before any vector is eligible
- Thread-safe via
asyncio.Lock
Eviction module at eviction.py (re-exported through rag.py for backward compat).
5.3 Operational Stats
GET /api/rag/stats (admin required) returns:
- vector_count, max_vectors, high_water_pct, low_water_pct, percent_full
- pinned_sources list, grace_hours
- at_risk_count, pinned_count, avg_retrieval_count
- eviction_counts_last_{1,5,30}m
5.4 Flush
POST /api/rag/flush (admin required) — deletes all non-pinned vectors. Returns {deleted_count, collection, status}.
6. AMQP Cluster Architecture (WIP)
RabbitMQ on ultron with dedicated jarvischat vhost:
| Exchange | Type | Purpose |
|---|---|---|
jc.admin |
topic | Commands: swap model, shutdown, heartbeat request |
jc.system |
topic | Events: model_ready, model_failed, heartbeat, registration |
Pending implementation (Tasks 10–15):
amqp.py— aio-pika connection manager with reconnect- Node agent on jarvis — registration, heartbeat, command consumer
triage.py— Phi-4-mini query classification (general/code/search/rag)- Dynamic model swap via llama-server RPC
7. SSE Protocol
All streaming endpoints yield data: {json}\n\n:
{token, conversation_id}— streaming token{searching: true}— web search triggered{search_results: N}— N results found (no raw payload){done: true, perplexity, tokens_per_sec, searched?}— terminal{error: "...", error_key: "..."}— error with incident key
8. Testing Strategy
8.1 Test Framework
- pytest with
tmp_path+ monkeypatched httpx.AsyncClient - No live external services required
- Test factories reset
SESSIONS,PIN_ATTEMPTS,RATE_EVENTSglobals per test
8.2 Test Coverage Areas (132 tests)
| Test file | Coverage |
|---|---|
| test_auth_capabilities.py | Guest/admin sessions, origin blocking, logout |
| test_chat_streaming_and_memory_paths.py | Streaming, auto-search, remember/forget, upload context injection |
| test_completions.py | API key auth, FIM, streaming, blocking, errors |
| test_conversations.py | Full CRUD, guest admin, attachment_count |
| test_ingest.py | Bearer auth, chunk/embed/upsert, validation |
| test_memories.py | Edit, search, stats |
| test_models_router.py | Models list, ps, show, stats, search/status |
| test_presets.py | Full CRUD, default preset protection |
| test_profile.py | Get, update, default, length validation |
| test_rag_management.py | Collection stats, eviction algorithm (pinned/grace/scoring/batch), maybe_evict hysteresis, operational stats, flush, concurrency lock |
| test_search_route.py | Explicit search flow, no results, errors |
| test_search_url_sanitization.py | URL sanitizer |
| test_settings_allowlist.py | Allowlisted key enforcement |
| test_skills_framework.py | List, toggle, unknown skill, prompt injection |
| test_ip_allowlist.py | IP allowlist helper + middleware |
| test_rate_and_payload_guardrails.py | Rate limits + payload size |
| test_error_envelopes.py | Global exception handler + stream errors |
| test_upload.py | Upload, delete, link, by-conversation, attachment_count |
8.3 DoD Process
For substantive changes:
- Implement code change
- Add/adjust tests proving behavior and guardrail intent
- Update this wiki and README in the same change set
- Validate with full test run before commit
9. Hardware Self-Assessment
On startup, assess_hardware() probes:
- RAM total/available (psutil)
- VRAM total/free (rocm-smi, best-effort)
- llama-server reachability + model list
- Qdrant reachability + collection list
- SearXNG reachability
Writes hardware_state.json to working directory.