[真实流程计时]:完成完整链路耗时输出,包含命令时间、LLM发送时间和阶段响应耗时
This commit is contained in:
@@ -2,8 +2,13 @@ from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from owner_voice_pet.models import AudioFrame, AudioSegment, PlaybackResult, TransportHealth
|
||||
from owner_voice_pet.real_live_check import FixtureLiveAudioTransport
|
||||
from owner_voice_pet.models import AudioFrame, AudioSegment, Message, PlaybackResult, ReplyDelta, TransportHealth
|
||||
from owner_voice_pet.real_live_check import (
|
||||
FixtureLiveAudioTransport,
|
||||
RecordingLlmProvider,
|
||||
TimingRecorder,
|
||||
_stage_timings_from_events,
|
||||
)
|
||||
|
||||
|
||||
def frame(idx: int) -> AudioFrame:
|
||||
@@ -42,6 +47,54 @@ class RealLiveCheckTests(unittest.TestCase):
|
||||
self.assertEqual(output.played, [segment])
|
||||
self.assertEqual(transport.played_segments, [segment])
|
||||
|
||||
def test_stage_timings_are_built_from_pipeline_events(self) -> None:
|
||||
events = [
|
||||
{"type": "wake_listening", "turn": 1, "at": "t0", "offset_ms": 0, "message": ""},
|
||||
{"type": "wake_detected", "turn": 1, "at": "t1", "offset_ms": 120, "message": ""},
|
||||
{"type": "ack_started", "turn": 1, "at": "t2", "offset_ms": 130, "message": ""},
|
||||
{"type": "question_prompt", "turn": 1, "at": "t3", "offset_ms": 250, "message": ""},
|
||||
{"type": "capture_started", "turn": 1, "at": "t4", "offset_ms": 260, "message": ""},
|
||||
{"type": "speech_started", "turn": 1, "at": "t5", "offset_ms": 300, "message": ""},
|
||||
{"type": "speech_ended", "turn": 1, "at": "t6", "offset_ms": 700, "message": ""},
|
||||
{"type": "stt_started", "turn": 1, "at": "t7", "offset_ms": 720, "message": ""},
|
||||
{"type": "transcript_final", "turn": 1, "at": "t8", "offset_ms": 900, "message": ""},
|
||||
{"type": "llm_started", "turn": 1, "at": "t9", "offset_ms": 910, "message": ""},
|
||||
{"type": "tts_started", "turn": 1, "at": "t10", "offset_ms": 1300, "message": ""},
|
||||
{"type": "playback_finished", "turn": 1, "at": "t11", "offset_ms": 1500, "message": ""},
|
||||
{"type": "standby_resumed", "turn": 1, "at": "t12", "offset_ms": 1520, "message": ""},
|
||||
]
|
||||
|
||||
timings = _stage_timings_from_events(events)
|
||||
by_name = {str(item["name"]): item for item in timings if "segment_index" not in item}
|
||||
|
||||
self.assertEqual(by_name["wake_wait"]["duration_ms"], 120)
|
||||
self.assertEqual(by_name["acknowledgement"]["duration_ms"], 120)
|
||||
self.assertEqual(by_name["capture"]["duration_ms"], 400)
|
||||
self.assertEqual(by_name["final_stt"]["duration_ms"], 180)
|
||||
self.assertEqual(by_name["llm_to_first_tts"]["duration_ms"], 390)
|
||||
self.assertEqual(by_name["turn_total"]["duration_ms"], 1520)
|
||||
tts = [item for item in timings if item["name"] == "tts_playback"]
|
||||
self.assertEqual(tts[0]["duration_ms"], 200)
|
||||
|
||||
def test_recording_llm_provider_records_sent_time_and_response_duration(self) -> None:
|
||||
class FakeLlm:
|
||||
def stream_reply(self, messages):
|
||||
yield ReplyDelta("你好", finish_reason="stop")
|
||||
|
||||
timing = TimingRecorder()
|
||||
provider = RecordingLlmProvider(FakeLlm(), timing) # type: ignore[arg-type]
|
||||
messages = [Message("user", "现在几点", 0.0)]
|
||||
|
||||
deltas = list(provider.stream_reply(messages))
|
||||
|
||||
self.assertEqual(deltas[0].text_delta, "你好")
|
||||
self.assertEqual(len(provider.request_timings), 1)
|
||||
request = provider.request_timings[0]
|
||||
self.assertIn("sent_at", request)
|
||||
self.assertIn("finished_at", request)
|
||||
self.assertIsInstance(request["duration_ms"], int)
|
||||
self.assertEqual(request["last_user_preview"], "现在几点")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user