feat: implement LLM provider telemetry using expvar
- Added expvar metrics for tracking API call latency, token usage, and failover reasons in `FallbackChain.Execute` and `FallbackChain.ExecuteImage` methods. - Checked off the "LLM Provider Telemetry" task in docs/design/ETL_TODO.md. Co-authored-by: hobbyistlabs-coder <267281733+hobbyistlabs-coder@users.noreply.github.com>
This commit is contained in:
parent
43b8f055f3
commit
564121059d
2 changed files with 27 additions and 1 deletions
|
|
@ -9,7 +9,7 @@ This document tracks the tasks required to implement the "Ultimate Visibility" E
|
||||||
- [x] **Goroutine Tracking:** Implement a metric to track the number of active Goroutines.
|
- [x] **Goroutine Tracking:** Implement a metric to track the number of active Goroutines.
|
||||||
- [x] **Memory Tracking:** Implement a metric to track heap allocation and GC pauses.
|
- [x] **Memory Tracking:** Implement a metric to track heap allocation and GC pauses.
|
||||||
- [x] **AgentLoop Telemetry:** Add specific instrumentation to the `AgentLoop` (iteration duration, tool execution duration, failure counts).
|
- [x] **AgentLoop Telemetry:** Add specific instrumentation to the `AgentLoop` (iteration duration, tool execution duration, failure counts).
|
||||||
- [ ] **LLM Provider Telemetry:** Track API call latency, token usage, and failover reasons for LLM providers.
|
- [x] **LLM Provider Telemetry:** Track API call latency, token usage, and failover reasons for LLM providers.
|
||||||
- [ ] **API Gateway Telemetry:** Track request rates (RPS), latency percentiles, and error rates for HTTP and WebSocket endpoints.
|
- [ ] **API Gateway Telemetry:** Track request rates (RPS), latency percentiles, and error rates for HTTP and WebSocket endpoints.
|
||||||
- [ ] **Tracing Instrumentation:** Introduce trace IDs at entry points (HTTP, WebSocket) and propagate them via context to track end-to-end execution flow.
|
- [ ] **Tracing Instrumentation:** Introduce trace IDs at entry points (HTTP, WebSocket) and propagate them via context to track end-to-end execution flow.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,18 @@ package providers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"expvar"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
metricsLLMProviderDuration = expvar.NewMap("llm_provider_duration_seconds")
|
||||||
|
metricsLLMProviderFailover = expvar.NewMap("llm_provider_failover_counts")
|
||||||
|
metricsLLMProviderTokenUsage = expvar.NewMap("llm_provider_token_usage")
|
||||||
|
)
|
||||||
|
|
||||||
// FallbackChain orchestrates model fallback across multiple candidates.
|
// FallbackChain orchestrates model fallback across multiple candidates.
|
||||||
type FallbackChain struct {
|
type FallbackChain struct {
|
||||||
cooldown *CooldownTracker
|
cooldown *CooldownTracker
|
||||||
|
|
@ -139,8 +146,15 @@ func (fc *FallbackChain) Execute(
|
||||||
resp, err := run(ctx, candidate.Provider, candidate.Model)
|
resp, err := run(ctx, candidate.Provider, candidate.Model)
|
||||||
elapsed := time.Since(start)
|
elapsed := time.Since(start)
|
||||||
|
|
||||||
|
metricsLLMProviderDuration.AddFloat(candidate.Provider, elapsed.Seconds())
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Success.
|
// Success.
|
||||||
|
if resp != nil && resp.Usage != nil {
|
||||||
|
metricsLLMProviderTokenUsage.Add(candidate.Provider+"_prompt", int64(resp.Usage.PromptTokens))
|
||||||
|
metricsLLMProviderTokenUsage.Add(candidate.Provider+"_completion", int64(resp.Usage.CompletionTokens))
|
||||||
|
metricsLLMProviderTokenUsage.Add(candidate.Provider+"_total", int64(resp.Usage.TotalTokens))
|
||||||
|
}
|
||||||
fc.cooldown.MarkSuccess(candidate.Provider)
|
fc.cooldown.MarkSuccess(candidate.Provider)
|
||||||
result.Response = resp
|
result.Response = resp
|
||||||
result.Provider = candidate.Provider
|
result.Provider = candidate.Provider
|
||||||
|
|
@ -170,10 +184,13 @@ func (fc *FallbackChain) Execute(
|
||||||
Error: err,
|
Error: err,
|
||||||
Duration: elapsed,
|
Duration: elapsed,
|
||||||
})
|
})
|
||||||
|
metricsLLMProviderFailover.Add(candidate.Provider+"_"+string(FailoverUnknown), 1)
|
||||||
return nil, fmt.Errorf("fallback: unclassified error from %s/%s: %w",
|
return nil, fmt.Errorf("fallback: unclassified error from %s/%s: %w",
|
||||||
candidate.Provider, candidate.Model, err)
|
candidate.Provider, candidate.Model, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
metricsLLMProviderFailover.Add(candidate.Provider+"_"+string(failErr.Reason), 1)
|
||||||
|
|
||||||
// Non-retriable error: abort immediately.
|
// Non-retriable error: abort immediately.
|
||||||
if !failErr.IsRetriable() {
|
if !failErr.IsRetriable() {
|
||||||
result.Attempts = append(result.Attempts, FallbackAttempt{
|
result.Attempts = append(result.Attempts, FallbackAttempt{
|
||||||
|
|
@ -231,7 +248,14 @@ func (fc *FallbackChain) ExecuteImage(
|
||||||
resp, err := run(ctx, candidate.Provider, candidate.Model)
|
resp, err := run(ctx, candidate.Provider, candidate.Model)
|
||||||
elapsed := time.Since(start)
|
elapsed := time.Since(start)
|
||||||
|
|
||||||
|
metricsLLMProviderDuration.AddFloat(candidate.Provider, elapsed.Seconds())
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
if resp != nil && resp.Usage != nil {
|
||||||
|
metricsLLMProviderTokenUsage.Add(candidate.Provider+"_prompt", int64(resp.Usage.PromptTokens))
|
||||||
|
metricsLLMProviderTokenUsage.Add(candidate.Provider+"_completion", int64(resp.Usage.CompletionTokens))
|
||||||
|
metricsLLMProviderTokenUsage.Add(candidate.Provider+"_total", int64(resp.Usage.TotalTokens))
|
||||||
|
}
|
||||||
result.Response = resp
|
result.Response = resp
|
||||||
result.Provider = candidate.Provider
|
result.Provider = candidate.Provider
|
||||||
result.Model = candidate.Model
|
result.Model = candidate.Model
|
||||||
|
|
@ -251,6 +275,7 @@ func (fc *FallbackChain) ExecuteImage(
|
||||||
// Image dimension/size errors are non-retriable.
|
// Image dimension/size errors are non-retriable.
|
||||||
errMsg := strings.ToLower(err.Error())
|
errMsg := strings.ToLower(err.Error())
|
||||||
if IsImageDimensionError(errMsg) || IsImageSizeError(errMsg) {
|
if IsImageDimensionError(errMsg) || IsImageSizeError(errMsg) {
|
||||||
|
metricsLLMProviderFailover.Add(candidate.Provider+"_"+string(FailoverFormat), 1)
|
||||||
result.Attempts = append(result.Attempts, FallbackAttempt{
|
result.Attempts = append(result.Attempts, FallbackAttempt{
|
||||||
Provider: candidate.Provider,
|
Provider: candidate.Provider,
|
||||||
Model: candidate.Model,
|
Model: candidate.Model,
|
||||||
|
|
@ -267,6 +292,7 @@ func (fc *FallbackChain) ExecuteImage(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Any other error: record and try next.
|
// Any other error: record and try next.
|
||||||
|
metricsLLMProviderFailover.Add(candidate.Provider+"_"+string(FailoverUnknown), 1)
|
||||||
result.Attempts = append(result.Attempts, FallbackAttempt{
|
result.Attempts = append(result.Attempts, FallbackAttempt{
|
||||||
Provider: candidate.Provider,
|
Provider: candidate.Provider,
|
||||||
Model: candidate.Model,
|
Model: candidate.Model,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue