All checks were successful
Deploy Development / deploy (push) Successful in 41s
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 1m14s
- Added `openrouter_model` field to the `ai_prompts` table, allowing for optional model overrides per prompt. - Updated the `exercise_ai` module to utilize the effective OpenRouter model based on prompt-specific settings, enhancing flexibility in AI interactions. - Enhanced the admin interface to support OpenRouter model configuration for prompts, improving usability for Superadmins. - Incremented application version to 0.8.161 and updated changelog to reflect these changes, including migration details and new functionality.
114 lines
3.3 KiB
Python
114 lines
3.3 KiB
Python
"""
|
|
Gemeinsame KI-Prompt-Laufzeit (Shinkan): DB-Lesezugriff ai_prompts + Kontext-Arten.
|
|
|
|
Bleibt ohne Import von exercise_ai (kein Zirkel). Domänen wie exercise_ai nutzen
|
|
load_ai_prompt_row und die Enum; Platzhalter bauen sie selbst oder über geteilte Builder.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from enum import Enum
|
|
from typing import Any, Dict, Mapping, Optional, Tuple
|
|
|
|
from prompt_resolver import MustacheRenderResult, render_mustache_template
|
|
|
|
_EXERCISE_AI_SLUGS = frozenset(
|
|
{
|
|
"exercise_summary",
|
|
"exercise_skill_suggestions",
|
|
}
|
|
)
|
|
|
|
|
|
class AiPromptContextKind(str, Enum):
|
|
"""
|
|
Logischer Kontext fuer Platzhalter/Builder — erweiterbar fuer Planung/Rahmen
|
|
ohne bestehende Slugs zu invalidieren.
|
|
"""
|
|
|
|
EXERCISE_FORM_AI = "exercise_form_ai"
|
|
|
|
|
|
def context_kind_for_slug(slug: str) -> Optional[AiPromptContextKind]:
|
|
"""Ordnet einen DB-Slug einer Kontext-Art zu, sofern registriert."""
|
|
s = (slug or "").strip().lower()
|
|
if s in _EXERCISE_AI_SLUGS:
|
|
return AiPromptContextKind.EXERCISE_FORM_AI
|
|
return None
|
|
|
|
|
|
def load_ai_prompt_row(cur, slug: str, *, active_only: bool = True) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
Laedt eine Zeile ai_prompts fuer Laufzeit-Orchestrierung.
|
|
|
|
active_only=True: inaktive Prompts werden wie fehlend behandelt (503 im Aufrufer).
|
|
"""
|
|
if active_only:
|
|
cur.execute(
|
|
"""
|
|
SELECT slug, display_name, template, output_format, active, openrouter_model
|
|
FROM ai_prompts
|
|
WHERE slug = %s AND active = true
|
|
""",
|
|
(slug,),
|
|
)
|
|
else:
|
|
cur.execute(
|
|
"""
|
|
SELECT slug, display_name, template, output_format, active, openrouter_model
|
|
FROM ai_prompts
|
|
WHERE slug = %s
|
|
""",
|
|
(slug,),
|
|
)
|
|
row = cur.fetchone()
|
|
if not row:
|
|
return None
|
|
d = dict(row)
|
|
if active_only and not d.get("active", True):
|
|
return None
|
|
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",
|
|
]
|