From cced65693c7a988367712e911fad61823e1d3b9f Mon Sep 17 00:00:00 2001 From: Oliver Hofmann Date: Fri, 8 May 2026 07:21:36 +0200 Subject: [PATCH] Log actual Ollama token counts and add user guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a second usage log line after each proxy response with actual ↑prompt ↓completion token counts from Ollama (prompt_eval_count/eval_count for native endpoints, usage object for OpenAI endpoint). Also adds KURZANLEITUNG.md for students and colleagues covering API access, model selection, Python examples, opencode setup, and quota/admin information. --- KURZANLEITUNG.md | 168 +++++++++++++++++++++++++++++++++++++++++++++++ backend/main.py | 19 +++++- 2 files changed, 184 insertions(+), 3 deletions(-) create mode 100644 KURZANLEITUNG.md diff --git a/KURZANLEITUNG.md b/KURZANLEITUNG.md new file mode 100644 index 0000000..de28b28 --- /dev/null +++ b/KURZANLEITUNG.md @@ -0,0 +1,168 @@ +# LLM-Dienst – Kurzanleitung + +## Worum geht es? + +Der Dienst stellt **große Sprachmodelle (LLMs)** über eine einfache HTTP-API bereit, die direkt aus Python-Skripten, Jupyter-Notebooks oder eigenen Anwendungen angesprochen werden kann. Die Modelle laufen lokal auf einem GPU-Server im Intranet – ohne Datenübertragung nach außen und ohne Cloud-Kosten. + +Typische Anwendungsfälle: + +- Texte zusammenfassen, übersetzen oder umformulieren + - KI-gestütztes Coding (z.B. mit **[opencode](https://opencode.ai)**) +- Experimente mit Prompt-Engineering und LLM-Integration in eigene Projekte + +--- + +## Zugang + +Der Dienst ist **nur im Intranet** erreichbar. + +| | | +|---|---| +| **API-Endpunkt** | `http://141.75.33.244:8000` | +| **Authentifizierung** | API-Key erforderlich (per E-Mail beim Admin anfragen) | + +--- + +## Verfügbare Modelle + +| Modell | Größe | Hinweis | +|---|---|---| +| `gemma4:31b` | 19 GB | kompakt, schnell | +| `gpt-oss:20b` | 13 GB | kompakt, schnell | +| `gpt-oss:120b` | 65 GB | sehr leistungsfähig | +| `qwen3.5:122b` | 81 GB | sehr leistungsfähig | +| `qwen3-coder-next:q8_0` | 84 GB | speziell für Code | + +> **Wichtig:** Es kann immer nur **ein Modell gleichzeitig** im GPU-Speicher geladen sein. +> Wechselt jemand das Modell, muss das vorherige entladen und das neue geladen werden – +> das kann **mehrere Minuten** dauern. Der erste Prompt nach einem Modellwechsel ist +> deshalb deutlich langsamer. Danach bleibt das Modell einige Zeit geladen. + +--- + +## Python-Beispiel – Einfacher Prompt + +Das API folgt dem **OpenAI-Standard**, d.h. die `openai`-Bibliothek kann direkt verwendet werden. + +```bash +pip install openai +``` + +```python +from openai import OpenAI + +API_KEY = "sk-..." # euren API-Key eintragen +BASE_URL = "http://141.75.33.244:8000/v1" +MODEL = "gemma4:31b" # Modell nach Bedarf wählen + +client = OpenAI(api_key=API_KEY, base_url=BASE_URL) + +response = client.chat.completions.create( + model=MODEL, + messages=[ + {"role": "user", "content": "Erkläre den Unterschied zwischen L1- und L2-Regularisierung."} + ] +) + +print(response.choices[0].message.content) +``` + +--- + +## Python-Beispiel – Modell wählen und auflisten + +```python +from openai import OpenAI + +API_KEY = "sk-..." +BASE_URL = "http://141.75.33.244:8000/v1" + +client = OpenAI(api_key=API_KEY, base_url=BASE_URL) + +# Verfügbare Modelle abrufen +models = client.models.list() +for m in models.data: + print(m.id) + +# Prompt mit einem bestimmten Modell +response = client.chat.completions.create( + model="qwen3-coder-next:q8_0", + messages=[ + {"role": "system", "content": "Du bist ein hilfreicher Coding-Assistent."}, + {"role": "user", "content": "Schreibe eine Python-Funktion zum Berechnen der Fibonacci-Folge."} + ] +) + +print(response.choices[0].message.content) +``` + +--- + +## Empfehlungen zur Nutzung + +- **Kleines Modell zuerst** (`gemma4:31b` oder `gpt-oss:20b`) – viel schneller, für viele Aufgaben ausreichend. +- **Großes Modell** nur bei komplexen Aufgaben (`qwen3.5:122b`, `gpt-oss:120b`). +- **Code-Aufgaben**: `qwen3-coder-next:q8_0` ist speziell dafür optimiert. +- Wenn möglich, **dasselbe Modell wie andere Nutzer** verwenden, um häufige Modellwechsel zu vermeiden. + +--- + +## Quotas + +Je nach API-Key können folgende Limits konfiguriert sein: + +- Maximale **Anfragen pro Tag / Monat** +- Maximale **Tokens pro Tag / Monat** + +Bei Überschreitung gibt die API den Statuscode `429 Too Many Requests` zurück. + +--- + +## Coding-Assistent: opencode + +[opencode](https://opencode.ai) ist ein terminal-basierter KI-Coding-Agent (ähnlich Claude Code), der OpenAI-kompatible APIs unterstützt und damit direkt auf den Intranet-Dienst zeigen kann. + +### Installation + +```bash +npm install -g opencode-ai +# oder +curl -fsSL https://opencode.ai/install | bash +``` + +### Konfiguration + +Konfigurationsdatei anlegen unter `~/.config/opencode/config.json`: + +```json +{ + "$schema": "https://opencode.ai/config.json", + "providers": { + "openai": { + "apiKey": "sk-...", + "baseURL": "http://141.75.33.244:8000/v1" + } + }, + "model": "openai/qwen3-coder-next:q8_0" +} +``` + +Für Code-Aufgaben empfiehlt sich `qwen3-coder-next:q8_0`, für allgemeine Aufgaben `gemma4:31b` oder `gpt-oss:20b`. + +### Starten + +```bash +opencode +``` + +opencode öffnet eine interaktive TUI im Terminal und kann dann im Projektverzeichnis eingesetzt werden – Dateien lesen, Code generieren, Refactoring vorschlagen usw. + +--- + +## Administration (nur für Admins) + +Das Web-Interface zur Verwaltung von API-Keys und Quotas ist erreichbar unter: + +**`http://141.75.33.244:8001`** + +Dort können API-Keys angelegt, deaktiviert und mit Quotas versehen werden. diff --git a/backend/main.py b/backend/main.py index 331fcba..fb87fbb 100644 --- a/backend/main.py +++ b/backend/main.py @@ -101,7 +101,11 @@ async def generate(request: Request, db: Session = Depends(get_db)): request.state.api_key_name, body.get("model", "?"), prompt_tokens, prompt_preview) try: response = await proxy_request(f"{ollama_url}/api/generate", method="POST", json_data=body) - return JSONResponse(content=response.json(), status_code=response.status_code) + resp_json = response.json() + usage_log.info('%s | /api/generate | %s | actual ↑%d ↓%d tokens', + request.state.api_key_name, body.get("model", "?"), + resp_json.get("prompt_eval_count", 0), resp_json.get("eval_count", 0)) + return JSONResponse(content=resp_json, status_code=response.status_code) except Exception as exc: error_log.error("Proxy error | %s | /api/generate | %s | %s: %s", request.state.api_key_name, body.get("model", "?"), type(exc).__name__, exc, exc_info=exc) @@ -121,7 +125,11 @@ async def chat(request: Request, db: Session = Depends(get_db)): request.state.api_key_name, body.get("model", "?"), prompt_tokens, _last_user_msg(messages)) try: response = await proxy_request(f"{ollama_url}/api/chat", method="POST", json_data=body) - return JSONResponse(content=response.json(), status_code=response.status_code) + resp_json = response.json() + usage_log.info('%s | /api/chat | %s | actual ↑%d ↓%d tokens', + request.state.api_key_name, body.get("model", "?"), + resp_json.get("prompt_eval_count", 0), resp_json.get("eval_count", 0)) + return JSONResponse(content=resp_json, status_code=response.status_code) except Exception as exc: error_log.error("Proxy error | %s | /api/chat | %s | %s: %s", request.state.api_key_name, body.get("model", "?"), type(exc).__name__, exc, exc_info=exc) @@ -185,7 +193,12 @@ async def openai_chat_completions(request: Request, db: Session = Depends(get_db try: response = await proxy_request(target, method="POST", json_data=body) - return JSONResponse(content=response.json(), status_code=response.status_code) + resp_json = response.json() + usage = resp_json.get("usage", {}) + usage_log.info('%s | /v1/chat/completions | %s | actual ↑%d ↓%d tokens', + request.state.api_key_name, model_name, + usage.get("prompt_tokens", 0), usage.get("completion_tokens", 0)) + return JSONResponse(content=resp_json, status_code=response.status_code) except Exception as exc: error_log.error("Proxy error | %s | /v1/chat/completions | %s | %s: %s", request.state.api_key_name, model_name, type(exc).__name__, exc, exc_info=exc)