Ollama-Proxy mit Cloud-Usage-Monitor, Port von ollama-usage-monitor (FastAPI -> axum)
  • Rust 99.4%
  • Dockerfile 0.6%
Find a file
Hermes 079890c3d3 feat: Rust-Port von ollama-usage-monitor (FastAPI -> axum)
- Regex-Scraping, Burn-Rate-Analyse, Keyword-Interception, Cloud-Key-Failover
- 26 Tests, darunter die Python-Fixtures aus test_parse_page.py und im Original
  erzeugte Text-Literale (summary/help byte-gleich)
- Dockerfile + compose als Ersatz fuer den Python-Container
2026-11-05 16:30:36 +01:00
src feat: Rust-Port von ollama-usage-monitor (FastAPI -> axum) 2026-11-05 16:30:36 +01:00
tests feat: Rust-Port von ollama-usage-monitor (FastAPI -> axum) 2026-11-05 16:30:36 +01:00
.gitignore feat: Rust-Port von ollama-usage-monitor (FastAPI -> axum) 2026-11-05 16:30:36 +01:00
Cargo.lock feat: Rust-Port von ollama-usage-monitor (FastAPI -> axum) 2026-11-05 16:30:36 +01:00
Cargo.toml feat: Rust-Port von ollama-usage-monitor (FastAPI -> axum) 2026-11-05 16:30:36 +01:00
docker-compose.yml feat: Rust-Port von ollama-usage-monitor (FastAPI -> axum) 2026-11-05 16:30:36 +01:00
Dockerfile feat: Rust-Port von ollama-usage-monitor (FastAPI -> axum) 2026-11-05 16:30:36 +01:00
README.md feat: Rust-Port von ollama-usage-monitor (FastAPI -> axum) 2026-11-05 16:30:36 +01:00

ollama-usage-monitor-rs

Rust-Port von ollama-usage-monitor: transparenter Proxy vor Ollama, der nach jedem Request die Cloud-Auslastung von ollama.com/settings nachlädt, analysiert und über /usage* sowie als Chat-Keyword ausgibt.

Warum portiert

Der Python-Dienst (FastAPI + httpx + uvicorn) hält einen kompletten ASGI-Stack für einen Proxy vor, der im Kern Bytes durchreicht. Dazu Regex-Scraping, das nur läuft, wenn jemand chattet. Ein Rust-Binary mit axum erledigt das ohne Interpreter, ohne venv, ohne pip — und der Durchreichpfad hat keine Kopie über einen Event-Loop mehr.

Bauen

cargo build --release      # target/release/ollama-usage-monitor
cargo test                 # 26 Tests, kein Netz, kein Ollama
docker compose up -d --build

Konfiguration

Alles wie im Original, dieselben Namen und Defaults:

OLLAMA_COOKIE, OLLAMA_BASE_URL (http://localhost:11434), OLLAMA_CLOUD_URL (https://ollama.com), OLLAMA_CLOUD_KEYS, OLLAMA_DEBOUNCE_SECONDS (auch DEBOUNCE_SECONDS, 30), HISTORY_PATH (/data/usage_history.json), WEBHOOK_URL, WEBHOOK_ALERTS, PROXY_TOKEN, CORS_ORIGINS (*), PORT (9462), HOST (0.0.0.0). Dazu HISTORY_SIZE (100).

Endpunkte

  • GET /health — immer offen, auch mit PROXY_TOKEN
  • GET /usage, GET /usage/raw, GET /usage/history, GET /usage/summary
  • POST /usage/refresh
  • GET /info
  • alles andere: transparenter Proxy nach OLLAMA_BASE_URL (alle Methoden)

Chat-Keywords (usage, summary, history, refresh, info, help) werden abgefangen, wenn sie die letzte User-Nachricht sind, und in beiden Formaten beantwortet: Ollama (NDJSON für /api/chat) und OpenAI (SSE für /v1/chat/completions). *:cloud-Modelle gehen bei gesetzten OLLAMA_CLOUD_KEYS direkt an ollama.com und fallen bei 401/402/403/429 auf den nächsten Key zurück (_preferred_key bleibt sticky).

Parität — was geprüft ist

cargo test (26 Tests):

  • tests/parse_page.rs (10) — 1:1 die Fälle aus test_parse_page.py, inklusive der Eigenheiten: 42,5% wird als 5.0 gelesen, 42,5 % rutscht auf den Wochenwert 12.0, Login-Seite liefert Nullen statt None, Tool-Zeilen werden gefiltert, Modelle dedupliziert und nach Requests sortiert.
  • tests/summary_parity.rs (4) — die Texte sind mit main._summary_lines aus dem Python-Original erzeugt und hier als Literale hinterlegt; info_answer() ist byte-gleich zum Python-_table-Ergebnis (live verglichen, siehe unten).
  • tests/proxy.rs (12) — Bearer-Auth (401 ohne/mit falschem Token, /health offen), Cloud-Key-Failover mit Stickiness, Fehlerdurchreichung bei erschöpften Keys, Keyword-Interception (nur letzte User-Nachricht), Streaming-Durchreichung inkl. GPU-Zähler aus eval_duration, GET-Weiterleitung bei leerem Body, HISTORY_SIZE.

Live-Vergleich gegen das Original (beide mit TestClient bzw. echtem Port, leere History):

/info      : JSON-Objekte gleich (commands, endpoints, chat_keywords)
 "help"    : Keyword-Antwort byte-identisch zu main._summary_lines/_table

Abweichungen

  • datetime-Format bleibt: reset_at und ts sind weiter …+00:00 mit Mikrosekunden.
  • Streaming-Status ist immer 200, wie im Original — der Upstream-Status wird hier bewusst nicht durchgereicht (Kommentar im Python-Code nennt das einen eigenen Change).
  • Chunk-Grenzen beim Streaming werden nicht garantiert: test_proxy_stream.py prüfte, dass drei Upstream-Reads drei Chunks ergeben. Über axum+reqwest kann ein Read zusammengelegt werden; geprüft wird die Byte-Gleichheit des Gesamtbodys und der Content-Type.
  • _load_history-Fehlerfälle: kaputte History-Datei wird wie im Original still verworfen.
  • PROXY_TOKEN wird pro Request aus der Umgebung gelesen (wie im Original), damit die Tests ihn umschalten können — nicht beim Start gecacht.
  • Kein /v1/models, kein Streaming-usage-Objekt: war im Original auch nicht da.

Herkunft

Portiert am 2026-11-05 aus ollama-usage-monitor (Python, 881 + 208 + 193 Zeilen). Das Original bleibt unverändert liegen; der Wechsel ist ein docker compose auf das neue Repo.