riffer 0.33.0 → 0.35.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 +19 -0
- data/docs/03_AGENTS.md +44 -0
- data/docs/10_CONFIGURATION.md +15 -15
- data/docs/16_TRACING.md +32 -4
- data/docs/17_METRICS.md +36 -4
- data/lib/riffer/agent/run.rb +82 -49
- data/lib/riffer/agent.rb +20 -13
- data/lib/riffer/config.rb +23 -24
- data/lib/riffer/guardrails/runner.rb +40 -11
- 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/amazon_bedrock.rb +7 -1
- data/lib/riffer/providers/anthropic.rb +8 -1
- data/lib/riffer/providers/base.rb +38 -26
- data/lib/riffer/providers/gemini.rb +5 -1
- data/lib/riffer/providers/open_ai.rb +10 -1
- data/lib/riffer/providers/open_router.rb +10 -1
- data/lib/riffer/tools/runtime.rb +30 -20
- 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 +47 -30
- data/sig/generated/riffer/agent.rbs +16 -9
- data/sig/generated/riffer/config.rbs +14 -14
- data/sig/generated/riffer/guardrails/runner.rbs +15 -4
- 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 +26 -20
- data/sig/generated/riffer/tools/runtime.rbs +24 -18
- 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
data/lib/riffer/tools/runtime.rb
CHANGED
|
@@ -18,14 +18,16 @@ class Riffer::Tools::Runtime
|
|
|
18
18
|
|
|
19
19
|
# Executes a batch of tool calls, returning <tt>[tool_call, response]</tt> pairs.
|
|
20
20
|
#--
|
|
21
|
-
#: (Array[Riffer::Messages::Assistant::ToolCall], tools: Array[singleton(Riffer::Tool)], context: Riffer::Agent::Context?, ?assistant_message: Riffer::Messages::Assistant?) -> Array[[Riffer::Messages::Assistant::ToolCall, Riffer::Tools::Response]]
|
|
22
|
-
def execute(tool_calls, tools:, context:, assistant_message: nil)
|
|
21
|
+
#: (Array[Riffer::Messages::Assistant::ToolCall], tools: Array[singleton(Riffer::Tool)], context: Riffer::Agent::Context?, ?assistant_message: Riffer::Messages::Assistant?, ?tags: Hash[String, String]) -> Array[[Riffer::Messages::Assistant::ToolCall, Riffer::Tools::Response]]
|
|
22
|
+
def execute(tool_calls, tools:, context:, assistant_message: nil, tags: {})
|
|
23
23
|
# Each Runner worker runs in its own thread/fiber, where the OTEL context
|
|
24
24
|
# starts empty — capture here so the execute_tool span parents correctly.
|
|
25
|
+
# tags are an ordinary local captured in the block, so they reach the
|
|
26
|
+
# worker's span/metric without re-propagation.
|
|
25
27
|
trace_context = Riffer::Tracing.current_context
|
|
26
28
|
@runner.map(tool_calls, context: context) do |tool_call|
|
|
27
29
|
Riffer::Tracing.with_context(trace_context) do
|
|
28
|
-
instrument_tool_call(tool_call) do
|
|
30
|
+
instrument_tool_call(tool_call, tags) do
|
|
29
31
|
around_tool_call(tool_call, context: context, assistant_message: assistant_message) do
|
|
30
32
|
dispatch_tool_call(tool_call, tools: tools, context: context, assistant_message: assistant_message)
|
|
31
33
|
end
|
|
@@ -57,12 +59,12 @@ class Riffer::Tools::Runtime
|
|
|
57
59
|
private
|
|
58
60
|
|
|
59
61
|
#--
|
|
60
|
-
#: (Riffer::Messages::Assistant::ToolCall) { () -> Riffer::Tools::Response } -> [Riffer::Messages::Assistant::ToolCall, Riffer::Tools::Response]
|
|
61
|
-
def instrument_tool_call(tool_call)
|
|
62
|
+
#: (Riffer::Messages::Assistant::ToolCall, ?Hash[String, String]) { () -> Riffer::Tools::Response } -> [Riffer::Messages::Assistant::ToolCall, Riffer::Tools::Response]
|
|
63
|
+
def instrument_tool_call(tool_call, tags = {})
|
|
62
64
|
start = Riffer::Metrics.monotonic_now
|
|
63
65
|
error_type = nil #: String?
|
|
64
66
|
begin
|
|
65
|
-
result = in_tool_span(tool_call) do |span|
|
|
67
|
+
result = in_tool_span(tool_call, tags) do |span|
|
|
66
68
|
response = yield
|
|
67
69
|
record_tool_outcome(span, response)
|
|
68
70
|
response
|
|
@@ -73,7 +75,7 @@ class Riffer::Tools::Runtime
|
|
|
73
75
|
error_type = error.class.name #: String?
|
|
74
76
|
raise
|
|
75
77
|
ensure
|
|
76
|
-
Riffer::Metrics::Instruments::OPERATION_DURATION.record(Riffer::Metrics.monotonic_now - start, attributes: tool_metric_attributes(tool_call, error_type))
|
|
78
|
+
Riffer::Metrics::Instruments::OPERATION_DURATION.record(Riffer::Metrics.monotonic_now - start, attributes: tool_metric_attributes(tool_call, error_type, tags))
|
|
77
79
|
end
|
|
78
80
|
end
|
|
79
81
|
|
|
@@ -113,9 +115,9 @@ class Riffer::Tools::Runtime
|
|
|
113
115
|
|
|
114
116
|
# Emitted outside +around_tool_call+ so host enrichment spans nest beneath it.
|
|
115
117
|
#--
|
|
116
|
-
#: [R] (Riffer::Messages::Assistant::ToolCall) { ((Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
117
|
-
def in_tool_span(tool_call)
|
|
118
|
-
Riffer::Tracing.in_span("execute_tool #{tool_call.name}", attributes: tool_span_attributes(tool_call), kind: :internal) do |span|
|
|
118
|
+
#: [R] (Riffer::Messages::Assistant::ToolCall, ?Hash[String, String]) { ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span)) -> R } -> R
|
|
119
|
+
def in_tool_span(tool_call, tags = {})
|
|
120
|
+
Riffer::Tracing.in_span("execute_tool #{tool_call.name}", attributes: tool_span_attributes(tool_call, tags), kind: :internal) do |span|
|
|
119
121
|
capture_tool_arguments(span, tool_call)
|
|
120
122
|
yield span
|
|
121
123
|
rescue => error
|
|
@@ -127,30 +129,38 @@ class Riffer::Tools::Runtime
|
|
|
127
129
|
end
|
|
128
130
|
|
|
129
131
|
#--
|
|
130
|
-
#: (Riffer::Messages::Assistant::ToolCall) -> Hash[String, untyped]
|
|
131
|
-
def tool_span_attributes(tool_call)
|
|
132
|
+
#: (Riffer::Messages::Assistant::ToolCall, ?Hash[String, String]) -> Hash[String, untyped]
|
|
133
|
+
def tool_span_attributes(tool_call, tags = {})
|
|
132
134
|
{
|
|
133
135
|
"gen_ai.operation.name" => "execute_tool",
|
|
134
136
|
"gen_ai.tool.name" => tool_call.name,
|
|
135
137
|
"gen_ai.tool.call.id" => tool_call.call_id
|
|
136
|
-
}
|
|
138
|
+
}.merge(tag_attributes(tags))
|
|
137
139
|
end
|
|
138
140
|
|
|
139
141
|
#--
|
|
140
|
-
#: (Riffer::Messages::Assistant::ToolCall, String?) -> Hash[String, untyped]
|
|
141
|
-
def tool_metric_attributes(tool_call, error_type)
|
|
142
|
+
#: (Riffer::Messages::Assistant::ToolCall, String?, ?Hash[String, String]) -> Hash[String, untyped]
|
|
143
|
+
def tool_metric_attributes(tool_call, error_type, tags = {})
|
|
142
144
|
attributes = {
|
|
143
145
|
"gen_ai.operation.name" => "execute_tool",
|
|
144
146
|
"gen_ai.tool.name" => tool_call.name
|
|
145
147
|
} #: Hash[String, untyped]
|
|
146
148
|
attributes["error.type"] = error_type if error_type
|
|
147
|
-
attributes
|
|
149
|
+
attributes.merge(tag_attributes(tags))
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Maps normalized tags to their namespaced span/metric attribute form. An
|
|
153
|
+
# empty map yields an empty hash, so merging it is a no-op.
|
|
154
|
+
#--
|
|
155
|
+
#: (Hash[String, String]) -> Hash[String, String]
|
|
156
|
+
def tag_attributes(tags)
|
|
157
|
+
tags.transform_keys { |key| "riffer.tag.#{key}" }
|
|
148
158
|
end
|
|
149
159
|
|
|
150
160
|
# A returned error Response is a handled outcome, so its status stays unset —
|
|
151
161
|
# an error span status is reserved for a raised exception.
|
|
152
162
|
#--
|
|
153
|
-
#: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
163
|
+
#: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Tools::Response) -> void
|
|
154
164
|
def record_tool_outcome(span, result)
|
|
155
165
|
error_type = result.error_type
|
|
156
166
|
span.set_attribute("error.type", error_type.to_s) if error_type
|
|
@@ -158,7 +168,7 @@ class Riffer::Tools::Runtime
|
|
|
158
168
|
end
|
|
159
169
|
|
|
160
170
|
#--
|
|
161
|
-
#: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
171
|
+
#: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Messages::Assistant::ToolCall) -> void
|
|
162
172
|
def capture_tool_arguments(span, tool_call)
|
|
163
173
|
return unless capture_tool_content?(span)
|
|
164
174
|
|
|
@@ -167,7 +177,7 @@ class Riffer::Tools::Runtime
|
|
|
167
177
|
end
|
|
168
178
|
|
|
169
179
|
#--
|
|
170
|
-
#: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
180
|
+
#: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Tools::Response) -> void
|
|
171
181
|
def capture_tool_result(span, result)
|
|
172
182
|
return unless capture_tool_content?(span)
|
|
173
183
|
|
|
@@ -175,7 +185,7 @@ class Riffer::Tools::Runtime
|
|
|
175
185
|
end
|
|
176
186
|
|
|
177
187
|
#--
|
|
178
|
-
#: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
188
|
+
#: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span)) -> bool
|
|
179
189
|
def capture_tool_content?(span)
|
|
180
190
|
Riffer.config.tracing.capture_messages && span.recording?
|
|
181
191
|
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
|
@@ -7,29 +7,34 @@ module Riffer::Agent::Run
|
|
|
7
7
|
# for prompt/files semantics.
|
|
8
8
|
#
|
|
9
9
|
# --
|
|
10
|
-
# : (agent: Riffer::Agent, ?prompt: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?) -> Riffer::Agent::Response
|
|
11
|
-
def generate: (agent: Riffer::Agent, ?prompt: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?) -> Riffer::Agent::Response
|
|
10
|
+
# : (agent: Riffer::Agent, ?prompt: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?tags: Hash[(String | Symbol), untyped]) -> Riffer::Agent::Response
|
|
11
|
+
def generate: (agent: Riffer::Agent, ?prompt: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?tags: Hash[String | Symbol, untyped]) -> Riffer::Agent::Response
|
|
12
12
|
|
|
13
13
|
# Runs the streaming loop for the given agent. See Riffer::Agent#stream
|
|
14
14
|
# for prompt/files semantics.
|
|
15
15
|
#
|
|
16
16
|
# --
|
|
17
|
-
# : (agent: Riffer::Agent, ?prompt: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?) -> Enumerator[Riffer::StreamEvents::Base, void]
|
|
18
|
-
def stream: (agent: Riffer::Agent, ?prompt: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?) -> Enumerator[Riffer::StreamEvents::Base, void]
|
|
17
|
+
# : (agent: Riffer::Agent, ?prompt: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?tags: Hash[(String | Symbol), untyped]) -> Enumerator[Riffer::StreamEvents::Base, void]
|
|
18
|
+
def stream: (agent: Riffer::Agent, ?prompt: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?tags: Hash[String | Symbol, untyped]) -> Enumerator[Riffer::StreamEvents::Base, void]
|
|
19
19
|
|
|
20
20
|
private
|
|
21
21
|
|
|
22
|
+
# Both +generate+ and +stream+ funnel here, so this is the single place raw
|
|
23
|
+
# +tags+ are normalized. The clean <tt>String => String</tt> map is then
|
|
24
|
+
# threaded to every span/metric builder in the run as +riffer.tag.*+ and to
|
|
25
|
+
# each provider call (via +merged_model_options+) for native request-metadata
|
|
26
|
+
# mapping.
|
|
22
27
|
# --
|
|
23
|
-
# : (Riffer::Agent, ?stream_yielder: Enumerator::Yielder?) -> Riffer::Agent::Response
|
|
24
|
-
def run_loop: (Riffer::Agent, ?stream_yielder: Enumerator::Yielder?) -> Riffer::Agent::Response
|
|
28
|
+
# : (Riffer::Agent, ?tags: Hash[(String | Symbol), untyped]?, ?stream_yielder: Enumerator::Yielder?) -> Riffer::Agent::Response
|
|
29
|
+
def run_loop: (Riffer::Agent, ?tags: Hash[String | Symbol, untyped]?, ?stream_yielder: Enumerator::Yielder?) -> Riffer::Agent::Response
|
|
25
30
|
|
|
26
31
|
# --
|
|
27
|
-
# : (Riffer::Agent, Enumerator::Yielder?) -> Riffer::Agent::Response
|
|
28
|
-
def execute_run: (Riffer::Agent, Enumerator::Yielder?) -> Riffer::Agent::Response
|
|
32
|
+
# : (Riffer::Agent, Enumerator::Yielder?, ?Hash[String, String]) -> Riffer::Agent::Response
|
|
33
|
+
def execute_run: (Riffer::Agent, Enumerator::Yielder?, ?Hash[String, String]) -> Riffer::Agent::Response
|
|
29
34
|
|
|
30
35
|
# --
|
|
31
|
-
# : (Riffer::Agent, Enumerator::Yielder) -> Riffer::Messages::Assistant
|
|
32
|
-
def accumulate_streamed_response: (Riffer::Agent, Enumerator::Yielder) -> Riffer::Messages::Assistant
|
|
36
|
+
# : (Riffer::Agent, Enumerator::Yielder, ?Hash[String, String]) -> Riffer::Messages::Assistant
|
|
37
|
+
def accumulate_streamed_response: (Riffer::Agent, Enumerator::Yielder, ?Hash[String, String]) -> Riffer::Messages::Assistant
|
|
33
38
|
|
|
34
39
|
# --
|
|
35
40
|
# : (Enumerator::Yielder?, Array[Riffer::Guardrails::Modification], Array[Riffer::Guardrails::Modification]) -> void
|
|
@@ -44,32 +49,32 @@ module Riffer::Agent::Run
|
|
|
44
49
|
def final_response: (Riffer::Agent, Array[Riffer::Guardrails::Modification], **untyped) -> Riffer::Agent::Response
|
|
45
50
|
|
|
46
51
|
# --
|
|
47
|
-
# : (Riffer::Agent) -> Riffer::Messages::Assistant
|
|
48
|
-
def call_llm: (Riffer::Agent) -> Riffer::Messages::Assistant
|
|
52
|
+
# : (Riffer::Agent, ?Hash[String, String]) -> Riffer::Messages::Assistant
|
|
53
|
+
def call_llm: (Riffer::Agent, ?Hash[String, String]) -> Riffer::Messages::Assistant
|
|
49
54
|
|
|
50
55
|
# --
|
|
51
|
-
# : (Riffer::Agent) -> Enumerator[Riffer::StreamEvents::Base, void]
|
|
52
|
-
def call_llm_stream: (Riffer::Agent) -> Enumerator[Riffer::StreamEvents::Base, void]
|
|
56
|
+
# : (Riffer::Agent, ?Hash[String, String]) -> Enumerator[Riffer::StreamEvents::Base, void]
|
|
57
|
+
def call_llm_stream: (Riffer::Agent, ?Hash[String, String]) -> Enumerator[Riffer::StreamEvents::Base, void]
|
|
53
58
|
|
|
54
59
|
# --
|
|
55
|
-
# : (Riffer::Agent, Riffer::Messages::Assistant, ?tool_calls: Array[Riffer::Messages::Assistant::ToolCall]) -> void
|
|
56
|
-
def execute_tool_calls: (Riffer::Agent, Riffer::Messages::Assistant, ?tool_calls: Array[Riffer::Messages::Assistant::ToolCall]) -> void
|
|
60
|
+
# : (Riffer::Agent, Riffer::Messages::Assistant, ?tool_calls: Array[Riffer::Messages::Assistant::ToolCall], ?tags: Hash[String, String]) -> void
|
|
61
|
+
def execute_tool_calls: (Riffer::Agent, Riffer::Messages::Assistant, ?tool_calls: Array[Riffer::Messages::Assistant::ToolCall], ?tags: Hash[String, String]) -> void
|
|
57
62
|
|
|
58
63
|
# --
|
|
59
64
|
# : (Riffer::Agent, Array[[Riffer::Messages::Assistant::ToolCall, Riffer::Tools::Response]]) -> void
|
|
60
65
|
def inject_discovered_tools: (Riffer::Agent, Array[[ Riffer::Messages::Assistant::ToolCall, Riffer::Tools::Response ]]) -> void
|
|
61
66
|
|
|
62
67
|
# --
|
|
63
|
-
# : (Riffer::Agent) -> void
|
|
64
|
-
def execute_pending_tool_calls: (Riffer::Agent) -> void
|
|
68
|
+
# : (Riffer::Agent, ?Hash[String, String]) -> void
|
|
69
|
+
def execute_pending_tool_calls: (Riffer::Agent, ?Hash[String, String]) -> void
|
|
65
70
|
|
|
66
71
|
# --
|
|
67
|
-
# : (Riffer::Agent, Enumerator::Yielder?, Array[Riffer::Guardrails::Modification]) { (Riffer::Guardrails::Tripwire) -> void } -> void
|
|
68
|
-
def run_before_guardrails: (Riffer::Agent, Enumerator::Yielder?, Array[Riffer::Guardrails::Modification]) { (Riffer::Guardrails::Tripwire) -> void } -> void
|
|
72
|
+
# : (Riffer::Agent, Enumerator::Yielder?, Array[Riffer::Guardrails::Modification], ?Hash[String, String]) { (Riffer::Guardrails::Tripwire) -> void } -> void
|
|
73
|
+
def run_before_guardrails: (Riffer::Agent, Enumerator::Yielder?, Array[Riffer::Guardrails::Modification], ?Hash[String, String]) { (Riffer::Guardrails::Tripwire) -> void } -> void
|
|
69
74
|
|
|
70
75
|
# --
|
|
71
|
-
# : (Riffer::Agent, Riffer::Messages::Assistant, Enumerator::Yielder?, Array[Riffer::Guardrails::Modification]) { (Riffer::Guardrails::Tripwire) -> void } -> untyped
|
|
72
|
-
def run_after_guardrails: (Riffer::Agent, Riffer::Messages::Assistant, Enumerator::Yielder?, Array[Riffer::Guardrails::Modification]) { (Riffer::Guardrails::Tripwire) -> void } -> untyped
|
|
76
|
+
# : (Riffer::Agent, Riffer::Messages::Assistant, Enumerator::Yielder?, Array[Riffer::Guardrails::Modification], ?Hash[String, String]) { (Riffer::Guardrails::Tripwire) -> void } -> untyped
|
|
77
|
+
def run_after_guardrails: (Riffer::Agent, Riffer::Messages::Assistant, Enumerator::Yielder?, Array[Riffer::Guardrails::Modification], ?Hash[String, String]) { (Riffer::Guardrails::Tripwire) -> void } -> untyped
|
|
73
78
|
|
|
74
79
|
# --
|
|
75
80
|
# : (Riffer::Agent, Riffer::Messages::Assistant?) -> Hash[Symbol, untyped]?
|
|
@@ -79,9 +84,13 @@ module Riffer::Agent::Run
|
|
|
79
84
|
# : (Riffer::Agent) -> Array[singleton(Riffer::Tool)]
|
|
80
85
|
def effective_tools: (Riffer::Agent) -> Array[singleton(Riffer::Tool)]
|
|
81
86
|
|
|
87
|
+
# +tags+ rides in the options hash as a curated key the providers extract for
|
|
88
|
+
# native request-metadata mapping (alongside +:structured_output+); it never
|
|
89
|
+
# reaches an SDK call verbatim. Span/metric tagging is threaded separately to
|
|
90
|
+
# each builder.
|
|
82
91
|
# --
|
|
83
|
-
# : (Riffer::Agent) -> Hash[Symbol, untyped]
|
|
84
|
-
def merged_model_options: (Riffer::Agent) -> Hash[Symbol, untyped]
|
|
92
|
+
# : (Riffer::Agent, ?Hash[String, String]) -> Hash[Symbol, untyped]
|
|
93
|
+
def merged_model_options: (Riffer::Agent, ?Hash[String, String]) -> Hash[Symbol, untyped]
|
|
85
94
|
|
|
86
95
|
# --
|
|
87
96
|
# : (Riffer::Agent, String, ?tripwire: Riffer::Guardrails::Tripwire?, ?modifications: Array[Riffer::Guardrails::Modification], ?interrupted: bool, ?interrupt_reason: (String | Symbol)?, ?structured_output: Hash[Symbol, untyped]?, ?healed_tool_call_ids: Array[String], ?token_usage: Riffer::Providers::TokenUsage?, ?steps: Integer) -> Riffer::Agent::Response
|
|
@@ -102,14 +111,22 @@ module Riffer::Agent::Run
|
|
|
102
111
|
def sum_usage: (Riffer::Providers::TokenUsage?, Riffer::Providers::TokenUsage?) -> Riffer::Providers::TokenUsage?
|
|
103
112
|
|
|
104
113
|
# --
|
|
105
|
-
# : (Riffer::Agent) -> Hash[String, untyped]
|
|
106
|
-
def run_span_attributes: (Riffer::Agent) -> Hash[String, untyped]
|
|
114
|
+
# : (Riffer::Agent, ?Hash[String, String]) -> Hash[String, untyped]
|
|
115
|
+
def run_span_attributes: (Riffer::Agent, ?Hash[String, String]) -> Hash[String, untyped]
|
|
107
116
|
|
|
108
117
|
# --
|
|
109
|
-
# : (Riffer::Agent, String?) -> Hash[String, untyped]
|
|
110
|
-
def run_metric_attributes: (Riffer::Agent, String?) -> Hash[String, untyped]
|
|
118
|
+
# : (Riffer::Agent, String?, ?Hash[String, String]) -> Hash[String, untyped]
|
|
119
|
+
def run_metric_attributes: (Riffer::Agent, String?, ?Hash[String, String]) -> Hash[String, untyped]
|
|
111
120
|
|
|
112
121
|
# --
|
|
113
|
-
# : (
|
|
114
|
-
def
|
|
122
|
+
# : (Hash[(String | Symbol), untyped]?) -> Hash[String, String]
|
|
123
|
+
def normalize_tags: (Hash[String | Symbol, untyped]?) -> Hash[String, String]
|
|
124
|
+
|
|
125
|
+
# --
|
|
126
|
+
# : (Hash[String, String]) -> Hash[String, String]
|
|
127
|
+
def tag_attributes: (Hash[String, String]) -> Hash[String, String]
|
|
128
|
+
|
|
129
|
+
# --
|
|
130
|
+
# : (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span, Riffer::Agent::Response) -> void
|
|
131
|
+
def record_run_outcome: (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span, Riffer::Agent::Response) -> void
|
|
115
132
|
end
|
|
@@ -124,13 +124,13 @@ class Riffer::Agent
|
|
|
124
124
|
|
|
125
125
|
# Generates a response using a new agent instance.
|
|
126
126
|
# --
|
|
127
|
-
# : (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?) -> Riffer::Agent::Response
|
|
128
|
-
def self.generate: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?) -> Riffer::Agent::Response
|
|
127
|
+
# : (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?, ?tags: Hash[(String | Symbol), untyped]) -> Riffer::Agent::Response
|
|
128
|
+
def self.generate: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?, ?tags: Hash[String | Symbol, untyped]) -> Riffer::Agent::Response
|
|
129
129
|
|
|
130
130
|
# Streams a response using a new agent instance.
|
|
131
131
|
# --
|
|
132
|
-
# : (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?) -> Enumerator[Riffer::StreamEvents::Base, void]
|
|
133
|
-
def self.stream: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?) -> Enumerator[Riffer::StreamEvents::Base, void]
|
|
132
|
+
# : (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?, ?tags: Hash[(String | Symbol), untyped]) -> Enumerator[Riffer::StreamEvents::Base, void]
|
|
133
|
+
def self.stream: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?, ?tags: Hash[String | Symbol, untyped]) -> Enumerator[Riffer::StreamEvents::Base, void]
|
|
134
134
|
|
|
135
135
|
# Reconstructs a runnable agent from a wire hash produced by +#to_h+.
|
|
136
136
|
# --
|
|
@@ -213,18 +213,25 @@ class Riffer::Agent
|
|
|
213
213
|
# against the current session, resuming a persisted conversation or pending
|
|
214
214
|
# tool calls. +files:+ requires +prompt+.
|
|
215
215
|
#
|
|
216
|
+
# +tags:+ is an optional flat hash of attribution tags applied to this single
|
|
217
|
+
# call: they propagate to the provider's native request-metadata field, and
|
|
218
|
+
# are stamped as +riffer.tag.*+ on every span and metric the call emits. See
|
|
219
|
+
# +docs/03_AGENTS.md+ for the per-provider mapping. The reserved key
|
|
220
|
+
# +user_id+ also maps to the provider's native user identifier where one
|
|
221
|
+
# exists.
|
|
222
|
+
#
|
|
216
223
|
# --
|
|
217
|
-
# : (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?) -> Riffer::Agent::Response
|
|
218
|
-
def generate: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?) -> Riffer::Agent::Response
|
|
224
|
+
# : (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?tags: Hash[(String | Symbol), untyped]) -> Riffer::Agent::Response
|
|
225
|
+
def generate: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?tags: Hash[String | Symbol, untyped]) -> Riffer::Agent::Response
|
|
219
226
|
|
|
220
227
|
# Streams a response from the agent, returning an +Enumerator+ of
|
|
221
|
-
# +Riffer::StreamEvents+. See +#generate+ for prompt/files semantics.
|
|
228
|
+
# +Riffer::StreamEvents+. See +#generate+ for prompt/files/tags semantics.
|
|
222
229
|
#
|
|
223
230
|
# Raises Riffer::ArgumentError if structured output is configured.
|
|
224
231
|
#
|
|
225
232
|
# --
|
|
226
|
-
# : (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?) -> Enumerator[Riffer::StreamEvents::Base, void]
|
|
227
|
-
def stream: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?) -> Enumerator[Riffer::StreamEvents::Base, void]
|
|
233
|
+
# : (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?tags: Hash[(String | Symbol), untyped]) -> Enumerator[Riffer::StreamEvents::Base, void]
|
|
234
|
+
def stream: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?tags: Hash[String | Symbol, untyped]) -> Enumerator[Riffer::StreamEvents::Base, void]
|
|
228
235
|
|
|
229
236
|
# Interrupts the agent loop from an +on_message+ callback. Equivalent to
|
|
230
237
|
# <tt>throw :riffer_interrupt, reason</tt>.
|
|
@@ -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
|
|
@@ -12,9 +12,12 @@ class Riffer::Guardrails::Runner
|
|
|
12
12
|
# The context passed to guardrails.
|
|
13
13
|
attr_reader context: untyped
|
|
14
14
|
|
|
15
|
+
# The normalized per-call tags, stamped as +riffer.tag.*+ on guardrail spans.
|
|
16
|
+
attr_reader tags: Hash[String, String]
|
|
17
|
+
|
|
15
18
|
# --
|
|
16
|
-
# : (Array[Hash[Symbol, untyped]], phase: Symbol, ?context: untyped) -> void
|
|
17
|
-
def initialize: (Array[Hash[Symbol, untyped]], phase: Symbol, ?context: untyped) -> void
|
|
19
|
+
# : (Array[Hash[Symbol, untyped]], phase: Symbol, ?context: untyped, ?tags: Hash[String, String]) -> void
|
|
20
|
+
def initialize: (Array[Hash[Symbol, untyped]], phase: Symbol, ?context: untyped, ?tags: Hash[String, String]) -> void
|
|
18
21
|
|
|
19
22
|
# Runs the guardrails sequentially. For the +:before+ phase +data+ is the
|
|
20
23
|
# messages array; for +:after+ it's the response (and +messages+ must be
|
|
@@ -37,6 +40,10 @@ class Riffer::Guardrails::Runner
|
|
|
37
40
|
# : (Riffer::Guardrail, untyped, messages: Array[Riffer::Messages::Base]?) -> Riffer::Guardrails::Result
|
|
38
41
|
def execute_guardrail: (Riffer::Guardrail, untyped, messages: Array[Riffer::Messages::Base]?) -> Riffer::Guardrails::Result
|
|
39
42
|
|
|
43
|
+
# --
|
|
44
|
+
# : (Riffer::Guardrail) { () -> Riffer::Guardrails::Result } -> Riffer::Guardrails::Result
|
|
45
|
+
def instrument_guardrail: (Riffer::Guardrail) { () -> Riffer::Guardrails::Result } -> Riffer::Guardrails::Result
|
|
46
|
+
|
|
40
47
|
# --
|
|
41
48
|
# : (Riffer::Guardrail, untyped, messages: Array[Riffer::Messages::Base]?) -> Riffer::Guardrails::Result
|
|
42
49
|
def run_guardrail_phase: (Riffer::Guardrail, untyped, messages: Array[Riffer::Messages::Base]?) -> Riffer::Guardrails::Result
|
|
@@ -45,9 +52,13 @@ class Riffer::Guardrails::Runner
|
|
|
45
52
|
# : (Riffer::Guardrail) -> Hash[String, untyped]
|
|
46
53
|
def guardrail_span_attributes: (Riffer::Guardrail) -> Hash[String, untyped]
|
|
47
54
|
|
|
55
|
+
# --
|
|
56
|
+
# : (Riffer::Guardrail, String?) -> Hash[String, untyped]
|
|
57
|
+
def guardrail_metric_attributes: (Riffer::Guardrail, String?) -> Hash[String, untyped]
|
|
58
|
+
|
|
48
59
|
# A block is a handled outcome, so its span status stays unset — an error
|
|
49
60
|
# span status is reserved for a raised exception.
|
|
50
61
|
# --
|
|
51
|
-
# : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
52
|
-
def record_guardrail_outcome: (Riffer::Tracing::Otel::Span | Riffer::Tracing::
|
|
62
|
+
# : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Guardrails::Result) -> void
|
|
63
|
+
def record_guardrail_outcome: (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span, Riffer::Guardrails::Result) -> void
|
|
53
64
|
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
|