108 lines
4.0 KiB
Python
108 lines
4.0 KiB
Python
"""Single prompt execution entry. Templates live in the DB; LLM egress only via Privacy Gateway."""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
import placeholder_mvp # noqa: F401
|
|
import placeholder_system # noqa: F401 registers system keys
|
|
import privacy_placeholders
|
|
from db import get_db, row_to_dict
|
|
from entitlements import EntitlementError, check_feature_access, increment_feature_usage
|
|
from placeholders import PlaceholderError, resolve_template
|
|
from privacy_gateway import GatewayRequest, PrivacyGatewayError, complete, last_trace, public_trace
|
|
|
|
|
|
class EngineError(Exception):
|
|
def __init__(self, code: str, message: str, status_code: int = 400):
|
|
super().__init__(message)
|
|
self.code = code
|
|
self.message = message
|
|
self.status_code = status_code
|
|
|
|
|
|
def load_active_prompt(slug: str) -> dict:
|
|
with get_db() as conn:
|
|
row = row_to_dict(
|
|
conn.execute(
|
|
"SELECT * FROM ai_prompts WHERE slug = ? AND active = 1",
|
|
(slug,),
|
|
).fetchone()
|
|
)
|
|
if not row:
|
|
raise EngineError("prompt_missing", f"Prompt fehlt oder ist inaktiv: {slug}", 404)
|
|
return row
|
|
|
|
|
|
def preview_prompt(prompt: dict, context: dict[str, Any] | None = None) -> dict:
|
|
template = prompt.get("template") or ""
|
|
if prompt.get("prompt_type") != "base":
|
|
raise EngineError("prompt_type_not_ready", "Pipeline- und Workflow-Typen sind vorbereitet, aber noch ohne Executor-Stufen.")
|
|
try:
|
|
rendered = resolve_template(template, context or {})
|
|
privacy_tokens = privacy_placeholders.validate_tokens(template)
|
|
except PlaceholderError as exc:
|
|
raise EngineError(exc.code, exc.message) from exc
|
|
return {
|
|
"prompt_id": prompt["id"],
|
|
"slug": prompt["slug"],
|
|
"rendered": rendered,
|
|
"privacy_tokens": privacy_tokens,
|
|
"llm": False,
|
|
}
|
|
|
|
|
|
def user_source_text(dialogue_context: str) -> str:
|
|
parts = []
|
|
for line in (dialogue_context or "").splitlines():
|
|
if line.startswith("user:"):
|
|
parts.append(line[5:].lstrip())
|
|
return "\n".join(parts)
|
|
|
|
|
|
def execute_prompt(prompt: dict, profile_id: str, purpose: str, data_class: str, context: dict[str, Any] | None = None) -> dict:
|
|
preview = preview_prompt(prompt, context)
|
|
feature_id = prompt.get("required_feature") or "ai_calls"
|
|
try:
|
|
check_feature_access(profile_id, feature_id)
|
|
except EntitlementError as exc:
|
|
raise EngineError(exc.code, exc.message, exc.status_code) from exc
|
|
try:
|
|
ctx = context or {}
|
|
source_text = user_source_text(ctx.get("dialogue_context") or "")
|
|
extras = []
|
|
if purpose == "journal_generate":
|
|
extras = [part for part in (ctx.get("existing_text"), ctx.get("writing_profile")) if part]
|
|
elif purpose == "profile_review":
|
|
source_text = ctx.get("source_text") or ""
|
|
extras = [part for part in (ctx.get("review_package"),) if part]
|
|
if extras:
|
|
source_text = "\n".join(part for part in (source_text, *extras) if part)
|
|
elif not source_text:
|
|
source_text = "\n".join(
|
|
part for part in (ctx.get("existing_text"), ctx.get("writing_profile")) if part
|
|
)
|
|
result = complete(
|
|
GatewayRequest(
|
|
prompt_id=prompt["id"],
|
|
purpose=purpose,
|
|
data_class=data_class,
|
|
profile_id=profile_id,
|
|
payload={
|
|
"rendered": preview["rendered"],
|
|
"source_text": source_text,
|
|
"privacy_tokens": preview["privacy_tokens"],
|
|
"prompt_slug": prompt.get("slug"),
|
|
},
|
|
)
|
|
)
|
|
except PrivacyGatewayError as exc:
|
|
raise EngineError(exc.code, exc.message, exc.status_code) from exc
|
|
increment_feature_usage(profile_id, feature_id)
|
|
return {
|
|
**preview,
|
|
"llm": True,
|
|
"content": result.content,
|
|
"provider": result.provider,
|
|
"trace": public_trace(last_trace()),
|
|
}
|