Log actual Ollama token counts and add user guide
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.
This commit is contained in:
parent
256bafe30d
commit
cced65693c
168
KURZANLEITUNG.md
Normal file
168
KURZANLEITUNG.md
Normal file
@ -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.
|
||||||
@ -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)
|
request.state.api_key_name, body.get("model", "?"), prompt_tokens, prompt_preview)
|
||||||
try:
|
try:
|
||||||
response = await proxy_request(f"{ollama_url}/api/generate", method="POST", json_data=body)
|
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:
|
except Exception as exc:
|
||||||
error_log.error("Proxy error | %s | /api/generate | %s | %s: %s",
|
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)
|
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))
|
request.state.api_key_name, body.get("model", "?"), prompt_tokens, _last_user_msg(messages))
|
||||||
try:
|
try:
|
||||||
response = await proxy_request(f"{ollama_url}/api/chat", method="POST", json_data=body)
|
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:
|
except Exception as exc:
|
||||||
error_log.error("Proxy error | %s | /api/chat | %s | %s: %s",
|
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)
|
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:
|
try:
|
||||||
response = await proxy_request(target, method="POST", json_data=body)
|
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:
|
except Exception as exc:
|
||||||
error_log.error("Proxy error | %s | /v1/chat/completions | %s | %s: %s",
|
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)
|
request.state.api_key_name, model_name, type(exc).__name__, exc, exc_info=exc)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user