## Why this exists
Running a model locally is easy for one person on one laptop. Running it for a
team is where things get messy: who gets access, which model is loaded, what
each request costs, why latency doubled last night, and what happens when the
GPU box falls over.
Eco-Guard is the layer around the model. It loads GGUF files (or talks to
Ollama, vLLM, TGI, or OpenAI), serves them over `/v1/chat/completions`, records
what happened, enforces limits, and puts a UI on top so nobody has to read a
log file to answer "is it up?". It deploys as one container with SQLite by
default and PostgreSQL + Redis when you need them.
I built it because I wanted the operational side of local inference to be
boring, and because most "LLM platform" repos are either a thin wrapper around
a single endpoint or an unmaintainable sprawl of half-finished features. This
one aims to be the middle: small enough to read, complete enough to run.
## What it does
**Model management**
- Scans a directory for GGUF files and reads their metadata (architecture,
parameter count, quantization, context length, chat template) without
loading them.
- Downloads models from Hugging Face with live progress, cancel, and resume-safe
`.part` files. Search and file listing are built into the UI.
- Loads and unloads models on demand, sizing context and GPU offload from the
GGUF header and live hardware telemetry (VRAM, RAM, CPU).
- Supports a dedicated embedding model via `EMBEDDING_MODEL_PATH`; otherwise the
loaded model serves embeddings when it can.
**Serving**
- OpenAI-compatible `/v1/chat/completions` (streaming and non-streaming, tools,
JSON mode), `/v1/completions`, `/v1/embeddings`, `/v1/models`.
- Native API with chat, completions, batch (up to 100 prompts with per-item
error isolation), model comparison, and SSE streaming.
- Real streaming: generation runs on a worker thread and is bridged to async
with backpressure, so the event loop stays free and client disconnects stop
the work.
- Backends: local `llama.cpp`, or any OpenAI-compatible endpoint (Ollama, vLLM,
TGI, OpenAI). Switching is one environment variable.
**Operating it**
- Prometheus metrics plus a WebSocket feed for the UI: latency, tokens/s,
time-to-first-token, queue depth, cache hit rate, GPU utilization, VRAM,
temperature, power.
- Structured JSON logs with request correlation, OpenTelemetry tracing (OTLP).
- Health and readiness probes that know the difference between "process is
alive" and "can serve inference".
- Latency drift detection with a z-score baseline and optional retraining
triggers.
**Controlling it**
- API keys stored as SHA-256 hashes with roles (`admin`, `operator`, `member`,
`viewer`) enforced per route.
- scrypt password hashing, account lockout, HTTP-only JWT cookies, SSO
(Google/GitHub) with CSRF state validation.
- Sliding-window rate limiting that is exact across workers when Redis is
present and correct in-process when it is not.
- Content safety pipeline: prompt-injection detection, PII redaction, content
classification, with per-stage actions and a dry-run endpoint.
- Per-workspace budgets and token quotas, audit logging, and retention cleanup.
- IP allowlisting by CIDR for the whole API surface, and optional trusted-host
enforcement.
**Chat and playground**
- Persistent chat sessions with streaming responses, system prompts, and
per-session sampling parameters.
- Playground that runs one prompt across up to four models and shows output,
latency, and tokens/s side by side.
## Screenshots
| Chat | Models |
|---|---|
|  |  |
| Observability | Playground |
|---|---|
|  |  |
## Quick start
### Docker Compose (recommended)
```bash
git clone https://github.com/swadhinbiswas/Ecoguard.git
cd Ecoguard
export ECOGUARD_DOCKER_POSTGRES_PASSWORD=$(openssl rand -hex 16)
export ECOGUARD_DOCKER_JWT_SECRET=$(openssl rand -hex 32)
export ECOGUARD_DOCKER_ADMIN_PASSWORD=$(openssl rand -hex 16)
docker compose up --build -d
```
Open