Documentation
¶
Overview ¶
Package telemetry provides the shared GenAI metrics recorder used by the executor implementations (claudeexecutor, googleexecutor, openaiexecutor) so each backend emits identically-shaped metrics, differing only in the gen_ai.provider.name attribute.
Index ¶
- type Recorder
- func (r *Recorder) RecordAPIRequest(ctx context.Context, err error)
- func (r *Recorder) RecordCacheTokens(ctx context.Context, cacheRead, cacheCreation int64)
- func (r *Recorder) RecordTokens(ctx context.Context, inputTokens, outputTokens int64)
- func (r *Recorder) RecordToolCall(ctx context.Context, toolName string)
- func (r *Recorder) RecordTurns(ctx context.Context, turns int, limitExceeded bool)
- func (r *Recorder) WithAPIRequestCounter(ctx context.Context, cfg retry.RetryConfig) retry.RetryConfig
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Recorder ¶
type Recorder struct {
// contains filtered or unexported fields
}
Recorder emits the GenAI metrics common to the executor backends, stamping every recording with the executor's model name, resource labels, and gen_ai.provider.name. Methods are safe for concurrent use: the attribute slice is built once at construction with exact capacity and only ever read afterwards, so concurrent Executes never append onto a shared backing array.
Example ¶
package main
import (
"context"
"fmt"
"chainguard.dev/driftlessaf/agents/executor/internal/telemetry"
"chainguard.dev/driftlessaf/agents/metrics"
)
func main() {
ctx := context.Background()
rec := telemetry.NewRecorder(
metrics.NewGenAI("chainguard.ai.agents"),
"gemini-2.5-flash",
"gcp.vertex_ai",
nil,
func(error) int { return -1 },
)
rec.RecordCacheTokens(ctx, 1024, 0)
rec.RecordToolCall(ctx, "read_file")
rec.RecordTurns(ctx, 3, false)
fmt.Println("execution metrics recorded")
}
Output: execution metrics recorded
func NewRecorder ¶
func NewRecorder(genai *metrics.GenAI, model, provider string, resourceLabels map[string]string, codeFromError func(error) int) *Recorder
NewRecorder builds a Recorder for one executor instance. provider is the OTel gen_ai.provider.name value for the serving backend (for example "gcp.vertex_ai", "anthropic", or "openai-compat"). codeFromError maps a backend API error to the HTTP-style response code recorded by RecordAPIRequest; backends that do not record genai.api.requests may pass nil and must not call RecordAPIRequest or WithAPIRequestCounter.
Example ¶
package main
import (
"context"
"fmt"
"chainguard.dev/driftlessaf/agents/executor/internal/telemetry"
"chainguard.dev/driftlessaf/agents/metrics"
)
func main() {
rec := telemetry.NewRecorder(
metrics.NewGenAI("chainguard.ai.agents"),
"claude-sonnet-4",
"anthropic",
map[string]string{"team": "platform"},
func(error) int { return -1 },
)
rec.RecordTokens(context.Background(), 150, 250)
fmt.Println("token metrics recorded")
}
Output: token metrics recorded
func (*Recorder) RecordAPIRequest ¶
RecordAPIRequest counts a single LLM API attempt with a response_code derived from err via the Recorder's codeFromError mapping. Call this after every retry-wrapped API call (whether the final outcome was success, retryable failure, or non-retryable failure) so the counter sees one increment per HTTP attempt — matching what GCP's serviceruntime metric sees on its side of the wire.
Example ¶
package main
import (
"context"
"errors"
"fmt"
"chainguard.dev/driftlessaf/agents/executor/internal/telemetry"
"chainguard.dev/driftlessaf/agents/metrics"
)
func main() {
ctx := context.Background()
rec := telemetry.NewRecorder(
metrics.NewGenAI("chainguard.ai.agents"),
"gemini-2.5-flash",
"gcp.vertex_ai",
nil,
func(err error) int {
if err == nil {
return 0
}
return 429
},
)
// A nil error counts as response_code "200"; failures carry the code the
// mapping recovers from the error.
rec.RecordAPIRequest(ctx, nil)
rec.RecordAPIRequest(ctx, errors.New("rate limited"))
fmt.Println("api request metrics recorded")
}
Output: api request metrics recorded
func (*Recorder) RecordCacheTokens ¶
RecordCacheTokens records prompt cache token usage: tokens served from cache and tokens written to it.
func (*Recorder) RecordTokens ¶
RecordTokens records prompt and completion token usage.
func (*Recorder) RecordToolCall ¶
RecordToolCall records a tool call metric.
func (*Recorder) RecordTurns ¶
RecordTurns records the number of turns used and, when limitExceeded is true, increments the turn_limit_exceeded counter.
func (*Recorder) WithAPIRequestCounter ¶
func (r *Recorder) WithAPIRequestCounter(ctx context.Context, cfg retry.RetryConfig) retry.RetryConfig
WithAPIRequestCounter extends cfg.OnAttemptError to also count each retried API attempt in genai.api.requests. The retry loop only invokes OnAttemptError for retryable errors that will be retried, so this captures the intermediate attempts that retry.RetryWithBackoff would otherwise hide; the final attempt is counted by the caller after RetryWithBackoff returns. Together they give exactly one increment per HTTP attempt.
Example ¶
package main
import (
"context"
"errors"
"fmt"
"chainguard.dev/driftlessaf/agents/executor/internal/telemetry"
"chainguard.dev/driftlessaf/agents/executor/retry"
"chainguard.dev/driftlessaf/agents/metrics"
)
func main() {
ctx := context.Background()
rec := telemetry.NewRecorder(
metrics.NewGenAI("chainguard.ai.agents"),
"gemini-2.5-flash",
"gcp.vertex_ai",
nil,
func(error) int { return 503 },
)
// Wrap a retry config so every retried attempt is counted in
// genai.api.requests alongside the final attempt.
cfg := rec.WithAPIRequestCounter(ctx, retry.DefaultRetryConfig())
cfg.OnAttemptError(errors.New("transient"))
fmt.Println("retried attempt counted")
}
Output: retried attempt counted