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.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +12 -0
- data/docs/10_CONFIGURATION.md +15 -15
- data/docs/16_TRACING.md +28 -4
- data/docs/17_METRICS.md +34 -4
- data/lib/riffer/agent/run.rb +1 -1
- data/lib/riffer/config.rb +23 -24
- data/lib/riffer/guardrails/runner.rb +33 -8
- data/lib/riffer/metrics/instruments.rb +6 -0
- data/lib/riffer/metrics/{null.rb → no_op.rb} +1 -1
- data/lib/riffer/metrics/otel.rb +4 -3
- data/lib/riffer/metrics.rb +7 -6
- data/lib/riffer/providers/base.rb +6 -6
- data/lib/riffer/tools/runtime.rb +5 -5
- data/lib/riffer/tracing/{null.rb → no_op.rb} +3 -3
- data/lib/riffer/tracing/otel.rb +4 -3
- data/lib/riffer/tracing.rb +9 -9
- data/lib/riffer/version.rb +1 -1
- data/sig/generated/riffer/agent/run.rbs +2 -2
- data/sig/generated/riffer/config.rbs +14 -14
- data/sig/generated/riffer/guardrails/runner.rbs +10 -2
- data/sig/generated/riffer/metrics/instruments.rbs +2 -0
- data/sig/generated/riffer/metrics/{null.rbs → no_op.rbs} +2 -2
- data/sig/generated/riffer/metrics/otel.rbs +4 -3
- data/sig/generated/riffer/metrics.rbs +7 -6
- data/sig/generated/riffer/providers/base.rbs +12 -12
- data/sig/generated/riffer/tools/runtime.rbs +10 -10
- data/sig/generated/riffer/tracing/{null.rbs → no_op.rbs} +5 -5
- data/sig/generated/riffer/tracing/otel.rbs +4 -3
- data/sig/generated/riffer/tracing.rbs +9 -9
- data/sig/manual/riffer/metrics/no_op.rbs +5 -0
- data/sig/manual/riffer/tracing/no_op.rbs +5 -0
- metadata +11 -11
- data/sig/manual/riffer/metrics/null.rbs +0 -5
- data/sig/manual/riffer/tracing/null.rbs +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bc442fb9eb0296a5a9c6f289c1c9e63e7cdb732ef52b013e1556d1765a874f2d
|
|
4
|
+
data.tar.gz: aa7512f0b3ac38e4f0c7be500095c73fca0ae1740c37370ceb2aeaf57349dac8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d9ace157326fa9ebfb356705f14cd4a8bac4af86e3ede2eb142adb4be36870399c81ee122fa02973135c9176b521bdee980745e3cc3f14422e12fbf1392fb009
|
|
7
|
+
data.tar.gz: cdc2e19b1aab77a93fb6ad7c4a800c5002a5efb63667366113fc04104a05c342863a1fa74219ed2569759f5f7be08dc534ac7c79b10c6d9ab61d6430db805113
|
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
|
|
data/docs/10_CONFIGURATION.md
CHANGED
|
@@ -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
|
|
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
|
-
| `
|
|
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
|
|
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
|
|
133
|
-
|
|
|
134
|
-
| `enabled`
|
|
135
|
-
| `
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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):
|
data/lib/riffer/agent/run.rb
CHANGED
|
@@ -353,7 +353,7 @@ module Riffer::Agent::Run
|
|
|
353
353
|
end
|
|
354
354
|
|
|
355
355
|
#--
|
|
356
|
-
#: (Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
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
|
-
#
|
|
65
|
-
#
|
|
66
|
-
attr_reader :
|
|
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
|
-
@
|
|
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
|
|
95
|
-
# Riffer::ArgumentError
|
|
96
|
-
#
|
|
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
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
@
|
|
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
|
-
#
|
|
117
|
-
#
|
|
118
|
-
attr_reader :
|
|
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
|
-
@
|
|
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
|
|
137
|
-
# Riffer::ArgumentError
|
|
138
|
-
#
|
|
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
|
|
142
|
-
|
|
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
|
-
@
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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::
|
|
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::
|
|
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.
|
data/lib/riffer/metrics/otel.rb
CHANGED
|
@@ -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
|
|
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
|
|
data/lib/riffer/metrics.rb
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
module Riffer::Metrics # :nodoc: all
|
|
8
8
|
extend self
|
|
9
9
|
|
|
10
|
-
# @rbs @backend:
|
|
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.
|
|
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
|
-
#: () ->
|
|
84
|
+
#: () -> untyped
|
|
84
85
|
def backend
|
|
85
86
|
@backend || MUTEX.synchronize { @backend ||= resolve_backend }
|
|
86
87
|
end
|
|
87
88
|
|
|
88
89
|
#--
|
|
89
|
-
#: () ->
|
|
90
|
+
#: () -> untyped
|
|
90
91
|
def resolve_backend
|
|
91
|
-
|
|
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::
|
|
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::
|
|
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::
|
|
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::
|
|
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::
|
|
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::
|
|
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
|
data/lib/riffer/tools/runtime.rb
CHANGED
|
@@ -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::
|
|
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::
|
|
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::
|
|
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::
|
|
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::
|
|
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::
|
|
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::
|
|
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::
|
|
43
|
+
#: [R] (String, **untyped) { (Riffer::Tracing::NoOp::Span) -> R } -> R
|
|
44
44
|
def in_span(_name, **)
|
|
45
45
|
yield SPAN
|
|
46
46
|
end
|
data/lib/riffer/tracing/otel.rb
CHANGED
|
@@ -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
|
|
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
|
|
data/lib/riffer/tracing.rb
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
module Riffer::Tracing # :nodoc: all
|
|
8
8
|
extend self
|
|
9
9
|
|
|
10
|
-
# @rbs @backend:
|
|
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::
|
|
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
|
|
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
|
|
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
|
|
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::
|
|
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
|
-
#: () ->
|
|
68
|
+
#: () -> untyped
|
|
69
69
|
def backend
|
|
70
70
|
@backend || MUTEX.synchronize { @backend ||= resolve_backend }
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
#--
|
|
74
|
-
#: () ->
|
|
74
|
+
#: () -> untyped
|
|
75
75
|
def resolve_backend
|
|
76
|
-
|
|
76
|
+
Riffer.config.tracing.backend || NoOp
|
|
77
77
|
end
|
|
78
78
|
end
|
data/lib/riffer/version.rb
CHANGED
|
@@ -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::
|
|
114
|
-
def record_run_outcome: (Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
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
|
-
#
|
|
111
|
-
#
|
|
112
|
-
attr_reader
|
|
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
|
|
133
|
-
# Riffer::ArgumentError
|
|
134
|
-
#
|
|
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
|
|
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
|
-
#
|
|
148
|
-
#
|
|
149
|
-
attr_reader
|
|
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
|
|
163
|
-
# Riffer::ArgumentError
|
|
164
|
-
#
|
|
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
|
|
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::
|
|
52
|
-
def record_guardrail_outcome: (Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
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
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# Generated from lib/riffer/metrics/
|
|
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::
|
|
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
|
|
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:
|
|
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
|
-
# : () ->
|
|
66
|
-
def backend: () ->
|
|
66
|
+
# : () -> untyped
|
|
67
|
+
def backend: () -> untyped
|
|
67
68
|
|
|
68
69
|
# --
|
|
69
|
-
# : () ->
|
|
70
|
-
def resolve_backend: () ->
|
|
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::
|
|
95
|
-
def in_chat_span: [R] (String?, Array[Riffer::Messages::Base], Hash[Symbol, untyped]) { (Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
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::
|
|
121
|
-
def record_finish_reason: (Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
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::
|
|
125
|
-
def record_stream_outcome: (Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
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::
|
|
129
|
-
def capture_input: (Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
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::
|
|
133
|
-
def capture_output: (Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
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::
|
|
137
|
-
def capture_messages?: (Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
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::
|
|
53
|
-
def in_tool_span: [R] (Riffer::Messages::Assistant::ToolCall) { (Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
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::
|
|
67
|
-
def record_tool_outcome: (Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
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::
|
|
71
|
-
def capture_tool_arguments: (Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
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::
|
|
75
|
-
def capture_tool_result: (Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
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::
|
|
79
|
-
def capture_tool_content?: (Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
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/
|
|
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::
|
|
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::
|
|
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::
|
|
35
|
-
def in_span: [R] (String, **untyped) { (Riffer::Tracing::
|
|
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
|
|
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:
|
|
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::
|
|
18
|
-
def in_span: [R] (String, ?attributes: Hash[String, untyped]?, ?kind: Symbol) { (Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
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::
|
|
36
|
-
def record_usage: (Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
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
|
-
# : () ->
|
|
47
|
-
def backend: () ->
|
|
46
|
+
# : () -> untyped
|
|
47
|
+
def backend: () -> untyped
|
|
48
48
|
|
|
49
49
|
# --
|
|
50
|
-
# : () ->
|
|
51
|
-
def resolve_backend: () ->
|
|
50
|
+
# : () -> untyped
|
|
51
|
+
def resolve_backend: () -> untyped
|
|
52
52
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
541
|
+
- sig/manual/riffer/tracing/no_op.rbs
|
|
542
542
|
homepage: https://riffer.ai
|
|
543
543
|
licenses:
|
|
544
544
|
- MIT
|