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:
@@ -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(),
|
||||
]
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user