feat: separate prompt opening and entry behavior
This commit is contained in:
@@ -366,6 +366,7 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
tools=[tool],
|
||||
startup={
|
||||
"execution_mode": "sequential",
|
||||
"entry_mode": "generate",
|
||||
"opening_message": {
|
||||
"title": "重要提示",
|
||||
"message": "请确认已阅读。",
|
||||
@@ -434,6 +435,7 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertEqual(called_tool_ids, [])
|
||||
self.assertEqual(client_calls[0][0], "show_message")
|
||||
self.assertFalse(client_calls[0][1]["dismissible"])
|
||||
self.assertTrue(client_calls[0][2]["interrupt_on_result"])
|
||||
self.assertTrue(
|
||||
any(
|
||||
isinstance(frame, TTSSpeakFrame)
|
||||
@@ -478,6 +480,157 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
1,
|
||||
)
|
||||
|
||||
async def test_opening_confirmation_can_wait_for_user_afterwards(self):
|
||||
cfg = AssistantConfig(
|
||||
type="prompt",
|
||||
greeting="请先确认",
|
||||
startup={
|
||||
"entry_mode": "wait_user",
|
||||
"opening_message": {
|
||||
"title": "重要提示",
|
||||
"message": "请确认已阅读。",
|
||||
},
|
||||
},
|
||||
)
|
||||
brain = build_brain(cfg)
|
||||
queued = []
|
||||
input_states = []
|
||||
|
||||
async def queue_frame(frame):
|
||||
queued.append(frame)
|
||||
|
||||
class FakeClientTools:
|
||||
async def call(self, *_args, **_kwargs):
|
||||
return {"status": "ok", "data": {"action": "confirmed"}}
|
||||
|
||||
await brain.setup(
|
||||
cfg,
|
||||
BrainRuntime(
|
||||
context=LLMContext(messages=[]),
|
||||
llm=FakeLLM(),
|
||||
queue_frame=queue_frame,
|
||||
set_system_prompt=lambda _prompt: None,
|
||||
set_tools=lambda _tools: None,
|
||||
call_end=FakeCallEnd(),
|
||||
client_tools=FakeClientTools(),
|
||||
set_input_enabled=input_states.append,
|
||||
),
|
||||
)
|
||||
|
||||
await brain.on_connected(greeting_pending=False)
|
||||
await brain.on_client_ready()
|
||||
|
||||
self.assertEqual(input_states, [False, True])
|
||||
self.assertFalse(any(isinstance(frame, LLMRunFrame) for frame in queued))
|
||||
|
||||
async def test_prompt_generate_entry_waits_for_shared_greeting_playback(self):
|
||||
cfg = AssistantConfig(
|
||||
type="prompt",
|
||||
greeting="固定开场白",
|
||||
startup={"entry_mode": "generate"},
|
||||
)
|
||||
brain = build_brain(cfg)
|
||||
queued = []
|
||||
input_states = []
|
||||
|
||||
async def queue_frame(frame):
|
||||
queued.append(frame)
|
||||
|
||||
await brain.setup(
|
||||
cfg,
|
||||
BrainRuntime(
|
||||
context=LLMContext(messages=[]),
|
||||
llm=FakeLLM(),
|
||||
queue_frame=queue_frame,
|
||||
set_system_prompt=lambda _prompt: None,
|
||||
set_tools=lambda _tools: None,
|
||||
call_end=FakeCallEnd(),
|
||||
set_input_enabled=input_states.append,
|
||||
),
|
||||
)
|
||||
|
||||
await brain.on_connected(greeting_pending=True)
|
||||
await brain.on_client_ready()
|
||||
self.assertEqual(input_states, [False])
|
||||
self.assertFalse(any(isinstance(frame, LLMRunFrame) for frame in queued))
|
||||
|
||||
await brain.on_greeting_finished()
|
||||
self.assertEqual(input_states, [False, True])
|
||||
self.assertEqual(
|
||||
sum(isinstance(frame, LLMRunFrame) for frame in queued),
|
||||
1,
|
||||
)
|
||||
|
||||
async def test_opening_confirmation_can_keep_speech_playing_before_entry(self):
|
||||
cfg = AssistantConfig(
|
||||
type="prompt",
|
||||
greeting="必须完整播放的开场白",
|
||||
startup={
|
||||
"entry_mode": "generate",
|
||||
"opening_message": {
|
||||
"title": "重要提示",
|
||||
"message": "请确认已阅读。",
|
||||
"skip_speech_on_confirm": False,
|
||||
},
|
||||
},
|
||||
)
|
||||
brain = build_brain(cfg)
|
||||
queued = []
|
||||
input_states = []
|
||||
message_displayed = asyncio.Event()
|
||||
|
||||
class TrackedCallEnd(FakeCallEnd):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.playback_completion = None
|
||||
|
||||
def track_speech(self):
|
||||
self.tracked_speeches += 1
|
||||
self.playback_completion = asyncio.get_running_loop().create_future()
|
||||
return self.playback_completion
|
||||
|
||||
class FakeClientTools:
|
||||
async def call(self, *_args, **options):
|
||||
self.options = options
|
||||
message_displayed.set()
|
||||
return {"status": "ok", "data": {"action": "confirmed"}}
|
||||
|
||||
async def queue_frame(frame):
|
||||
queued.append(frame)
|
||||
|
||||
call_end = TrackedCallEnd()
|
||||
client_tools = FakeClientTools()
|
||||
await brain.setup(
|
||||
cfg,
|
||||
BrainRuntime(
|
||||
context=LLMContext(messages=[]),
|
||||
llm=FakeLLM(),
|
||||
queue_frame=queue_frame,
|
||||
set_system_prompt=lambda _prompt: None,
|
||||
set_tools=lambda _tools: None,
|
||||
call_end=call_end,
|
||||
client_tools=client_tools,
|
||||
set_input_enabled=input_states.append,
|
||||
),
|
||||
)
|
||||
|
||||
await brain.on_connected(greeting_pending=False)
|
||||
opening_task = asyncio.create_task(brain.on_client_ready())
|
||||
await message_displayed.wait()
|
||||
await asyncio.sleep(0)
|
||||
|
||||
self.assertFalse(client_tools.options["interrupt_on_result"])
|
||||
self.assertFalse(opening_task.done())
|
||||
self.assertFalse(any(isinstance(frame, LLMRunFrame) for frame in queued))
|
||||
|
||||
call_end.playback_completion.set_result(None)
|
||||
await opening_task
|
||||
self.assertEqual(input_states, [False, True])
|
||||
self.assertEqual(
|
||||
sum(isinstance(frame, LLMRunFrame) for frame in queued),
|
||||
1,
|
||||
)
|
||||
|
||||
async def test_required_opening_failure_keeps_input_blocked_and_ends_call(self):
|
||||
cfg = AssistantConfig(
|
||||
type="prompt",
|
||||
|
||||
@@ -109,6 +109,8 @@ class StartupActionValidationTests(unittest.IsolatedAsyncioTestCase):
|
||||
|
||||
self.assertEqual(body.startup.actions, [])
|
||||
self.assertEqual(body.startup.opening_message.confirm_label, "确认")
|
||||
self.assertTrue(body.startup.opening_message.skip_speech_on_confirm)
|
||||
self.assertEqual(body.startup.entry_mode, "generate")
|
||||
|
||||
async def test_startup_tool_does_not_need_conversation_binding(self):
|
||||
tool = SimpleNamespace(
|
||||
|
||||
Reference in New Issue
Block a user