< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.Langfuse.LangfuseServiceCollectionExtensions
Assembly: NexusLabs.Needlr.AgentFramework.Langfuse
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Langfuse/LangfuseServiceCollectionExtensions.cs
Line coverage
87%
Covered lines: 47
Uncovered lines: 7
Coverable lines: 54
Total lines: 109
Line coverage: 87%
Branch coverage
66%
Covered branches: 8
Total branches: 12
Branch coverage: 66.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
AddNeedlrLangfuse(...)66.66%121286%
ConfigureOtlp(...)100%11100%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Langfuse/LangfuseServiceCollectionExtensions.cs

#LineLine coverage
 1using Microsoft.Extensions.DependencyInjection;
 2using Microsoft.Extensions.DependencyInjection.Extensions;
 3
 4using OpenTelemetry.Exporter;
 5using OpenTelemetry.Metrics;
 6using OpenTelemetry.Resources;
 7using OpenTelemetry.Trace;
 8
 9namespace NexusLabs.Needlr.AgentFramework.Langfuse;
 10
 11/// <summary>
 12/// Registers Langfuse OTLP export on an <see cref="IServiceCollection"/> for ASP.NET Core and
 13/// generic-host applications.
 14/// </summary>
 15/// <remarks>
 16/// Unlike <see cref="LangfuseTelemetry.Start(LangfuseOptions)"/> — which builds standalone
 17/// providers for evals and console apps — this integrates with the host's
 18/// <c>AddOpenTelemetry()</c> pipeline so the tracer and meter providers share the application
 19/// lifecycle. When the supplied options are not configured (for example, missing credentials),
 20/// registration is skipped and the application starts normally without exporting.
 21/// </remarks>
 22public static class LangfuseServiceCollectionExtensions
 23{
 24    /// <summary>
 25    /// Adds Langfuse OTLP/HTTP export of Needlr agent telemetry to the host's OpenTelemetry
 26    /// pipeline.
 27    /// </summary>
 28    /// <param name="services">The service collection to configure.</param>
 29    /// <param name="configure">
 30    /// Optional callback to customise the <see cref="LangfuseOptions"/>. When <see langword="null"/>,
 31    /// options are read from the environment via <see cref="LangfuseOptions.FromEnvironment"/>.
 32    /// </param>
 33    /// <returns>The same <see cref="IServiceCollection"/> for chaining.</returns>
 34    /// <exception cref="ArgumentNullException"><paramref name="services"/> is <see langword="null"/>.</exception>
 35    public static IServiceCollection AddNeedlrLangfuse(
 36        this IServiceCollection services,
 37        Action<LangfuseOptions>? configure = null)
 38    {
 339        ArgumentNullException.ThrowIfNull(services);
 40
 341        var options = LangfuseOptions.FromEnvironment();
 342        configure?.Invoke(options);
 43
 344        if (!options.IsConfigured)
 45        {
 246            LangfuseExportGuard.WarnIfCredentialsWithoutTarget(options);
 247            services.TryAddSingleton<ILangfuseScoreClient>(new DisabledLangfuseScoreClient());
 248            services.TryAddSingleton<ILangfuseDatasetClient>(new DisabledLangfuseDatasetClient());
 249            services.TryAddSingleton<ILangfuseScoreConfigClient>(new DisabledLangfuseScoreConfigClient());
 250            return services;
 51        }
 52
 153        var endpoints = LangfuseEndpoints.Resolve(options);
 54
 155        var otelBuilder = services
 156            .AddOpenTelemetry()
 157            .ConfigureResource(resource =>
 158            {
 159                if (string.IsNullOrWhiteSpace(options.ServiceVersion))
 160                {
 161                    resource.AddService(options.ServiceName);
 162                }
 163                else
 164                {
 065                    resource.AddService(options.ServiceName, serviceVersion: options.ServiceVersion);
 166                }
 067            })
 268            .WithTracing(tracing => tracing
 269                .AddSource(LangfuseActivitySource.Name)
 270                .AddSource(options.AgentActivitySourceName)
 271                .AddSource([.. options.AdditionalActivitySources])
 272                .AddProcessor(new LangfuseTraceAttributeProcessor(options.Environment, options.Release))
 373                .AddOtlpExporter(otlp => ConfigureOtlp(otlp, endpoints.TracesEndpoint, endpoints.Headers)));
 74
 175        if (options.IncludeMetrics)
 76        {
 077            otelBuilder.WithMetrics(metrics => metrics
 078                .AddMeter(options.AgentMeterName)
 079                .AddMeter(options.GenAiMeterName)
 080                .AddMeter([.. options.AdditionalMeters])
 081                .AddOtlpExporter(otlp => ConfigureOtlp(otlp, endpoints.MetricsEndpoint, endpoints.Headers)));
 82        }
 83
 184        var httpClient = new HttpClient();
 185        var scoreApiClient = new LangfuseScoreApiClient(
 186            httpClient,
 187            endpoints.ScoresEndpoint,
 188            endpoints.AuthorizationHeaderValue);
 189        var apiClient = new LangfuseApiClient(
 190            httpClient,
 191            endpoints.BaseUrl,
 192            endpoints.AuthorizationHeaderValue);
 193        var failureSink = new LangfuseScoreFailureSink(options.ScoreFailureMode, options.ScoreErrorCallback);
 194        var recorder = new LangfuseScoreRecorder(scoreApiClient, failureSink, options.NormalizeScoreNames);
 95
 196        services.TryAddSingleton<ILangfuseScoreClient>(new LangfuseScoreClient(recorder, failureSink));
 197        services.TryAddSingleton<ILangfuseDatasetClient>(new LangfuseDatasetClient(apiClient));
 198        services.TryAddSingleton<ILangfuseScoreConfigClient>(new LangfuseScoreConfigClient(apiClient));
 99
 1100        return services;
 101    }
 102
 103    private static void ConfigureOtlp(OtlpExporterOptions otlp, Uri endpoint, string headers)
 104    {
 1105        otlp.Endpoint = endpoint;
 1106        otlp.Protocol = OtlpExportProtocol.HttpProtobuf;
 1107        otlp.Headers = headers;
 1108    }
 109}