shinkan-jinkendo/.claude/docs/working/AI_SKILL_RETRIEVAL_PROFILES_SPEC.md

122 lines
5.6 KiB
Markdown
Raw Permalink Normal View History

# 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 gespeicherte `exercise_focus_areas` zur gleichen Retrieval-Logik wie Suggest.
**Pflege der Profile:** Superadmin ohne Mandantenwahl — **`GET|POST /api/admin/ai-skill-retrieval-profiles`**, **`GET|PUT|DELETE /api/admin/ai-skill-retrieval-profiles/{id}`** (`routers/ai_skill_retrieval_admin.py`); Web-UI Superadmin unter **`/admin/ai-skill-retrieval`**.
## 6. Changelog
- **2026-05-29:** Superadmin-Pflege-Endpoints + UI‑Route dokumentiert (`/admin/ai-skill-retrieval`).
- **2026-05-29:** Erstellt; gekoppelt an Migration **068** und erste `exercise_ai`-Integration.