Files
Jay Brown 72de690894 Merged in feature/chatbot1 (pull request #223)
Chatbot functionality

* baseline working

* missing test file

* more tests
2026-05-07 20:56:18 +00:00

50 lines
1.7 KiB
Go

package bot
import "time"
// SummarizeMetadata flattens a *Metadata into the three scalars the M4
// AddTurn tx2 path persists into bot_turns: latency_ms, tokens_in,
// tokens_out. Plan §7 mapping table mandates:
//
// - latency_ms = metadata.total_latency_ms when present and > 0;
// otherwise wall-clock (receivedAt - sentAt).
// - tokens_in / tokens_out = sum across metadata.agents map values.
// - nil metadata or nil Agents map -> tokens default to 0.
//
// metadata.retry_count is intentionally ignored here; the FastAPI
// client's AttemptCount is the authoritative retry tally per plan §7
// ("attempt count is returned by the FastAPI client and persisted in
// error JSON consistently").
func SummarizeMetadata(m *Metadata, sentAt, receivedAt time.Time) (latencyMs, tokensIn, tokensOut int) {
if m == nil {
return wallClockMs(sentAt, receivedAt), 0, 0
}
tokensIn, tokensOut = sumAgentTokens(m.Agents)
if m.TotalLatencyMs > 0 {
return m.TotalLatencyMs, tokensIn, tokensOut
}
return wallClockMs(sentAt, receivedAt), tokensIn, tokensOut
}
// sumAgentTokens iterates the Agents map and sums input/output tokens.
// A nil or empty map returns (0, 0). The map values are non-pointer
// AgentTelemetry structs per the swagger.
func sumAgentTokens(agents map[string]AgentTelemetry) (in, out int) {
for _, a := range agents {
in += a.InputTokens
out += a.OutputTokens
}
return in, out
}
// wallClockMs returns receivedAt - sentAt rounded down to whole
// milliseconds. Negative values (clock skew or zero-value timestamps)
// return 0 so the caller never persists a negative latency.
func wallClockMs(sentAt, receivedAt time.Time) int {
delta := receivedAt.Sub(sentAt)
if delta < 0 {
return 0
}
return int(delta / time.Millisecond)
}