OpenTelemetry Integration

The Wollax.Cupel.Diagnostics.OpenTelemetry companion package bridges the core pipeline’s ITraceCollector/TraceCollector abstraction to .NET ActivitySource, emitting structured telemetry that traces context-window construction.

Overview

Wollax.Cupel.Diagnostics.OpenTelemetry is a separate NuGet assembly. The core Wollax.Cupel assembly has zero dependency on OpenTelemetry (R032/D039). Callers who do not need telemetry incur no transitive dependency on the OpenTelemetry SDK.

The companion registers a single ActivitySource with the canonical name "Wollax.Cupel". To receive traces, callers add this name in their OpenTelemetry configuration:

services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddSource("Wollax.Cupel")
        // ... exporters ...
    );

Pre-Stability Disclaimer

Warning: All cupel.* attribute names documented in this chapter are pre-stable. The OpenTelemetry LLM semantic conventions are under active development, and cupel.* names may be renamed, merged, or removed as the conventions stabilize.

Do not build alert rules, dashboards, or automation that hard-code specific cupel.* attribute names. Use the attribute names only for ad-hoc debugging and observability during development and staging.

Activity Hierarchy

A single pipeline run produces a tree of Activities:

cupel.pipeline                            ← root; spans the full pipeline run
├── cupel.stage.classify
├── cupel.stage.score
├── cupel.stage.deduplicate
├── cupel.stage.slice
└── cupel.stage.place

There are exactly five stage Activities, one per diagnostic stage. Stage names in cupel.stage.{name} attribute values use lowercase only, matching the PipelineStage enum value lowercasing.

Sort is omitted. Sort is an internal ordering step with no diagnostic boundary and no meaningful duration to report independently. It does not receive its own Activity, consistent with the PipelineStage enum omission documented in Events.

Verbosity Tiers

The CupelVerbosity enum controls how much diagnostic data is emitted per pipeline run. Choose the tier appropriate for the target environment.

StageOnly — Production-safe

Emits the root Activity and five stage Activities with item-count boundaries only. No per-item events are recorded.

Root Activity (cupel.pipeline) attributes:

AttributeTypeDescription
cupel.budget.max_tokensintThe pipeline’s maximum token budget
cupel.verbositystringThe verbosity tier name ("StageOnly")

Each stage Activity (cupel.stage.{name}) attributes:

AttributeTypeDescription
cupel.stage.namestringStage name in lowercase (e.g., "classify", "score")
cupel.stage.item_count_inintNumber of items entering this stage
cupel.stage.item_count_outintNumber of items leaving this stage

Note: No duration_ms attribute is present. Stage duration is provided by the Activity’s own start and end timestamps; recording it as an attribute would duplicate information already available in the trace span.

StageAndExclusions — Staging environments

Emits all StageOnly attributes plus per-exclusion Events on each stage Activity where an exclusion occurred.

Additions over StageOnly:

On each stage Activity, for every item excluded during that stage, one cupel.exclusion Event is recorded:

Event AttributeTypeDescription
cupel.exclusion.reasonstringExclusionReason variant name (e.g., "BudgetExceeded")
cupel.exclusion.item_kindstringThe kind of the excluded item
cupel.exclusion.item_tokensintToken count of the excluded item

Additionally, a summary attribute is added to the stage Activity:

AttributeTypeDescription
cupel.exclusion.countintTotal number of exclusions in this stage

Note: cupel.exclusion.reason values are open-ended. New ExclusionReason variants may appear in future spec versions without a schema change. Trace backends and dashboards MUST NOT hard-code the set of expected reason values.

Full — Development only

Emits all StageAndExclusions attributes plus per-included-item Events recorded after the Place stage completes.

Additions over StageAndExclusions:

After the Place stage, for every item included in the final output, one cupel.item.included Event is recorded:

Event AttributeTypeDescription
cupel.item.kindstringThe kind of the included item
cupel.item.tokensintToken count of the included item
cupel.item.scorefloat64The item’s final composite score

Note: No placement-position attribute is recorded. Position is a derived property of ordering and not a stable attribute of the item itself.

Cardinality

VerbosityEvents / Pipeline RunRecommended Environment
StageOnly~10 (5 stage Activities + root)Production
StageAndExclusions~10 + 0–300 (depends on exclusion count)Staging
Full~10 + 0–1000 (depends on item count)Development only

