Kairo-Jinkendo/backend/prompt_validation.py
Lars 53047b6c16
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 18s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / compose-smoke (push) Has been skipped
Test Suite / k6 /api/health Baseline (push) Successful in 18s
Test Suite / playwright-smoke (push) Successful in 24s
AP0.4: Feature-, Prompt- und Config-Registry mit Single-Render und Entitlements-Features.
Migration 005, Registry-Sync, Placeholder-Validation, capability-geschuetzte API, Tests und Abschlussbericht v0.1.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-04 23:29:02 +02:00

82 lines
2.5 KiB
Python

"""Placeholder extraction and validation for prompt templates."""
from __future__ import annotations
import re
from typing import Any
from placeholder_registry import load_placeholders_for_context
PLACEHOLDER_PATTERN = re.compile(r"\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}")
class PlaceholderValidationError(Exception):
def __init__(
self,
message: str,
*,
missing: list[str] | None = None,
type_errors: list[str] | None = None,
unknown: list[str] | None = None,
):
super().__init__(message)
self.missing = missing or []
self.type_errors = type_errors or []
self.unknown = unknown or []
def extract_placeholders(template: str) -> set[str]:
return set(PLACEHOLDER_PATTERN.findall(template))
def _check_value_type(value: Any, value_type: str) -> bool:
if value_type == "string":
return isinstance(value, str)
if value_type == "number":
return isinstance(value, (int, float)) and not isinstance(value, bool)
if value_type == "boolean":
return isinstance(value, bool)
if value_type == "object":
return isinstance(value, dict)
if value_type == "array":
return isinstance(value, list)
if value_type == "json":
return True
return True
def validate_placeholder_input(
*,
template: str,
context_kind: str,
values: dict[str, Any],
) -> dict[str, Any]:
"""Validate input values against registry; return metadata including unknown placeholders."""
registry = {p["placeholder_key"]: p for p in load_placeholders_for_context(context_kind)}
used = extract_placeholders(template)
missing: list[str] = []
type_errors: list[str] = []
unknown = sorted(key for key in used if key not in registry)
for key, meta in registry.items():
if meta["required"] and key not in values:
missing.append(key)
if key in values and not _check_value_type(values[key], meta["value_type"]):
type_errors.append(f"{key}: expected {meta['value_type']}")
if missing or type_errors:
raise PlaceholderValidationError(
"Placeholder validation failed",
missing=missing,
type_errors=type_errors,
unknown=unknown,
)
return {
"used_placeholders": sorted(used),
"unknown_placeholders": unknown,
"optional_missing": sorted(
key for key in registry if not registry[key]["required"] and key not in values
),
}