Como criar um plug-in de telemetria do Genkit

As bibliotecas do Firebase Genkit são instrumentadas com o OpenTelemetry. para dar suporte à coleta de traces, métricas e registros. Os usuários do Genkit podem exportar isto dados de telemetria para ferramentas de monitoramento e visualização, instalando um plug-in que configura o SDK do OpenTelemetry Go. para exportar para um sistema compatível com OpenTelemetry.

O Genkit inclui um plug-in que configura o OpenTelemetry para exportar dados para o Google Cloud Monitoring e Cloud Logging. Para o suporte a outros sistemas de monitoramento, é possível ampliar o Genkit escrevendo um plug-in de telemetria, conforme descrito nesta página.

Antes de começar

Leia Como escrever plug-ins do Genkit (em inglês) para informações sobre como escrever qualquer tipo de plug-in Genkit, incluindo plug-ins de telemetria. Observe especificamente que cada plug-in precisa exportar uma função Init, que os usuários precisam chamar antes de usar o plug-in.

Exportadores e loggers

Como já mencionamos, a principal função de um plug-in de telemetria é configurar OpenTelemetry, que já foi instrumentado no Genkit, para exportar dados a um serviço específico. Para isso, você precisa do seguinte:

  • Uma implementação do SpanExporter do OpenTelemetry que exporta dados para o serviço de sua escolha.
  • Uma implementação do metric.Exporter do OpenTelemetry que exporta dados para o serviço de sua escolha.
  • Um slog.Logger ou uma implementação da interface slog.Handler que exporta registros para o serviço que você escolher.

Dependendo do serviço para o qual você quer exportar, pode ser um um esforço relativamente pequeno ou grande.

Como o OpenTelemetry é um padrão do setor, muitos serviços de monitoramento já têm bibliotecas que implementam essas interfaces. Por exemplo, o plug-in googlecloud para Genkit faz uso do opentelemetry-operations-go , mantido pela equipe do Google Cloud. Da mesma forma, muitos serviços de monitoramento oferecem bibliotecas que implementam as interfaces slog padrão.

Por outro lado, se não houver bibliotecas desse tipo disponíveis para seu serviço, implementar as interfaces necessárias pode ser um projeto substancial.

Verifique o registro do OpenTelemetry. ou os documentos do serviço de monitoramento para conferir se as integrações já estão disponíveis.

Se você precisar criar essas integrações por conta própria, dê uma olhada na fonte dos exportadores oficiais do OpenTelemetry e a página Um guia para criar gerenciadores slog.

Compilação do plug-in

Dependências

Todo plug-in de telemetria precisa importar a biblioteca principal do Genkit e vários Bibliotecas do 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"
}

Se você estiver criando um plug-in baseado em um OpenTelemetry ou uma integração slog, você também precisará importá-los.

Config

Um plug-in de telemetria precisa, no mínimo, ser compatível com a seguinte configuração: opções:

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
}

Os exemplos a seguir presumem que você está disponibilizando essas opções e que algumas orientações sobre como lidar com eles.

A maioria dos plugins também incluirá definições de configuração para o serviço para o qual está sendo exportado (chave de API, nome do projeto e assim por diante).

Init()

A função Init() de um plug-in de telemetria precisa realizar todas as ações a seguir:

  • Retorne antecipadamente se o Genkit estiver sendo executado em um ambiente de desenvolvimento (como quando está em execução com genkit start) e a opção Config.ForceExport não estiver definida:

    shouldExport := cfg.ForceExport || os.Getenv("GENKIT_ENV") != "dev"
    if !shouldExport {
    	return nil
    }
    
  • Inicialize seu exportador de períodos de trace e registre-o com o Genkit:

    spanProcessor := trace.NewBatchSpanProcessor(YourCustomSpanExporter{})
    core.RegisterSpanProcessor(spanProcessor)
    
  • Inicializar e registrar o exportador de métricas na biblioteca OpenTelemetry:

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

    Use o intervalo de coleta configurado pelo usuário (Config.MetricInterval) quando inicializar o PeriodicReader.

  • Registre o gerenciador slog como o registrador padrão:

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

    Você deve configurar seu manipulador para respeitar o nível mínimo de log especificado pelo usuário (Config.LogLevel).

Encobrimento de PII

Como a maioria dos fluxos de IA generativa começa com algum tipo de entrada do usuário, é possibilidade provável de que alguns rastreamentos de fluxo contenham informações (PII). Para proteger as informações dos seus usuários, você deve remover as PII dos rastros antes de exportá-los.

Se você estiver criando seu próprio exportador de períodos, poderá criar essa funcionalidade nele.

Se você estiver criando um plug-in com base em uma integração atual do OpenTelemetry, pode unir o exportador de períodos fornecido com um exportador personalizado que realiza essa tarefa. Por exemplo, o plug-in googlecloud remove o genkit:input e os atributos genkit:output de cada período antes de exportá-los usando um wrapper semelhante a:

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
}

Solução de problemas

Se você estiver com problemas para fazer com que os dados apareçam no lugar esperado, o OpenTelemetry oferece uma ferramenta de diagnóstico útil. que ajuda a localizar a origem do problema.