feat: show per-stage timing (듣기/LLM/TTS) in the transcript channel
The transcript channel only showed STT and LLM seconds. Add wall-clock start/end times and durations for listening, LLM and TTS so it's obvious what takes long; STT surfaces as the gap between listening end and LLM start. - bridge: emit llm_start_ms/llm_end_ms on meta and tts_*_ms on the end event - bot: capture the listening window, assemble full timing after the stream, and render a per-stage breakdown in the transcript message Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -453,6 +453,12 @@ def http_converse_stream():
|
||||
|
||||
def gen():
|
||||
import time
|
||||
|
||||
def now_ms() -> int:
|
||||
# Wall-clock epoch ms so the Node side can line these up against its
|
||||
# own Date.now() capture timestamps (same host, same clock).
|
||||
return int(time.time() * 1000)
|
||||
|
||||
t0 = time.monotonic()
|
||||
stt = transcribe(raw)
|
||||
t_stt = time.monotonic()
|
||||
@@ -464,8 +470,10 @@ def http_converse_stream():
|
||||
"stt_sec": round(t_stt - t0, 1), "broadcast_action": None}) + "\n"
|
||||
yield json.dumps({"type": "end"}) + "\n"
|
||||
return
|
||||
llm_start_ms = now_ms()
|
||||
result = think(transcript, stt.get("language"), broadcasting)
|
||||
t_think = time.monotonic()
|
||||
llm_end_ms = now_ms()
|
||||
reply = result.get("reply", "")
|
||||
yield json.dumps({
|
||||
"type": "meta",
|
||||
@@ -476,20 +484,37 @@ def http_converse_stream():
|
||||
"note": "ok" if reply.strip() else "답변 없음",
|
||||
"stt_sec": round(t_stt - t0, 1),
|
||||
"think_sec": round(t_think - t_stt, 1),
|
||||
# Wall-clock LLM window (epoch ms) for the transcript-channel timing
|
||||
# breakdown. STT shows up as the gap between the Node-side capture
|
||||
# end and llm_start_ms.
|
||||
"llm_start_ms": llm_start_ms,
|
||||
"llm_end_ms": llm_end_ms,
|
||||
"broadcast_action": result.get("broadcast_action"),
|
||||
}) + "\n"
|
||||
tts_total = 0.0
|
||||
tts_start_ms = None
|
||||
tts_end_ms = None
|
||||
for seq, sentence in enumerate(split_sentences(reply)):
|
||||
ts = time.monotonic()
|
||||
if tts_start_ms is None:
|
||||
tts_start_ms = now_ms()
|
||||
audio = synthesize(sentence)
|
||||
tts_total += time.monotonic() - ts
|
||||
tts_end_ms = now_ms()
|
||||
if audio:
|
||||
yield json.dumps({
|
||||
"type": "audio",
|
||||
"seq": seq,
|
||||
"audio_b64": base64.b64encode(audio).decode("ascii"),
|
||||
}) + "\n"
|
||||
yield json.dumps({"type": "end"}) + "\n"
|
||||
# The end event carries TTS timing because synthesis happens AFTER the
|
||||
# meta line (it is pipelined sentence-by-sentence).
|
||||
yield json.dumps({
|
||||
"type": "end",
|
||||
"tts_sec": round(tts_total, 1),
|
||||
"tts_start_ms": tts_start_ms,
|
||||
"tts_end_ms": tts_end_ms,
|
||||
}) + "\n"
|
||||
print(
|
||||
f"[bridge] ⏱️ turn stt={t_stt - t0:.1f}s think(LLM)={t_think - t_stt:.1f}s "
|
||||
f"tts={tts_total:.1f}s total={time.monotonic() - t0:.1f}s replylen={len(reply)} "
|
||||
|
||||
Reference in New Issue
Block a user