feat(router): capture latency + tokens on ROUTER ChatTurnEvent

ChatTurnEvent gains nullable latencyMs and tokensUsed fields (defaults preserve
backward-compat; legacy JSON without them deserializes cleanly). emitChatTurn
accepts optional metrics; the ROUTER emit site passes inferenceResponse values
through; the USER emit site leaves both null.
This commit is contained in:
2026-06-03 15:11:37 +04:00
parent e9a87febc4
commit 8d8b2914e0
3 changed files with 89 additions and 2 deletions
@@ -1,6 +1,7 @@
package com.correx.core.events.events
import com.correx.core.events.types.SessionId
import com.correx.core.inference.TokenUsage
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -12,6 +13,8 @@ data class ChatTurnEvent(
val role: ChatTurnRole,
val content: String,
val timestampMs: Long,
val latencyMs: Long? = null,
val tokensUsed: TokenUsage? = null,
) : EventPayload
@Serializable
@@ -18,6 +18,7 @@ import com.correx.core.inference.InferenceRequest
import com.correx.core.inference.InferenceRouter
import com.correx.core.inference.ModelCapability
import com.correx.core.inference.ResponseFormat
import com.correx.core.inference.TokenUsage
import com.correx.core.router.l3.L3MemoryEntry
import com.correx.core.router.l3.L3MemoryStore
import com.correx.core.router.l3.L3Query
@@ -148,7 +149,11 @@ class DefaultRouterFacade(
val content = inferenceResponse.text
// Emit ROUTER turn event
emitChatTurn(sessionId, content, ChatTurnRole.ROUTER)
emitChatTurn(
sessionId, content, ChatTurnRole.ROUTER,
latencyMs = inferenceResponse.latencyMs,
tokensUsed = inferenceResponse.tokensUsed,
)
if (mode == ChatMode.STEERING) {
val validationError = validateSteering?.invoke(content)
@@ -160,7 +165,13 @@ class DefaultRouterFacade(
return RouterResponse(content = content, steeringEmitted = (mode == ChatMode.STEERING))
}
private suspend fun emitChatTurn(sessionId: SessionId, content: String, role: ChatTurnRole): String {
private suspend fun emitChatTurn(
sessionId: SessionId,
content: String,
role: ChatTurnRole,
latencyMs: Long? = null,
tokensUsed: TokenUsage? = null,
): String {
val turnId = UUID.randomUUID().toString()
val now = Clock.System.now()
eventStore.append(
@@ -179,6 +190,8 @@ class DefaultRouterFacade(
role = role,
content = content,
timestampMs = now.toEpochMilliseconds(),
latencyMs = latencyMs,
tokensUsed = tokensUsed,
),
),
)