Refactor AI Prompt System and Enhance Functionality
All checks were successful
Deploy Development / deploy (push) Successful in 46s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m18s
All checks were successful
Deploy Development / deploy (push) Successful in 46s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m18s
- Introduced `load_and_render_ai_prompt` and `render_ai_prompt_template_for_row` in `ai_prompt_runtime` to streamline prompt loading and rendering processes. - Added `AiPromptUnavailableError` for better error handling when prompts are inactive or missing. - Created `ai_prompt_job` module with `ExerciseFormAiPromptContext` and `resolve_exercise_form_variables` to support admin preview functionality. - Updated documentation and target architecture to reflect changes in the AI prompt system. - Incremented application version to 0.8.160 and updated changelog accordingly.
This commit is contained in:
parent
e22266a18c
commit
0551bb3d80
|
|
@ -26,7 +26,7 @@ Alle produktiven KI-Aufrufe sollten mittelfristig über eine **einheitliche Fass
|
|||
|
||||
Router und Frontend rufen diese Schicht oder schmale Orchestratoren — **nicht** direkt `httpx`/OpenRouter an jeder Ecke verteilt.
|
||||
|
||||
**Frühere Konkretisierung (Umsetzung gestartet):** Modul `backend/ai_prompt_runtime.py` mit **Kontext-Arten** und **gemeinsamen DB-Ladeschritten** für `ai_prompts`; Übungs-KI konsumiert diese Schicht ohne Zirkelschluss zu Domänlogik (`exercise_ai`).
|
||||
**Frühere Konkretisierung (Umsetzung gestartet):** Modul `backend/ai_prompt_runtime.py` (`load_ai_prompt_row`, `load_and_render_ai_prompt`, Kontext-Arten) sowie `backend/ai_prompt_job.py` (Pydantic `ExerciseFormAiPromptContext` fuer Uebungs-Prompts — Admin-Vorschau + erweiterbare Router-Nutzung); `exercise_ai` orchestriert OpenRouter nach dem Rendern.
|
||||
|
||||
### 2.2 Trennung: Semantik vs. Transport
|
||||
|
||||
|
|
@ -122,22 +122,24 @@ Konzeptionell **gleiche Bausteine** (admin-konfigurierbare Prompts, Platzhalter,
|
|||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph heute
|
||||
subgraph laufzeit
|
||||
A[ai_prompts DB]
|
||||
B[prompt_resolver Mustache]
|
||||
C[ai_prompt_runtime Loader + ContextKind]
|
||||
D[exercise_ai]
|
||||
C[ai_prompt_runtime]
|
||||
J[ai_prompt_job Pydantic]
|
||||
D[exercise_ai OpenRouter]
|
||||
end
|
||||
A --> B
|
||||
A --> C
|
||||
C --> B
|
||||
J --> D
|
||||
C --> D
|
||||
B --> D
|
||||
```
|
||||
|
||||
| Phase | Inhalt |
|
||||
|-------|--------|
|
||||
| **P0 (gestartet)** | `AiPromptContextKind`, `load_ai_prompt_row` zentral; Übungs-KI nutzt Laufzeit; Platzhalter-Katalog pro Kontext erweiterbar. |
|
||||
| **P1** | Einheitliche `run_ai_job`-Fassade (Slug + Kind + Pydantic-Payload + Validierung); Router nur noch dünne Adapter. |
|
||||
| **P0** | `AiPromptContextKind`, `load_ai_prompt_row` zentral; Übungs-KI über Laufzeit. |
|
||||
| **P1 (teilweise)** | `load_and_render_ai_prompt`, `AiPromptUnavailableError`, `render_ai_prompt_template_for_row`; **Pydantic** `ExerciseFormAiPromptContext` / `resolve_exercise_form_variables` in `ai_prompt_job` (Admin-Vorschau + gemeinsame Schnittstelle). Nächster Schritt: `POST /exercises/ai/suggest` kann dasselbe Kontextmodell nutzen; optionale `execute_*`-Fassade fuer OpenRouter-Schritt. |
|
||||
| **P2** | Versionierung oder Audit-Spalten; optionale Modell-/Temperatur-Overrides pro Slug in DB oder Config-Tabelle. |
|
||||
| **P3** | Composition/Segmente (JSON Schema Version 1) + UI nur für komplexe Slugs. |
|
||||
| **P4** | Erste Planungs-/Rahmen-Slugs mit dedizierten Buildern und Token-Budget-Strategien. |
|
||||
|
|
@ -157,7 +159,7 @@ flowchart LR
|
|||
- Ist-Implementierung Prompts/UI: `AI_PROMPT_SYSTEM_SPEC.md`
|
||||
- Zugriffsrecht Admin-Prompts: `ACCESS_LAYER_ENDPOINT_AUDIT.md`
|
||||
- Retrieval-Profile: `.claude/docs/working/AI_SKILL_RETRIEVAL_PROFILES_SPEC.md`
|
||||
- Übungs-KI-Codepfad: `backend/exercise_ai.py`, `backend/prompt_resolver.py`, `backend/ai_prompt_runtime.py`
|
||||
- Übungs-KI-Codepfad: `backend/exercise_ai.py`, `backend/prompt_resolver.py`, `backend/ai_prompt_runtime.py`, `backend/ai_prompt_job.py`
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
58
backend/ai_prompt_job.py
Normal file
58
backend/ai_prompt_job.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
"""
|
||||
KI-Prompt Jobs: validierter Kontext (Pydantic) + Zugriff auf gemeinsame Resolver.
|
||||
|
||||
Importiert exercise_ai nur fuer Platzhalter-Builder — Router importieren dieses Modul oder exercise_ai,
|
||||
nicht umgekehrt von exercise_ai nach ai_prompt_job (Zirkel vermeiden).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from exercise_ai import build_exercise_placeholder_variables
|
||||
|
||||
|
||||
class ExerciseFormAiFocusRow(BaseModel):
|
||||
"""Fokusbereich fuer Skill-Retrieval-Kontext (wie ExerciseAiFocusCtx / Admin-Preview)."""
|
||||
|
||||
focus_area_id: int = Field(..., ge=1)
|
||||
is_primary: Optional[bool] = False
|
||||
|
||||
|
||||
class ExerciseFormAiPromptContext(BaseModel):
|
||||
"""
|
||||
Eingabe fuer Uebungsbezogene Prompts (Kurzfassung / Skill-JSON-Vorschlag).
|
||||
Entspricht fachlich dem Preview-Body unter /api/admin/ai-prompts/*/preview.
|
||||
"""
|
||||
|
||||
title: Optional[str] = ""
|
||||
goal: Optional[str] = None
|
||||
execution: Optional[str] = None
|
||||
focus_hint: Optional[str] = None
|
||||
focus_areas_context: Optional[List[ExerciseFormAiFocusRow]] = None
|
||||
|
||||
def focus_area_tuples(self) -> Optional[List[Tuple[int, bool]]]:
|
||||
if not self.focus_areas_context:
|
||||
return None
|
||||
return [(int(x.focus_area_id), bool(x.is_primary)) for x in self.focus_areas_context]
|
||||
|
||||
|
||||
def resolve_exercise_form_variables(cur, slug: str, ctx: ExerciseFormAiPromptContext) -> Dict[str, str]:
|
||||
"""Baut die Mustache-Map fuer exercise_summary / exercise_skill_suggestions."""
|
||||
return build_exercise_placeholder_variables(
|
||||
cur,
|
||||
slug=slug,
|
||||
title=(ctx.title or "").strip(),
|
||||
goal=ctx.goal,
|
||||
execution=ctx.execution,
|
||||
focus_area_hint=ctx.focus_hint,
|
||||
focus_areas_context=ctx.focus_area_tuples(),
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"ExerciseFormAiFocusRow",
|
||||
"ExerciseFormAiPromptContext",
|
||||
"resolve_exercise_form_variables",
|
||||
]
|
||||
|
|
@ -7,7 +7,9 @@ load_ai_prompt_row und die Enum; Platzhalter bauen sie selbst oder über geteilt
|
|||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any, Dict, Mapping, Optional, Tuple
|
||||
|
||||
from prompt_resolver import MustacheRenderResult, render_mustache_template
|
||||
|
||||
_EXERCISE_AI_SLUGS = frozenset(
|
||||
{
|
||||
|
|
@ -67,8 +69,45 @@ def load_ai_prompt_row(cur, slug: str, *, active_only: bool = True) -> Optional[
|
|||
return d
|
||||
|
||||
|
||||
class AiPromptUnavailableError(LookupError):
|
||||
"""Kein aktiver Prompt fuer slug (oder Zeile fehlt)."""
|
||||
|
||||
def __init__(self, slug: str) -> None:
|
||||
self.slug = (slug or "").strip()
|
||||
super().__init__(self.slug)
|
||||
|
||||
|
||||
def render_ai_prompt_template_for_row(
|
||||
row: Mapping[str, Any],
|
||||
variables: Mapping[str, str],
|
||||
) -> MustacheRenderResult:
|
||||
"""Ersetzt Platzhalter anhand einer bereits geladenen ai_prompts-Zeile (z. B. Admin-Vorschauch, inkl. inaktiv)."""
|
||||
return render_mustache_template(str(row.get("template") or ""), variables)
|
||||
|
||||
|
||||
def load_and_render_ai_prompt(
|
||||
cur,
|
||||
slug: str,
|
||||
variables: Mapping[str, str],
|
||||
*,
|
||||
active_only: bool = True,
|
||||
) -> Tuple[Dict[str, Any], MustacheRenderResult]:
|
||||
"""
|
||||
Laedt einen aktiven Prompt und wendet Mustache-Variablen an.
|
||||
Wirft AiPromptUnavailableError, wenn die Zeile fehlt oder (bei active_only) inaktiv ist.
|
||||
"""
|
||||
row = load_ai_prompt_row(cur, slug, active_only=active_only)
|
||||
if not row:
|
||||
raise AiPromptUnavailableError(slug)
|
||||
rr = render_ai_prompt_template_for_row(row, variables)
|
||||
return dict(row), rr
|
||||
|
||||
|
||||
__all__ = [
|
||||
"AiPromptContextKind",
|
||||
"AiPromptUnavailableError",
|
||||
"context_kind_for_slug",
|
||||
"load_ai_prompt_row",
|
||||
"load_and_render_ai_prompt",
|
||||
"render_ai_prompt_template_for_row",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -18,8 +18,7 @@ from fastapi import HTTPException
|
|||
|
||||
from openrouter_chat import OpenRouterError, normalize_openrouter_env, openrouter_chat_completion
|
||||
|
||||
from ai_prompt_runtime import load_ai_prompt_row
|
||||
from prompt_resolver import render_mustache_template
|
||||
from ai_prompt_runtime import AiPromptUnavailableError, load_and_render_ai_prompt
|
||||
|
||||
_LOGGER = logging.getLogger("shinkan.exercise_ai")
|
||||
|
||||
|
|
@ -715,9 +714,6 @@ def run_exercise_ai_suggestion(
|
|||
)
|
||||
|
||||
if want_summary:
|
||||
prow = load_ai_prompt_row(cur, "exercise_summary")
|
||||
if not prow:
|
||||
raise HTTPException(status_code=503, detail="Prompt exercise_summary nicht aktiv oder fehlt in DB.")
|
||||
try:
|
||||
ctx = build_exercise_placeholder_variables(
|
||||
cur,
|
||||
|
|
@ -730,7 +726,13 @@ def run_exercise_ai_suggestion(
|
|||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||
rendered = render_mustache_template(str(prow["template"]), ctx)
|
||||
try:
|
||||
prow, rendered = load_and_render_ai_prompt(cur, "exercise_summary", ctx)
|
||||
except AiPromptUnavailableError:
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail="Prompt exercise_summary nicht aktiv oder fehlt in DB.",
|
||||
) from None
|
||||
prompt = rendered.text
|
||||
if _ai_debug_on():
|
||||
_LOGGER.warning(
|
||||
|
|
@ -755,12 +757,6 @@ def run_exercise_ai_suggestion(
|
|||
result["summary"] = {"text": text, "ai_generated": True, "model": model}
|
||||
|
||||
if want_skills:
|
||||
srow = load_ai_prompt_row(cur, "exercise_skill_suggestions")
|
||||
if not srow:
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail="Prompt exercise_skill_suggestions nicht aktiv oder fehlt in DB.",
|
||||
)
|
||||
try:
|
||||
ctx = build_exercise_placeholder_variables(
|
||||
cur,
|
||||
|
|
@ -773,7 +769,13 @@ def run_exercise_ai_suggestion(
|
|||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||
rendered = render_mustache_template(str(srow["template"]), ctx)
|
||||
try:
|
||||
srow, rendered = load_and_render_ai_prompt(cur, "exercise_skill_suggestions", ctx)
|
||||
except AiPromptUnavailableError:
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail="Prompt exercise_skill_suggestions nicht aktiv oder fehlt in DB.",
|
||||
) from None
|
||||
prompt = rendered.text
|
||||
if _ai_debug_on():
|
||||
_LOGGER.warning(
|
||||
|
|
|
|||
|
|
@ -12,9 +12,10 @@ from pydantic import BaseModel, Field
|
|||
|
||||
from auth import require_auth
|
||||
from club_tenancy import is_superadmin
|
||||
from ai_prompt_job import ExerciseFormAiPromptContext, resolve_exercise_form_variables
|
||||
from ai_prompt_runtime import render_ai_prompt_template_for_row
|
||||
from db import get_cursor, get_db, r2d
|
||||
from exercise_ai import build_exercise_placeholder_variables
|
||||
from prompt_resolver import exercise_placeholder_catalog, render_mustache_template
|
||||
from prompt_resolver import exercise_placeholder_catalog
|
||||
|
||||
router = APIRouter(tags=["admin_ai_prompts"])
|
||||
|
||||
|
|
@ -211,28 +212,13 @@ def preview_ai_prompt(prompt_id: int, body: AiPromptPreviewBody, session: dict =
|
|||
raise HTTPException(status_code=503, detail="Tabelle ai_prompts fehlt.")
|
||||
row = _fetch_prompt_any(cur, prompt_id)
|
||||
slug = (row.get("slug") or "").strip().lower()
|
||||
tpl_raw = row.get("template") or ""
|
||||
|
||||
fctx_list: Optional[List[tuple[int, bool]]] = None
|
||||
if body.focus_areas_context:
|
||||
pairs: List[tuple[int, bool]] = []
|
||||
for x in body.focus_areas_context:
|
||||
pairs.append((int(x.focus_area_id), bool(x.is_primary)))
|
||||
fctx_list = pairs
|
||||
|
||||
vars_map: Dict[str, str]
|
||||
warn: Optional[str] = None
|
||||
if slug in ("exercise_summary", "exercise_skill_suggestions"):
|
||||
try:
|
||||
vars_map = build_exercise_placeholder_variables(
|
||||
cur,
|
||||
slug=slug,
|
||||
title=(body.title or "").strip(),
|
||||
goal=body.goal,
|
||||
execution=body.execution,
|
||||
focus_area_hint=body.focus_hint,
|
||||
focus_areas_context=fctx_list,
|
||||
)
|
||||
pf_ctx = ExerciseFormAiPromptContext.model_validate(body.model_dump())
|
||||
vars_map = resolve_exercise_form_variables(cur, slug, pf_ctx)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||
elif slug == "pipeline":
|
||||
|
|
@ -242,7 +228,7 @@ def preview_ai_prompt(prompt_id: int, body: AiPromptPreviewBody, session: dict =
|
|||
vars_map = {}
|
||||
warn = f"Slug {slug!r}: noch kein Vorschau-Kontext definiert — Roh-Template ohne Ersetzung."
|
||||
|
||||
rendered = render_mustache_template(str(tpl_raw), vars_map)
|
||||
rendered = render_ai_prompt_template_for_row(row, vars_map)
|
||||
return {
|
||||
"slug": slug,
|
||||
"resolved_template": rendered.text,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Shinkan Jinkendo Version Information
|
||||
|
||||
APP_VERSION = "0.8.159"
|
||||
APP_VERSION = "0.8.160"
|
||||
BUILD_DATE = "2026-05-30"
|
||||
DB_SCHEMA_VERSION = "20260530069"
|
||||
|
||||
|
|
@ -19,13 +19,14 @@ MODULE_VERSIONS = {
|
|||
"media_legal_hold": "1.0.0", # P-11: Sofortsperre-Services (set_legal_hold, release_legal_hold)
|
||||
"media_lifecycle": "1.1.0", # P-11: Retention-Job ueberspringt Legal-Hold-Assets
|
||||
"admin_ai_skill_retrieval": "1.0.0", # Superadmin CRUD /api/admin/ai-skill-retrieval-profiles (Migration 068)
|
||||
"admin_ai_prompts": "1.0.1", # Prompt-Pflege + Zielarchitektur-Doku; gemeinsamer DB-Load uber ai_prompt_runtime
|
||||
"ai_prompt_runtime": "0.1.0", # AiPromptContextKind, load_ai_prompt_row — Erweiterung Planung ohne Zirkel zu exercise_ai
|
||||
"admin_ai_prompts": "1.0.2", # Preview: ai_prompt_job + render_ai_prompt_template_for_row
|
||||
"ai_prompt_job": "0.1.0", # ExerciseFormAiPromptContext, resolve_exercise_form_variables — P1 Kontext-Schnittstelle
|
||||
"ai_prompt_runtime": "0.2.0", # load_and_render_ai_prompt, AiPromptUnavailableError, render_ai_prompt_template_for_row
|
||||
"groups": "0.1.0",
|
||||
"skills": "0.1.1", # DB 065 karate_relevance + relevance_level; CRUD unterstützt Felder
|
||||
"skill_profiles": "1.0.0", # Phase 3: gewichtetes Fähigkeiten-Profil + skill-discovery/suggestions
|
||||
"methods": "0.1.0",
|
||||
"exercises": "2.31.1", # AI nutzt load_ai_prompt_row aus ai_prompt_runtime
|
||||
"exercises": "2.31.2", # exercise_ai: load_and_render_ai_prompt statt getrennt load+render
|
||||
"training_units": "0.4.0", # POST .../publish-to-framework: Ablauf aus geplanter Einheit → Rahmen-Slot-Blueprint
|
||||
"training_programs": "0.1.0",
|
||||
"planning": "0.15.0", # Vorlagen: Strukturvorschau, Bearbeiten inkl. Split-Sessions + Beschreibung
|
||||
|
|
@ -40,6 +41,15 @@ MODULE_VERSIONS = {
|
|||
}
|
||||
|
||||
CHANGELOG = [
|
||||
{
|
||||
"version": "0.8.160",
|
||||
"date": "2026-05-30",
|
||||
"changes": [
|
||||
"KI Prompt P1: ai_prompt_runtime load_and_render_ai_prompt + render_ai_prompt_template_for_row; AiPromptUnavailableError;",
|
||||
"Neu ai_prompt_job: ExerciseFormAiPromptContext (Pydantic), resolve_exercise_form_variables; Admin-Prompt-Vorschau nutzt gleichen Pfad wie exercise_ai-Logik;",
|
||||
"Zielarchitektur-Doku: Phasendiagramm P0/P1 angepasst.",
|
||||
],
|
||||
},
|
||||
{
|
||||
"version": "0.8.159",
|
||||
"date": "2026-05-30",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Shinkan Jinkendo – Entwicklungsstand & Handover
|
||||
|
||||
**Stand:** 2026-05-30
|
||||
**App-Version / DB-Schema:** App **`0.8.159`** u. a. **KI-Prompt-Zielarchitektur** + gemeinsames Modul **`ai_prompt_runtime`**; DB-Schema **`backend/version.py`** → `APP_VERSION`, `DB_SCHEMA_VERSION` (aktuell `20260530069`).
|
||||
**App-Version / DB-Schema:** App **`0.8.160`** (u. a. KI Prompt P1: `load_and_render_ai_prompt`, `ai_prompt_job`); Zielarchitektur-Doku weiterhin **`.claude/docs/technical/AI_PROMPT_TARGET_ARCHITECTURE.md`**. DB: maßgeblich **`backend/version.py`** → `APP_VERSION`, `DB_SCHEMA_VERSION` (aktuell `20260530069`).
|
||||
|
||||
Diese Datei ist die **Einstiegs-Doku für neue Chat-Sessions**: Anforderungen im Detail stehen in `.claude/docs/` (siehe unten); hier der **implementierte Stand**, **Medien-Meilenstein** und **sinnvolle nächste Schritte**.
|
||||
|
||||
|
|
@ -89,11 +89,11 @@ Das Schema ist gegenüber dem Code zurück: Migration **`022_skills_schema_compl
|
|||
- **Varianten:** Speichern in der **Aktionsleiste** persistiert zuerst geänderte Varianten (`persistPendingVariantChanges`), dann Übungs-Stammdaten; „Variante anlegen“ als `type="button"` ohne verschachteltes Formular (`createVariantFromDraft`)
|
||||
- **Governance (Übungen):** Owner = `created_by`; Bearbeiten = Ersteller, Plattform-Admin oder `can_plan_in_club` bei `visibility=club`; Löschen `club` = nur `club_admin`; Details **`FEATURES_DELIVERED_2026-Q2.md`** §16, **`EXERCISES_API_SPEC.md`** Permissions
|
||||
|
||||
### 2.8 KI Assistenz Übungen & Skill-Katalog-Retrieval (Stand **0.8.159**)
|
||||
### 2.8 KI Assistenz Übungen & Skill-Katalog-Retrieval (Stand **0.8.160**)
|
||||
|
||||
- **Zielarchitektur (Pflicht fuer Erweiterungen):** `.claude/docs/technical/AI_PROMPT_TARGET_ARCHITECTURE.md` — Kontext-Arten, Composition, Einbindung Planung/Rahmen; Phasenplan P0–P4.
|
||||
- **Doku:** Umsetzung `.claude/docs/working/AI_EXERCISE_IMPLEMENTATION_PLAN.md`; Profil-/JSON-Konzept `.claude/docs/working/AI_SKILL_RETRIEVAL_PROFILES_SPEC.md`; Ist-Prompt/UI **`AI_PROMPT_SYSTEM_SPEC.md`**; API-Felder **`KI_FEATURES_SPEC.md`** §5.2
|
||||
- **Runtime:** **`backend/ai_prompt_runtime.py`** — `AiPromptContextKind`, `load_ai_prompt_row` (gemeinsamer DB-Lesezugriff, kein Import von `exercise_ai`); **`exercise_ai`** nutzt `load_ai_prompt_row` fuer aktive Prompts
|
||||
- **Runtime / Job:** **`ai_prompt_runtime`** (`load_and_render_ai_prompt`, `AiPromptUnavailableError`, Rendern aus DB-Zeile fuer Vorschau); **`ai_prompt_job`** — Pydantic **`ExerciseFormAiPromptContext`**, **`resolve_exercise_form_variables`** (Admin-Preview + Schnittstelle fuer weitere Router); **`exercise_ai`** laedt/rendert ueber Laufzeit und ruft OpenRouter
|
||||
- **DB:** Migration **`067`** **`ai_prompts`** (Slug **`exercise_summary`**, **`exercise_skill_suggestions`** — müssen **aktiv** sein); Migration **`069`** setzt **`default_template`** wo leer; Migration **`068`** **`ai_skill_retrieval_profiles`** (Seed Standard + ggf. Gewaltschutz-Fokus)
|
||||
- **`exercise_ai`:** Gewichtungen, Kategorie‑Anteil‑Caps (~Token), Keyword-Patches aus Ziel/Durchführung (z. B. Rollenspiel vs. Befreiung/Haltegriff)
|
||||
- **API:** `POST /api/exercises/ai/suggest` optional **`focus_areas_context`**; **`POST …/ai/regenerate`** nutzt gespeicherte `exercise_focus_areas` — **Pflege:** Superadmin **`/api/admin/ai-skill-retrieval-profiles*`** (`routers/ai_skill_retrieval_admin.py`), **`/api/admin/ai-prompts*`** (`routers/ai_prompts_admin.py`), UI **`/admin/ai-prompts`**
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user