Cómo escribir un complemento de telemetría de Genkit

Las bibliotecas de Firebase Genkit están instrumentadas con OpenTelemetry para admitir la recopilación de seguimientos, métricas y registros. Los usuarios de Genkit pueden exportar estos datos de telemetría a las herramientas de supervisión y visualización mediante la instalación de un complemento que configure el SDK de OpenTelemetry para Go para exportarlos a un sistema que admita OpenTelemetry.

Genkit incluye un complemento que configura OpenTelemetry para exportar datos a Google Cloud Monitoring y Cloud Logging. Para admitir otros sistemas de supervisión, puedes ampliar Genkit escribiendo un complemento de telemetría, como se describe en esta página.

Antes de comenzar

Lee Cómo escribir complementos de Genkit para obtener información sobre cómo escribir cualquier tipo de complemento Genkit, incluidos los complementos de telemetría. En particular, ten en cuenta que cada complemento debe exportar una función Init, a la que se espera que los usuarios llamen antes de usar el complemento.

Exportadores y registradores

Como se mencionó antes, el trabajo principal de un complemento de telemetría es configurar OpenTelemetry (con el que ya se instrumentó Genkit) para exportar datos a un servicio en particular. Para hacerlo, necesitas lo siguiente:

  • Una implementación de la interfaz SpanExporter de OpenTelemetry que exporta datos al servicio que elijas.
  • Una implementación de la interfaz metric.Exporter de OpenTelemetry que exporta datos al servicio que elijas.
  • Un slog.Logger o una implementación de la interfaz slog.Handler, que exporta registros al servicio que elijas.

Según el servicio al que te interese realizar la exportación, esto podría ser un esfuerzo relativamente menor o uno grande.

Debido a que OpenTelemetry es un estándar de la industria, muchos servicios de supervisión tienen bibliotecas que implementan estas interfaces. Por ejemplo, el complemento googlecloud para Genkit usa la biblioteca opentelemetry-operations-go, administrada por el equipo de Google Cloud. De forma similar, muchos servicios de supervisión proporcionan bibliotecas que implementan las interfaces slog estándar.

Por otro lado, si esas bibliotecas no están disponibles para tu servicio, implementar las interfaces necesarias puede ser un proyecto sustancial.

Consulta el registro de OpenTelemetry o los documentos del servicio de supervisión para ver si las integraciones ya están disponibles.

Si necesitas compilar estas integraciones por tu cuenta, echa un vistazo a la fuente de los exportadores oficiales de OpenTelemetry y la página A Guide to Writing slog Handlers.

Cómo compilar el complemento

Dependencias

Cada complemento de telemetría necesita importar la biblioteca principal de Genkit y varias bibliotecas de OpenTelemetry:

import {
	// Import the Genkit core library.
	"github.com/firebase/genkit/go/core"

	// Import the OpenTelemetry libraries.
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/sdk/metric"
	"go.opentelemetry.io/otel/sdk/trace"
}

Si compilas un complemento en torno a una integración existente de OpenTelemetry o slog, también deberás importarlas.

Config

Un complemento de telemetría debería, como mínimo, admitir las siguientes opciones de configuración:

type Config struct {
	// Export even in the dev environment.
	ForceExport bool

	// The interval for exporting metric data.
	// The default is 60 seconds.
	MetricInterval time.Duration

	// The minimum level at which logs will be written.
	// Defaults to [slog.LevelInfo].
	LogLevel slog.Leveler
}

En los siguientes ejemplos, se supone que estas opciones están disponibles y te daremos información sobre cómo manejarlas.

La mayoría de los complementos también incluirán parámetros de configuración del servicio al que se exportará (clave de API, nombre del proyecto, etcétera).

Init()

La función Init() de un complemento de telemetría debe hacer lo siguiente:

  • Retornar anticipadamente si Genkit se ejecuta en un entorno de desarrollo (por ejemplo, cuando se ejecuta con genkit start) y la opción Config.ForceExport no está establecida:

    shouldExport := cfg.ForceExport || os.Getenv("GENKIT_ENV") != "dev"
    if !shouldExport {
    	return nil
    }
    
  • Inicializar el exportador de intervalos de seguimiento y registrarlo en Genkit:

    spanProcessor := trace.NewBatchSpanProcessor(YourCustomSpanExporter{})
    core.RegisterSpanProcessor(spanProcessor)
    
  • Inicializar el exportador de métricas y registrarlo con la biblioteca de OpenTelemetry:

    r := metric.NewPeriodicReader(
    	YourCustomMetricExporter{},
    	metric.WithInterval(cfg.MetricInterval),
    )
    mp := metric.NewMeterProvider(metric.WithReader(r))
    otel.SetMeterProvider(mp)
    

    Usa el intervalo de recopilación configurado por el usuario (Config.MetricInterval) cuando se inicialice PeriodicReader.

  • Registrar tu controlador slog como el registrador predeterminado:

    logger := slog.New(YourCustomHandler{
    	Options: &slog.HandlerOptions{Level: cfg.LogLevel},
    })
    slog.SetDefault(logger)
    

    Debes configurar tu controlador para respetar el nivel de registro mínimo especificado por el usuario (Config.LogLevel).

Ocultamiento de PII

Debido a que la mayoría de los flujos de IA generativa comienzan con algún tipo de entrada del usuario, es probable que algunos seguimientos de flujo contengan información de identificación personal (PII). Para proteger la información de tus usuarios, debes ocultar la PII de los seguimientos antes de exportarlos.

Si compilarás tu propio exportador de intervalos, puedes integrar esta funcionalidad en él.

Si compilarás tu complemento en función de una integración existente de OpenTelemetry, puedes unir el exportador de intervalos proporcionado con un exportador personalizado que se ocupe de esta tarea. Por ejemplo, el complemento googlecloud quita los atributos genkit:input y genkit:output de cada intervalo antes de exportarlos con un wrapper similar al siguiente:

type redactingSpanExporter struct {
	trace.SpanExporter
}

func (e *redactingSpanExporter) ExportSpans(ctx context.Context, spanData []trace.ReadOnlySpan) error {
	var redacted []trace.ReadOnlySpan
	for _, s := range spanData {
		redacted = append(redacted, redactedSpan{s})
	}
	return e.SpanExporter.ExportSpans(ctx, redacted)
}

func (e *redactingSpanExporter) Shutdown(ctx context.Context) error {
	return e.SpanExporter.Shutdown(ctx)
}

type redactedSpan struct {
	trace.ReadOnlySpan
}

func (s redactedSpan) Attributes() []attribute.KeyValue {
	// Omit input and output, which may contain PII.
	var ts []attribute.KeyValue
	for _, a := range s.ReadOnlySpan.Attributes() {
		if a.Key == "genkit:input" || a.Key == "genkit:output" {
			continue
		}
		ts = append(ts, a)
	}
	return ts
}

Soluciona problemas

Si tienes problemas para que los datos se muestren donde esperas, OpenTelemetry proporciona una herramienta de diagnóstico útil que ayuda a encontrar la fuente del problema.