telemetry

package
v0.9.10 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 18, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

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

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

func (r *Recorder) RecordAPIRequest(ctx context.Context, err error)

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

func (r *Recorder) RecordCacheTokens(ctx context.Context, cacheRead, cacheCreation int64)

RecordCacheTokens records prompt cache token usage: tokens served from cache and tokens written to it.

func (*Recorder) RecordTokens

func (r *Recorder) RecordTokens(ctx context.Context, inputTokens, outputTokens int64)

RecordTokens records prompt and completion token usage.

func (*Recorder) RecordToolCall

func (r *Recorder) RecordToolCall(ctx context.Context, toolName string)

RecordToolCall records a tool call metric.

func (*Recorder) RecordTurns

func (r *Recorder) RecordTurns(ctx context.Context, turns int, limitExceeded bool)

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

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL