fix: release input mute after async client tools
This commit is contained in:
@@ -257,8 +257,12 @@ class UserInputProcessor(FrameProcessor):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if self._should_ignore_input():
|
if self._should_ignore_input():
|
||||||
logger.debug("通话正在结束,忽略后续文字输入")
|
logger.debug("当前会话输入已暂停,忽略用户输入")
|
||||||
await self._emit_result(user_input.input_id, "error", "当前不能接收新的用户输入")
|
await self._emit_result(
|
||||||
|
user_input.input_id,
|
||||||
|
"error",
|
||||||
|
"当前暂不能接收新的用户输入",
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
await self._call_event_handler("on_user_input", user_input)
|
await self._call_event_handler("on_user_input", user_input)
|
||||||
@@ -438,8 +442,22 @@ class ToolInterruptionUserMuteStrategy(BaseUserMuteStrategy):
|
|||||||
elif isinstance(frame, FunctionCallResultFrame):
|
elif isinstance(frame, FunctionCallResultFrame):
|
||||||
call = self._calls.get(frame.tool_call_id)
|
call = self._calls.get(frame.tool_call_id)
|
||||||
if call is not None:
|
if call is not None:
|
||||||
call.tool_finished = True
|
properties = frame.properties
|
||||||
self._release_completed()
|
is_final = properties.is_final if properties else True
|
||||||
|
if is_final:
|
||||||
|
call.tool_finished = True
|
||||||
|
run_llm = (
|
||||||
|
properties.run_llm
|
||||||
|
if properties and properties.run_llm is not None
|
||||||
|
else frame.run_llm
|
||||||
|
)
|
||||||
|
if run_llm is False and not call.response_started:
|
||||||
|
# A tool-only result has no following bot response whose
|
||||||
|
# speech boundary could release the mute. If a preamble
|
||||||
|
# is already playing, BotStoppedSpeakingFrame still owns
|
||||||
|
# the release so allow_interruptions=False is respected.
|
||||||
|
call.response_finished = True
|
||||||
|
self._release_completed()
|
||||||
elif isinstance(frame, FunctionCallCancelFrame):
|
elif isinstance(frame, FunctionCallCancelFrame):
|
||||||
# A canceled call cannot reliably produce a follow-up response.
|
# A canceled call cannot reliably produce a follow-up response.
|
||||||
self._calls.pop(frame.tool_call_id, None)
|
self._calls.pop(frame.tool_call_id, None)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from pipecat.frames.frames import (
|
|||||||
FunctionCallResultFrame,
|
FunctionCallResultFrame,
|
||||||
FunctionCallsStartedFrame,
|
FunctionCallsStartedFrame,
|
||||||
)
|
)
|
||||||
|
from pipecat.services.llm_service import FunctionCallResultProperties
|
||||||
from services.pipecat.processors import ToolInterruptionUserMuteStrategy
|
from services.pipecat.processors import ToolInterruptionUserMuteStrategy
|
||||||
from services.tool_policy import policy_for_tool
|
from services.tool_policy import policy_for_tool
|
||||||
|
|
||||||
@@ -143,6 +144,106 @@ class ToolInterruptionStrategyTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
await strategy.process_frame(BotStoppedSpeakingFrame())
|
await strategy.process_frame(BotStoppedSpeakingFrame())
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def test_async_tool_only_result_releases_without_followup_speech(self):
|
||||||
|
strategy = ToolInterruptionUserMuteStrategy(
|
||||||
|
{"set_photo_button_visible": "async"}
|
||||||
|
)
|
||||||
|
await strategy.process_frame(
|
||||||
|
FunctionCallsStartedFrame(
|
||||||
|
function_calls=[
|
||||||
|
SimpleNamespace(
|
||||||
|
function_name="set_photo_button_visible",
|
||||||
|
tool_call_id="call_4",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertFalse(
|
||||||
|
await strategy.process_frame(
|
||||||
|
FunctionCallResultFrame(
|
||||||
|
function_name="set_photo_button_visible",
|
||||||
|
tool_call_id="call_4",
|
||||||
|
arguments={"visible": True},
|
||||||
|
result={"status": "ok"},
|
||||||
|
properties=FunctionCallResultProperties(run_llm=False),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertFalse(strategy.is_muted)
|
||||||
|
|
||||||
|
async def test_async_tool_only_result_waits_for_active_preamble(self):
|
||||||
|
strategy = ToolInterruptionUserMuteStrategy(
|
||||||
|
{"set_photo_button_visible": "async"}
|
||||||
|
)
|
||||||
|
await strategy.process_frame(BotStartedSpeakingFrame())
|
||||||
|
await strategy.process_frame(
|
||||||
|
FunctionCallsStartedFrame(
|
||||||
|
function_calls=[
|
||||||
|
SimpleNamespace(
|
||||||
|
function_name="set_photo_button_visible",
|
||||||
|
tool_call_id="call_5",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(
|
||||||
|
await strategy.process_frame(
|
||||||
|
FunctionCallResultFrame(
|
||||||
|
function_name="set_photo_button_visible",
|
||||||
|
tool_call_id="call_5",
|
||||||
|
arguments={"visible": True},
|
||||||
|
result={"status": "ok"},
|
||||||
|
properties=FunctionCallResultProperties(run_llm=False),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
await strategy.process_frame(BotStoppedSpeakingFrame())
|
||||||
|
)
|
||||||
|
|
||||||
|
async def test_async_intermediate_result_does_not_release_mute(self):
|
||||||
|
strategy = ToolInterruptionUserMuteStrategy(
|
||||||
|
{"set_photo_button_visible": "async"}
|
||||||
|
)
|
||||||
|
await strategy.process_frame(
|
||||||
|
FunctionCallsStartedFrame(
|
||||||
|
function_calls=[
|
||||||
|
SimpleNamespace(
|
||||||
|
function_name="set_photo_button_visible",
|
||||||
|
tool_call_id="call_6",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(
|
||||||
|
await strategy.process_frame(
|
||||||
|
FunctionCallResultFrame(
|
||||||
|
function_name="set_photo_button_visible",
|
||||||
|
tool_call_id="call_6",
|
||||||
|
arguments={"visible": True},
|
||||||
|
result={"status": "pending"},
|
||||||
|
properties=FunctionCallResultProperties(
|
||||||
|
run_llm=False,
|
||||||
|
is_final=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
await strategy.process_frame(
|
||||||
|
FunctionCallResultFrame(
|
||||||
|
function_name="set_photo_button_visible",
|
||||||
|
tool_call_id="call_6",
|
||||||
|
arguments={"visible": True},
|
||||||
|
result={"status": "ok"},
|
||||||
|
properties=FunctionCallResultProperties(run_llm=False),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user