Add dynamic variable support to Assistant model and related components

- Introduce dynamic variable definitions in AssistantConfig and Assistant models, allowing for flexible prompt customization.
- Implement validation for dynamic variable names and types in the schema.
- Update backend services and routes to handle dynamic variables in assistant configurations and runtime processing.
- Enhance frontend components to support dynamic variable definitions, including a new editor for managing variables.
- Add tests to ensure proper functionality and validation of dynamic variables in various scenarios.
This commit is contained in:
Xin Wang
2026-07-12 23:42:56 +08:00
parent 7c9a18c806
commit deaf3d7730
21 changed files with 1396 additions and 12 deletions

View File

@@ -308,6 +308,41 @@ class VisionCaptureProcessor(FrameProcessor):
await self.push_frame(frame, direction)
class RealtimeDynamicVariableProcessor(FrameProcessor):
"""Keep realtime system turn/history variables current between responses."""
def __init__(self, brain: Brain, cfg: AssistantConfig, realtime):
super().__init__()
self._brain = brain
self._cfg = cfg
self._realtime = realtime
async def _refresh_instructions(self) -> None:
update = getattr(self._realtime, "update_instructions", None)
if callable(update):
await update(self._brain.system_prompt(self._cfg))
async def process_frame(self, frame, direction: FrameDirection):
await super().process_frame(frame, direction)
if isinstance(frame, OutputTransportMessageUrgentFrame):
message = frame.message
if isinstance(message, dict):
event_type = message.get("type")
if event_type == "transcript" and message.get("role") == "user":
content = str(message.get("content") or "").strip()
if content:
self._brain.record_user_message(content)
await self._refresh_instructions()
elif event_type == "assistant-text-end":
await self._brain.on_assistant_text_end(
str(message.get("turn_id") or ""),
str(message.get("content") or ""),
bool(message.get("interrupted", False)),
)
await self._refresh_instructions()
await self.push_frame(frame, direction)
class RealtimeTextInputProcessor(FrameProcessor):
"""Route text input directly to a realtime service without cascade semantics."""
@@ -709,6 +744,7 @@ async def run_pipeline(
assistant_name=cfg.name,
channel=channel,
runtime_mode=cfg.runtimeMode,
session_id=cfg.conversation_id or None,
)
pipeline = Pipeline(
[
@@ -911,6 +947,7 @@ async def run_realtime_pipeline(
instructions=brain.system_prompt(cfg),
)
text_input = RealtimeTextInputProcessor()
dynamic_variables = RealtimeDynamicVariableProcessor(brain, cfg, realtime)
greeting = await brain.greeting(cfg)
recorder = await ConversationRecorder.start(
@@ -918,12 +955,14 @@ async def run_realtime_pipeline(
assistant_name=cfg.name,
channel=channel,
runtime_mode=cfg.runtimeMode,
session_id=cfg.conversation_id or None,
)
pipeline = Pipeline(
[
transport.input(),
text_input,
realtime,
dynamic_variables,
ConversationHistoryProcessor(recorder),
transport.output(),
]

View File

@@ -289,6 +289,12 @@ class StepFunRealtimeService(AIService):
wait_until_ready=False,
)
async def update_instructions(self, instructions: str) -> None:
"""Refresh model instructions without rebuilding the realtime session."""
self._instructions = instructions
if self._session_ready.is_set():
await self._send_session_update()
async def _send_event(
self, payload: dict[str, Any], *, wait_until_ready: bool = True
) -> None: