feat: add prompt opening behavior modes
This commit is contained in:
@@ -132,6 +132,9 @@ class BaseBrain:
|
||||
async def on_greeting_finished(self) -> None:
|
||||
"""Continue startup after the shared greeting has actually played."""
|
||||
|
||||
async def on_interruption_processed(self) -> None:
|
||||
"""Observe that an output interruption reached assistant aggregation."""
|
||||
|
||||
def prepare_greeting_context(
|
||||
self,
|
||||
greeting: str,
|
||||
@@ -214,6 +217,8 @@ class Brain(Protocol):
|
||||
|
||||
async def on_greeting_finished(self) -> None: ...
|
||||
|
||||
async def on_interruption_processed(self) -> None: ...
|
||||
|
||||
def prepare_greeting_context(
|
||||
self,
|
||||
greeting: str,
|
||||
|
||||
@@ -81,7 +81,7 @@ class PromptBrain(BaseBrain):
|
||||
async def greeting(self, cfg: AssistantConfig) -> str:
|
||||
# The built-in opening Message owns the greeting so speech and the
|
||||
# client dialog can start as one atomic stage.
|
||||
if self._opening_message() is not None:
|
||||
if self._opening_mode() == "confirmation":
|
||||
return ""
|
||||
return self._render_greeting(cfg)
|
||||
|
||||
@@ -155,8 +155,19 @@ class PromptBrain(BaseBrain):
|
||||
|
||||
async def on_connected(self, *, greeting_pending: bool = False) -> None:
|
||||
self._greeting_pending = greeting_pending
|
||||
should_block_input = (
|
||||
self._has_opening_stage()
|
||||
or (
|
||||
greeting_pending
|
||||
and self._opening_mode() == "playback"
|
||||
)
|
||||
or (
|
||||
not greeting_pending
|
||||
and self._entry_mode() == "generate"
|
||||
)
|
||||
)
|
||||
if (
|
||||
(self._has_opening_stage() or self._entry_mode() == "generate")
|
||||
should_block_input
|
||||
and self._runtime is not None
|
||||
and self._runtime.set_input_enabled is not None
|
||||
):
|
||||
@@ -168,6 +179,16 @@ class PromptBrain(BaseBrain):
|
||||
self._greeting_pending = False
|
||||
await self._enter_prompt_if_ready()
|
||||
|
||||
async def on_interruption_processed(self) -> None:
|
||||
if (
|
||||
self._greeting_pending
|
||||
and self._opening_mode() == "interruptible"
|
||||
):
|
||||
# The user's input now owns the first Agent turn. Do not also queue
|
||||
# an empty-context entry reply when the interrupted greeting stops.
|
||||
self._entry_dispatched = True
|
||||
logger.debug("Prompt 可打断开场白已被用户输入接管")
|
||||
|
||||
async def on_client_ready(self) -> None:
|
||||
if self._output is not None:
|
||||
await self._output.mark_client_ready()
|
||||
@@ -180,7 +201,11 @@ class PromptBrain(BaseBrain):
|
||||
runtime = self._runtime
|
||||
if runtime is None:
|
||||
raise RuntimeError("PromptBrain 尚未初始化")
|
||||
opening_message = self._opening_message()
|
||||
opening_message = (
|
||||
self._opening_message()
|
||||
if self._opening_mode() == "confirmation"
|
||||
else None
|
||||
)
|
||||
opening_actions = self._startup_actions("opening")
|
||||
speech = (
|
||||
self._render_greeting(self._cfg).strip()
|
||||
@@ -238,6 +263,19 @@ class PromptBrain(BaseBrain):
|
||||
value = startup.get("opening_message", startup.get("openingMessage"))
|
||||
return value if isinstance(value, dict) else None
|
||||
|
||||
def _opening_mode(self) -> str:
|
||||
startup = self._cfg.startup if isinstance(self._cfg.startup, dict) else {}
|
||||
value = str(startup.get("opening_mode", startup.get("openingMode")) or "")
|
||||
if value in {"interruptible", "playback", "confirmation"}:
|
||||
return value
|
||||
# Before openingMode existed, an opening message meant confirmation;
|
||||
# ordinary Prompt greetings were interruptible.
|
||||
return (
|
||||
"confirmation"
|
||||
if self._opening_message() is not None
|
||||
else "interruptible"
|
||||
)
|
||||
|
||||
def _entry_mode(self) -> str:
|
||||
startup = self._cfg.startup if isinstance(self._cfg.startup, dict) else {}
|
||||
value = str(startup.get("entry_mode", startup.get("entryMode")) or "")
|
||||
@@ -245,7 +283,7 @@ class PromptBrain(BaseBrain):
|
||||
return value
|
||||
# Raw runtime configs saved before entryMode existed generated a first
|
||||
# reply after their opening confirmation. Keep that legacy behavior.
|
||||
return "generate" if self._opening_message() is not None else "wait_user"
|
||||
return "generate" if self._opening_mode() == "confirmation" else "wait_user"
|
||||
|
||||
async def _enter_prompt_if_ready(self) -> None:
|
||||
if (
|
||||
@@ -269,7 +307,7 @@ class PromptBrain(BaseBrain):
|
||||
self._opening_input_blocked = False
|
||||
|
||||
def _has_opening_stage(self) -> bool:
|
||||
return self._opening_message() is not None or bool(
|
||||
return self._opening_mode() == "confirmation" or bool(
|
||||
self._startup_actions("opening")
|
||||
)
|
||||
|
||||
@@ -322,12 +360,6 @@ class PromptBrain(BaseBrain):
|
||||
).strip(),
|
||||
),
|
||||
completion_policy=MESSAGE_CONFIRMATION,
|
||||
skip_speech_on_confirmation=bool(
|
||||
config.get(
|
||||
"skip_speech_on_confirm",
|
||||
config.get("skipSpeechOnConfirm", True),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
async def _publish_opening_outcome(
|
||||
|
||||
Reference in New Issue
Block a user