Kairo-Jinkendo/backend/prompt_rendering.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

173 lines
5.7 KiB
Python

"""Single-mode prompt rendering without LLM execution."""
from __future__ import annotations
import json
import re
from typing import Any
from db import get_connection
from prompt_registry import load_active_prompt_version, load_prompt_definition, load_template_steps
from prompt_validation import PLACEHOLDER_PATTERN, PlaceholderValidationError, validate_placeholder_input
from services.audit import log_audit
PLACEHOLDER_SUB = re.compile(r"\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}")
class PromptRenderError(Exception):
def __init__(self, message: str, *, code: str = "render_error"):
super().__init__(message)
self.code = code
def _render_template(template: str, values: dict[str, Any]) -> str:
def replacer(match: re.Match[str]) -> str:
key = match.group(1)
if key not in values:
return match.group(0)
value = values[key]
if isinstance(value, (dict, list)):
return json.dumps(value, ensure_ascii=False)
return str(value)
return PLACEHOLDER_SUB.sub(replacer, template)
def _resolve_template_body(version: dict) -> tuple[str, str]:
"""Return (template_body, source) where source is 'body' or 'step'."""
steps = load_template_steps(version["id"])
template_steps = [s for s in steps if s["step_type"] == "template" and s["template_body"]]
if template_steps:
step = template_steps[0]
return step["template_body"], f"step:{step['step_key']}"
if version.get("body"):
return version["body"], "body"
raise PromptRenderError("No template body or template step found", code="no_template")
def _write_execution_log(
*,
definition: dict,
version: dict,
status: str,
rendered_preview: str | None,
input_summary: dict | None,
error_message: str | None,
user_id: str | None,
) -> None:
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"""
INSERT INTO prompt_execution_logs (
prompt_definition_id, prompt_version_id, execution_mode,
status, rendered_preview, input_summary, error_message, created_by
)
VALUES (%s::uuid, %s::uuid, %s, %s, %s, %s::jsonb, %s, %s::uuid)
""",
(
definition["id"],
version["id"],
definition["execution_mode"],
status,
rendered_preview,
json.dumps(input_summary) if input_summary else None,
error_message,
user_id,
),
)
conn.commit()
finally:
conn.close()
def render_prompt_single(
prompt_key: str,
*,
values: dict[str, Any],
user_id: str | None = None,
log_execution: bool = True,
) -> dict[str, Any]:
definition = load_prompt_definition(prompt_key)
if not definition:
raise PromptRenderError(f"Unknown prompt: {prompt_key}", code="not_found")
if definition["status"] != "active":
raise PromptRenderError(f"Prompt not active: {prompt_key}", code="inactive")
execution_mode = definition["execution_mode"]
if execution_mode in ("pipeline", "workflow"):
raise PromptRenderError(
f"execution_mode '{execution_mode}' is not implemented in AP0.4",
code="mode_not_implemented",
)
version = load_active_prompt_version(definition)
if not version:
raise PromptRenderError("No active prompt version", code="no_version")
template_body, template_source = _resolve_template_body(version)
try:
validation = validate_placeholder_input(
template=template_body,
context_kind=definition["context_kind"],
values=values,
)
rendered = _render_template(template_body, values)
result = {
"prompt_key": prompt_key,
"version": version["version"],
"execution_mode": execution_mode,
"template_source": template_source,
"rendered_text": rendered,
"validation": validation,
}
if log_execution:
_write_execution_log(
definition=definition,
version=version,
status="success",
rendered_preview=rendered[:2000],
input_summary={"keys": sorted(values.keys())},
error_message=None,
user_id=user_id,
)
return result
except PlaceholderValidationError as exc:
if log_execution:
_write_execution_log(
definition=definition,
version=version,
status="error",
rendered_preview=None,
input_summary={"keys": sorted(values.keys())},
error_message=str(exc),
user_id=user_id,
)
log_audit(
"prompt.render.failed",
user_id=user_id,
details={
"prompt_key": prompt_key,
"missing": exc.missing,
"type_errors": exc.type_errors,
"unknown": exc.unknown,
},
)
raise PromptRenderError(str(exc), code="validation_failed") from exc
except PromptRenderError:
raise
except Exception as exc:
if log_execution:
_write_execution_log(
definition=definition,
version=version,
status="error",
rendered_preview=None,
input_summary={"keys": sorted(values.keys())},
error_message=str(exc),
user_id=user_id,
)
raise PromptRenderError(str(exc), code="render_error") from exc