Add system-level dynamic variables support in session management. Implement methods to generate and apply built-in variables for current session time, UTC time, and timezone. Update documentation to reflect new variables and enhance tests for dynamic variable handling in the UI components.

This commit is contained in:
Xin Wang
2026-02-27 12:08:18 +08:00
parent 71cbfa2b48
commit 6178cc05bb
5 changed files with 242 additions and 21 deletions

View File

@@ -5,6 +5,7 @@ import hashlib
import json
import re
import time
from datetime import datetime, timezone
from enum import Enum
from typing import Optional, Dict, Any, List
from loguru import logger
@@ -32,6 +33,7 @@ _DYNAMIC_VARIABLE_KEY_RE = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]{0,63}$")
_DYNAMIC_VARIABLE_PLACEHOLDER_RE = re.compile(r"\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}")
_DYNAMIC_VARIABLE_MAX_ITEMS = 30
_DYNAMIC_VARIABLE_VALUE_MAX_CHARS = 1000
_SYSTEM_DYNAMIC_VARIABLE_KEYS = {"system__time", "system_utc", "system_timezone"}
class WsSessionState(str, Enum):
@@ -845,6 +847,16 @@ class Session:
rendered = _DYNAMIC_VARIABLE_PLACEHOLDER_RE.sub(_replace, str(template or ""))
return rendered, sorted(missing)
def _system_dynamic_variables(self) -> Dict[str, str]:
"""Build system-level dynamic variables for the current session timestamp."""
local_now = datetime.now().astimezone()
utc_now = local_now.astimezone(timezone.utc)
return {
"system__time": local_now.strftime("%Y-%m-%d %H:%M:%S"),
"system_utc": utc_now.strftime("%Y-%m-%d %H:%M:%S"),
"system_timezone": str(local_now.tzinfo or ""),
}
def _apply_dynamic_variables(
self,
metadata: Dict[str, Any],
@@ -859,7 +871,7 @@ class Session:
"""
merged = dict(metadata or {})
raw_dynamic_vars = client_metadata.get("dynamicVariables")
dynamic_vars: Dict[str, str] = {}
dynamic_vars: Dict[str, str] = self._system_dynamic_variables()
if raw_dynamic_vars is not None:
if not isinstance(raw_dynamic_vars, dict):
@@ -888,6 +900,9 @@ class Session:
f"'{raw_key}'. Expected ^[a-zA-Z_][a-zA-Z0-9_]{{0,63}}$"
),
}
if key in _SYSTEM_DYNAMIC_VARIABLE_KEYS:
# Reserved system variables are generated by server time context.
continue
if key in dynamic_vars:
return merged, {
"code": "protocol.dynamic_variables_invalid",
@@ -912,12 +927,6 @@ class Session:
template_keys.update(self._extract_dynamic_template_keys(merged.get("greeting")))
if not template_keys:
return merged, None
if raw_dynamic_vars is None:
missing = ", ".join(sorted(template_keys))
return merged, {
"code": "protocol.dynamic_variables_missing",
"message": f"Missing dynamic variables for placeholders: {missing}",
}
missing_keys = sorted([key for key in template_keys if key not in dynamic_vars])
if missing_keys:

View File

@@ -83,6 +83,10 @@ Rules:
- Max entries: 30
- Max value length: 1000 chars
- Placeholder format in `systemPrompt` and `greeting`: `{{variable_name}}`.
- Built-in system variables (always available): `{{system__time}}`, `{{system_utc}}`, `{{system_timezone}}`.
- `system__time`: current local time (`YYYY-MM-DD HH:mm:ss`)
- `system_utc`: current UTC time (`YYYY-MM-DD HH:mm:ss`)
- `system_timezone`: current local timezone
- Missing referenced placeholders reject `session.start` with `protocol.dynamic_variables_missing`.
- Invalid `dynamicVariables` payload rejects `session.start` with `protocol.dynamic_variables_invalid`.

View File

@@ -152,6 +152,10 @@
- key 正则:`^[a-zA-Z_][a-zA-Z0-9_]{0,63}$`
- 最多 30 个变量,单个 value 最长 1000 字符。
- `systemPrompt` / `greeting` 中支持占位符语法:`{{variable_name}}`
- 内置系统变量(始终可用):`{{system__time}}``{{system_utc}}``{{system_timezone}}`
- `system__time`:会话开始时的本地时间(`YYYY-MM-DD HH:mm:ss`
- `system_utc`:会话开始时的 UTC 时间(`YYYY-MM-DD HH:mm:ss`
- `system_timezone`:会话开始时的本地时区
- 若模板引用了缺失变量,`session.start` 会被拒绝,错误码 `protocol.dynamic_variables_missing`
-`dynamicVariables` 结构/内容非法,`session.start` 会被拒绝,错误码 `protocol.dynamic_variables_invalid`

View File

@@ -94,3 +94,39 @@ def test_apply_dynamic_variables_no_placeholder_keeps_metadata():
assert error is None
assert resolved == metadata
def test_apply_dynamic_variables_supports_system_time_variables_without_client_payload():
session = _session()
metadata = {
"systemPrompt": "Now={{system__time}} utc={{system_utc}} tz={{system_timezone}}",
"greeting": "Clock {{system__time}}",
}
resolved, error = session._apply_dynamic_variables(metadata, {})
assert error is None
assert "{{system__time}}" not in resolved["systemPrompt"]
assert "{{system_utc}}" not in resolved["systemPrompt"]
assert "{{system_timezone}}" not in resolved["systemPrompt"]
assert "{{system__time}}" not in resolved["greeting"]
def test_apply_dynamic_variables_keeps_system_variables_reserved():
session = _session()
metadata = {
"systemPrompt": "Clock={{system__time}} Name={{customer_name}}",
"greeting": "Hi {{customer_name}}",
}
client_metadata = {
"dynamicVariables": {
"system__time": "manual_override",
"customer_name": "Alice",
}
}
resolved, error = session._apply_dynamic_variables(metadata, client_metadata)
assert error is None
assert "manual_override" not in resolved["systemPrompt"]
assert "Alice" in resolved["systemPrompt"]