shinkan-jinkendo/backend/prompt_resolver.py
Lars 9f4678f418
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 36s
Test Suite / playwright-tests (push) Successful in 1m16s
Implement exercise_instruction_rewrite for AI Prompt System
- Added `exercise_instruction_rewrite` functionality to enhance AI-generated instructions, incorporating fields for goal, execution, preparation, and trainer notes.
- Updated `ExerciseFormAiPromptContext` to include new fields and methods for instruction handling.
- Enhanced the `run_exercise_form_ai_suggestion` function to support instruction rewriting and validation.
- Modified API endpoints and frontend components to integrate instruction features, including a new button for AI instruction revision.
- Incremented application version to 0.8.163 and updated changelog to reflect these changes, including migration details and new functionality.
2026-05-22 18:53:36 +02:00

141 lines
4.8 KiB
Python

"""
Mustache-aehnliche Platzhalter {{schluessel}} fuer KI-Templates aus ai_prompts.
Kein Vereinsbezug — reine Textersetzung; Aufrufe aus exercise_ai und Admin-Vorschau.
"""
from __future__ import annotations
import re
from dataclasses import dataclass
from typing import Dict, List, Mapping
_PLACEHOLDER_RE = re.compile(r"\{\{\s*([a-zA-Z0-9_]+)\s*\}\}")
def _placeholder_pattern_for_key(key: str) -> re.Pattern[str]:
return re.compile(r"\{\{\s*" + re.escape(str(key).strip()) + r"\s*\}\}")
@dataclass(frozen=True)
class MustacheRenderResult:
"""Ergebnis von render_mustache_template."""
text: str
keys_in_template: List[str]
keys_substituted: List[str]
keys_missing_variables: List[str]
placeholders_remaining: List[str]
def extract_mustache_keys(template: str) -> List[str]:
"""Platzhalter-Namen in Vorkommensreihenfolge, ohne erstes Duplikat."""
seen: set[str] = set()
ordered: List[str] = []
for m in _PLACEHOLDER_RE.finditer(template or ""):
k = str(m.group(1) or "").strip()
if not k or k in seen:
continue
seen.add(k)
ordered.append(k)
return ordered
def render_mustache_template(template: str, variables: Mapping[str, str]) -> MustacheRenderResult:
"""
Ersetzt {{keys}} durch die passenden Strings.
Variablen, die fuer einen im Template genutzten Key fehlen, werden als Leerstring ersetzt.
Rueckgabe-liste keys_missing_variables: Keys, die im Template vorkommen, aber nicht als Map-Keys
uebergeben wurden (oder None-Wert entsprachen Leerung).
"""
tpl_in = template or ""
vars_norm: Dict[str, str] = {}
for k, v in variables.items():
vars_norm[str(k)] = "" if v is None else str(v)
keys_in = extract_mustache_keys(tpl_in)
missing_known: List[str] = []
out = tpl_in
substituted: List[str] = []
for key in keys_in:
pat = _placeholder_pattern_for_key(key)
repl = vars_norm.get(key)
if key not in vars_norm:
missing_known.append(key)
repl = ""
substituted.append(key)
out = pat.sub(repl, out)
still = extract_mustache_keys(out)
return MustacheRenderResult(
text=out,
keys_in_template=keys_in,
keys_substituted=substituted,
keys_missing_variables=missing_known,
placeholders_remaining=still,
)
def exercise_placeholder_catalog() -> dict:
"""
Statischer Platzhalter-Katalog fuer Uebungs-KI-Templates — deckt aktuelle Seeds ab.
(Erweiterung andere Kontexte: matrix/import folgen separat.)
"""
defs = [
{
"key": "exercise_title",
"placeholder": "{{exercise_title}}",
"description": "Titel der Uebung (oder Platzhalter, wenn leer).",
"used_by_slugs": ["exercise_summary", "exercise_skill_suggestions", "exercise_instruction_rewrite"],
},
{
"key": "exercise_focus_area",
"placeholder": "{{exercise_focus_area}}",
"description": "Fokuskontext (Text-Hinweis aus Formular, optional).",
"used_by_slugs": ["exercise_summary", "exercise_skill_suggestions", "exercise_instruction_rewrite"],
},
{
"key": "exercise_goal",
"placeholder": "{{exercise_goal}}",
"description": "Ziel aus dem Formular, als Plaintext ohne HTML-Zeichen.",
"used_by_slugs": ["exercise_summary", "exercise_skill_suggestions", "exercise_instruction_rewrite"],
},
{
"key": "exercise_execution",
"placeholder": "{{exercise_execution}}",
"description": "Durchfuehrung als Plaintext ohne HTML-Zeichen.",
"used_by_slugs": ["exercise_summary", "exercise_skill_suggestions", "exercise_instruction_rewrite"],
},
{
"key": "exercise_preparation",
"placeholder": "{{exercise_preparation}}",
"description": "Vorbereitung/Aufbau als Plaintext ohne HTML.",
"used_by_slugs": ["exercise_instruction_rewrite"],
},
{
"key": "exercise_trainer_notes",
"placeholder": "{{exercise_trainer_notes}}",
"description": "Trainer-Hinweise als Plaintext ohne HTML.",
"used_by_slugs": ["exercise_instruction_rewrite"],
},
{
"key": "skills_catalog",
"placeholder": "{{skills_catalog}}",
"description": (
"Gewichtete, kontextbezogene Liste aus dem Skill-Katalog (retrieval_profiles). "
"Nur fuer exercise_skill_suggestions."
),
"used_by_slugs": ["exercise_skill_suggestions"],
},
]
return {"context": "exercise", "placeholders": defs}
__all__ = [
"MustacheRenderResult",
"exercise_placeholder_catalog",
"extract_mustache_keys",
"render_mustache_template",
]