Warning: Full verbosity can produce high-cardinality traces with large item sets. Do not enable Full in production without a sampling strategy that caps trace volume.

Attribute Reference

Complete flat table of all cupel.* attributes and events for quick lookup.

NameTypeTier(s)CarrierDescription
cupel.budget.max_tokensintAllRoot ActivityPipeline maximum token budget
cupel.verbositystringAllRoot ActivityVerbosity tier name
cupel.stage.namestringAllStage ActivityStage name, lowercase
cupel.stage.item_count_inintAllStage ActivityItems entering the stage
cupel.stage.item_count_outintAllStage ActivityItems leaving the stage
cupel.exclusion.countintStageAndExclusions, FullStage ActivityTotal exclusions in the stage
cupel.exclusion.reasonstringStageAndExclusions, Fullcupel.exclusion EventExclusionReason variant name
cupel.exclusion.item_kindstringStageAndExclusions, Fullcupel.exclusion EventKind of the excluded item
cupel.exclusion.item_tokensintStageAndExclusions, Fullcupel.exclusion EventToken count of the excluded item
cupel.item.kindstringFullcupel.item.included EventKind of the included item
cupel.item.tokensintFullcupel.item.included EventToken count of the included item
cupel.item.scorefloat64Fullcupel.item.included EventFinal composite score of the included item

Conformance Notes

  • cupel.exclusion.reason mapping: Values MUST be the canonical ExclusionReason variant name string (e.g., "BudgetExceeded", "Deduplicated"). Implementations MUST NOT use numeric codes, display strings, or locale-specific representations. New ExclusionReason variants in future spec versions will appear as new attribute values — trace backends must not hard-code the set.
  • ActivitySource name: Implementations MUST register the ActivitySource with the name "Wollax.Cupel" exactly. Callers configure their OpenTelemetry pipeline using this string; any deviation breaks trace collection silently.
  • Zero-dependency core: The Wollax.Cupel assembly MUST have no compile-time or runtime dependency on any OpenTelemetry SDK assembly. The companion package provides the bridge; the core pipeline operates through ITraceCollector only.
  • Stage Activity names: Stage Activity names MUST follow the pattern cupel.stage.{name} where {name} is the lowercase stage name. The five canonical values are classify, score, deduplicate, slice, place.

Rust (cupel-otel)

The cupel-otel crate is the Rust equivalent of Wollax.Cupel.Diagnostics.OpenTelemetry. It implements the TraceCollector trait from the core cupel crate and bridges pipeline execution data to the opentelemetry API.

Source name: The Rust implementation registers with the source name "cupel" (distinct from the .NET source name "Wollax.Cupel" — D163). Callers must configure "cupel" in their OpenTelemetry SDK tracer provider:

#![allow(unused)]
fn main() {
// Register the tracer provider with the cupel source name
let tracer_provider = SdkTracerProvider::builder()
    .with_batch_exporter(exporter)
    .build();
opentelemetry::global::set_tracer_provider(tracer_provider);
}

Adding to a project

Add cupel-otel as a dependency in Cargo.toml:

[dependencies]
cupel-otel = "0.1"

Usage example

#![allow(unused)]
fn main() {
use cupel_otel::{CupelOtelTraceCollector, CupelVerbosity};

let collector = CupelOtelTraceCollector::new(CupelVerbosity::StageOnly);
let result = pipeline.run_traced(&items, &collector).await;
collector.on_pipeline_completed(&result.stage_traces);
// Flush the tracer provider before process exit
}

Implementation notes

  • Explicit .end() is mandatory. The opentelemetry 0.27 Span type does not auto-end on drop (D169). The CupelOtelTraceCollector calls .end() explicitly on each span after recording its attributes and events. Callers who implement a custom TraceCollector using the opentelemetry API must also call .end() explicitly.
  • SpanData import path. The correct import is opentelemetry_sdk::export::trace::SpanData, not opentelemetry_sdk::trace::SpanData. The latter does not exist in opentelemetry_sdk 0.27.
  • Unknown ExclusionReason variants. The cupel.exclusion.reason attribute uses a _ => "Unknown" match arm for any unrecognised ExclusionReason variant. This provides forward compatibility with future spec variants — unknown reasons are emitted as "Unknown" rather than causing a compilation failure or panic.