riffer 0.33.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +12 -0
  4. data/docs/10_CONFIGURATION.md +15 -15
  5. data/docs/16_TRACING.md +28 -4
  6. data/docs/17_METRICS.md +34 -4
  7. data/lib/riffer/agent/run.rb +1 -1
  8. data/lib/riffer/config.rb +23 -24
  9. data/lib/riffer/guardrails/runner.rb +33 -8
  10. data/lib/riffer/metrics/instruments.rb +6 -0
  11. data/lib/riffer/metrics/{null.rb → no_op.rb} +1 -1
  12. data/lib/riffer/metrics/otel.rb +4 -3
  13. data/lib/riffer/metrics.rb +7 -6
  14. data/lib/riffer/providers/base.rb +6 -6
  15. data/lib/riffer/tools/runtime.rb +5 -5
  16. data/lib/riffer/tracing/{null.rb → no_op.rb} +3 -3
  17. data/lib/riffer/tracing/otel.rb +4 -3
  18. data/lib/riffer/tracing.rb +9 -9
  19. data/lib/riffer/version.rb +1 -1
  20. data/sig/generated/riffer/agent/run.rbs +2 -2
  21. data/sig/generated/riffer/config.rbs +14 -14
  22. data/sig/generated/riffer/guardrails/runner.rbs +10 -2
  23. data/sig/generated/riffer/metrics/instruments.rbs +2 -0
  24. data/sig/generated/riffer/metrics/{null.rbs → no_op.rbs} +2 -2
  25. data/sig/generated/riffer/metrics/otel.rbs +4 -3
  26. data/sig/generated/riffer/metrics.rbs +7 -6
  27. data/sig/generated/riffer/providers/base.rbs +12 -12
  28. data/sig/generated/riffer/tools/runtime.rbs +10 -10
  29. data/sig/generated/riffer/tracing/{null.rbs → no_op.rbs} +5 -5
  30. data/sig/generated/riffer/tracing/otel.rbs +4 -3
  31. data/sig/generated/riffer/tracing.rbs +9 -9
  32. data/sig/manual/riffer/metrics/no_op.rbs +5 -0
  33. data/sig/manual/riffer/tracing/no_op.rbs +5 -0
  34. metadata +11 -11
  35. data/sig/manual/riffer/metrics/null.rbs +0 -5
  36. data/sig/manual/riffer/tracing/null.rbs +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e72e36f6d29b3a2387246615224e19becb1d157e4a2327f8229ee481f3704e7e
4
- data.tar.gz: 89deb58a1ec3a5f0af904d86b38057a2d7bf301490deeadf492bbe11942d0e96
3
+ metadata.gz: bc442fb9eb0296a5a9c6f289c1c9e63e7cdb732ef52b013e1556d1765a874f2d
4
+ data.tar.gz: aa7512f0b3ac38e4f0c7be500095c73fca0ae1740c37370ceb2aeaf57349dac8
5
5
  SHA512:
