feat: unify debug runtime timelines
This commit is contained in:
@@ -521,6 +521,24 @@ class PromptBrainTests(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertIn("点击“确认”", confirmation_messages[0]["content"])
|
||||
self.assertIn("弹窗标题:重要提示", confirmation_messages[0]["content"])
|
||||
self.assertIn("弹窗内容:请确认已阅读。", confirmation_messages[0]["content"])
|
||||
message_traces = [
|
||||
frame.message
|
||||
for frame in queued
|
||||
if isinstance(frame, OutputTransportMessageUrgentFrame)
|
||||
and frame.message.get("type") == "workflow-event"
|
||||
and frame.message.get("event")
|
||||
in {"message_started", "message_completed"}
|
||||
]
|
||||
self.assertEqual(
|
||||
[event["event"] for event in message_traces],
|
||||
["message_started", "message_completed"],
|
||||
)
|
||||
self.assertEqual(message_traces[0]["messageTitle"], "重要提示")
|
||||
self.assertEqual(
|
||||
message_traces[0]["messageContent"],
|
||||
"请确认已阅读。",
|
||||
)
|
||||
self.assertEqual(message_traces[1]["action"], "confirmed")
|
||||
self.assertEqual(
|
||||
sum(isinstance(frame, LLMRunFrame) for frame in queued),
|
||||
1,
|
||||
|
||||
@@ -11,12 +11,27 @@ from pipecat.frames.frames import (
|
||||
)
|
||||
from services.pipecat.call_lifecycle import (
|
||||
CallEndCoordinator,
|
||||
call_end_trace_event,
|
||||
playback_marker_for,
|
||||
)
|
||||
from services.pipecat.processors import CallEndingUserMuteStrategy
|
||||
|
||||
|
||||
class CallEndCoordinatorTest(unittest.IsolatedAsyncioTestCase):
|
||||
def test_shared_trace_event_uses_runtime_record_contract(self):
|
||||
event = call_end_trace_event(
|
||||
"pending",
|
||||
reason="用户已完成咨询",
|
||||
interruptible=True,
|
||||
voice_interruptible=True,
|
||||
)
|
||||
|
||||
self.assertEqual(event["type"], "workflow-event")
|
||||
self.assertEqual(event["event"], "conversation_end_requested")
|
||||
self.assertEqual(event["reason"], "用户已完成咨询")
|
||||
self.assertTrue(event["interruptible"])
|
||||
self.assertTrue(event["voiceInterruptible"])
|
||||
|
||||
async def asyncSetUp(self):
|
||||
self.reasons: list[str] = []
|
||||
self.states: list[tuple[str, bool]] = []
|
||||
@@ -87,7 +102,7 @@ class CallEndCoordinatorTest(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertTrue(self.coordinator.accepts_input)
|
||||
self.assertTrue(self.coordinator.cancel_pending_end())
|
||||
await asyncio.sleep(0)
|
||||
self.assertEqual(self.states[-1], ("cancelled", False))
|
||||
self.assertEqual(self.states[-1], ("cancelled", True))
|
||||
self.assertFalse(self.coordinator.ending)
|
||||
self.assertTrue(self.coordinator.accepts_input)
|
||||
self.assertTrue(self.coordinator.speaking)
|
||||
@@ -95,6 +110,7 @@ class CallEndCoordinatorTest(unittest.IsolatedAsyncioTestCase):
|
||||
await self.coordinator.observe(BotStoppedSpeakingFrame())
|
||||
self.assertFalse(self.coordinator.speaking)
|
||||
self.assertEqual(self.reasons, [])
|
||||
self.assertEqual(self.coordinator.reason, "user_goodbye")
|
||||
|
||||
async def test_committed_end_rejects_cancellation(self):
|
||||
self.coordinator.begin("required_shutdown")
|
||||
|
||||
@@ -144,6 +144,22 @@ class ConversationRecorderTest(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertEqual(saved["sessionId"], "conv_test")
|
||||
self.assertEqual(saved["sequence"], 1)
|
||||
|
||||
async def test_internal_message_stage_transport_trace_is_not_persisted_twice(self):
|
||||
recorder = ConversationRecorder("conv_test")
|
||||
event = {
|
||||
"type": "workflow-event",
|
||||
"eventId": "wfe_internal",
|
||||
"event": "client_tool_completed",
|
||||
"functionName": "show_message",
|
||||
"internalStage": True,
|
||||
}
|
||||
|
||||
with patch("services.conversation_history.SessionLocal") as session_local:
|
||||
await recorder.record_transport_message(event)
|
||||
|
||||
session_local.assert_not_called()
|
||||
self.assertEqual(recorder._trace_sequence, 0)
|
||||
|
||||
async def test_image_is_persisted_as_message_with_artifact(self):
|
||||
conversation = SimpleNamespace(message_count=0)
|
||||
|
||||
|
||||
@@ -10,10 +10,13 @@ from services.runtime_variables import DynamicVariableStore, prepare_dynamic_con
|
||||
from services.test_runs.mock_tools import MockToolExecutor, TextClientToolPort
|
||||
from services.test_runs.text_runner import (
|
||||
_TextCaptureProcessor,
|
||||
_opening_speech_from_message,
|
||||
_resolve_startup_greeting,
|
||||
_runtime_record_from_message,
|
||||
_wait_for_startup,
|
||||
_wait_for_quiescence,
|
||||
)
|
||||
from test_schemas import BatchRunCreate
|
||||
from test_schemas import BatchRunCreate, BatchStartupResult
|
||||
|
||||
|
||||
class FakeLLM:
|
||||
@@ -126,6 +129,25 @@ class DebugAutoTestContractTests(unittest.TestCase):
|
||||
"你好,测试用户",
|
||||
)
|
||||
|
||||
def test_startup_result_exposes_real_runtime_records(self):
|
||||
result = BatchStartupResult.model_validate(
|
||||
{
|
||||
"completed": True,
|
||||
"runtimeRecords": [
|
||||
{
|
||||
"type": "workflow-event",
|
||||
"eventId": "wfe_opening",
|
||||
"event": "message_started",
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
result.runtime_records[0]["event"],
|
||||
"message_started",
|
||||
)
|
||||
|
||||
def test_text_client_port_records_automatic_message_confirmation(self):
|
||||
port = TextClientToolPort()
|
||||
|
||||
@@ -142,6 +164,51 @@ class DebugAutoTestContractTests(unittest.TestCase):
|
||||
[{"title": "重要提示", "message": "请确认"}],
|
||||
)
|
||||
|
||||
def test_confirmation_owned_speech_is_kept_as_startup_greeting(self):
|
||||
self.assertEqual(
|
||||
_resolve_startup_greeting(
|
||||
"",
|
||||
["您好,请先阅读弹窗。"],
|
||||
),
|
||||
"您好,请先阅读弹窗。",
|
||||
)
|
||||
self.assertEqual(
|
||||
_resolve_startup_greeting(
|
||||
"普通开场白",
|
||||
["其他固定播报"],
|
||||
),
|
||||
"普通开场白",
|
||||
)
|
||||
self.assertEqual(
|
||||
_resolve_startup_greeting(
|
||||
"",
|
||||
[],
|
||||
),
|
||||
"",
|
||||
)
|
||||
|
||||
def test_only_prompt_opening_transcript_is_identified_as_greeting(self):
|
||||
self.assertEqual(
|
||||
_opening_speech_from_message(
|
||||
{
|
||||
"type": "transcript",
|
||||
"content": "您好,请先阅读弹窗。",
|
||||
"source": "prompt-opening-speech",
|
||||
}
|
||||
),
|
||||
"您好,请先阅读弹窗。",
|
||||
)
|
||||
self.assertEqual(
|
||||
_opening_speech_from_message(
|
||||
{
|
||||
"type": "transcript",
|
||||
"content": "普通 Workflow 播报",
|
||||
"source": "workflow-message-speech",
|
||||
}
|
||||
),
|
||||
"",
|
||||
)
|
||||
|
||||
|
||||
class DebugAutoTestRuntimeTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_system_end_tool_keeps_real_lifecycle_in_text_tests(self):
|
||||
@@ -180,6 +247,29 @@ class DebugAutoTestRuntimeTests(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertEqual(params.result["action"], "ending_call")
|
||||
self.assertEqual(executor_holder["executor"].unmocked_calls, [])
|
||||
|
||||
async def test_text_capture_keeps_runtime_records_for_auto_test_ui(self):
|
||||
event = {
|
||||
"type": "workflow-event",
|
||||
"eventId": "wfe_end",
|
||||
"event": "conversation_end_requested",
|
||||
"reason": "测试完成",
|
||||
}
|
||||
|
||||
self.assertEqual(_runtime_record_from_message(event), event)
|
||||
self.assertIsNone(
|
||||
_runtime_record_from_message({"type": "call-end-state"})
|
||||
)
|
||||
self.assertIsNone(
|
||||
_runtime_record_from_message(
|
||||
{
|
||||
"type": "workflow-event",
|
||||
"eventId": "wfe_internal",
|
||||
"event": "client_tool_completed",
|
||||
"internalStage": True,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
async def test_startup_waits_for_queued_entry_reply(self):
|
||||
capture = _TextCaptureProcessor(LLMContext(messages=[]))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user