Implement ExerciseFormAiPromptContext and Refactor AI Prompt Job Functionality
All checks were successful
Deploy Development / deploy (push) Successful in 40s
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 34s
Test Suite / playwright-tests (push) Successful in 1m23s
All checks were successful
Deploy Development / deploy (push) Successful in 40s
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 34s
Test Suite / playwright-tests (push) Successful in 1m23s
- Introduced `ExerciseFormAiPromptContext` for unified handling of prompt-related data, enhancing the admin preview and exercise API. - Added `run_exercise_form_ai_suggestion` function to streamline AI suggestion processing, integrating with the OpenRouter. - Updated various modules to utilize the new context model, improving code clarity and reducing redundancy. - Incremented application version to 0.8.162 and updated changelog to reflect these changes, including migration details and new functionality.
This commit is contained in:
parent
93b8d09d05
commit
5331eab39c
|
|
@ -139,7 +139,7 @@ flowchart LR
|
|||
| Phase | Inhalt |
|
||||
|-------|--------|
|
||||
| **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. |
|
||||
| **P1** | `load_and_render_ai_prompt`, `AiPromptUnavailableError`, `render_ai_prompt_template_for_row`; **`ExerciseFormAiPromptContext`** in `ai_prompt_context.py`; **`run_exercise_form_ai_suggestion`**; Übungs-API und Admin-Vorschau nutzen denselben Kontext. |
|
||||
| **P2** | Versionierung oder Audit-Spalten; **teilweise:** optionales OpenRouter-Modell pro Zeile (`openrouter_model`, Migration 070, Fallback `OPENROUTER_MODEL`); weitere Overrides (Temperatur) offen. |
|
||||
| **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. |
|
||||
|
|
@ -159,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`, `backend/ai_prompt_job.py`
|
||||
- Übungs-KI-Codepfad: `backend/exercise_ai.py`, `backend/prompt_resolver.py`, `backend/ai_prompt_runtime.py`, `backend/ai_prompt_context.py`, `backend/ai_prompt_job.py`
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
86
backend/ai_prompt_context.py
Normal file
86
backend/ai_prompt_context.py
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
"""
|
||||
Gemeinsame Pydantic-Modelle fuer Uebungs-KI-Kontext (Formularfelder → Prompt-Platzhalter).
|
||||
|
||||
Keine Imports aus exercise_ai — vermeidet Zirkelimporte mit ai_prompt_job / exercise_ai.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional, Sequence, Tuple
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ExerciseFormAiFocusRow(BaseModel):
|
||||
"""Fokusbereich fuer Skill-Retrieval (ai_skill_retrieval_profiles)."""
|
||||
|
||||
focus_area_id: int = Field(..., ge=1)
|
||||
is_primary: Optional[bool] = False
|
||||
|
||||
|
||||
class ExerciseFormAiPromptContext(BaseModel):
|
||||
"""
|
||||
Inhaltliche Eingabe fuer Uebungs-Prompts (Kurzfassung / Skill-Vorschlaege).
|
||||
|
||||
Wird genutzt von Admin-Prompt-Vorschau und POST /exercises/ai/suggest (via Mapping).
|
||||
"""
|
||||
|
||||
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]
|
||||
|
||||
@classmethod
|
||||
def from_api_suggest(
|
||||
cls,
|
||||
*,
|
||||
title: Optional[str] = None,
|
||||
goal: Optional[str] = None,
|
||||
execution: Optional[str] = None,
|
||||
focus_area_hint: Optional[str] = None,
|
||||
focus_areas_context: Optional[Sequence[ExerciseFormAiFocusRow]] = None,
|
||||
) -> ExerciseFormAiPromptContext:
|
||||
"""Mappt Felder aus POST /exercises/ai/suggest (focus_area_hint → focus_hint)."""
|
||||
hint = (focus_area_hint or "").strip() or None
|
||||
return cls(
|
||||
title=(title or "").strip(),
|
||||
goal=goal,
|
||||
execution=execution,
|
||||
focus_hint=hint,
|
||||
focus_areas_context=list(focus_areas_context) if focus_areas_context else None,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_focus_tuples(
|
||||
cls,
|
||||
*,
|
||||
title: str = "",
|
||||
goal: Optional[str] = None,
|
||||
execution: Optional[str] = None,
|
||||
focus_hint: Optional[str] = None,
|
||||
focus_tuples: Optional[Sequence[Tuple[int, bool]]] = None,
|
||||
) -> ExerciseFormAiPromptContext:
|
||||
rows = None
|
||||
if focus_tuples:
|
||||
rows = [
|
||||
ExerciseFormAiFocusRow(focus_area_id=int(fid), is_primary=bool(prim))
|
||||
for fid, prim in focus_tuples
|
||||
]
|
||||
return cls(
|
||||
title=(title or "").strip(),
|
||||
goal=goal,
|
||||
execution=execution,
|
||||
focus_hint=(focus_hint or "").strip() or None,
|
||||
focus_areas_context=rows,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"ExerciseFormAiFocusRow",
|
||||
"ExerciseFormAiPromptContext",
|
||||
]
|
||||
|
|
@ -1,47 +1,16 @@
|
|||
"""
|
||||
KI-Prompt Jobs: validierter Kontext (Pydantic) + Zugriff auf gemeinsame Resolver.
|
||||
KI-Prompt Jobs: Resolver + oeffentliche Fassade fuer Uebungs-KI-Aufrufe.
|
||||
|
||||
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).
|
||||
Importiert exercise_ai fuer Platzhalter-Builder und OpenRouter-Orchestrierung.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Any, Dict
|
||||
|
||||
from ai_prompt_context import ExerciseFormAiFocusRow, ExerciseFormAiPromptContext
|
||||
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.
|
||||
|
||||
Abgrenzung: POST /exercises/ai/suggest nutzt ExerciseAiSuggestBody mit include_summary /
|
||||
include_skills usw.; dieses Modell bildet nur die gemeinsamen Formularfelder — wie die
|
||||
Admin-Vorschau-Body (AiPromptPreviewBody).
|
||||
"""
|
||||
|
||||
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(
|
||||
|
|
@ -55,8 +24,31 @@ def resolve_exercise_form_variables(cur, slug: str, ctx: ExerciseFormAiPromptCon
|
|||
)
|
||||
|
||||
|
||||
def run_exercise_form_ai_suggestion(
|
||||
cur,
|
||||
ctx: ExerciseFormAiPromptContext,
|
||||
*,
|
||||
want_summary: bool,
|
||||
want_skills: bool,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Fuehrt Uebungs-KI aus (OpenRouter) — ein Einstieg fuer Router und kuenftige Jobs.
|
||||
|
||||
``ctx`` = Formularinhalt; ``want_*`` = welche Prompt-Slugs angefragt werden.
|
||||
"""
|
||||
from exercise_ai import run_exercise_ai_suggestion
|
||||
|
||||
return run_exercise_ai_suggestion(
|
||||
cur,
|
||||
form_ctx=ctx,
|
||||
want_summary=want_summary,
|
||||
want_skills=want_skills,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"ExerciseFormAiFocusRow",
|
||||
"ExerciseFormAiPromptContext",
|
||||
"resolve_exercise_form_variables",
|
||||
"run_exercise_form_ai_suggestion",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ from openrouter_chat import (
|
|||
openrouter_chat_completion,
|
||||
)
|
||||
|
||||
from ai_prompt_context import ExerciseFormAiPromptContext
|
||||
from ai_prompt_runtime import AiPromptUnavailableError, load_and_render_ai_prompt
|
||||
|
||||
_LOGGER = logging.getLogger("shinkan.exercise_ai")
|
||||
|
|
@ -682,16 +683,18 @@ def _require_openrouter_key() -> str:
|
|||
def run_exercise_ai_suggestion(
|
||||
cur,
|
||||
*,
|
||||
title: Optional[str],
|
||||
goal: Optional[str],
|
||||
execution: Optional[str],
|
||||
focus_area_hint: Optional[str],
|
||||
focus_areas_context: Optional[Sequence[Tuple[int, bool]]] = None,
|
||||
form_ctx: ExerciseFormAiPromptContext,
|
||||
want_summary: bool,
|
||||
want_skills: bool,
|
||||
) -> Dict[str, Any]:
|
||||
key = _require_openrouter_key()
|
||||
|
||||
title = form_ctx.title
|
||||
goal = form_ctx.goal
|
||||
execution = form_ctx.execution
|
||||
focus_area_hint = form_ctx.focus_hint
|
||||
focus_areas_context = form_ctx.focus_area_tuples()
|
||||
|
||||
g_plain = strip_html_to_plain(goal)
|
||||
e_plain = strip_html_to_plain(execution)
|
||||
if not (g_plain.strip() or e_plain.strip()):
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ 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_context import ExerciseFormAiPromptContext
|
||||
from ai_prompt_job import resolve_exercise_form_variables
|
||||
from ai_prompt_runtime import render_ai_prompt_template_for_row
|
||||
from db import get_cursor, get_db, r2d
|
||||
from prompt_resolver import exercise_placeholder_catalog
|
||||
|
|
@ -60,17 +61,8 @@ class AiPromptUpdateBody(BaseModel):
|
|||
openrouter_model: Optional[str] = Field(None, max_length=200)
|
||||
|
||||
|
||||
class AiPromptPreviewFocus(BaseModel):
|
||||
focus_area_id: int = Field(..., ge=1)
|
||||
is_primary: Optional[bool] = False
|
||||
|
||||
|
||||
class AiPromptPreviewBody(BaseModel):
|
||||
title: Optional[str] = ""
|
||||
goal: Optional[str] = None
|
||||
execution: Optional[str] = None
|
||||
focus_hint: Optional[str] = None
|
||||
focus_areas_context: Optional[List[AiPromptPreviewFocus]] = None
|
||||
class AiPromptPreviewBody(ExerciseFormAiPromptContext):
|
||||
"""Preview-POST: gleiche Felder wie ExerciseFormAiPromptContext (focus_hint, nicht focus_area_hint)."""
|
||||
|
||||
|
||||
@router.get("/api/admin/ai-prompts/catalog/placeholders")
|
||||
|
|
@ -228,8 +220,7 @@ def preview_ai_prompt(prompt_id: int, body: AiPromptPreviewBody, session: dict =
|
|||
warn: Optional[str] = None
|
||||
if slug in ("exercise_summary", "exercise_skill_suggestions"):
|
||||
try:
|
||||
pf_ctx = ExerciseFormAiPromptContext.model_validate(body.model_dump())
|
||||
vars_map = resolve_exercise_form_variables(cur, slug, pf_ctx)
|
||||
vars_map = resolve_exercise_form_variables(cur, slug, body)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||
elif slug == "pipeline":
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ from tenant_context import TenantContext, get_tenant_context, get_tenant_context
|
|||
from media_storage import get_effective_media_root, library_storage_key, path_under_media_root
|
||||
from media_rights import assert_rights_for_exercise_link, validate_rights_declaration, write_rights_declaration, update_rights_quick_fields
|
||||
from media_legal_hold import assert_not_under_legal_hold
|
||||
from exercise_ai import run_exercise_ai_suggestion
|
||||
from ai_prompt_context import ExerciseFormAiFocusRow, ExerciseFormAiPromptContext
|
||||
from ai_prompt_job import run_exercise_form_ai_suggestion
|
||||
|
||||
from exercise_rich_text import (
|
||||
RICH_HTML_EXERCISE_FIELDS,
|
||||
|
|
@ -358,11 +359,8 @@ class ExerciseMediaFromAsset(BaseModel):
|
|||
media_type: Optional[str] = None
|
||||
|
||||
|
||||
class ExerciseAiFocusCtx(BaseModel):
|
||||
"""Fokusbereich fuer Skill-Kataloggewichte (Migration 068 ai_skill_retrieval_profiles)."""
|
||||
|
||||
focus_area_id: int = Field(..., ge=1)
|
||||
is_primary: Optional[bool] = False
|
||||
class ExerciseAiFocusCtx(ExerciseFormAiFocusRow):
|
||||
"""Alias fuer OpenAPI — identisch zu ExerciseFormAiFocusRow."""
|
||||
|
||||
|
||||
class ExerciseAiSuggestBody(BaseModel):
|
||||
|
|
@ -370,7 +368,7 @@ class ExerciseAiSuggestBody(BaseModel):
|
|||
goal: Optional[str] = Field(None, max_length=64000)
|
||||
execution: Optional[str] = Field(None, max_length=128000)
|
||||
focus_area_hint: Optional[str] = Field(None, max_length=1200)
|
||||
focus_areas_context: Optional[list[ExerciseAiFocusCtx]] = Field(
|
||||
focus_areas_context: Optional[list[ExerciseFormAiFocusRow]] = Field(
|
||||
None,
|
||||
description="Optionale Reihenfolge Primär zuerst; steuert Katalogpriorisierung",
|
||||
)
|
||||
|
|
@ -383,6 +381,15 @@ class ExerciseAiSuggestBody(BaseModel):
|
|||
raise ValueError("Mindestens include_summary oder include_skills aktivieren.")
|
||||
return self
|
||||
|
||||
def to_form_context(self) -> ExerciseFormAiPromptContext:
|
||||
return ExerciseFormAiPromptContext.from_api_suggest(
|
||||
title=self.title,
|
||||
goal=self.goal,
|
||||
execution=self.execution,
|
||||
focus_area_hint=self.focus_area_hint,
|
||||
focus_areas_context=self.focus_areas_context,
|
||||
)
|
||||
|
||||
|
||||
class ExerciseAiRegenerateBody(BaseModel):
|
||||
"""Welche Artefakte neu angefragt werden sollen."""
|
||||
|
|
@ -2306,17 +2313,9 @@ def exercise_ai_suggest_endpoint(
|
|||
_ = tenant.profile_id
|
||||
with get_db() as conn:
|
||||
cur = get_cursor(conn)
|
||||
fctx = None
|
||||
if body.focus_areas_context:
|
||||
fctx = [(x.focus_area_id, bool(x.is_primary)) for x in body.focus_areas_context]
|
||||
|
||||
payload = run_exercise_ai_suggestion(
|
||||
payload = run_exercise_form_ai_suggestion(
|
||||
cur,
|
||||
title=(body.title or "").strip(),
|
||||
goal=body.goal,
|
||||
execution=body.execution,
|
||||
focus_area_hint=(body.focus_area_hint or "").strip() or None,
|
||||
focus_areas_context=fctx,
|
||||
body.to_form_context(),
|
||||
want_summary=body.include_summary,
|
||||
want_skills=body.include_skills,
|
||||
)
|
||||
|
|
@ -2344,13 +2343,16 @@ def exercise_ai_regenerate_endpoint(
|
|||
focus = _focus_area_hint_from_detail(exercise)
|
||||
fctx = _focus_areas_ai_ctx_from_detail(exercise)
|
||||
|
||||
payload = run_exercise_ai_suggestion(
|
||||
cur,
|
||||
ctx = ExerciseFormAiPromptContext.from_focus_tuples(
|
||||
title=str(exercise.get("title") or "").strip(),
|
||||
goal=exercise.get("goal"),
|
||||
execution=exercise.get("execution"),
|
||||
focus_area_hint=focus or None,
|
||||
focus_areas_context=fctx or None,
|
||||
focus_hint=focus or None,
|
||||
focus_tuples=fctx or None,
|
||||
)
|
||||
payload = run_exercise_form_ai_suggestion(
|
||||
cur,
|
||||
ctx,
|
||||
want_summary=want_summary,
|
||||
want_skills=want_skills,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Shinkan Jinkendo Version Information
|
||||
|
||||
APP_VERSION = "0.8.161"
|
||||
APP_VERSION = "0.8.162"
|
||||
BUILD_DATE = "2026-05-31"
|
||||
DB_SCHEMA_VERSION = "20260531070"
|
||||
|
||||
|
|
@ -20,13 +20,14 @@ MODULE_VERSIONS = {
|
|||
"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.3", # Migration 070: openrouter_model; PUT/Liste/Detail
|
||||
"ai_prompt_job": "0.1.0", # ExerciseFormAiPromptContext, resolve_exercise_form_variables — P1 Kontext-Schnittstelle
|
||||
"ai_prompt_job": "0.2.0", # run_exercise_form_ai_suggestion; Kontext in ai_prompt_context
|
||||
"ai_prompt_context": "0.1.0", # ExerciseFormAiPromptContext — Formularfelder fuer Uebungs-Prompts
|
||||
"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.3", # exercise_ai: OpenRouter-Modell pro Prompt-Slug; Response models_by_slug
|
||||
"exercises": "2.31.4", # ai/suggest + regenerate: ExerciseFormAiPromptContext via run_exercise_form_ai_suggestion
|
||||
"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
|
||||
|
|
@ -41,6 +42,14 @@ MODULE_VERSIONS = {
|
|||
}
|
||||
|
||||
CHANGELOG = [
|
||||
{
|
||||
"version": "0.8.162",
|
||||
"date": "2026-05-31",
|
||||
"changes": [
|
||||
"KI Prompt P1 abgeschlossen: ai_prompt_context (Formular-Kontext), run_exercise_form_ai_suggestion als gemeinsamer Einstieg;",
|
||||
"POST /exercises/ai/suggest und regenerate bauen ExerciseFormAiPromptContext; Admin-Vorschau nutzt dasselbe Modell.",
|
||||
],
|
||||
},
|
||||
{
|
||||
"version": "0.8.161",
|
||||
"date": "2026-05-31",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Shinkan Jinkendo – Entwicklungsstand & Handover
|
||||
|
||||
**Stand:** 2026-05-30
|
||||
**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`).
|
||||
**Stand:** 2026-05-31
|
||||
**App-Version / DB-Schema:** App **`0.8.162`** (KI Prompt P1: gemeinsamer Formular-Kontext `ExerciseFormAiPromptContext`, `run_exercise_form_ai_suggestion`); Zielarchitektur **`.claude/docs/technical/AI_PROMPT_TARGET_ARCHITECTURE.md`**. DB: **`backend/version.py`** → `DB_SCHEMA_VERSION` (aktuell `20260531070`).
|
||||
|
||||
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,12 +89,12 @@ 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.160**)
|
||||
### 2.8 KI Assistenz Übungen & Skill-Katalog-Retrieval (Stand **0.8.162**)
|
||||
|
||||
- **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 / 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)
|
||||
- **Kontext / Job:** **`ai_prompt_context`** — **`ExerciseFormAiPromptContext`** (Titel, Ziel, Durchführung, Fokus — ein Modell fuer Admin-Vorschau und Übungs-API); **`ai_prompt_job`** — **`run_exercise_form_ai_suggestion`**, **`resolve_exercise_form_variables`**; **`ai_prompt_runtime`** — Laden/Rendern aus DB; **`exercise_ai`** — OpenRouter nach Rendern
|
||||
- **DB:** Migration **`067`** **`ai_prompts`**; **`069`** **`default_template`**; **`068`** **`ai_skill_retrieval_profiles`**; **`070`** **`openrouter_model`** (optional pro Prompt, Fallback **`OPENROUTER_MODEL`**)
|
||||
- **`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`**
|
||||
- **Diagnose bei leerem Dialog / Fehlern:** Umgebungsvariable **`SHINKAN_AI_DEBUG=1`** auf der API; in den Logs dann **`AI_DEBUG`** (`shinkan.exercise_ai`) und **`[AI_DEBUG/openrouter]`** (`shinkan.openrouter`) mit Prompt-Längen, Token-Zahlen und ggf. JSON-Parse-Anfang
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user