6
- metadata.gz: 11fadbbfd249ce864885709684c51801d3ec2aeba83a3f750ee7afdb390750798d9598df69796bf047afa4c4b4e7136cfc7cff0edf851a0a8d945bb9f8549204
7
- data.tar.gz: 60b6c3b2c8e7201cdfb6822709531640b501f4218e63fc2768349c9509b67ba286299fc00372a0c019b07a01390f4a7e77c5a4382e7ffe21ed7cfd250acb67b9
6
+ metadata.gz: d9ace157326fa9ebfb356705f14cd4a8bac4af86e3ede2eb142adb4be36870399c81ee122fa02973135c9176b521bdee980745e3cc3f14422e12fbf1392fb009
7
+ data.tar.gz: cdc2e19b1aab77a93fb6ad7c4a800c5002a5efb63667366113fc04104a05c342863a1fa74219ed2569759f5f7be08dc534ac7c79b10c6d9ab61d6430db805113
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.33.0"
2
+ ".": "0.34.0"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.34.0](https://github.com/janeapp/riffer/compare/riffer/v0.33.0...riffer/v0.34.0) (2026-06-22)
9
+
10
+
11
+ ### ⚠ BREAKING CHANGES
12
+
13
+ * **telemetry:** `config.tracing.tracer_provider` and `config.metrics.meter_provider` are removed. Assign the OTEL backend instead: `config.tracing.backend = Riffer::Tracing::Otel.build` (pass `provider:` to override the global provider).
14
+
15
+ ### Features
16
+
17
+ * emit a riffer.guardrail.duration metric for guardrail execution ([#338](https://github.com/janeapp/riffer/issues/338)) ([7b0dfb0](https://github.com/janeapp/riffer/commit/7b0dfb014f710d839e53d25ee345df5b404c8c60))
18
+ * **telemetry:** pluggable tracing/metrics backend (OTEL opt-in) ([#329](https://github.com/janeapp/riffer/issues/329)) ([d647f54](https://github.com/janeapp/riffer/commit/d647f54b814bb8e331b89811c2ed661d6534b20c))
19
+
8
20
  ## [0.33.0](https://github.com/janeapp/riffer/compare/riffer/v0.32.1...riffer/v0.33.0) (2026-06-18)
9
21
 
10
22
 
@@ -103,38 +103,38 @@ Accepts a `Riffer::Skills::Backend` instance or a `Proc` that receives `context`
103
103
 
104
104
  ### Tracing
105
105
 
106
- Tracing-related global configuration lives under `config.tracing`. Riffer detects the OpenTelemetry API at runtimewithout it (or without a host-configured OTEL SDK) every span is a silent no-op, and riffer carries no OTEL gem dependency either way.
106
+ Tracing-related global configuration lives under `config.tracing`. Riffer emits spans only through the backend you assign to `config.tracing.backend` there is **no auto-detection**. OpenTelemetry is a built-in backend you opt into explicitly with `Riffer::Tracing::Otel.build`; with no backend set every span is a silent no-op, and riffer carries no OTEL gem dependency either way.
107
107
 
108
108
  ```ruby
109
109
  Riffer.configure do |config|
110
110
  config.tracing.enabled = ENV.fetch("RIFFER_TRACING_ENABLED", "true")
111
+ # Opt into OpenTelemetry (uses the global tracer provider):
112
+ config.tracing.backend = Riffer::Tracing::Otel.build
111
113
  end
112
114
  ```
113
115
 
114
- | Option | Description |
115
- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
116
- | `enabled` | The kill switch, consulted on every span — flipping it at runtime takes effect immediately. Accepts booleans or `'true'`/`'false'`/`'1'`/`'0'`. Defaults to `true`. |
117
- | `capture_messages` | Opt-in capture of full message content on LLM-call spans (`gen_ai.input.messages`, `gen_ai.output.messages`, `gen_ai.system_instructions`) as GenAI-semconv JSON. Defaults to `false` — message content routinely carries sensitive data. File attachments serialize as metadata-only stubs (media type and name, never bytes), and riffer applies no size limit of its own — cap oversized attributes with the OTEL SDK attribute length limits. |
118
- | `tracer_provider` | Explicit OTEL tracer provider (e.g. the SDK's in-memory provider in tests). Defaults to `nil`, which resolves the global `OpenTelemetry.tracer_provider` lazily at first span. Raises `Riffer::ArgumentError` if the `opentelemetry-api` gem isn't available at a supported version (>= 1.1, < 2). |
119
-
120
- Hosts own SDK and exporter wiring — riffer only emits spans through whatever provider the host configures. See [Tracing](16_TRACING.md) for the emitted span contract — names, attributes, hierarchy, and host wiring.
116
+ | Option | Description |
117
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
118
+ | `enabled` | The kill switch, consulted on every span — flipping it at runtime takes effect immediately, short-circuiting to a no-op ahead of the backend. Accepts booleans or `'true'`/`'false'`/`'1'`/`'0'`. Defaults to `true`. |
119
+ | `capture_messages` | Opt-in capture of full message content on LLM-call spans (`gen_ai.input.messages`, `gen_ai.output.messages`, `gen_ai.system_instructions`) as GenAI-semconv JSON. Defaults to `false` — message content routinely carries sensitive data. File attachments serialize as metadata-only stubs (media type and name, never bytes), and riffer applies no size limit of its own — cap oversized attributes with the OTEL SDK attribute length limits. |
120
+ | `backend` | The backend riffer routes spans through. Assign `Riffer::Tracing::Otel.build` (pass `provider:` to override the global tracer provider e.g. an in-memory provider in tests), or any object satisfying the duck-typed contract (`in_span` / `current_context` / `with_context`) to route into a non-OTEL system (e.g. Datadog APM). Defaults to `nil` — a no-op. Raises `Riffer::ArgumentError` unless the value is `nil` or responds to `in_span`. See [Tracing Routing to a non-OpenTelemetry backend](16_TRACING.md#routing-to-a-non-opentelemetry-backend). |
121
121
 
122
122
  ### Metrics
123
123
 
124
- Metrics-related global configuration lives under `config.metrics`, **independent** of `config.tracing` — each has its own kill switch, so you can run one signal without the other. Riffer detects the OpenTelemetry metrics API at runtimewithout it (or without a host-configured OTEL metrics SDK) every measurement is a silent no-op, and riffer carries no OTEL gem dependency either way. The metrics API and SDK are separate, still-experimental (pre-1.0) gems from the traces API.
124
+ Metrics-related global configuration lives under `config.metrics`, **independent** of `config.tracing` — each has its own kill switch and its own backend, so you can run one signal without the other. Riffer records measurements only through the backend you assign to `config.metrics.backend`there is **no auto-detection**. OpenTelemetry is a built-in backend you opt into explicitly with `Riffer::Metrics::Otel.build`; with no backend set every measurement is a silent no-op, and riffer carries no OTEL gem dependency either way. The metrics API and SDK are separate, still-experimental (pre-1.0) gems from the traces API.
125
125
 
126
126
  ```ruby
127
127
  Riffer.configure do |config|
128
128
  config.metrics.enabled = ENV.fetch("RIFFER_METRICS_ENABLED", "true")
129
+ # Opt into OpenTelemetry (uses the global meter provider):
130
+ config.metrics.backend = Riffer::Metrics::Otel.build
129
131
  end
130
132
  ```
131
133
 
132
- | Option | Description |
133
- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
134
- | `enabled` | The kill switch, consulted on every measurement — flipping it at runtime takes effect immediately. Accepts booleans or `'true'`/`'false'`/`'1'`/`'0'`. Defaults to `true`. |
135
- | `meter_provider` | Explicit OTEL meter provider (e.g. the SDK's in-memory provider in tests). Defaults to `nil`, which resolves the global `OpenTelemetry.meter_provider` lazily at first record. Raises `Riffer::ArgumentError` if the `opentelemetry-metrics-api` gem isn't available at a supported version (>= 0.2, < 1.0). |
136
-
137
- Hosts own SDK, reader, exporter, and aggregation wiring — riffer only records instruments through whatever provider the host configures, and histogram bucket boundaries are set host-side via Views. See [Metrics](17_METRICS.md) for the instrument contract — names, units, attributes, and host wiring.
134
+ | Option | Description |
135
+ | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
136
+ | `enabled` | The kill switch, consulted on every measurement — flipping it at runtime takes effect immediately, short-circuiting to a no-op ahead of the backend. Accepts booleans or `'true'`/`'false'`/`'1'`/`'0'`. Defaults to `true`. |
137
+ | `backend` | The backend riffer routes measurements through. Assign `Riffer::Metrics::Otel.build` (pass `provider:` to override the global meter provider e.g. an in-memory provider in tests), or any object responding to `record_histogram` to route into a non-OTEL system (e.g. DogStatsD). Defaults to `nil` a no-op. Raises `Riffer::ArgumentError` unless the value is `nil` or responds to `record_histogram`. See [Metrics Routing to a non-OpenTelemetry backend](17_METRICS.md#routing-to-a-non-opentelemetry-backend). |
138
138
 
139
139
  ### Pricing
140
140
 
data/docs/16_TRACING.md CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Riffer instruments its agent loop with [OpenTelemetry](https://opentelemetry.io/) spans, following the [GenAI semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/). The emitted span shape — names, attributes, and hierarchy — is a public, versioned contract you can build dashboards, alerts, and cost reporting against. This page is the reference for that contract.
4
4
 
5
- Riffer only _emits_ spans. The host application owns the OpenTelemetry SDK, the exporter, sampling, and service naming — the standard OTEL split. Without a host SDK, every span is a silent no-op and Riffer carries no OpenTelemetry gem dependency.
5
+ Riffer only _emits_ spans, and only through a backend you assign to `config.tracing.backend` OpenTelemetry is the built-in option you opt into (the host application owns the SDK, exporter, sampling, and service naming — the standard OTEL split), but never a default. With no backend assigned, every span is a silent no-op and Riffer carries no OpenTelemetry gem dependency.
6
6
 
7
7
  ## Enabling tracing
8
8
 
9
- Add the OpenTelemetry SDK to your host application and configure an exporter. Riffer detects the API at runtime and starts emitting through whatever provider the SDK configures there is nothing to switch on in Riffer beyond the SDK being present.
9
+ Riffer emits spans only through a backend you assign to `config.tracing.backend` it does **not** auto-detect OpenTelemetry. To use OTEL, add the SDK, configure an exporter, and assign Riffer's built-in OTEL backend with `Riffer::Tracing::Otel.build`.
10
10
 
11
11
  ```ruby
12
12
  # Gemfile
@@ -19,8 +19,14 @@ require "opentelemetry/sdk"
19
19
  OpenTelemetry::SDK.configure do |c|
20
20
  c.service_name = "my-agent-host"
21
21
  end
22
+
23
+ Riffer.configure do |config|
24
+ config.tracing.backend = Riffer::Tracing::Otel.build
25
+ end
22
26
  ```
23
27
 
28
+ `Riffer::Tracing::Otel.build` wraps the global `OpenTelemetry.tracer_provider` by default; pass `provider:` to wrap a specific one (an in-memory provider in tests, say). It returns `nil` — leaving tracing a no-op rather than raising — when the `opentelemetry-api` gem is absent or outside the supported range (>= 1.1, < 2), so the same configuration is safe on a host that doesn't bundle OTEL.
29
+
24
30
  To see Riffer's spans on stdout while developing locally, wire in the console exporter:
25
31
 
26
32
  ```ruby
@@ -36,12 +42,30 @@ OpenTelemetry::SDK.configure do |c|
36
42
  end
37
43
  ```
38
44
 
39
- Any backend that implements the OpenTelemetry Traces API ingests Riffer's spans with no second pipeline — including the `datadog` gem (`require "datadog/opentelemetry"`), which routes them through an existing tracer so they nest inside the host's live trace. For real exporter and collector setup (OTLP, sampling, resource attributes), see the [OpenTelemetry Ruby docs](https://opentelemetry.io/docs/languages/ruby/).
45
+ Any backend that implements the OpenTelemetry Traces API then ingests Riffer's spans with no second pipeline. For real exporter and collector setup (OTLP, sampling, resource attributes), see the [OpenTelemetry Ruby docs](https://opentelemetry.io/docs/languages/ruby/). A host on a non-OTEL stack (e.g. Datadog APM) assigns its own backend instead — see [Routing to a non-OpenTelemetry backend](#routing-to-a-non-opentelemetry-backend).
40
46
 
41
- The three tracing knobs — the `enabled` kill switch, opt-in message-content capture, and an explicit tracer provider for tests — live in [Configuration — Tracing](10_CONFIGURATION.md#tracing).
47
+ The tracing knobs — the `enabled` kill switch, opt-in message-content capture, and the `backend` itself — live in [Configuration — Tracing](10_CONFIGURATION.md#tracing).
42
48
 
43
49
  Spans are emitted under the instrumentation scope named `riffer`, versioned with the Riffer gem version. That scope version is the runtime signal for which release produced a span; see [Stability](#stability).
44
50
 
51
+ ## Routing to a non-OpenTelemetry backend
52
+
53
+ OpenTelemetry is one backend, not the only one. A host already invested in another stack — Datadog APM, say — can route Riffer's spans into it with **no `opentelemetry-*` gem installed** by assigning its own backend to `config.tracing.backend` in place of `Riffer::Tracing::Otel.build`. Whatever you assign is the backend; there is no fallback and no auto-detection — an unset backend is a no-op.
54
+
55
+ ```ruby
56
+ Riffer.configure do |config|
57
+ config.tracing.backend = MyDatadogTracingBackend.new
58
+ end
59
+ ```
60
+
61
+ The backend is duck-typed — any object satisfying the contract works, and the setter validates only that it responds to `in_span` (otherwise it raises `Riffer::ArgumentError`). It must respond to:
62
+
63
+ - `in_span(name, attributes:, kind:) { |span| … }` — open a span around the block, yield a span object, and return the block's value.
64
+ - `current_context` — return the active trace context (for re-attaching across fiber/thread boundaries), or `nil` when there is none.
65
+ - `with_context(context) { … }` — run the block with the given context active; a `nil` context passes straight through, so a span re-attached while tracing was dark stays harmless.
66
+
67
+ The yielded span must respond to `set_attribute(key, value)`, `add_event(name, attributes:)`, `record_exception(exception)`, `error!(description)`, and `recording?` — the same surface the OTEL span exposes. `Riffer::Tracing::NoOp` is the reference shape for both the backend and the span contract. The `enabled` kill switch is still honoured ahead of the backend: with `config.tracing.enabled = false`, spans short-circuit to the no-op without ever reaching a custom backend.
68
+
45
69
  ## Spans
46
70
 
47
71
  Riffer emits four span types. A single agent run produces one `invoke_agent` span wrapping one `chat` span per model call, one `execute_tool` span per tool call, and one `execute_guardrail` span per guardrail execution, interleaved in execution order:
data/docs/17_METRICS.md CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  Riffer can record [OpenTelemetry](https://opentelemetry.io/) metric instruments alongside its [spans](16_TRACING.md), following the [GenAI semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/). Metric names, instrument types, units, and attributes are a public, versioned contract you can build dashboards and alerts against. This page is the reference for that contract.
4
4
 
5
- As with tracing, Riffer only _records_ instruments. The host application owns the OpenTelemetry SDK, the metric reader, the exporter, and the aggregation — the standard OTEL split. Without a host SDK, every measurement is a silent no-op and Riffer carries no OpenTelemetry gem dependency.
5
+ As with tracing, Riffer only _records_ instruments, and only through a backend you assign to `config.metrics.backend` OpenTelemetry is the built-in option you opt into (the host application owns the SDK, metric reader, exporter, and aggregation — the standard OTEL split), but never a default. With no backend assigned, every measurement is a silent no-op and Riffer carries no OpenTelemetry gem dependency.
6
6
 
7
7
  > **OpenTelemetry metrics for Ruby is still pre-1.0.** The metrics API and SDK ship as separate, experimental gems (`opentelemetry-metrics-api`, `opentelemetry-metrics-sdk`) from the stable 1.x traces API. Riffer guards against an incompatible API and falls back to a no-op outside the supported range, but expect the host-side wiring below to evolve with those gems.
8
8
 
9
9
  ## Enabling metrics
10
10
 
11
- Add the OpenTelemetry metrics SDK to your host application and register a metric reader with an exporter. Riffer detects the metrics API at runtime and starts recording through whatever meter provider the SDK configures.
11
+ Riffer records measurements only through a backend you assign to `config.metrics.backend` it does **not** auto-detect OpenTelemetry. To use OTEL, add the metrics SDK, register a metric reader with an exporter, and assign Riffer's built-in OTEL backend with `Riffer::Metrics::Otel.build`.
12
12
 
13
13
  ```ruby
14
14
  # Gemfile
@@ -21,14 +21,32 @@ require "opentelemetry-metrics-sdk"
21
21
  OpenTelemetry::SDK.configure do |c|
22
22
  c.service_name = "my-agent-host"
23
23
  end
24
+
25
+ Riffer.configure do |config|
26
+ config.metrics.backend = Riffer::Metrics::Otel.build
27
+ end
24
28
  ```
25
29
 
26
- The metrics SDK is **separate** from the traces SDK (`opentelemetry-sdk`); add it explicitly even if you already trace. Any backend implementing the OpenTelemetry Metrics API ingests Riffer's instruments. For real reader and exporter setup (OTLP, periodic export, Views), see the [OpenTelemetry Ruby docs](https://opentelemetry.io/docs/languages/ruby/).
30
+ The metrics SDK is **separate** from the traces SDK (`opentelemetry-sdk`); add it explicitly even if you already trace. `Riffer::Metrics::Otel.build` wraps the global `OpenTelemetry.meter_provider` by default; pass `provider:` to wrap a specific one. It returns `nil` — leaving metrics a no-op rather than raising — when the `opentelemetry-metrics-api` gem is absent or outside the supported range (>= 0.2, < 1.0). Any backend implementing the OpenTelemetry Metrics API then ingests Riffer's instruments. For real reader and exporter setup (OTLP, periodic export, Views), see the [OpenTelemetry Ruby docs](https://opentelemetry.io/docs/languages/ruby/).
27
31
 
28
- The two metrics knobs — the `enabled` kill switch and an explicit meter provider for tests — live in [Configuration — Metrics](10_CONFIGURATION.md#metrics). They are **independent** of the tracing knobs: you can run tracing while metrics are off, or the reverse.
32
+ The metrics knobs — the `enabled` kill switch and the `backend` itself — live in [Configuration — Metrics](10_CONFIGURATION.md#metrics). They are **independent** of the tracing knobs (each signal has its own backend): you can run tracing while metrics are off, or the reverse.
29
33
 
30
34
  Instruments are recorded under the instrumentation scope named `riffer`, versioned with the Riffer gem version — the runtime signal for which release produced a measurement; see [Stability](#stability).
31
35
 
36
+ ## Routing to a non-OpenTelemetry backend
37
+
38
+ OpenTelemetry is one backend, not the only one. A host that already runs another metrics stack — DogStatsD, say — can route Riffer's measurements into it with **no `opentelemetry-*` gem installed** by assigning its own backend to `config.metrics.backend` in place of `Riffer::Metrics::Otel.build`. Whatever you assign is the backend; there is no fallback and no auto-detection — an unset backend is a no-op.
39
+
40
+ ```ruby
41
+ Riffer.configure do |config|
42
+ config.metrics.backend = MyDogStatsdMetricsBackend.new
43
+ end
44
+ ```
45
+
46
+ The backend is duck-typed: any object that responds to `record_histogram(name, value, unit:, description:, attributes:)` works, and the setter validates only that (otherwise it raises `Riffer::ArgumentError`). `Riffer::Metrics::NoOp` is the reference shape. All four instruments are histograms, so the single `record_histogram` method covers the full contract; tell them apart by `name`. Two carry unit `s` — `gen_ai.client.operation.duration` and `riffer.guardrail.duration` — so `name` is the reliable discriminator; `{token}` is `gen_ai.client.token.usage` and `USD` is `riffer.gen_ai.cost`.
47
+
48
+ A custom backend counts as a **live** sink, so the providers still compute the token counts and cost that feed it — the same data that, under OTEL, populates `gen_ai.client.token.usage` and `riffer.gen_ai.cost`. The `enabled` kill switch is honoured ahead of the backend: with `config.metrics.enabled = false`, measurements short-circuit to the no-op without ever reaching a custom backend.
49
+
32
50
  ### Bucket boundaries
33
51
 
34
52
  Histogram bucket boundaries are a **host-side** concern. The OpenTelemetry metrics API does not let an instrumenting library attach bucket boundaries at instrument creation, so Riffer does not set them — the SDK's default buckets apply unless you override them. To match the GenAI semantic conventions' recommended boundaries (or your own), register a [View](https://opentelemetry.io/docs/specs/otel/metrics/sdk/#view) on the meter provider that targets the instrument by name and sets explicit bucket boundaries.
@@ -99,6 +117,18 @@ Pricing is **consumer-configured** — no price table ships with the gem (see [C
99
117
 
100
118
  > **Per-call only.** Cost is never recorded at the run (`invoke_agent`) level, for the same reason as token usage: metrics pre-aggregate, so emitting both per-call points and a run total would double-count. Sum the per-call points in your backend for a run total.
101
119
 
120
+ ### `riffer.guardrail.duration`
121
+
122
+ Histogram, unit `s`. The latency of a single guardrail execution, recorded around the same wrap as the [`execute_guardrail` span](16_TRACING.md) on both the success and raise paths and timed with a monotonic clock. Guardrails run on the request hot path — before every model turn and after every response — so this is the guardrail counterpart to `gen_ai.client.operation.duration`. Recording is independent of tracing — the metric fires even with `config.tracing.enabled = false`.
123
+
124
+ This instrument is Riffer-owned (`riffer.*`, not `gen_ai.*`) by the same reasoning as its span: a guardrail is not a GenAI semantic-convention operation, so the `execute_guardrail` span deliberately carries no `gen_ai.operation.name` and lives in riffer's own `riffer.guardrail.*` namespace (see [Tracing](16_TRACING.md)). Folding the metric into `gen_ai.client.operation.duration` would contradict that, so the duration is its own riffer-owned histogram instead; see [Stability](#stability).
125
+
126
+ | Value | Attributes |
127
+ | ------------------------------------ | --------------------------------------------------------------------- |
128
+ | duration of the guardrail execution | `riffer.guardrail.name`, `riffer.guardrail.phase`, `error.type` (on raise) |
129
+
130
+ `riffer.guardrail.phase` is `before` or `after`. A pass, transform, or block is a **handled** outcome and records no `error.type` — that attribute carries the exception class only when a guardrail raises, mirroring the span. One time series exists per `riffer.guardrail.name` × `riffer.guardrail.phase`; guardrail names are bounded by the guardrails you configure, so cardinality is safe — unlike the dynamic tool names on `gen_ai.client.operation.duration`.
131
+
102
132
  ## Stability
103
133
 
104
134
  The instrument shape is a public, versioned contract, in two tiers — mirroring the [tracing contract](16_TRACING.md#stability):
@@ -353,7 +353,7 @@ module Riffer::Agent::Run
353
353
  end
354
354
 
355
355
  #--
356
- #: (Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span, Riffer::Agent::Response) -> void
356
+ #: (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span, Riffer::Agent::Response) -> void
357
357
  def record_run_outcome(span, response)
358
358
  span.set_attribute("riffer.steps", response.steps)
359
359
  Riffer::Tracing.record_usage(span, response.token_usage)
data/lib/riffer/config.rb CHANGED
@@ -61,16 +61,16 @@ class Riffer::Config
61
61
  # content routinely carries sensitive data.
62
62
  attr_reader :capture_messages #: bool
63
63
 
64
- # Explicit OTEL tracer provider; defaults to +nil+, which resolves the
65
- # global <tt>OpenTelemetry.tracer_provider</tt> at first span.
66
- attr_reader :tracer_provider #: untyped
64
+ # The backend riffer routes spans through; defaults to +nil+, a no-op.
65
+ # Riffer auto-detects no backend; assigning one is opt-in.
66
+ attr_reader :backend #: untyped
67
67
 
68
68
  #--
69
69
  #: () -> void
70
70
  def initialize
71
71
  @enabled = true
72
72
  @capture_messages = false
73
- @tracer_provider = nil
73
+ @backend = nil
74
74
  end
75
75
 
76
76
  # Sets the enabled flag, coercing boolean-ish values so an env-var
@@ -91,17 +91,17 @@ class Riffer::Config
91
91
  @capture_messages = Riffer::Helpers::Boolean.coerce(value, attribute: "capture_messages")
92
92
  end
93
93
 
94
- # Sets an explicit tracer provider, forcing the OTEL backend. Raises
95
- # Riffer::ArgumentError when the OpenTelemetry API gem isn't available
96
- # at a supported version.
94
+ # Sets the tracing backend riffer routes spans through. Raises
95
+ # Riffer::ArgumentError unless the value is +nil+ or responds to the full
96
+ # delegated contract: +in_span+, +current_context+, and +with_context+.
97
97
  #--
98
98
  #: (untyped) -> void
99
- def tracer_provider=(value)
100
- if !value.nil? && !Riffer::Tracing::Otel.available?
101
- raise Riffer::ArgumentError,
102
- "tracer_provider requires the opentelemetry-api gem (#{Riffer::Tracing::Otel::SUPPORTED_API_VERSIONS})"
99
+ def backend=(value)
100
+ contract = %i[in_span current_context with_context]
101
+ unless value.nil? || contract.all? { |method| value.respond_to?(method) }
102
+ raise Riffer::ArgumentError, "tracing backend must respond to #in_span, #current_context, and #with_context"
103
103
  end
104
- @tracer_provider = value
104
+ @backend = value
105
105
  Riffer::Tracing.reset!
106
106
  end
107
107
  end
@@ -113,15 +113,15 @@ class Riffer::Config
113
113
  # no-op until a host wires an OTEL metrics SDK.
114
114
  attr_reader :enabled #: bool
115
115
 
116
- # Explicit OTEL meter provider; defaults to +nil+, which resolves the
117
- # global <tt>OpenTelemetry.meter_provider</tt> at first record.
118
- attr_reader :meter_provider #: untyped
116
+ # The backend riffer routes measurements through; defaults to +nil+, a no-op.
117
+ # Riffer auto-detects no backend; assigning one is opt-in.
118
+ attr_reader :backend #: untyped
119
119
 
120
120
  #--
121
121
  #: () -> void
122
122
  def initialize
123
123
  @enabled = true
124
- @meter_provider = nil
124
+ @backend = nil
125
125
  end
126
126
 
127
127
  # Sets the enabled flag, coercing boolean-ish values so an env-var
@@ -133,17 +133,16 @@ class Riffer::Config
133
133
  @enabled = Riffer::Helpers::Boolean.coerce(value, attribute: "enabled")
134
134
  end
135
135
 
136
- # Sets an explicit meter provider, forcing the OTEL backend. Raises
137
- # Riffer::ArgumentError when the OpenTelemetry metrics API gem isn't
138
- # available at a supported version.
136
+ # Sets the metrics backend riffer routes measurements through. Raises
137
+ # Riffer::ArgumentError unless the value is +nil+ or responds to
138
+ # +record_histogram+.
139
139
  #--
140
140
  #: (untyped) -> void
141
- def meter_provider=(value)
142
- if !value.nil? && !Riffer::Metrics::Otel.available?
143
- raise Riffer::ArgumentError,
144
- "meter_provider requires the opentelemetry-metrics-api gem (#{Riffer::Metrics::Otel::SUPPORTED_API_VERSIONS})"
141
+ def backend=(value)
142
+ unless value.nil? || value.respond_to?(:record_histogram)
143
+ raise Riffer::ArgumentError, "metrics backend must respond to #record_histogram"
145
144
  end
146
- @meter_provider = value
145
+ @backend = value
147
146
  Riffer::Metrics.reset!
148
147
  end
149
148
  end
@@ -81,15 +81,32 @@ class Riffer::Guardrails::Runner
81
81
  #--
82
82
  #: (Riffer::Guardrail, untyped, messages: Array[Riffer::Messages::Base]?) -> Riffer::Guardrails::Result
83
83
  def execute_guardrail(guardrail, data, messages:)
84
- Riffer::Tracing.in_span("execute_guardrail #{guardrail.name}", attributes: guardrail_span_attributes(guardrail), kind: :internal) do |span|
85
- result = run_guardrail_phase(guardrail, data, messages: messages)
86
- record_guardrail_outcome(span, result)
87
- result
84
+ instrument_guardrail(guardrail) do
85
+ Riffer::Tracing.in_span("execute_guardrail #{guardrail.name}", attributes: guardrail_span_attributes(guardrail), kind: :internal) do |span|
86
+ result = run_guardrail_phase(guardrail, data, messages: messages)
87
+ record_guardrail_outcome(span, result)
88
+ result
89
+ rescue => error
90
+ # The backend records the exception and error status on the re-raise;
91
+ # error.type is the one semconv attribute it doesn't set.
92
+ span.set_attribute("error.type", error.class.name)
93
+ raise
94
+ end
95
+ end
96
+ end
97
+
98
+ #--
99
+ #: (Riffer::Guardrail) { () -> Riffer::Guardrails::Result } -> Riffer::Guardrails::Result
100
+ def instrument_guardrail(guardrail)
101
+ start = Riffer::Metrics.monotonic_now
102
+ error_type = nil #: String?
103
+ begin
104
+ yield
88
105
  rescue => error
89
- # The backend records the exception and error status on the re-raise;
90
- # error.type is the one semconv attribute it doesn't set.
91
- span.set_attribute("error.type", error.class.name)
106
+ error_type = error.class.name #: String?
92
107
  raise
108
+ ensure
109
+ Riffer::Metrics::Instruments::GUARDRAIL_DURATION.record(Riffer::Metrics.monotonic_now - start, attributes: guardrail_metric_attributes(guardrail, error_type))
93
110
  end
94
111
  end
95
112
 
@@ -115,10 +132,18 @@ class Riffer::Guardrails::Runner
115
132
  }
116
133
  end
117
134
 
135
+ #--
136
+ #: (Riffer::Guardrail, String?) -> Hash[String, untyped]
137
+ def guardrail_metric_attributes(guardrail, error_type)
138
+ attributes = guardrail_span_attributes(guardrail)
139
+ attributes["error.type"] = error_type if error_type
140
+ attributes
141
+ end
142
+
118
143
  # A block is a handled outcome, so its span status stays unset — an error
119
144
  # span status is reserved for a raised exception.
120
145
  #--
121
- #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span), Riffer::Guardrails::Result) -> void
146
+ #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Guardrails::Result) -> void
122
147
  def record_guardrail_outcome(span, result)
123
148
  span.set_attribute("riffer.guardrail.action", result.type.to_s)
124
149
  span.set_attribute("riffer.tripwire.reason", result.data) if result.block?
@@ -22,4 +22,10 @@ module Riffer::Metrics::Instruments # :nodoc: all
22
22
  unit: "USD",
23
23
  description: "Cost of GenAI client operations in USD"
24
24
  ) #: Riffer::Metrics::Histogram
25
+
26
+ GUARDRAIL_DURATION = Riffer::Metrics.create_histogram(
27
+ "riffer.guardrail.duration",
28
+ unit: "s",
29
+ description: "Duration of guardrail execution"
30
+ ) #: Riffer::Metrics::Histogram
25
31
  end
@@ -3,7 +3,7 @@
3
3
 
4
4
  # No-op metrics backend, used when the OpenTelemetry metrics API is unavailable
5
5
  # or metrics are disabled.
6
- module Riffer::Metrics::Null # :nodoc: all
6
+ module Riffer::Metrics::NoOp # :nodoc: all
7
7
  extend self
8
8
 
9
9
  # Ignores the measurement; there is no meter without the OTEL metrics API.
@@ -9,10 +9,11 @@ class Riffer::Metrics::Otel # :nodoc: all
9
9
 
10
10
  class << self
11
11
  # Builds a backend when the OpenTelemetry metrics API is loadable at a
12
- # supported version; returns +nil+ so resolution falls back to Null.
12
+ # supported version; returns +nil+ so resolution falls back to NoOp.
13
+ # +provider+ defaults to the global <tt>OpenTelemetry.meter_provider</tt>.
13
14
  #--
14
- #: (provider: untyped) -> Riffer::Metrics::Otel?
15
- def build(provider:)
15
+ #: (?provider: untyped) -> Riffer::Metrics::Otel?
16
+ def build(provider: nil)
16
17
  version = api_version
17
18
  return nil unless version
18
19
 
@@ -7,7 +7,7 @@
7
7
  module Riffer::Metrics # :nodoc: all
8
8
  extend self
9
9
 
10
- # @rbs @backend: (Riffer::Metrics::Otel | singleton(Riffer::Metrics::Null))?
10
+ # @rbs @backend: untyped
11
11
 
12
12
  MUTEX = Mutex.new #: Mutex
13
13
 
@@ -54,11 +54,12 @@ module Riffer::Metrics # :nodoc: all
54
54
  end
55
55
 
56
56
  # Mirrors a span's +recording?+ so a caller can skip work that exists only to
57
- # feed a metric.
57
+ # feed a metric. True for any live backend — the OTEL backend or a
58
+ # consumer-supplied one — so a custom metrics sink still gets fed.
58
59
  #--
59
60
  #: () -> bool
60
61
  def recording?
61
- Riffer.config.metrics.enabled && backend.is_a?(Otel)
62
+ Riffer.config.metrics.enabled && !backend.equal?(NoOp)
62
63
  end
63
64
 
64
65
  # Reads the monotonic clock in seconds — the time source for duration metrics,
@@ -80,14 +81,14 @@ module Riffer::Metrics # :nodoc: all
80
81
  private
81
82
 
82
83
  #--
83
- #: () -> (Riffer::Metrics::Otel | singleton(Riffer::Metrics::Null))
84
+ #: () -> untyped
84
85
  def backend
85
86
  @backend || MUTEX.synchronize { @backend ||= resolve_backend }
86
87
  end
87
88
 
88
89
  #--
89
- #: () -> (Riffer::Metrics::Otel | singleton(Riffer::Metrics::Null))
90
+ #: () -> untyped
90
91
  def resolve_backend
91
- Otel.build(provider: Riffer.config.metrics.meter_provider) || Null
92
+ Riffer.config.metrics.backend || NoOp
92
93
  end
93
94
  end
@@ -220,7 +220,7 @@ class Riffer::Providers::Base
220
220
  }.freeze #: Hash[Symbol, String]
221
221
 
222
222
  #--
223
- #: [R] (String?, Array[Riffer::Messages::Base], Hash[Symbol, untyped]) { (Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span) -> R } -> R
223
+ #: [R] (String?, Array[Riffer::Messages::Base], Hash[Symbol, untyped]) { (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span) -> R } -> R
224
224
  def in_chat_span(model, messages, options)
225
225
  start = Riffer::Metrics.monotonic_now
226
226
  error_type = nil #: String?
@@ -302,7 +302,7 @@ class Riffer::Providers::Base
302
302
  end
303
303
 
304
304
  #--
305
- #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span), Symbol?, String?) -> void
305
+ #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Symbol?, String?) -> void
306
306
  def record_finish_reason(span, reason, raw)
307
307
  return unless reason
308
308
 
@@ -311,7 +311,7 @@ class Riffer::Providers::Base
311
311
  end
312
312
 
313
313
  #--
314
- #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span), Riffer::Tracing::StreamRecorder) -> void
314
+ #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Tracing::StreamRecorder) -> void
315
315
  def record_stream_outcome(span, recorder)
316
316
  Riffer::Tracing.record_usage(span, recorder.token_usage)
317
317
  record_finish_reason(span, recorder.finish_reason, recorder.raw_finish_reason)
@@ -319,7 +319,7 @@ class Riffer::Providers::Base
319
319
  end
320
320
 
321
321
  #--
322
- #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span), Array[Riffer::Messages::Base]) -> void
322
+ #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Array[Riffer::Messages::Base]) -> void
323
323
  def capture_input(span, messages)
324
324
  return unless capture_messages?(span)
325
325
 
@@ -329,7 +329,7 @@ class Riffer::Providers::Base
329
329
  end
330
330
 
331
331
  #--
332
- #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span), content: String?, tool_calls: Array[Riffer::Messages::Assistant::ToolCall], finish_reason: Symbol?) -> void
332
+ #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), content: String?, tool_calls: Array[Riffer::Messages::Assistant::ToolCall], finish_reason: Symbol?) -> void
333
333
  def capture_output(span, content:, tool_calls:, finish_reason:)
334
334
  return unless capture_messages?(span)
335
335
 
@@ -337,7 +337,7 @@ class Riffer::Providers::Base
337
337
  end
338
338
 
339
339
  #--
340
- #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span)) -> bool
340
+ #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span)) -> bool
341
341
  def capture_messages?(span)
342
342
  Riffer.config.tracing.capture_messages && span.recording?
343
343
  end
@@ -113,7 +113,7 @@ class Riffer::Tools::Runtime
113
113
 
114
114
  # Emitted outside +around_tool_call+ so host enrichment spans nest beneath it.
115
115
  #--
116
- #: [R] (Riffer::Messages::Assistant::ToolCall) { ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span)) -> R } -> R
116
+ #: [R] (Riffer::Messages::Assistant::ToolCall) { ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span)) -> R } -> R
117
117
  def in_tool_span(tool_call)
118
118
  Riffer::Tracing.in_span("execute_tool #{tool_call.name}", attributes: tool_span_attributes(tool_call), kind: :internal) do |span|
119
119
  capture_tool_arguments(span, tool_call)
@@ -150,7 +150,7 @@ class Riffer::Tools::Runtime
150
150
  # A returned error Response is a handled outcome, so its status stays unset —
151
151
  # an error span status is reserved for a raised exception.
152
152
  #--
153
- #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span), Riffer::Tools::Response) -> void
153
+ #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Tools::Response) -> void
154
154
  def record_tool_outcome(span, result)
155
155
  error_type = result.error_type
156
156
  span.set_attribute("error.type", error_type.to_s) if error_type
@@ -158,7 +158,7 @@ class Riffer::Tools::Runtime
158
158
  end
159
159
 
160
160
  #--
161
- #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span), Riffer::Messages::Assistant::ToolCall) -> void
161
+ #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Messages::Assistant::ToolCall) -> void
162
162
  def capture_tool_arguments(span, tool_call)
163
163
  return unless capture_tool_content?(span)
164
164
 
@@ -167,7 +167,7 @@ class Riffer::Tools::Runtime
167
167
  end
168
168
 
169
169
  #--
170
- #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span), Riffer::Tools::Response) -> void
170
+ #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Tools::Response) -> void
171
171
  def capture_tool_result(span, result)
172
172
  return unless capture_tool_content?(span)
173
173
 
@@ -175,7 +175,7 @@ class Riffer::Tools::Runtime
175
175
  end
176
176
 
177
177
  #--
178
- #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span)) -> bool
178
+ #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span)) -> bool
179
179
  def capture_tool_content?(span)
180
180
  Riffer.config.tracing.capture_messages && span.recording?
181
181
  end
@@ -3,7 +3,7 @@
3
3
 
4
4
  # No-op tracing backend, used when OTEL is unavailable or tracing is
5
5
  # disabled.
6
- module Riffer::Tracing::Null # :nodoc: all
6
+ module Riffer::Tracing::NoOp # :nodoc: all
7
7
  extend self
8
8
 
9
9
  # No-op stand-in for a span; answers <tt>recording?</tt> with +false+ so
@@ -36,11 +36,11 @@ module Riffer::Tracing::Null # :nodoc: all
36
36
  end
37
37
  end
38
38
 
39
- SPAN = Span.new.freeze #: Riffer::Tracing::Null::Span
39
+ SPAN = Span.new.freeze #: Riffer::Tracing::NoOp::Span
40
40
 
41
41
  # Yields the no-op span, ignoring all span options.
42
42
  #--
43
- #: [R] (String, **untyped) { (Riffer::Tracing::Null::Span) -> R } -> R
43
+ #: [R] (String, **untyped) { (Riffer::Tracing::NoOp::Span) -> R } -> R
44
44
  def in_span(_name, **)
45
45
  yield SPAN
46
46
  end
@@ -52,10 +52,11 @@ class Riffer::Tracing::Otel # :nodoc: all
52
52
 
53
53
  class << self
54
54
  # Builds a backend when the OpenTelemetry API is loadable at a supported
55
- # version; returns +nil+ so resolution falls back to Null.
55
+ # version; returns +nil+ so resolution falls back to NoOp. +provider+
56
+ # defaults to the global <tt>OpenTelemetry.tracer_provider</tt>.
56
57
  #--
57
- #: (provider: untyped) -> Riffer::Tracing::Otel?
58
- def build(provider:)
58
+ #: (?provider: untyped) -> Riffer::Tracing::Otel?
59
+ def build(provider: nil)
59
60
  version = api_version
60
61
  return nil unless version
61
62
 
@@ -7,7 +7,7 @@
7
7
  module Riffer::Tracing # :nodoc: all
8
8
  extend self
9
9
 
10
- # @rbs @backend: (Riffer::Tracing::Otel | singleton(Riffer::Tracing::Null))?
10
+ # @rbs @backend: untyped
11
11
 
12
12
  MUTEX = Mutex.new #: Mutex
13
13
 
@@ -17,9 +17,9 @@ module Riffer::Tracing # :nodoc: all
17
17
 
18
18
  # Opens a span around the block, yielding the span.
19
19
  #--
20
- #: [R] (String, ?attributes: Hash[String, untyped]?, ?kind: Symbol) { (Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span) -> R } -> R
20
+ #: [R] (String, ?attributes: Hash[String, untyped]?, ?kind: Symbol) { (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span) -> R } -> R
21
21
  def in_span(name, attributes: nil, kind: :internal, &block)
22
- return Null.in_span(name, &block) unless Riffer.config.tracing.enabled
22
+ return NoOp.in_span(name, &block) unless Riffer.config.tracing.enabled
23
23
  backend.in_span(name, attributes: attributes, kind: kind, &block)
24
24
  end
25
25
 
@@ -28,7 +28,7 @@ module Riffer::Tracing # :nodoc: all
28
28
  #--
29
29
  #: () -> untyped
30
30
  def current_context
31
- return Null.current_context unless Riffer.config.tracing.enabled
31
+ return NoOp.current_context unless Riffer.config.tracing.enabled
32
32
  backend.current_context
33
33
  end
34
34
 
@@ -37,14 +37,14 @@ module Riffer::Tracing # :nodoc: all
37
37
  #--
38
38
  #: [R] (untyped) { () -> R } -> R
39
39
  def with_context(context, &block)
40
- return Null.with_context(context, &block) unless Riffer.config.tracing.enabled
40
+ return NoOp.with_context(context, &block) unless Riffer.config.tracing.enabled
41
41
  backend.with_context(context, &block)
42
42
  end
43
43
 
44
44
  # Stamps token usage onto the span — the <tt>gen_ai.usage.*</tt> counts and,
45
45
  # when the model was priced, <tt>riffer.cost</tt>.
46
46
  #--
47
- #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span), Riffer::Providers::TokenUsage?) -> void
47
+ #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Providers::TokenUsage?) -> void
48
48
  def record_usage(span, usage)
49
49
  return unless usage
50
50
 
@@ -65,14 +65,14 @@ module Riffer::Tracing # :nodoc: all
65
65
  private
66
66
 
67
67
  #--
68
- #: () -> (Riffer::Tracing::Otel | singleton(Riffer::Tracing::Null))
68
+ #: () -> untyped
69
69
  def backend
70
70
  @backend || MUTEX.synchronize { @backend ||= resolve_backend }
71
71
  end
72
72
 
73
73
  #--
74
- #: () -> (Riffer::Tracing::Otel | singleton(Riffer::Tracing::Null))
74
+ #: () -> untyped
75
75
  def resolve_backend
76
- Otel.build(provider: Riffer.config.tracing.tracer_provider) || Null
76
+ Riffer.config.tracing.backend || NoOp
77
77
  end
78
78
  end
@@ -2,5 +2,5 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  module Riffer
5
- VERSION = "0.33.0" #: String
5
+ VERSION = "0.34.0" #: String
6
6
  end
@@ -110,6 +110,6 @@ module Riffer::Agent::Run
110
110
  def run_metric_attributes: (Riffer::Agent, String?) -> Hash[String, untyped]
111
111
 
112
112
  # --
113
- # : (Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span, Riffer::Agent::Response) -> void
114
- def record_run_outcome: (Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span, Riffer::Agent::Response) -> void
113
+ # : (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span, Riffer::Agent::Response) -> void
114
+ def record_run_outcome: (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span, Riffer::Agent::Response) -> void
115
115
  end
@@ -107,9 +107,9 @@ class Riffer::Config
107
107
  # content routinely carries sensitive data.
108
108
  attr_reader capture_messages: bool
109
109
 
110
- # Explicit OTEL tracer provider; defaults to +nil+, which resolves the
111
- # global <tt>OpenTelemetry.tracer_provider</tt> at first span.
112
- attr_reader tracer_provider: untyped
110
+ # The backend riffer routes spans through; defaults to +nil+, a no-op.
111
+ # Riffer auto-detects no backend; assigning one is opt-in.
112
+ attr_reader backend: untyped
113
113
 
114
114
  # --
115
115
  # : () -> void
@@ -129,12 +129,12 @@ class Riffer::Config
129
129
  # : (untyped) -> void
130
130
  def capture_messages=: (untyped) -> void
131
131
 
132
- # Sets an explicit tracer provider, forcing the OTEL backend. Raises
133
- # Riffer::ArgumentError when the OpenTelemetry API gem isn't available
134
- # at a supported version.
132
+ # Sets the tracing backend riffer routes spans through. Raises
133
+ # Riffer::ArgumentError unless the value is +nil+ or responds to the full
134
+ # delegated contract: +in_span+, +current_context+, and +with_context+.
135
135
  # --
136
136
  # : (untyped) -> void
137
- def tracer_provider=: (untyped) -> void
137
+ def backend=: (untyped) -> void
138
138
  end
139
139
 
140
140
  # Metrics-related global configuration, independent of +config.tracing+ so a
@@ -144,9 +144,9 @@ class Riffer::Config
144
144
  # no-op until a host wires an OTEL metrics SDK.
145
145
  attr_reader enabled: bool
146
146
 
147
- # Explicit OTEL meter provider; defaults to +nil+, which resolves the
148
- # global <tt>OpenTelemetry.meter_provider</tt> at first record.
149
- attr_reader meter_provider: untyped
147
+ # The backend riffer routes measurements through; defaults to +nil+, a no-op.
148
+ # Riffer auto-detects no backend; assigning one is opt-in.
149
+ attr_reader backend: untyped
150
150
 
151
151
  # --
152
152
  # : () -> void
@@ -159,12 +159,12 @@ class Riffer::Config
159
159
  # : (untyped) -> void
160
160
  def enabled=: (untyped) -> void
161
161
 
162
- # Sets an explicit meter provider, forcing the OTEL backend. Raises
163
- # Riffer::ArgumentError when the OpenTelemetry metrics API gem isn't
164
- # available at a supported version.
162
+ # Sets the metrics backend riffer routes measurements through. Raises
163
+ # Riffer::ArgumentError unless the value is +nil+ or responds to
164
+ # +record_histogram+.
165
165
  # --
166
166
  # : (untyped) -> void
167
- def meter_provider=: (untyped) -> void
167
+ def backend=: (untyped) -> void
168
168
  end
169
169
 
170
170
  # Consumer-configured token pricing, keyed by +provider/model+ id. Riffer
@@ -37,6 +37,10 @@ class Riffer::Guardrails::Runner
37
37
  # : (Riffer::Guardrail, untyped, messages: Array[Riffer::Messages::Base]?) -> Riffer::Guardrails::Result
38
38
  def execute_guardrail: (Riffer::Guardrail, untyped, messages: Array[Riffer::Messages::Base]?) -> Riffer::Guardrails::Result
39
39
 
40
+ # --
41
+ # : (Riffer::Guardrail) { () -> Riffer::Guardrails::Result } -> Riffer::Guardrails::Result
42
+ def instrument_guardrail: (Riffer::Guardrail) { () -> Riffer::Guardrails::Result } -> Riffer::Guardrails::Result
43
+
40
44
  # --
41
45
  # : (Riffer::Guardrail, untyped, messages: Array[Riffer::Messages::Base]?) -> Riffer::Guardrails::Result
42
46
  def run_guardrail_phase: (Riffer::Guardrail, untyped, messages: Array[Riffer::Messages::Base]?) -> Riffer::Guardrails::Result
@@ -45,9 +49,13 @@ class Riffer::Guardrails::Runner
45
49
  # : (Riffer::Guardrail) -> Hash[String, untyped]
46
50
  def guardrail_span_attributes: (Riffer::Guardrail) -> Hash[String, untyped]
47
51
 
52
+ # --
53
+ # : (Riffer::Guardrail, String?) -> Hash[String, untyped]
54
+ def guardrail_metric_attributes: (Riffer::Guardrail, String?) -> Hash[String, untyped]
55
+
48
56
  # A block is a handled outcome, so its span status stays unset — an error
49
57
  # span status is reserved for a raised exception.
50
58
  # --
51
- # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span), Riffer::Guardrails::Result) -> void
52
- def record_guardrail_outcome: (Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span, Riffer::Guardrails::Result) -> void
59
+ # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Guardrails::Result) -> void
60
+ def record_guardrail_outcome: (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span, Riffer::Guardrails::Result) -> void
53
61
  end
@@ -10,4 +10,6 @@ module Riffer::Metrics::Instruments
10
10
  TOKEN_USAGE: Riffer::Metrics::Histogram
11
11
 
12
12
  COST: Riffer::Metrics::Histogram
13
+
14
+ GUARDRAIL_DURATION: Riffer::Metrics::Histogram
13
15
  end
@@ -1,8 +1,8 @@
1
- # Generated from lib/riffer/metrics/null.rb with RBS::Inline
1
+ # Generated from lib/riffer/metrics/no_op.rb with RBS::Inline
2
2
 
3
3
  # No-op metrics backend, used when the OpenTelemetry metrics API is unavailable
4
4
  # or metrics are disabled.
5
- module Riffer::Metrics::Null
5
+ module Riffer::Metrics::NoOp
6
6
  # Ignores the measurement; there is no meter without the OTEL metrics API.
7
7
  # --
8
8
  # : (String, Numeric, unit: String?, description: String?, attributes: Hash[String, untyped]?) -> void
@@ -8,10 +8,11 @@ class Riffer::Metrics::Otel
8
8
  SUPPORTED_API_VERSIONS: Gem::Requirement
9
9
 
10
10
  # Builds a backend when the OpenTelemetry metrics API is loadable at a
11
- # supported version; returns +nil+ so resolution falls back to Null.
11
+ # supported version; returns +nil+ so resolution falls back to NoOp.
12
+ # +provider+ defaults to the global <tt>OpenTelemetry.meter_provider</tt>.
12
13
  # --
13
- # : (provider: untyped) -> Riffer::Metrics::Otel?
14
- def self.build: (provider: untyped) -> Riffer::Metrics::Otel?
14
+ # : (?provider: untyped) -> Riffer::Metrics::Otel?
15
+ def self.build: (?provider: untyped) -> Riffer::Metrics::Otel?
15
16
 
16
17
  # Whether the OpenTelemetry metrics API gem is loadable at a supported
17
18
  # version.
@@ -4,7 +4,7 @@
4
4
  # the OpenTelemetry metrics API and no-ops otherwise, so riffer never declares
5
5
  # an OTEL dependency.
6
6
  module Riffer::Metrics
7
- @backend: (Riffer::Metrics::Otel | singleton(Riffer::Metrics::Null))?
7
+ @backend: untyped
8
8
 
9
9
  MUTEX: Mutex
10
10
 
@@ -42,7 +42,8 @@ module Riffer::Metrics
42
42
  def record_histogram: (String, Numeric, ?unit: String?, ?description: String?, ?attributes: Hash[String, untyped]?) -> void
43
43
 
44
44
  # Mirrors a span's +recording?+ so a caller can skip work that exists only to
45
- # feed a metric.
45
+ # feed a metric. True for any live backend — the OTEL backend or a
46
+ # consumer-supplied one — so a custom metrics sink still gets fed.
46
47
  # --
47
48
  # : () -> bool
48
49
  def recording?: () -> bool
@@ -62,10 +63,10 @@ module Riffer::Metrics
62
63
  private
63
64
 
64
65
  # --
65
- # : () -> (Riffer::Metrics::Otel | singleton(Riffer::Metrics::Null))
66
- def backend: () -> (Riffer::Metrics::Otel | singleton(Riffer::Metrics::Null))
66
+ # : () -> untyped
67
+ def backend: () -> untyped
67
68
 
68
69
  # --
69
- # : () -> (Riffer::Metrics::Otel | singleton(Riffer::Metrics::Null))
70
- def resolve_backend: () -> (Riffer::Metrics::Otel | singleton(Riffer::Metrics::Null))
70
+ # : () -> untyped
71
+ def resolve_backend: () -> untyped
71
72
  end
@@ -91,8 +91,8 @@ class Riffer::Providers::Base
91
91
  REQUEST_PARAM_ATTRIBUTES: Hash[Symbol, String]
92
92
 
93
93
  # --
94
- # : [R] (String?, Array[Riffer::Messages::Base], Hash[Symbol, untyped]) { (Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span) -> R } -> R
95
- def in_chat_span: [R] (String?, Array[Riffer::Messages::Base], Hash[Symbol, untyped]) { (Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span) -> R } -> R
94
+ # : [R] (String?, Array[Riffer::Messages::Base], Hash[Symbol, untyped]) { (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span) -> R } -> R
95
+ def in_chat_span: [R] (String?, Array[Riffer::Messages::Base], Hash[Symbol, untyped]) { (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span) -> R } -> R
96
96
 
97
97
  # --
98
98
  # : (String?, Hash[Symbol, untyped]) -> Hash[String, untyped]
@@ -117,24 +117,24 @@ class Riffer::Providers::Base
117
117
  def record_cost_metric: (String?, Riffer::Providers::TokenUsage?) -> void
118
118
 
119
119
  # --
120
- # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span), Symbol?, String?) -> void
121
- def record_finish_reason: (Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span, Symbol?, String?) -> void
120
+ # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Symbol?, String?) -> void
121
+ def record_finish_reason: (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span, Symbol?, String?) -> void
122
122
 
123
123
  # --
124
- # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span), Riffer::Tracing::StreamRecorder) -> void
125
- def record_stream_outcome: (Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span, Riffer::Tracing::StreamRecorder) -> void
124
+ # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Tracing::StreamRecorder) -> void
125
+ def record_stream_outcome: (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span, Riffer::Tracing::StreamRecorder) -> void
126
126
 
127
127
  # --
128
- # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span), Array[Riffer::Messages::Base]) -> void
129
- def capture_input: (Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span, Array[Riffer::Messages::Base]) -> void
128
+ # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Array[Riffer::Messages::Base]) -> void
129
+ def capture_input: (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span, Array[Riffer::Messages::Base]) -> void
130
130
 
131
131
  # --
132
- # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span), content: String?, tool_calls: Array[Riffer::Messages::Assistant::ToolCall], finish_reason: Symbol?) -> void
133
- def capture_output: (Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span, content: String?, tool_calls: Array[Riffer::Messages::Assistant::ToolCall], finish_reason: Symbol?) -> void
132
+ # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), content: String?, tool_calls: Array[Riffer::Messages::Assistant::ToolCall], finish_reason: Symbol?) -> void
133
+ def capture_output: (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span, content: String?, tool_calls: Array[Riffer::Messages::Assistant::ToolCall], finish_reason: Symbol?) -> void
134
134
 
135
135
  # --
136
- # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span)) -> bool
137
- def capture_messages?: (Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span) -> bool
136
+ # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span)) -> bool
137
+ def capture_messages?: (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span) -> bool
138
138
 
139
139
  # --
140
140
  # : (Riffer::Providers::_EventSink, Riffer::Providers::FinishReason?) -> void
@@ -49,8 +49,8 @@ class Riffer::Tools::Runtime
49
49
 
50
50
  # Emitted outside +around_tool_call+ so host enrichment spans nest beneath it.
51
51
  # --
52
- # : [R] (Riffer::Messages::Assistant::ToolCall) { ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span)) -> R } -> R
53
- def in_tool_span: [R] (Riffer::Messages::Assistant::ToolCall) { (Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span) -> R } -> R
52
+ # : [R] (Riffer::Messages::Assistant::ToolCall) { ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span)) -> R } -> R
53
+ def in_tool_span: [R] (Riffer::Messages::Assistant::ToolCall) { (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span) -> R } -> R
54
54
 
55
55
  # --
56
56
  # : (Riffer::Messages::Assistant::ToolCall) -> Hash[String, untyped]
@@ -63,18 +63,18 @@ class Riffer::Tools::Runtime
63
63
  # A returned error Response is a handled outcome, so its status stays unset —
64
64
  # an error span status is reserved for a raised exception.
65
65
  # --
66
- # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span), Riffer::Tools::Response) -> void
67
- def record_tool_outcome: (Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span, Riffer::Tools::Response) -> void
66
+ # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Tools::Response) -> void
67
+ def record_tool_outcome: (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span, Riffer::Tools::Response) -> void
68
68
 
69
69
  # --
70
- # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span), Riffer::Messages::Assistant::ToolCall) -> void
71
- def capture_tool_arguments: (Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span, Riffer::Messages::Assistant::ToolCall) -> void
70
+ # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Messages::Assistant::ToolCall) -> void
71
+ def capture_tool_arguments: (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span, Riffer::Messages::Assistant::ToolCall) -> void
72
72
 
73
73
  # --
74
- # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span), Riffer::Tools::Response) -> void
75
- def capture_tool_result: (Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span, Riffer::Tools::Response) -> void
74
+ # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Tools::Response) -> void
75
+ def capture_tool_result: (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span, Riffer::Tools::Response) -> void
76
76
 
77
77
  # --
78
- # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span)) -> bool
79
- def capture_tool_content?: (Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span) -> bool
78
+ # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span)) -> bool
79
+ def capture_tool_content?: (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span) -> bool
80
80
  end
@@ -1,8 +1,8 @@
1
- # Generated from lib/riffer/tracing/null.rb with RBS::Inline
1
+ # Generated from lib/riffer/tracing/no_op.rb with RBS::Inline
2
2
 
3
3
  # No-op tracing backend, used when OTEL is unavailable or tracing is
4
4
  # disabled.
5
- module Riffer::Tracing::Null
5
+ module Riffer::Tracing::NoOp
6
6
  # No-op stand-in for a span; answers <tt>recording?</tt> with +false+ so
7
7
  # callers can skip expensive attribute serialization.
8
8
  class Span
@@ -27,12 +27,12 @@ module Riffer::Tracing::Null
27
27
  def recording?: () -> bool
28
28
  end
29
29
 
30
- SPAN: Riffer::Tracing::Null::Span
30
+ SPAN: Riffer::Tracing::NoOp::Span
31
31
 
32
32
  # Yields the no-op span, ignoring all span options.
33
33
  # --
34
- # : [R] (String, **untyped) { (Riffer::Tracing::Null::Span) -> R } -> R
35
- def in_span: [R] (String, **untyped) { (Riffer::Tracing::Null::Span) -> R } -> R
34
+ # : [R] (String, **untyped) { (Riffer::Tracing::NoOp::Span) -> R } -> R
35
+ def in_span: [R] (String, **untyped) { (Riffer::Tracing::NoOp::Span) -> R } -> R
36
36
 
37
37
  # Returns +nil+; there is no trace context without OTEL.
38
38
  # --
@@ -39,10 +39,11 @@ class Riffer::Tracing::Otel
39
39
  end
40
40
 
41
41
  # Builds a backend when the OpenTelemetry API is loadable at a supported
42
- # version; returns +nil+ so resolution falls back to Null.
42
+ # version; returns +nil+ so resolution falls back to NoOp. +provider+
43
+ # defaults to the global <tt>OpenTelemetry.tracer_provider</tt>.
43
44
  # --
44
- # : (provider: untyped) -> Riffer::Tracing::Otel?
45
- def self.build: (provider: untyped) -> Riffer::Tracing::Otel?
45
+ # : (?provider: untyped) -> Riffer::Tracing::Otel?
46
+ def self.build: (?provider: untyped) -> Riffer::Tracing::Otel?
46
47
 
47
48
  # Whether the OpenTelemetry API gem is loadable at a supported version.
48
49
  # --
@@ -4,7 +4,7 @@
4
4
  # OpenTelemetry API and no-ops otherwise, so riffer never declares an OTEL
5
5
  # dependency.
6
6
  module Riffer::Tracing
7
- @backend: (Riffer::Tracing::Otel | singleton(Riffer::Tracing::Null))?
7
+ @backend: untyped
8
8
 
9
9
  MUTEX: Mutex
10
10
 
@@ -14,8 +14,8 @@ module Riffer::Tracing
14
14
 
15
15
  # Opens a span around the block, yielding the span.
16
16
  # --
17
- # : [R] (String, ?attributes: Hash[String, untyped]?, ?kind: Symbol) { (Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span) -> R } -> R
18
- def in_span: [R] (String, ?attributes: Hash[String, untyped]?, ?kind: Symbol) { (Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span) -> R } -> R
17
+ # : [R] (String, ?attributes: Hash[String, untyped]?, ?kind: Symbol) { (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span) -> R } -> R
18
+ def in_span: [R] (String, ?attributes: Hash[String, untyped]?, ?kind: Symbol) { (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span) -> R } -> R
19
19
 
20
20
  # Returns the active trace context, for re-attachment across fiber or
21
21
  # thread boundaries.
@@ -32,8 +32,8 @@ module Riffer::Tracing
32
32
  # Stamps token usage onto the span — the <tt>gen_ai.usage.*</tt> counts and,
33
33
  # when the model was priced, <tt>riffer.cost</tt>.
34
34
  # --
35
- # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span), Riffer::Providers::TokenUsage?) -> void
36
- def record_usage: (Riffer::Tracing::Otel::Span | Riffer::Tracing::Null::Span, Riffer::Providers::TokenUsage?) -> void
35
+ # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Providers::TokenUsage?) -> void
36
+ def record_usage: (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span, Riffer::Providers::TokenUsage?) -> void
37
37
 
38
38
  # Discards the resolved backend so the next span re-resolves it.
39
39
  # --
@@ -43,10 +43,10 @@ module Riffer::Tracing
43
43
  private
44
44
 
45
45
  # --
46
- # : () -> (Riffer::Tracing::Otel | singleton(Riffer::Tracing::Null))
47
- def backend: () -> (Riffer::Tracing::Otel | singleton(Riffer::Tracing::Null))
46
+ # : () -> untyped
47
+ def backend: () -> untyped
48
48
 
49
49
  # --
50
- # : () -> (Riffer::Tracing::Otel | singleton(Riffer::Tracing::Null))
51
- def resolve_backend: () -> (Riffer::Tracing::Otel | singleton(Riffer::Tracing::Null))
50
+ # : () -> untyped
51
+ def resolve_backend: () -> untyped
52
52
  end
@@ -0,0 +1,5 @@
1
+ # `Riffer::Metrics::NoOp` uses `extend self`; rbs-inline doesn't emit that, so
2
+ # re-extend here to expose its instance methods as singleton methods.
3
+ module Riffer::Metrics::NoOp
4
+ extend ::Riffer::Metrics::NoOp
5
+ end
@@ -0,0 +1,5 @@
1
+ # `Riffer::Tracing::NoOp` uses `extend self`; rbs-inline doesn't emit that, so
2
+ # re-extend here to expose its instance methods as singleton methods.
3
+ module Riffer::Tracing::NoOp
4
+ extend ::Riffer::Tracing::NoOp
5
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: riffer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.33.0
4
+ version: 0.34.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jake Bottrall
@@ -35,14 +35,14 @@ dependencies:
35
35
  requirements:
36
36
  - - "~>"
37
37
  - !ruby/object:Gem::Version
38
- version: 1.48.1
38
+ version: 1.49.0
39
39
  type: :development
40
40
  prerelease: false
41
41
  version_requirements: !ruby/object:Gem::Requirement
42
42
  requirements:
43
43
  - - "~>"
44
44
  - !ruby/object:Gem::Version
45
- version: 1.48.1
45
+ version: 1.49.0
46
46
  - !ruby/object:Gem::Dependency
47
47
  name: aws-sdk-bedrockruntime
48
48
  requirement: !ruby/object:Gem::Requirement
@@ -91,14 +91,14 @@ dependencies:
91
91
  requirements:
92
92
  - - "~>"
93
93
  - !ruby/object:Gem::Version
94
- version: 0.66.1
94
+ version: 0.68.0
95
95
  type: :development
96
96
  prerelease: false
97
97
  version_requirements: !ruby/object:Gem::Requirement
98
98
  requirements:
99
99
  - - "~>"
100
100
  - !ruby/object:Gem::Version
101
- version: 0.66.1
101
+ version: 0.68.0
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: async
104
104
  requirement: !ruby/object:Gem::Requirement
@@ -336,7 +336,7 @@ files:
336
336
  - lib/riffer/messages/user.rb
337
337
  - lib/riffer/metrics.rb
338
338
  - lib/riffer/metrics/instruments.rb
339
- - lib/riffer/metrics/null.rb
339
+ - lib/riffer/metrics/no_op.rb
340
340
  - lib/riffer/metrics/otel.rb
341
341
  - lib/riffer/params.rb
342
342
  - lib/riffer/params/boolean.rb
@@ -393,7 +393,7 @@ files:
393
393
  - lib/riffer/tools/toolable.rb
394
394
  - lib/riffer/tracing.rb
395
395
  - lib/riffer/tracing/capture.rb
396
- - lib/riffer/tracing/null.rb
396
+ - lib/riffer/tracing/no_op.rb
397
397
  - lib/riffer/tracing/otel.rb
398
398
  - lib/riffer/tracing/stream_recorder.rb
399
399
  - lib/riffer/version.rb
@@ -457,7 +457,7 @@ files:
457
457
  - sig/generated/riffer/messages/user.rbs
458
458
  - sig/generated/riffer/metrics.rbs
459
459
  - sig/generated/riffer/metrics/instruments.rbs
460
- - sig/generated/riffer/metrics/null.rbs
460
+ - sig/generated/riffer/metrics/no_op.rbs
461
461
  - sig/generated/riffer/metrics/otel.rbs
462
462
  - sig/generated/riffer/params.rbs
463
463
  - sig/generated/riffer/params/boolean.rbs
@@ -514,7 +514,7 @@ files:
514
514
  - sig/generated/riffer/tools/toolable.rbs
515
515
  - sig/generated/riffer/tracing.rbs
516
516
  - sig/generated/riffer/tracing/capture.rbs
517
- - sig/generated/riffer/tracing/null.rbs
517
+ - sig/generated/riffer/tracing/no_op.rbs
518
518
  - sig/generated/riffer/tracing/otel.rbs
519
519
  - sig/generated/riffer/tracing/stream_recorder.rbs
520
520
  - sig/generated/riffer/version.rbs
@@ -533,12 +533,12 @@ files:
533
533
  - sig/manual/riffer/mcp/registry.rbs
534
534
  - sig/manual/riffer/mcp/tool_factory.rbs
535
535
  - sig/manual/riffer/metrics.rbs
536
- - sig/manual/riffer/metrics/null.rbs
536
+ - sig/manual/riffer/metrics/no_op.rbs
537
537
  - sig/manual/riffer/providers.rbs
538
538
  - sig/manual/riffer/providers/repository.rbs
539
539
  - sig/manual/riffer/tracing.rbs
540
540
  - sig/manual/riffer/tracing/capture.rbs
541
- - sig/manual/riffer/tracing/null.rbs
541
+ - sig/manual/riffer/tracing/no_op.rbs
542
542
  homepage: https://riffer.ai
543
543
  licenses:
544
544
  - MIT
@@ -1,5 +0,0 @@
1
- # `Riffer::Metrics::Null` uses `extend self`; rbs-inline doesn't emit that, so
2
- # re-extend here to expose its instance methods as singleton methods.
3
- module Riffer::Metrics::Null
4
- extend ::Riffer::Metrics::Null
5
- end
@@ -1,5 +0,0 @@
1
- # `Riffer::Tracing::Null` uses `extend self`; rbs-inline doesn't emit that, so
2
- # re-extend here to expose its instance methods as singleton methods.
3
- module Riffer::Tracing::Null
4
- extend ::Riffer::Tracing::Null
5
- end