rtx-csm: Phase 13.6 — emotion metrics for converse_server
Production observability on the Phase 13.4/13.5 stack. Adds Prometheus
counters to the existing /metrics endpoint:
rtx_csm_reactive_emotion_calls_total — detector invocations
rtx_csm_reactive_emotion_total{label=...} — 5 buckets (neutral,
calm, sad, angry,
excited)
rtx_csm_emotion_aware_llm_applied_total — LLM augmentations
that actually fired
Verified end-to-end on Metal: server with Q8 + LoRA + reactive-emotion
+ emotion-aware-llm, 1 bench turn → /metrics shows
calls=1, calm=1 (all other labels 0), llm_applied=1. Lib suite 110/110.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
@@ -389,6 +389,18 @@ struct Metrics {
|
|||||||
/// Total turn wall time: turn start → done event sent.
|
/// Total turn wall time: turn start → done event sent.
|
||||||
total_turn_ms_sum: AtomicU64,
|
total_turn_ms_sum: AtomicU64,
|
||||||
total_turn_ms_count: AtomicU64,
|
total_turn_ms_count: AtomicU64,
|
||||||
|
/// Reactive emotion observability (Phase 13.6). Each turn that ran
|
||||||
|
/// the detector increments `_calls_total`; the per-label counters
|
||||||
|
/// add to one of the five labels. `emotion_aware_llm_applied_total`
|
||||||
|
/// counts turns where the LLM-history augmentation actually fired
|
||||||
|
/// (only when the label is non-Neutral AND --emotion-aware-llm is on).
|
||||||
|
reactive_emotion_calls_total: AtomicU64,
|
||||||
|
reactive_emotion_neutral_total: AtomicU64,
|
||||||
|
reactive_emotion_calm_total: AtomicU64,
|
||||||
|
reactive_emotion_sad_total: AtomicU64,
|
||||||
|
reactive_emotion_angry_total: AtomicU64,
|
||||||
|
reactive_emotion_excited_total: AtomicU64,
|
||||||
|
emotion_aware_llm_applied_total: AtomicU64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ASR backend wrapped behind a single Mutex so the receive loop can
|
/// ASR backend wrapped behind a single Mutex so the receive loop can
|
||||||
@@ -916,7 +928,17 @@ async fn metrics_handler(State(shared): State<Arc<Shared>>) -> impl IntoResponse
|
|||||||
# TYPE rtx_csm_conv_total_ms_avg gauge\n\
|
# TYPE rtx_csm_conv_total_ms_avg gauge\n\
|
||||||
rtx_csm_conv_total_ms_avg {}\n\
|
rtx_csm_conv_total_ms_avg {}\n\
|
||||||
# TYPE rtx_csm_total_turn_ms_avg gauge\n\
|
# TYPE rtx_csm_total_turn_ms_avg gauge\n\
|
||||||
rtx_csm_total_turn_ms_avg {}\n",
|
rtx_csm_total_turn_ms_avg {}\n\
|
||||||
|
# TYPE rtx_csm_reactive_emotion_calls_total counter\n\
|
||||||
|
rtx_csm_reactive_emotion_calls_total {}\n\
|
||||||
|
# TYPE rtx_csm_reactive_emotion_total counter\n\
|
||||||
|
rtx_csm_reactive_emotion_total{{label=\"neutral\"}} {}\n\
|
||||||
|
rtx_csm_reactive_emotion_total{{label=\"calm\"}} {}\n\
|
||||||
|
rtx_csm_reactive_emotion_total{{label=\"sad\"}} {}\n\
|
||||||
|
rtx_csm_reactive_emotion_total{{label=\"angry\"}} {}\n\
|
||||||
|
rtx_csm_reactive_emotion_total{{label=\"excited\"}} {}\n\
|
||||||
|
# TYPE rtx_csm_emotion_aware_llm_applied_total counter\n\
|
||||||
|
rtx_csm_emotion_aware_llm_applied_total {}\n",
|
||||||
m.turns_total.load(Ordering::Relaxed),
|
m.turns_total.load(Ordering::Relaxed),
|
||||||
m.errors_total.load(Ordering::Relaxed),
|
m.errors_total.load(Ordering::Relaxed),
|
||||||
m.connections_total.load(Ordering::Relaxed),
|
m.connections_total.load(Ordering::Relaxed),
|
||||||
@@ -928,6 +950,13 @@ async fn metrics_handler(State(shared): State<Arc<Shared>>) -> impl IntoResponse
|
|||||||
m.llm_to_first_audio_ms_sum.load(Ordering::Relaxed) / llm_to_audio_count,
|
m.llm_to_first_audio_ms_sum.load(Ordering::Relaxed) / llm_to_audio_count,
|
||||||
m.conv_total_ms_sum.load(Ordering::Relaxed) / conv_count,
|
m.conv_total_ms_sum.load(Ordering::Relaxed) / conv_count,
|
||||||
m.total_turn_ms_sum.load(Ordering::Relaxed) / total_count,
|
m.total_turn_ms_sum.load(Ordering::Relaxed) / total_count,
|
||||||
|
m.reactive_emotion_calls_total.load(Ordering::Relaxed),
|
||||||
|
m.reactive_emotion_neutral_total.load(Ordering::Relaxed),
|
||||||
|
m.reactive_emotion_calm_total.load(Ordering::Relaxed),
|
||||||
|
m.reactive_emotion_sad_total.load(Ordering::Relaxed),
|
||||||
|
m.reactive_emotion_angry_total.load(Ordering::Relaxed),
|
||||||
|
m.reactive_emotion_excited_total.load(Ordering::Relaxed),
|
||||||
|
m.emotion_aware_llm_applied_total.load(Ordering::Relaxed),
|
||||||
);
|
);
|
||||||
(
|
(
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
@@ -1293,11 +1322,34 @@ async fn handle_connection(mut socket: WebSocket, shared: Arc<Shared>) {
|
|||||||
.len()
|
.len()
|
||||||
.saturating_sub((PCM_RATE as f32 * 2.0) as usize);
|
.saturating_sub((PCM_RATE as f32 * 2.0) as usize);
|
||||||
let speech = &user_audio_24k[..trim_to];
|
let speech = &user_audio_24k[..trim_to];
|
||||||
|
shared
|
||||||
|
.metrics
|
||||||
|
.reactive_emotion_calls_total
|
||||||
|
.fetch_add(1, Ordering::Relaxed);
|
||||||
match rtx_csm::audio_io::resample(speech, PCM_RATE, 16_000) {
|
match rtx_csm::audio_io::resample(speech, PCM_RATE, 16_000) {
|
||||||
Ok(speech_16k) => {
|
Ok(speech_16k) => {
|
||||||
let det = rtx_csm::ser::ProsodyDetector::default();
|
let det = rtx_csm::ser::ProsodyDetector::default();
|
||||||
let label = rtx_csm::ser::EmotionDetector::classify(&det, &speech_16k)
|
let label = rtx_csm::ser::EmotionDetector::classify(&det, &speech_16k)
|
||||||
.unwrap_or(rtx_csm::ser::EmotionLabel::Neutral);
|
.unwrap_or(rtx_csm::ser::EmotionLabel::Neutral);
|
||||||
|
// Per-label counters.
|
||||||
|
let bucket = match label {
|
||||||
|
rtx_csm::ser::EmotionLabel::Neutral => {
|
||||||
|
&shared.metrics.reactive_emotion_neutral_total
|
||||||
|
}
|
||||||
|
rtx_csm::ser::EmotionLabel::Calm => {
|
||||||
|
&shared.metrics.reactive_emotion_calm_total
|
||||||
|
}
|
||||||
|
rtx_csm::ser::EmotionLabel::Sad => {
|
||||||
|
&shared.metrics.reactive_emotion_sad_total
|
||||||
|
}
|
||||||
|
rtx_csm::ser::EmotionLabel::Angry => {
|
||||||
|
&shared.metrics.reactive_emotion_angry_total
|
||||||
|
}
|
||||||
|
rtx_csm::ser::EmotionLabel::Excited => {
|
||||||
|
&shared.metrics.reactive_emotion_excited_total
|
||||||
|
}
|
||||||
|
};
|
||||||
|
bucket.fetch_add(1, Ordering::Relaxed);
|
||||||
if label == rtx_csm::ser::EmotionLabel::Neutral {
|
if label == rtx_csm::ser::EmotionLabel::Neutral {
|
||||||
None // fall back to static --emotion-hint
|
None // fall back to static --emotion-hint
|
||||||
} else {
|
} else {
|
||||||
@@ -1362,6 +1414,10 @@ async fn handle_connection(mut socket: WebSocket, shared: Arc<Shared>) {
|
|||||||
"{}\n\n[user audio tone: {label} — adjust your response in tone and content to match]",
|
"{}\n\n[user audio tone: {label} — adjust your response in tone and content to match]",
|
||||||
last.content
|
last.content
|
||||||
);
|
);
|
||||||
|
shared
|
||||||
|
.metrics
|
||||||
|
.emotion_aware_llm_applied_total
|
||||||
|
.fetch_add(1, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
h
|
h
|
||||||
|
|||||||
Reference in New Issue
Block a user