shinkan-jinkendo/backend/prompt_resolver.py
Lars 2148d0aa7f
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m16s
Update AI Prompt System and Admin API
- Incremented version to 1.1 and updated the status to reflect the implementation of core features including `ai_prompts`, `prompt_resolver`, and the Superadmin HTTP API.
- Documented the current API endpoints for managing AI prompts, including CRUD operations and preview functionality.
- Introduced a new placeholder catalog and preview capabilities for the Superadmin interface.
- Enhanced the backend with new functions for handling AI prompt templates and integrated them into the API.
- Updated frontend components to include navigation and routing for the new Admin AI Prompts page.
- Incremented application version to 0.8.158 and updated changelog to reflect these changes.
2026-05-22 11:02:02 +02:00

129 lines
4.2 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"],
},
{
"key": "exercise_focus_area",
"placeholder": "{{exercise_focus_area}}",
"description": "Fokuskontext (Text-Hinweis aus Formular, optional).",
"used_by_slugs": ["exercise_summary", "exercise_skill_suggestions"],
},
{
"key": "exercise_goal",
"placeholder": "{{exercise_goal}}",
"description": "Ziel aus dem Formular, als Plaintext ohne HTML-Zeichen.",
"used_by_slugs": ["exercise_summary", "exercise_skill_suggestions"],
},
{
"key": "exercise_execution",
"placeholder": "{{exercise_execution}}",
"description": "Durchfuehrung als Plaintext ohne HTML-Zeichen.",
"used_by_slugs": ["exercise_summary", "exercise_skill_suggestions"],
},
{
"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",
]