feat: trigger prompt reply after opening confirmation
This commit is contained in:
@@ -10,7 +10,11 @@ from uuid import uuid4
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
from models import AssistantConfig
|
from models import AssistantConfig
|
||||||
from pipecat.adapters.schemas.function_schema import FunctionSchema
|
from pipecat.adapters.schemas.function_schema import FunctionSchema
|
||||||
from pipecat.frames.frames import OutputTransportMessageUrgentFrame, TTSSpeakFrame
|
from pipecat.frames.frames import (
|
||||||
|
LLMRunFrame,
|
||||||
|
OutputTransportMessageUrgentFrame,
|
||||||
|
TTSSpeakFrame,
|
||||||
|
)
|
||||||
from pipecat.processors.aggregators.llm_context import LLMContext
|
from pipecat.processors.aggregators.llm_context import LLMContext
|
||||||
from pipecat.processors.frame_processor import FrameProcessor
|
from pipecat.processors.frame_processor import FrameProcessor
|
||||||
from pipecat.services.llm_service import (
|
from pipecat.services.llm_service import (
|
||||||
@@ -165,6 +169,7 @@ class PromptBrain(BaseBrain):
|
|||||||
raise RuntimeError("PromptBrain 尚未初始化")
|
raise RuntimeError("PromptBrain 尚未初始化")
|
||||||
opening_message = self._opening_message()
|
opening_message = self._opening_message()
|
||||||
opening_actions = self._startup_actions("opening")
|
opening_actions = self._startup_actions("opening")
|
||||||
|
generate_after_confirmation = opening_message is not None
|
||||||
speech = (
|
speech = (
|
||||||
self._render_greeting(self._cfg).strip()
|
self._render_greeting(self._cfg).strip()
|
||||||
if opening_message is not None
|
if opening_message is not None
|
||||||
@@ -179,7 +184,8 @@ class PromptBrain(BaseBrain):
|
|||||||
speak=self._speak_opening,
|
speak=self._speak_opening,
|
||||||
set_input_enabled=runtime.set_input_enabled,
|
set_input_enabled=runtime.set_input_enabled,
|
||||||
input_already_blocked=self._opening_input_blocked,
|
input_already_blocked=self._opening_input_blocked,
|
||||||
release_input_on_success=not bool(opening_actions),
|
# Keep the gate until the automatic first reply is queued.
|
||||||
|
release_input_on_success=False,
|
||||||
release_input_on_failure=False,
|
release_input_on_failure=False,
|
||||||
)
|
)
|
||||||
if not message_result.succeeded:
|
if not message_result.succeeded:
|
||||||
@@ -191,7 +197,13 @@ class PromptBrain(BaseBrain):
|
|||||||
if opening_actions:
|
if opening_actions:
|
||||||
result = await self._action_stages.run(
|
result = await self._action_stages.run(
|
||||||
self._opening_actions_stage_spec(),
|
self._opening_actions_stage_spec(),
|
||||||
set_input_enabled=runtime.set_input_enabled,
|
# The Prompt opening lifecycle owns the input gate when a
|
||||||
|
# confirmation message will trigger an automatic reply.
|
||||||
|
set_input_enabled=(
|
||||||
|
None
|
||||||
|
if generate_after_confirmation
|
||||||
|
else runtime.set_input_enabled
|
||||||
|
),
|
||||||
input_already_blocked=self._opening_input_blocked,
|
input_already_blocked=self._opening_input_blocked,
|
||||||
release_input_on_failure=False,
|
release_input_on_failure=False,
|
||||||
on_outcome=self._publish_opening_outcome,
|
on_outcome=self._publish_opening_outcome,
|
||||||
@@ -204,6 +216,11 @@ class PromptBrain(BaseBrain):
|
|||||||
raise
|
raise
|
||||||
self._opening_finished = True
|
self._opening_finished = True
|
||||||
self._opening_input_blocked = False
|
self._opening_input_blocked = False
|
||||||
|
if generate_after_confirmation and not runtime.call_end.ending:
|
||||||
|
logger.debug("Prompt 开场确认完成,触发自动首句")
|
||||||
|
await runtime.queue_frame(LLMRunFrame())
|
||||||
|
if runtime.set_input_enabled is not None:
|
||||||
|
runtime.set_input_enabled(True)
|
||||||
|
|
||||||
async def _speak_opening(self, content: str) -> Awaitable[None] | None:
|
async def _speak_opening(self, content: str) -> Awaitable[None] | None:
|
||||||
if self._output is None:
|
if self._output is None:
|
||||||
|
|||||||
@@ -353,7 +353,7 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
["preflight_1", "preflight_2"],
|
["preflight_1", "preflight_2"],
|
||||||
)
|
)
|
||||||
|
|
||||||
async def test_opening_stage_starts_speech_and_releases_on_confirmation(self):
|
async def test_opening_stage_triggers_reply_after_confirmation(self):
|
||||||
tool = RuntimeTool(
|
tool = RuntimeTool(
|
||||||
id="opening_data",
|
id="opening_data",
|
||||||
name="加载开场数据",
|
name="加载开场数据",
|
||||||
@@ -454,6 +454,10 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
await opening_task
|
await opening_task
|
||||||
self.assertEqual(input_states, [False, True])
|
self.assertEqual(input_states, [False, True])
|
||||||
self.assertEqual(called_tool_ids, ["opening_data"])
|
self.assertEqual(called_tool_ids, ["opening_data"])
|
||||||
|
self.assertEqual(
|
||||||
|
sum(isinstance(frame, LLMRunFrame) for frame in queued),
|
||||||
|
1,
|
||||||
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
len(
|
len(
|
||||||
[
|
[
|
||||||
@@ -469,6 +473,10 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
# Replayed client-ready must not execute startup actions twice.
|
# Replayed client-ready must not execute startup actions twice.
|
||||||
await brain.on_client_ready()
|
await brain.on_client_ready()
|
||||||
self.assertEqual(brain._actions.execute.await_count, 1)
|
self.assertEqual(brain._actions.execute.await_count, 1)
|
||||||
|
self.assertEqual(
|
||||||
|
sum(isinstance(frame, LLMRunFrame) for frame in queued),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
|
||||||
async def test_required_opening_failure_keeps_input_blocked_and_ends_call(self):
|
async def test_required_opening_failure_keeps_input_blocked_and_ends_call(self):
|
||||||
cfg = AssistantConfig(
|
cfg = AssistantConfig(
|
||||||
|
|||||||
Reference in New Issue
Block a user