160 lines
4.6 KiB
Python
160 lines
4.6 KiB
Python
"""
|
|
Variable resolver for prompt templates.
|
|
[AC-AISVC-56] Built-in and custom variable replacement engine.
|
|
"""
|
|
|
|
import logging
|
|
import re
|
|
from datetime import datetime
|
|
from typing import Any
|
|
|
|
from app.core.middleware import PROMPT_PROTECTED_VARIABLES
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
VARIABLE_PATTERN = re.compile(r"\{\{(\w+)\}\}")
|
|
|
|
BUILTIN_VARIABLES = {
|
|
"persona_name": "小N",
|
|
"current_time": lambda: datetime.now().strftime("%Y-%m-%d %H:%M"),
|
|
"channel_type": "default",
|
|
"tenant_name": "平台",
|
|
"session_id": "",
|
|
"available_tools": "",
|
|
"query": "",
|
|
"history": "",
|
|
"internal_protocol": "",
|
|
"output_contract": "",
|
|
}
|
|
|
|
|
|
class VariableResolver:
|
|
"""
|
|
[AC-AISVC-56] Variable replacement engine for prompt templates.
|
|
|
|
Supports:
|
|
- Built-in variables: persona_name, current_time, channel_type, tenant_name, session_id
|
|
- Custom variables: defined in template with defaults
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
channel_type: str = "default",
|
|
tenant_name: str = "平台",
|
|
session_id: str = "",
|
|
):
|
|
self._context = {
|
|
"channel_type": channel_type,
|
|
"tenant_name": tenant_name,
|
|
"session_id": session_id,
|
|
}
|
|
|
|
def resolve(
|
|
self,
|
|
template: str,
|
|
variables: list[dict[str, Any]] | None = None,
|
|
extra_context: dict[str, Any] | None = None,
|
|
) -> str:
|
|
"""
|
|
Resolve all {{variable}} placeholders in the template.
|
|
|
|
Args:
|
|
template: Template string with {{variable}} placeholders
|
|
variables: Custom variable definitions from template
|
|
extra_context: Additional context for resolution
|
|
|
|
Returns:
|
|
Template with all variables replaced
|
|
"""
|
|
context = self._build_context(variables, extra_context)
|
|
|
|
def replace_var(match: re.Match) -> str:
|
|
var_name = match.group(1)
|
|
if var_name in context:
|
|
value = context[var_name]
|
|
if callable(value):
|
|
return str(value())
|
|
return str(value)
|
|
logger.warning(f"Unknown variable in template: {var_name}")
|
|
return match.group(0)
|
|
|
|
resolved = VARIABLE_PATTERN.sub(replace_var, template)
|
|
return resolved
|
|
|
|
def _build_context(
|
|
self,
|
|
variables: list[dict[str, Any]] | None,
|
|
extra_context: dict[str, Any] | None,
|
|
) -> dict[str, Any]:
|
|
"""Build the complete context for variable resolution."""
|
|
context = {}
|
|
|
|
for key, value in BUILTIN_VARIABLES.items():
|
|
if key in self._context:
|
|
context[key] = self._context[key]
|
|
else:
|
|
context[key] = value
|
|
|
|
if variables:
|
|
for var in variables:
|
|
name = var.get("name")
|
|
default = var.get("default", "")
|
|
if not name:
|
|
continue
|
|
if name in PROMPT_PROTECTED_VARIABLES:
|
|
logger.warning(
|
|
"Protected prompt variable '%s' cannot be overridden by template defaults",
|
|
name,
|
|
)
|
|
continue
|
|
context[name] = default
|
|
|
|
if extra_context:
|
|
# Runtime/system context has the highest priority, including protected vars.
|
|
context.update(extra_context)
|
|
|
|
return context
|
|
|
|
def extract_variables(self, template: str) -> list[str]:
|
|
"""
|
|
Extract all variable names from a template.
|
|
|
|
Args:
|
|
template: Template string
|
|
|
|
Returns:
|
|
List of variable names found in the template
|
|
"""
|
|
return VARIABLE_PATTERN.findall(template)
|
|
|
|
def validate_variables(
|
|
self,
|
|
template: str,
|
|
defined_variables: list[dict[str, Any]] | None,
|
|
) -> dict[str, Any]:
|
|
"""
|
|
Validate that all variables in template are defined.
|
|
|
|
Args:
|
|
template: Template string
|
|
defined_variables: Variables defined in template metadata
|
|
|
|
Returns:
|
|
Dict with 'valid' boolean and 'missing' list
|
|
"""
|
|
used_vars = set(self.extract_variables(template))
|
|
builtin_vars = set(BUILTIN_VARIABLES.keys())
|
|
|
|
defined_names = set()
|
|
if defined_variables:
|
|
defined_names = {v.get("name") for v in defined_variables if v.get("name")}
|
|
|
|
available_vars = builtin_vars | defined_names
|
|
missing = used_vars - available_vars
|
|
|
|
return {
|
|
"valid": len(missing) == 0,
|
|
"missing": list(missing),
|
|
"used_variables": list(used_vars),
|
|
}
|