feat(ki): Einstellungen -> KI mit OpenAI-Schluessel und Modellwahl
Neuer Admin-Reiter "KI": zeigt die letzten vier Zeichen des hinterlegten OpenAI-Schluessels samt Quelle, nimmt einen neuen an (vorher gegen /v1/models geprueft) und laesst je Einsatzzweck das Modell per Dropdown waehlen - Scanner-Bewertung, Abgleich "bereits beworben", erweiterte Suche. Die Auswahl kommt live von OpenAI, Vorgaben sind die bisher fest verdrahteten Modelle. Reasoning-Modelle bekommen reasoning_effort statt temperature, damit ein Wechsel keinen 400er ausloest. Schluesselfeld aus dem Scanner-Reiter entfernt; Doku in docs/14-ki.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
0d41d46a3c
commit
e35c7518db
+102
@@ -0,0 +1,102 @@
|
||||
# 14 KI: Schlüssel und Modelle
|
||||
|
||||
Das CRM ruft OpenAI an drei Stellen auf. Schlüssel und Modell pflegen
|
||||
Administratoren unter **Einstellungen → KI** - ohne SSH, ohne Neustart.
|
||||
|
||||

|
||||
|
||||
## Wo die KI arbeitet
|
||||
|
||||
| Einsatzzweck | Code | Schlüssel in `crm_settings` | Vorgabe |
|
||||
| --- | --- | --- | --- |
|
||||
| Scanner: Bewertung Lebenslauf gegen Projekt | `scanner/services/matchingEngine.js` | `ai_model_matching` | `gpt-4o-mini` |
|
||||
| Scanner: Abgleich „bereits beworben" | `scanner/services/appliedCheck.js` | `ai_model_applied` | `gpt-5.6-luna` |
|
||||
| Erweiterte Suche | `routes/search.js` | `ai_model_search` | `gpt-4o-mini` (bzw. `OPENAI_MODEL`) |
|
||||
|
||||
Die Vorgaben sind genau die Modelle, die vor der Einstellung fest im Code
|
||||
standen. Wer nichts umstellt, merkt also nichts. Steht in `crm_settings` kein
|
||||
oder ein ungültiger Eintrag, gilt die Vorgabe.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
UI["Einstellungen → KI"] -->|"PUT /api/ai/key"| V{"GET /v1/models<br/>mit neuem Schlüssel"}
|
||||
V -->|"abgelehnt"| X["Fehlermeldung,<br/>alter Schlüssel bleibt"]
|
||||
V -->|"ok"| K[("perinco_scanner.settings<br/>openai_api_key")]
|
||||
UI -->|"PUT /api/ai/models"| M[("perinco_CRM.crm_settings<br/>ai_model_*")]
|
||||
K --> AI["server/src/ai.js<br/>aiKey() / modelFor()"]
|
||||
E["OPENAI_API_KEY<br/>server/.env"] -.->|"nur wenn DB leer"| AI
|
||||
M --> AI
|
||||
AI --> S1["Scanner-Bewertung"]
|
||||
AI --> S2["Abgleich bereits beworben"]
|
||||
AI --> S3["Erweiterte Suche"]
|
||||
```
|
||||
|
||||
## Schlüssel
|
||||
|
||||
Es gibt **genau einen** OpenAI-Schlüssel für Scanner und CRM. Er liegt
|
||||
unverändert als `openai_api_key` in der Scanner-Tabelle `settings` - dort, wo er
|
||||
schon immer lag. Nichts musste umziehen. Ersatzweise gilt `OPENAI_API_KEY` aus
|
||||
`server/.env`.
|
||||
|
||||
* **Anzeige nur mit Ende.** Die Oberfläche zeigt `sk-…` und die letzten vier
|
||||
Zeichen, dazu die Quelle (*hier hinterlegt* oder *server/.env*). Mehr gibt der
|
||||
Server nie heraus, auch nicht an Administratoren. Bei Schlüsseln unter zwölf
|
||||
Zeichen steht nur *ja*.
|
||||
* **Vor dem Speichern geprüft.** Ein neuer Schlüssel wird erst gegen
|
||||
`GET https://api.openai.com/v1/models` ausprobiert. Lehnt OpenAI ab, bleibt der
|
||||
alte gültig, und die Meldung von OpenAI steht im Fehlertext. Der Abruf kostet
|
||||
nichts.
|
||||
* **Verwerfen** leert den hinterlegten Wert. Danach gilt wieder die `.env` -
|
||||
oder, wenn dort nichts steht, gibt es keinen Schlüssel mehr.
|
||||
* Der Schlüssel liegt **unverschlüsselt** in der Scanner-Datenbank, wie bisher.
|
||||
Ein Dump von `perinco_scanner` enthält ihn also.
|
||||
|
||||
Im Reiter *Scanner* gibt es kein Schlüsselfeld mehr. `POST /api/scanner/settings`
|
||||
nimmt `openai_api_key` aus Kompatibilität weiter an.
|
||||
|
||||
## Modelle
|
||||
|
||||
Das Dropdown füllt sich **live von OpenAI**: alle Modelle, die der Schlüssel
|
||||
nutzen darf, gefiltert auf Textmodelle (`gpt-*`, `o1`/`o3`/`o4…`, `chatgpt-*`,
|
||||
ohne Audio, Bild, Embedding, Realtime, TTS, Suche, Moderation). Die aktuell
|
||||
eingestellten Modelle stehen immer mit drin, die Vorgabe ist markiert.
|
||||
|
||||
Ist die Liste nicht abrufbar (kein Schlüssel, OpenAI nicht erreichbar), zeigt
|
||||
das Dropdown eine feste Auswahl, und die Seite sagt, warum.
|
||||
|
||||
Die Wahl gilt ab dem nächsten Aufruf - es gibt keinen Zwischenspeicher.
|
||||
|
||||
### Reasoning- und klassische Modelle
|
||||
|
||||
Die beiden Modellfamilien nehmen unterschiedliche Parameter. Damit ein Wechsel
|
||||
im Dropdown keinen `400` von OpenAI auslöst, entscheidet `isReasoningModel()`
|
||||
(Namen `o1…`, `o3…`, `gpt-5…`) je Aufruf:
|
||||
|
||||
| | Reasoning (`o*`, `gpt-5*`) | klassisch (`gpt-4o*`, `gpt-4.1*`) |
|
||||
| --- | --- | --- |
|
||||
| Scanner-Bewertung | `reasoning_effort: low` | `temperature: 0.1` |
|
||||
| Abgleich „bereits beworben" | `reasoning_effort: low` | `temperature: 0` |
|
||||
| Erweiterte Suche | `max_completion_tokens` | `max_completion_tokens` |
|
||||
|
||||
`max_completion_tokens` verstehen beide Familien, das alte `max_tokens` die
|
||||
Reasoning-Modelle nicht.
|
||||
|
||||
## Endpunkte
|
||||
|
||||
Alle unter `/api/ai`, nur für Administratoren.
|
||||
|
||||
| Methode | Pfad | Zweck |
|
||||
| --- | --- | --- |
|
||||
| GET | `/settings` | Schlüsselzustand, Modell je Zweck, Auswahlliste |
|
||||
| PUT | `/key` | `{ key }` - prüfen, dann speichern |
|
||||
| DELETE | `/key` | hinterlegten Schlüssel verwerfen |
|
||||
| PUT | `/models` | `{ matching?, applied?, search? }` - Namen geprüft gegen `^[A-Za-z0-9][A-Za-z0-9._:-]{0,99}$` |
|
||||
|
||||
## Dateien
|
||||
|
||||
| Datei | Aufgabe |
|
||||
| --- | --- |
|
||||
| `server/src/ai.js` | Schlüssel, Modellwahl, Modellliste, Aufruf der Suche |
|
||||
| `server/src/routes/ai.js` | die vier Endpunkte |
|
||||
| `src/components/settings/AiSettings.tsx` | Reiter *KI* |
|
||||
| `server/test/ai.test.mjs` | Modellnamen und Reasoning-Erkennung |
|
||||
Reference in New Issue
Block a user