feat(debug): encart temps = génération seule (sans la durée de lecture)

L'encart « dernier tour » ne compte plus que le temps de PRODUCTION de la réponse
(STT · Thinker · Speaker · TTS jusqu'au 1er son). La durée de lecture audio est
retirée du décompte (ce n'est pas de la génération, elle dépend de la longueur du
texte). Les lignes s'additionnent exactement au « TOTAL gén. ».

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kazeia Team 2026-06-21 14:48:28 +02:00
parent 078804e2de
commit 2e6c55a068
1 changed files with 16 additions and 18 deletions

View File

@ -141,26 +141,24 @@ class PipelineMetrics(val tag: String = "Pipeline") {
// Bloc temps par étape (ne liste que les étapes réellement mesurées). // Bloc temps par étape (ne liste que les étapes réellement mesurées).
// Construit une fois, envoyé aux DEUX puits : summarySink (flot de logs) et // Construit une fois, envoyé aux DEUX puits : summarySink (flot de logs) et
// timingsSink (encart persistant qui ne défile pas). // timingsSink (encart persistant qui ne défile pas).
// DEUX BLOCS additifs : TRAITEMENT (latence avant le 1er son, ce qui compte // GÉNÉRATION seule : on ne compte que le temps pour PRODUIRE la réponse
// pour la perf) + LECTURE (durée de la voix, dépend de la longueur du texte). // (STT · Thinker · Speaker · TTS jusqu'au 1er son). La durée de LECTURE
// Traitement + Lecture == TOTAL (exact). Le TTFT est un REPÈRE inclus dans // audio est exclue (ce n'est pas de la génération, elle dépend de la
// Speaker, pas un poste à additionner. // longueur du texte). Les lignes s'additionnent exactement au TOTAL gén.
// Le TTFT est un REPÈRE inclus dans Speaker, pas un poste à additionner.
run { run {
val b = StringBuilder("⏱─── TEMPS DU TOUR ($status) ───\n") val b = StringBuilder("⏱─── GÉNÉRATION ($status) ───\n")
fun row(name: String, value: String) { b.append(" ${name.padEnd(11)} $value\n") } fun row(name: String, value: String) { b.append(" ${name.padEnd(11)} $value\n") }
// first_audio = frontière traitement / lecture ; à défaut (pas de TTS, var gen = 0L
// ex. mode texte) tout est « traitement ». if (sttDur != null) { row("STT", "$sttDur ms"); gen += sttDur }
val processingMs = firstAudio ?: total if (thinkerDur != null) { row("Thinker", "$thinkerDur ms"); gen += thinkerDur }
val readingMs = (total - processingMs).coerceAtLeast(0) if (speakerGen != null) {
b.append(" TRAITEMENT (avant le 1er son) : $processingMs ms\n") row("Speaker", "$speakerGen ms" + (speakerTtft?.let { " (dont 1er token $it)" } ?: ""))
if (sttDur != null) row("· STT", "$sttDur ms") gen += speakerGen
if (thinkerDur != null) row("· Thinker", "$thinkerDur ms") }
if (speakerGen != null) row("· Speaker", "$speakerGen ms" + if (ttsSynth != null) { row("TTS→son", "$ttsSynth ms"); gen += ttsSynth }
(speakerTtft?.let { " (dont 1er token $it)" } ?: "")) b.append(" ──────────\n")
if (ttsSynth != null) row("· TTS→son", "$ttsSynth ms") b.append(" ${"TOTAL gén.".padEnd(11)} $gen ms")
if (firstAudio != null) b.append(" LECTURE (durée parlée) : $readingMs ms\n")
b.append(" ─────────────────────────────\n")
b.append(" TOTAL : $total ms")
val block = b.toString() val block = b.toString()
runCatching { timingsSink?.invoke(block) } runCatching { timingsSink?.invoke(block) }
summarySink?.let { sink -> block.lineSequence().forEach { runCatching { sink(it) } } } summarySink?.let { sink -> block.lineSequence().forEach { runCatching { sink(it) } } }