< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.Langfuse.LangfuseTelemetry
Assembly: NexusLabs.Needlr.AgentFramework.Langfuse
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Langfuse/LangfuseTelemetry.cs
Line coverage
6%
Covered lines: 4
Uncovered lines: 54
Coverable lines: 58
Total lines: 139
Line coverage: 6.8%
Branch coverage
10%
Covered branches: 1
Total branches: 10
Branch coverage: 10%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Start(...)25%1648.88%
BuildResource(...)0%620%
BuildSampler(...)0%2040%
ConfigureOtlp(...)100%210%

File(s)

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

#LineLine coverage
 1using OpenTelemetry;
 2using OpenTelemetry.Exporter;
 3using OpenTelemetry.Metrics;
 4using OpenTelemetry.Resources;
 5using OpenTelemetry.Trace;
 6
 7namespace NexusLabs.Needlr.AgentFramework.Langfuse;
 8
 9/// <summary>
 10/// Entry point for exporting Needlr agent telemetry to Langfuse without requiring a generic
 11/// host. Designed for evals, console apps, and test fixtures that build telemetry by hand.
 12/// </summary>
 13/// <remarks>
 14/// <para>
 15/// <see cref="Start(LangfuseOptions)"/> constructs standalone OpenTelemetry tracer and meter
 16/// providers that subscribe to Needlr's <c>gen_ai</c> activity source and meters and export them
 17/// to Langfuse over OTLP/HTTP. Dispose the returned <see cref="ILangfuseSession"/> to flush and
 18/// tear them down.
 19/// </para>
 20/// <para>
 21/// For ASP.NET Core / generic-host applications that already call <c>AddOpenTelemetry()</c>, use
 22/// <see cref="LangfuseServiceCollectionExtensions.AddNeedlrLangfuse(Microsoft.Extensions.DependencyInjection.IServiceCo
 23/// instead so the providers participate in the host lifecycle.
 24/// </para>
 25/// <example>
 26/// <code>
 27/// using var langfuse = LangfuseTelemetry.Start(LangfuseOptions.FromEnvironment());
 28/// using (LangfuseTrace.BeginScenario("trip-planner: NYC -> Tokyo", sessionId: runId))
 29/// {
 30///     var run = await runner.RunAsync(...);
 31///     // ... evaluate and record scores ...
 32/// }
 33/// </code>
 34/// </example>
 35/// </remarks>
 36public static class LangfuseTelemetry
 37{
 38    /// <summary>
 39    /// Starts a Langfuse export session for the supplied options.
 40    /// </summary>
 41    /// <param name="options">
 42    /// The export configuration. When <see cref="LangfuseOptions.IsConfigured"/> is
 43    /// <see langword="false"/> (for example, missing credentials), a disabled no-op session is
 44    /// returned so callers never need to branch on configuration state.
 45    /// </param>
 46    /// <returns>
 47    /// An <see cref="ILangfuseSession"/> that exports telemetry until disposed.
 48    /// </returns>
 49    /// <exception cref="ArgumentNullException"><paramref name="options"/> is <see langword="null"/>.</exception>
 50    public static ILangfuseSession Start(LangfuseOptions options)
 51    {
 452        ArgumentNullException.ThrowIfNull(options);
 53
 454        if (!options.IsConfigured)
 55        {
 456            LangfuseExportGuard.WarnIfCredentialsWithoutTarget(options);
 457            return new DisabledLangfuseSession();
 58        }
 59
 060        var endpoints = LangfuseEndpoints.Resolve(options);
 061        var resource = BuildResource(options);
 62
 063        var tracerProvider = Sdk.CreateTracerProviderBuilder()
 064            .SetResourceBuilder(resource)
 065            .SetSampler(BuildSampler(options.SamplingRatio))
 066            .AddSource(LangfuseActivitySource.Name)
 067            .AddSource(options.AgentActivitySourceName)
 068            .AddSource([.. options.AdditionalActivitySources])
 069            .AddProcessor(new LangfuseTraceAttributeProcessor(options.Environment, options.Release))
 070            .AddOtlpExporter(otlp => ConfigureOtlp(otlp, endpoints.TracesEndpoint, endpoints.Headers))
 071            .Build();
 72
 073        MeterProvider? meterProvider = null;
 074        if (options.IncludeMetrics)
 75        {
 076            meterProvider = Sdk.CreateMeterProviderBuilder()
 077                .SetResourceBuilder(resource)
 078                .AddMeter(options.AgentMeterName)
 079                .AddMeter(options.GenAiMeterName)
 080                .AddMeter([.. options.AdditionalMeters])
 081                .AddOtlpExporter(otlp => ConfigureOtlp(otlp, endpoints.MetricsEndpoint, endpoints.Headers))
 082                .Build();
 83        }
 84
 085        var httpClient = new HttpClient();
 086        var scoreApiClient = new LangfuseScoreApiClient(
 087            httpClient,
 088            endpoints.ScoresEndpoint,
 089            endpoints.AuthorizationHeaderValue);
 090        var apiClient = new LangfuseApiClient(
 091            httpClient,
 092            endpoints.BaseUrl,
 093            endpoints.AuthorizationHeaderValue);
 94
 095        var failureSink = new LangfuseScoreFailureSink(options.ScoreFailureMode, options.ScoreErrorCallback);
 096        var recorder = new LangfuseScoreRecorder(scoreApiClient, failureSink, options.NormalizeScoreNames);
 097        var commentRecorder = new LangfuseCommentRecorder(apiClient, options.DiagnosticsCallback);
 98
 099        return new LangfuseSession(
 0100            tracerProvider,
 0101            meterProvider,
 0102            httpClient,
 0103            recorder,
 0104            failureSink,
 0105            commentRecorder,
 0106            apiClient,
 0107            options.DiagnosticsCallback);
 108    }
 109
 110    private static ResourceBuilder BuildResource(LangfuseOptions options)
 111    {
 0112        var builder = ResourceBuilder.CreateDefault();
 0113        return string.IsNullOrWhiteSpace(options.ServiceVersion)
 0114            ? builder.AddService(options.ServiceName)
 0115            : builder.AddService(options.ServiceName, serviceVersion: options.ServiceVersion);
 116    }
 117
 118    private static Sampler BuildSampler(double ratio)
 119    {
 0120        if (ratio >= 1.0)
 121        {
 0122            return new AlwaysOnSampler();
 123        }
 124
 0125        if (ratio <= 0.0)
 126        {
 0127            return new AlwaysOffSampler();
 128        }
 129
 0130        return new TraceIdRatioBasedSampler(ratio);
 131    }
 132
 133    private static void ConfigureOtlp(OtlpExporterOptions otlp, Uri endpoint, string headers)
 134    {
 0135        otlp.Endpoint = endpoint;
 0136        otlp.Protocol = OtlpExportProtocol.HttpProtobuf;
 0137        otlp.Headers = headers;
 0138    }
 139}