121 lines
5.3 KiB
Markdown
121 lines
5.3 KiB
Markdown
|
|
# KI Skill-Retrieval-Profile (`ai_skill_retrieval_profiles`)
|
|||
|
|
|
|||
|
|
**Version:** 0.1
|
|||
|
|
**Datum:** 2026-05-29
|
|||
|
|
**Status:** Umsetzung gestartet (Migration **068**)
|
|||
|
|
**Ziel:** Für `POST /api/exercises/ai/suggest` (Skill-Katalogauszug) **Gewichte und Quoten** steuerbar machen:
|
|||
|
|
|
|||
|
|
- gebunden an **Übungs-Fokusbereich** (`focus_areas.id`),
|
|||
|
|
- ein **Standardprofil** ohne Fokus,
|
|||
|
|
- **optional zusammengeführte** Profile bei mehreren Fokusbereichen,
|
|||
|
|
- **optional Keyword-Übersteuerungen** aus Ziel/Durchführung (z. B. Rollenspiel vs. Befreiung).
|
|||
|
|
|
|||
|
|
**Technische Basis:** Skills mit `skills.main_category_id` → `skill_main_categories.slug` (`karate` | `allgemeine`) und `skills.category_id` → `skill_categories.slug` (`kondition`, `selbstverteidigung`, …).
|
|||
|
|
|
|||
|
|
**Bezüge:** `.claude/docs/working/AI_EXERCISE_IMPLEMENTATION_PLAN.md` · `backend/exercise_ai.py`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. Datenmodell
|
|||
|
|
|
|||
|
|
### Tabelle `ai_skill_retrieval_profiles`
|
|||
|
|
|
|||
|
|
| Spalte | Typ | Beschreibung |
|
|||
|
|
|--------|-----|--------------|
|
|||
|
|
| `id` | serial | Primärschlüssel |
|
|||
|
|
| `focus_area_id` | int NULL FK → `focus_areas(id)` ON DELETE SET NULL | **`NULL`** nur für Standardeintrag möglich (siehe `is_default`) |
|
|||
|
|
| `is_default` | boolean | Genau **eine** Zeile mit `true` |
|
|||
|
|
| `name` | varchar | Kurzer Name (Admin später) |
|
|||
|
|
| `description` | text | Hinweise für Pflege |
|
|||
|
|
| `active` | boolean | Nur aktive werden geladen |
|
|||
|
|
| `config` | jsonb | Siehe §2 |
|
|||
|
|
|
|||
|
|
**Constraints / Indizes**
|
|||
|
|
|
|||
|
|
- Eindeutig: `(focus_area_id)` WHERE `focus_area_id IS NOT NULL`
|
|||
|
|
- Eindeutig: `(is_default)` WHERE `is_default = true`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. JSON-Konfiguration `config.version = 1`
|
|||
|
|
|
|||
|
|
Alle Schlüssel **optional**; fehlende Werte fallen auf **einprogrammierten Fallback** in `exercise_ai.py` zurück (entspricht bisher grob „neutral“).
|
|||
|
|
|
|||
|
|
### 2.1 Gewichtungen (Ranking)
|
|||
|
|
|
|||
|
|
| Schlüssel | Typ | Bedeutung |
|
|||
|
|
|-----------|-----|------------|
|
|||
|
|
| `main_slug_weights` | `object[str, float]` | Multiplikator pro Hauptkategorie-Slug (`karate`, `allgemeine`) |
|
|||
|
|
| `category_slug_weights` | `object[str, float]` | Multiplikator pro `skill_categories.slug` |
|
|||
|
|
|
|||
|
|
Basis-Score (vereinfacht):
|
|||
|
|
`(importance oder 3) × main_w × cat_w × text_overlap_bonus × importance_multiplier`
|
|||
|
|
|
|||
|
|
### 2.2 Kapazitätsbegrenzung (Liste)
|
|||
|
|
|
|||
|
|
`_MAX_SKILLS_CATALOG_LINES` (aktuell **240**) Zeilen Gesamt:
|
|||
|
|
|
|||
|
|
| Schlüssel | Typ | Bedeutung |
|
|||
|
|
|-----------|-----|------------|
|
|||
|
|
| `category_max_share` | `object[str, float]` | Max. Anteil dieser **Unterkategorie** am Endergebnis (0–1), z. B. `{ "kondition": 0.25 }` |
|
|||
|
|
| `main_min_share` | `object[str, float]` | Mindest-Zielanteil Hauptkategorie beim **Auswahl-Greedy** (weich; Rest nach Score aufgefüllt) |
|
|||
|
|
|
|||
|
|
### 2.3 Text / Token-Sparen
|
|||
|
|
|
|||
|
|
| Schlüssel | Typ | Standard | Bedeutung |
|
|||
|
|
|-----------|-----|----------|------------|
|
|||
|
|
| `description_plain_max_len` | int | 160 | Gekürzte Beschreibung pro Zeile |
|
|||
|
|
| `karate_relevance_max_len` | int | **0** oder 80 | **`0`** = Feld `karate_relevance`/`relevance_level` in der Promptzeile **weglassen** |
|
|||
|
|
|
|||
|
|
### 2.4 Keyword-Overrides (optional)
|
|||
|
|
|
|||
|
|
Liste `keyword_overrides`: jedes Element:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"keywords_any": ["befreiung", "haltegriff"],
|
|||
|
|
"case_insensitive": true,
|
|||
|
|
"patch": {
|
|||
|
|
"category_slug_weights": { "selbstverteidigung": 2.5 },
|
|||
|
|
"category_max_share": { "koordination": 0.1 }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Textsuche in verkettetem Korpus **Titel, Ziel, Durchführung, Focus-Hint** (bereits plaintext). Reihenfolge: erst Basis-Profile zusammenmergen, dann **alle treffenden Overrides**‑`patch`‑Objekte **flach zusammenführen** (Gewichte multiplikativ übereinander, Caps den strengsten Wert nehmen – aktuelle Implementierung im Code dokumentiert).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. Mehrere Fokusbereiche auf der Übung
|
|||
|
|
|
|||
|
|
Request-Body: `focus_areas_context: [{ "focus_area_id": n, "is_primary": bool }, …]`
|
|||
|
|
|
|||
|
|
**Aktuelle Merge-Strategie (v1):** Profile laden → **gleichgewichtete Mittelwert-Bildung** der numerischen Gewichte / Caps (implementiert für `main_slug_weights`, `category_slug_weights`, `category_max_share`, `main_min_share`, `*_max_len`). Anschließend **Keyword-Overrides** anwenden.
|
|||
|
|
|
|||
|
|
**Primär-Fokus:** Im Frontend soll die **primäre** Zeile aus `focus_areas_multi` **zuerst** in der Liste stehen; die Merge-Strategie kann später zu „Primär dominate“ erweitert werden.
|
|||
|
|
|
|||
|
|
Ohne Kontext oder ohne Treffer auf aktive Profile: **nur Standardprofil** (`is_default`).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. Seed-Daten (Migration)
|
|||
|
|
|
|||
|
|
- **`is_default=true`:** ausgewogene Standard-Gewichte, moderate Caps auf `kondition`/`koordination`, Karate-Relevanz gekürzt.
|
|||
|
|
- **`Gewaltschutz`:** `focus_area_id` per `(SELECT id FROM focus_areas WHERE name = 'Gewaltschutz' LIMIT 1)` — höhere Gewichte für `kognition`, `psychische_faehigkeiten`, `soziale_faehigkeiten`, `selbstverteidigung`; gedrosseltes `kondition`/`koordination`; `karate_relevance_max_len`: 0; Keyword-Patches wie oben können nachgeschärft werden.
|
|||
|
|
|
|||
|
|
Weitere Profile (Karate-Schwerpunkt etc.) später per Admin-SQL oder UI.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. API
|
|||
|
|
|
|||
|
|
`ExerciseAiSuggestBody` erweitert um **`focus_areas_context`** (Liste). Feld **`focus_area_hint`** bleibt für den **Prompt-Kontext** (bestehende Prompts).
|
|||
|
|
|
|||
|
|
`POST …/ai/regenerate` nutzt später dieselbe Retrieval-Logik aus den Detail-Daten der Übung (**To-do:** dort `focus_areas_context` aus `exercise_focus_areas` ableiten).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. Changelog
|
|||
|
|
|
|||
|
|
- **2026-05-29:** Erstellt; gekoppelt an Migration **068** und erste `exercise_ai`-Integration.
|