riffer 0.32.1 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/.ruby-version +1 -1
  4. data/CHANGELOG.md +39 -0
  5. data/README.md +13 -11
  6. data/docs/01_OVERVIEW.md +2 -0
  7. data/docs/04_AGENT_LIFECYCLE.md +15 -13
  8. data/docs/08_MESSAGES.md +39 -5
  9. data/docs/09_STREAM_EVENTS.md +14 -0
  10. data/docs/10_CONFIGURATION.md +73 -4
  11. data/docs/13_SKILLS.md +62 -4
  12. data/docs/14_MCP.md +2 -1
  13. data/docs/16_TRACING.md +274 -0
  14. data/docs/17_METRICS.md +153 -0
  15. data/docs/providers/07_CUSTOM_PROVIDERS.md +44 -0
  16. data/lib/riffer/agent/response.rb +11 -2
  17. data/lib/riffer/agent/run.rb +136 -35
  18. data/lib/riffer/agent.rb +1 -1
  19. data/lib/riffer/config.rb +230 -15
  20. data/lib/riffer/guardrail.rb +8 -0
  21. data/lib/riffer/guardrails/runner.rb +58 -0
  22. data/lib/riffer/helpers/boolean.rb +22 -0
  23. data/lib/riffer/mcp/authenticated_tool.rb +14 -20
  24. data/lib/riffer/mcp/registration.rb +4 -4
  25. data/lib/riffer/mcp/tool.rb +23 -0
  26. data/lib/riffer/mcp/tool_factory.rb +14 -22
  27. data/lib/riffer/messages/assistant.rb +15 -3
  28. data/lib/riffer/messages/base.rb +2 -1
  29. data/lib/riffer/metrics/instruments.rb +31 -0
  30. data/lib/riffer/metrics/no_op.rb +14 -0
  31. data/lib/riffer/metrics/otel.rb +80 -0
  32. data/lib/riffer/metrics.rb +94 -0
  33. data/lib/riffer/providers/amazon_bedrock.rb +57 -21
  34. data/lib/riffer/providers/anthropic.rb +59 -24
  35. data/lib/riffer/providers/azure_open_ai.rb +7 -0
  36. data/lib/riffer/providers/base.rb +247 -15
  37. data/lib/riffer/providers/finish_reason.rb +27 -0
  38. data/lib/riffer/providers/gemini.rb +59 -11
  39. data/lib/riffer/providers/mock.rb +30 -9
  40. data/lib/riffer/providers/open_ai.rb +78 -24
  41. data/lib/riffer/providers/open_router.rb +56 -16
  42. data/lib/riffer/providers/repository.rb +9 -0
  43. data/lib/riffer/providers/token_usage.rb +27 -11
  44. data/lib/riffer/skills/activate_tool.rb +11 -2
  45. data/lib/riffer/skills/adapter.rb +15 -0
  46. data/lib/riffer/skills/context.rb +63 -11
  47. data/lib/riffer/skills/markdown_adapter.rb +1 -1
  48. data/lib/riffer/skills/xml_adapter.rb +1 -1
  49. data/lib/riffer/stream_events/finish_reason_done.rb +34 -0
  50. data/lib/riffer/tools/runtime.rb +99 -3
  51. data/lib/riffer/tracing/capture.rb +92 -0
  52. data/lib/riffer/tracing/no_op.rb +61 -0
  53. data/lib/riffer/tracing/otel.rb +132 -0
  54. data/lib/riffer/tracing/stream_recorder.rb +51 -0
  55. data/lib/riffer/tracing.rb +78 -0
  56. data/lib/riffer/version.rb +1 -1
  57. data/sig/_private/opentelemetry.rbs +22 -0
  58. data/sig/generated/riffer/agent/response.rbs +9 -2
  59. data/sig/generated/riffer/agent/run.rbs +28 -8
  60. data/sig/generated/riffer/config.rbs +162 -16
  61. data/sig/generated/riffer/guardrail.rbs +6 -0
  62. data/sig/generated/riffer/guardrails/runner.rbs +22 -0
  63. data/sig/generated/riffer/helpers/boolean.rbs +11 -0
  64. data/sig/generated/riffer/mcp/authenticated_tool.rbs +6 -8
  65. data/sig/generated/riffer/mcp/registration.rbs +4 -4
  66. data/sig/generated/riffer/mcp/tool.rbs +19 -0
  67. data/sig/generated/riffer/mcp/tool_factory.rbs +8 -7
  68. data/sig/generated/riffer/messages/assistant.rbs +10 -4
  69. data/sig/generated/riffer/metrics/instruments.rbs +15 -0
  70. data/sig/generated/riffer/metrics/no_op.rbs +10 -0
  71. data/sig/generated/riffer/metrics/otel.rbs +48 -0
  72. data/sig/generated/riffer/metrics.rbs +72 -0
  73. data/sig/generated/riffer/providers/amazon_bedrock.rbs +35 -14
  74. data/sig/generated/riffer/providers/anthropic.rbs +41 -20
  75. data/sig/generated/riffer/providers/azure_open_ai.rbs +5 -0
  76. data/sig/generated/riffer/providers/base.rbs +78 -2
  77. data/sig/generated/riffer/providers/finish_reason.rbs +19 -0
  78. data/sig/generated/riffer/providers/gemini.rbs +25 -2
  79. data/sig/generated/riffer/providers/mock.rbs +16 -5
  80. data/sig/generated/riffer/providers/open_ai.rbs +44 -22
  81. data/sig/generated/riffer/providers/open_router.rbs +31 -12
  82. data/sig/generated/riffer/providers/repository.rbs +7 -0
  83. data/sig/generated/riffer/providers/token_usage.rbs +20 -10
  84. data/sig/generated/riffer/skills/activate_tool.rbs +7 -1
  85. data/sig/generated/riffer/skills/adapter.rbs +10 -0
  86. data/sig/generated/riffer/skills/context.rbs +42 -4
  87. data/sig/generated/riffer/stream_events/finish_reason_done.rbs +21 -0
  88. data/sig/generated/riffer/tools/runtime.rbs +35 -0
  89. data/sig/generated/riffer/tracing/capture.rbs +46 -0
  90. data/sig/generated/riffer/tracing/no_op.rbs +46 -0
  91. data/sig/generated/riffer/tracing/otel.rbs +84 -0
  92. data/sig/generated/riffer/tracing/stream_recorder.rbs +31 -0
  93. data/sig/generated/riffer/tracing.rbs +52 -0
  94. data/sig/manual/riffer/helpers/boolean.rbs +5 -0
  95. data/sig/manual/riffer/metrics/no_op.rbs +5 -0
  96. data/sig/manual/riffer/metrics.rbs +5 -0
  97. data/sig/manual/riffer/providers.rbs +9 -0
  98. data/sig/manual/riffer/tracing/capture.rbs +5 -0
  99. data/sig/manual/riffer/tracing/no_op.rbs +5 -0
  100. data/sig/manual/riffer/tracing.rbs +5 -0
  101. metadata +42 -6
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ # Internal tracing port — emits OTEL spans when the host bundles the
5
+ # OpenTelemetry API and no-ops otherwise, so riffer never declares an OTEL
6
+ # dependency.
7
+ module Riffer::Tracing # :nodoc: all
8
+ extend self
9
+
10
+ # @rbs @backend: untyped
11
+
12
+ MUTEX = Mutex.new #: Mutex
13
+
14
+ # The Ruby API cannot attach a schema URL to a tracer, so the semconv pin
15
+ # lives here as the documented contract version.
16
+ SCHEMA_URL = "https://opentelemetry.io/schemas/1.37.0" #: String
17
+
18
+ # Opens a span around the block, yielding the span.
19
+ #--
20
+ #: [R] (String, ?attributes: Hash[String, untyped]?, ?kind: Symbol) { (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span) -> R } -> R
21
+ def in_span(name, attributes: nil, kind: :internal, &block)
22
+ return NoOp.in_span(name, &block) unless Riffer.config.tracing.enabled
23
+ backend.in_span(name, attributes: attributes, kind: kind, &block)
24
+ end
25
+
26
+ # Returns the active trace context, for re-attachment across fiber or
27
+ # thread boundaries.
28
+ #--
29
+ #: () -> untyped
30
+ def current_context
31
+ return NoOp.current_context unless Riffer.config.tracing.enabled
32
+ backend.current_context
33
+ end
34
+
35
+ # Runs the block with the given trace context active; +nil+ passes through
36
+ # so captures taken while tracing was dark stay harmless.
37
+ #--
38
+ #: [R] (untyped) { () -> R } -> R
39
+ def with_context(context, &block)
40
+ return NoOp.with_context(context, &block) unless Riffer.config.tracing.enabled
41
+ backend.with_context(context, &block)
42
+ end
43
+
44
+ # Stamps token usage onto the span — the <tt>gen_ai.usage.*</tt> counts and,
45
+ # when the model was priced, <tt>riffer.cost</tt>.
46
+ #--
47
+ #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Providers::TokenUsage?) -> void
48
+ def record_usage(span, usage)
49
+ return unless usage
50
+
51
+ span.set_attribute("gen_ai.usage.input_tokens", usage.input_tokens)
52
+ span.set_attribute("gen_ai.usage.output_tokens", usage.output_tokens)
53
+ span.set_attribute("gen_ai.usage.cache_read.input_tokens", usage.cache_read_tokens) if usage.cache_read_tokens
54
+ span.set_attribute("gen_ai.usage.cache_creation.input_tokens", usage.cache_write_tokens) if usage.cache_write_tokens
55
+ span.set_attribute("riffer.cost", usage.cost) if usage.cost
56
+ end
57
+
58
+ # Discards the resolved backend so the next span re-resolves it.
59
+ #--
60
+ #: () -> void
61
+ def reset!
62
+ MUTEX.synchronize { @backend = nil }
63
+ end
64
+
65
+ private
66
+
67
+ #--
68
+ #: () -> untyped
69
+ def backend
70
+ @backend || MUTEX.synchronize { @backend ||= resolve_backend }
71
+ end
72
+
73
+ #--
74
+ #: () -> untyped
75
+ def resolve_backend
76
+ Riffer.config.tracing.backend || NoOp
77
+ end
78
+ end
@@ -2,5 +2,5 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  module Riffer
5
- VERSION = "0.32.1" #: String
5
+ VERSION = "0.34.0" #: String
6
6
  end
@@ -0,0 +1,22 @@
1
+ # Minimal signatures for the `opentelemetry-api` and `opentelemetry-metrics-api`
2
+ # gems (undeclared, runtime detected), which ship no RBS.
3
+ #
4
+ # Only the surface used by Riffer::Tracing::Otel and Riffer::Metrics::Otel is
5
+ # declared.
6
+ module OpenTelemetry
7
+ def self.tracer_provider: () -> untyped
8
+
9
+ def self.meter_provider: () -> untyped
10
+
11
+ class Context
12
+ def self.current: () -> OpenTelemetry::Context
13
+
14
+ def self.with_current: [R] (OpenTelemetry::Context) { () -> R } -> R
15
+ end
16
+
17
+ module Trace
18
+ class Status
19
+ def self.error: (?String) -> OpenTelemetry::Trace::Status
20
+ end
21
+ end
22
+ end
@@ -27,6 +27,13 @@ class Riffer::Agent::Response
27
27
  # The parsed structured output, if structured output was configured.
28
28
  attr_reader structured_output: Hash[Symbol, untyped]?
29
29
 
30
+ # The aggregate token usage across this run's LLM calls, if any was reported.
31
+ attr_reader token_usage: Riffer::Providers::TokenUsage?
32
+
33
+ # The number of LLM calls made during this run (0 when a before-guardrail
34
+ # blocks before any call). Distinct from the session's cumulative step count.
35
+ attr_reader steps: Integer
36
+
30
37
  # The full message history from the agent conversation.
31
38
  attr_reader messages: Array[Riffer::Messages::Base]
32
39
 
@@ -35,8 +42,8 @@ class Riffer::Agent::Response
35
42
  attr_reader healed_tool_call_ids: Array[String]
36
43
 
37
44
  # --
38
- # : (String, ?tripwire: Riffer::Guardrails::Tripwire?, ?modifications: Array[Riffer::Guardrails::Modification], ?interrupted: bool, ?interrupt_reason: (String | Symbol)?, ?structured_output: Hash[Symbol, untyped]?, ?messages: Array[Riffer::Messages::Base], ?healed_tool_call_ids: Array[String]) -> void
39
- def initialize: (String, ?tripwire: Riffer::Guardrails::Tripwire?, ?modifications: Array[Riffer::Guardrails::Modification], ?interrupted: bool, ?interrupt_reason: (String | Symbol)?, ?structured_output: Hash[Symbol, untyped]?, ?messages: Array[Riffer::Messages::Base], ?healed_tool_call_ids: Array[String]) -> void
45
+ # : (String, ?tripwire: Riffer::Guardrails::Tripwire?, ?modifications: Array[Riffer::Guardrails::Modification], ?interrupted: bool, ?interrupt_reason: (String | Symbol)?, ?structured_output: Hash[Symbol, untyped]?, ?messages: Array[Riffer::Messages::Base], ?healed_tool_call_ids: Array[String], ?token_usage: Riffer::Providers::TokenUsage?, ?steps: Integer) -> void
46
+ def initialize: (String, ?tripwire: Riffer::Guardrails::Tripwire?, ?modifications: Array[Riffer::Guardrails::Modification], ?interrupted: bool, ?interrupt_reason: (String | Symbol)?, ?structured_output: Hash[Symbol, untyped]?, ?messages: Array[Riffer::Messages::Base], ?healed_tool_call_ids: Array[String], ?token_usage: Riffer::Providers::TokenUsage?, ?steps: Integer) -> void
40
47
 
41
48
  # Returns true if the response was blocked by a guardrail.
42
49
  #
@@ -23,6 +23,10 @@ module Riffer::Agent::Run
23
23
  # : (Riffer::Agent, ?stream_yielder: Enumerator::Yielder?) -> Riffer::Agent::Response
24
24
  def run_loop: (Riffer::Agent, ?stream_yielder: Enumerator::Yielder?) -> Riffer::Agent::Response
25
25
 
26
+ # --
27
+ # : (Riffer::Agent, Enumerator::Yielder?) -> Riffer::Agent::Response
28
+ def execute_run: (Riffer::Agent, Enumerator::Yielder?) -> Riffer::Agent::Response
29
+
26
30
  # --
27
31
  # : (Riffer::Agent, Enumerator::Yielder) -> Riffer::Messages::Assistant
28
32
  def accumulate_streamed_response: (Riffer::Agent, Enumerator::Yielder) -> Riffer::Messages::Assistant
@@ -32,8 +36,8 @@ module Riffer::Agent::Run
32
36
  def record_modifications!: (Enumerator::Yielder?, Array[Riffer::Guardrails::Modification], Array[Riffer::Guardrails::Modification]) -> void
33
37
 
34
38
  # --
35
- # : (Riffer::Agent, Enumerator::Yielder?, Riffer::Guardrails::Tripwire, Array[Riffer::Guardrails::Modification]) -> Riffer::Agent::Response
36
- def tripwire_response: (Riffer::Agent, Enumerator::Yielder?, Riffer::Guardrails::Tripwire, Array[Riffer::Guardrails::Modification]) -> Riffer::Agent::Response
39
+ # : (Riffer::Agent, Enumerator::Yielder?, Riffer::Guardrails::Tripwire, Array[Riffer::Guardrails::Modification], ?token_usage: Riffer::Providers::TokenUsage?, ?steps: Integer) -> Riffer::Agent::Response
40
+ def tripwire_response: (Riffer::Agent, Enumerator::Yielder?, Riffer::Guardrails::Tripwire, Array[Riffer::Guardrails::Modification], ?token_usage: Riffer::Providers::TokenUsage?, ?steps: Integer) -> Riffer::Agent::Response
37
41
 
38
42
  # --
39
43
  # : (Riffer::Agent, Array[Riffer::Guardrails::Modification], **untyped) -> Riffer::Agent::Response
@@ -60,12 +64,12 @@ module Riffer::Agent::Run
60
64
  def execute_pending_tool_calls: (Riffer::Agent) -> void
61
65
 
62
66
  # --
63
- # : (Riffer::Agent, Enumerator::Yielder?, Array[Riffer::Guardrails::Modification]) { (Riffer::Agent::Response) -> void } -> void
64
- def run_before_guardrails: (Riffer::Agent, Enumerator::Yielder?, Array[Riffer::Guardrails::Modification]) { (Riffer::Agent::Response) -> void } -> void
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
65
69
 
66
70
  # --
67
- # : (Riffer::Agent, Riffer::Messages::Assistant, Enumerator::Yielder?, Array[Riffer::Guardrails::Modification]) { (Riffer::Agent::Response) -> void } -> untyped
68
- def run_after_guardrails: (Riffer::Agent, Riffer::Messages::Assistant, Enumerator::Yielder?, Array[Riffer::Guardrails::Modification]) { (Riffer::Agent::Response) -> void } -> untyped
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
69
73
 
70
74
  # --
71
75
  # : (Riffer::Agent, Riffer::Messages::Assistant?) -> Hash[Symbol, untyped]?
@@ -80,8 +84,8 @@ module Riffer::Agent::Run
80
84
  def merged_model_options: (Riffer::Agent) -> Hash[Symbol, untyped]
81
85
 
82
86
  # --
83
- # : (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]) -> Riffer::Agent::Response
84
- def build_response: (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]) -> Riffer::Agent::Response
87
+ # : (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
88
+ def build_response: (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
85
89
 
86
90
  # Raises when +files+ are supplied without a +prompt+ — the provider needs
87
91
  # text to anchor the attachments.
@@ -92,4 +96,20 @@ module Riffer::Agent::Run
92
96
  # --
93
97
  # : (Riffer::Agent, Riffer::Providers::TokenUsage?) -> void
94
98
  def track_token_usage: (Riffer::Agent, Riffer::Providers::TokenUsage?) -> void
99
+
100
+ # --
101
+ # : (Riffer::Providers::TokenUsage?, Riffer::Providers::TokenUsage?) -> Riffer::Providers::TokenUsage?
102
+ def sum_usage: (Riffer::Providers::TokenUsage?, Riffer::Providers::TokenUsage?) -> Riffer::Providers::TokenUsage?
103
+
104
+ # --
105
+ # : (Riffer::Agent) -> Hash[String, untyped]
106
+ def run_span_attributes: (Riffer::Agent) -> Hash[String, untyped]
107
+
108
+ # --
109
+ # : (Riffer::Agent, String?) -> Hash[String, untyped]
110
+ def run_metric_attributes: (Riffer::Agent, String?) -> Hash[String, untyped]
111
+
112
+ # --
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
95
115
  end
@@ -7,15 +7,15 @@ class Riffer::Config
7
7
 
8
8
  attr_accessor region(): untyped
9
9
 
10
- def self.new: (?api_token: untyped, ?region: untyped) -> instance
11
- | ({ ?api_token: untyped, ?region: untyped }) -> instance
10
+ def self.new: (?untyped api_token, ?untyped region) -> instance
11
+ | (?api_token: untyped, ?region: untyped) -> instance
12
12
  end
13
13
 
14
14
  class Anthropic < Struct[untyped]
15
15
  attr_accessor api_key(): untyped
16
16
 
17
- def self.new: (?api_key: untyped) -> instance
18
- | ({ ?api_key: untyped }) -> instance
17
+ def self.new: (?untyped api_key) -> instance
18
+ | (?api_key: untyped) -> instance
19
19
  end
20
20
 
21
21
  class AzureOpenAI < Struct[untyped]
@@ -23,8 +23,8 @@ class Riffer::Config
23
23
 
24
24
  attr_accessor endpoint(): untyped
25
25
 
26
- def self.new: (?api_key: untyped, ?endpoint: untyped) -> instance
27
- | ({ ?api_key: untyped, ?endpoint: untyped }) -> instance
26
+ def self.new: (?untyped api_key, ?untyped endpoint) -> instance
27
+ | (?api_key: untyped, ?endpoint: untyped) -> instance
28
28
  end
29
29
 
30
30
  class Gemini < Struct[untyped]
@@ -34,29 +34,29 @@ class Riffer::Config
34
34
 
35
35
  attr_accessor read_timeout(): untyped
36
36
 
37
- def self.new: (?api_key: untyped, ?open_timeout: untyped, ?read_timeout: untyped) -> instance
38
- | ({ ?api_key: untyped, ?open_timeout: untyped, ?read_timeout: untyped }) -> instance
37
+ def self.new: (?untyped api_key, ?untyped open_timeout, ?untyped read_timeout) -> instance
38
+ | (?api_key: untyped, ?open_timeout: untyped, ?read_timeout: untyped) -> instance
39
39
  end
40
40
 
41
41
  class OpenAI < Struct[untyped]
42
42
  attr_accessor api_key(): untyped
43
43
 
44
- def self.new: (?api_key: untyped) -> instance
45
- | ({ ?api_key: untyped }) -> instance
44
+ def self.new: (?untyped api_key) -> instance
45
+ | (?api_key: untyped) -> instance
46
46
  end
47
47
 
48
48
  class OpenRouter < Struct[untyped]
49
49
  attr_accessor api_key(): untyped
50
50
 
51
- def self.new: (?api_key: untyped) -> instance
52
- | ({ ?api_key: untyped }) -> instance
51
+ def self.new: (?untyped api_key) -> instance
52
+ | (?api_key: untyped) -> instance
53
53
  end
54
54
 
55
55
  class Evals < Struct[untyped]
56
56
  attr_accessor judge_model(): untyped
57
57
 
58
- def self.new: (?judge_model: untyped) -> instance
59
- | ({ ?judge_model: untyped }) -> instance
58
+ def self.new: (?untyped judge_model) -> instance
59
+ | (?judge_model: untyped) -> instance
60
60
  end
61
61
 
62
62
  class Mcp < Struct[untyped]
@@ -64,8 +64,8 @@ class Riffer::Config
64
64
 
65
65
  attr_accessor discovery_runner(): untyped
66
66
 
67
- def self.new: (?credentials: untyped, ?discovery_runner: untyped) -> instance
68
- | ({ ?credentials: untyped, ?discovery_runner: untyped }) -> instance
67
+ def self.new: (?untyped credentials, ?untyped discovery_runner) -> instance
68
+ | (?credentials: untyped, ?discovery_runner: untyped) -> instance
69
69
  end
70
70
 
71
71
  # Skills-related global configuration.
@@ -95,6 +95,143 @@ class Riffer::Config
95
95
  def default_backend=: ((Riffer::Skills::Backend | Proc)?) -> void
96
96
  end
97
97
 
98
+ # Tracing-related global configuration.
99
+ class Tracing
100
+ # Whether riffer emits OTEL spans; defaults to +true+, a no-op until a
101
+ # host wires an OTEL SDK.
102
+ attr_reader enabled: bool
103
+
104
+ # Whether LLM-call spans capture full message content
105
+ # (<tt>gen_ai.input.messages</tt>, <tt>gen_ai.output.messages</tt>,
106
+ # <tt>gen_ai.system_instructions</tt>); defaults to +false+ — message
107
+ # content routinely carries sensitive data.
108
+ attr_reader capture_messages: bool
109
+
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
+
114
+ # --
115
+ # : () -> void
116
+ def initialize: () -> void
117
+
118
+ # Sets the enabled flag, coercing boolean-ish values so an env-var
119
+ # +"false"+ (truthy in Ruby) doesn't silently keep tracing on. Raises
120
+ # Riffer::ArgumentError on an unrecognized value.
121
+ # --
122
+ # : (untyped) -> void
123
+ def enabled=: (untyped) -> void
124
+
125
+ # Sets the capture_messages flag, coercing boolean-ish values so an
126
+ # env-var +"false"+ (truthy in Ruby) doesn't silently enable content
127
+ # capture. Raises Riffer::ArgumentError on an unrecognized value.
128
+ # --
129
+ # : (untyped) -> void
130
+ def capture_messages=: (untyped) -> void
131
+
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
+ # --
136
+ # : (untyped) -> void
137
+ def backend=: (untyped) -> void
138
+ end
139
+
140
+ # Metrics-related global configuration, independent of +config.tracing+ so a
141
+ # host can run one signal without the other.
142
+ class Metrics
143
+ # Whether riffer records OTEL metric instruments; defaults to +true+, a
144
+ # no-op until a host wires an OTEL metrics SDK.
145
+ attr_reader enabled: bool
146
+
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
+
151
+ # --
152
+ # : () -> void
153
+ def initialize: () -> void
154
+
155
+ # Sets the enabled flag, coercing boolean-ish values so an env-var
156
+ # +"false"+ (truthy in Ruby) doesn't silently keep metrics on. Raises
157
+ # Riffer::ArgumentError on an unrecognized value.
158
+ # --
159
+ # : (untyped) -> void
160
+ def enabled=: (untyped) -> void
161
+
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
+ # --
166
+ # : (untyped) -> void
167
+ def backend=: (untyped) -> void
168
+ end
169
+
170
+ # Consumer-configured token pricing, keyed by +provider/model+ id. Riffer
171
+ # ships no price table, so an unconfigured model carries no cost.
172
+ class Pricing
173
+ @rates: Hash[String, Riffer::Config::Pricing::Rates]
174
+
175
+ # Per-million-token rates for one model's four token buckets. +cache_read+
176
+ # and +cache_write+ fall back to the +input+ rate when unset.
177
+ class Rates
178
+ # Input rate per million tokens.
179
+ attr_reader input: Float
180
+
181
+ # Output rate per million tokens.
182
+ attr_reader output: Float
183
+
184
+ # Cache-read rate per million tokens.
185
+ attr_reader cache_read: Float?
186
+
187
+ # Cache-write rate per million tokens.
188
+ attr_reader cache_write: Float?
189
+
190
+ # --
191
+ # : (input: Float, output: Float, ?cache_read: Float?, ?cache_write: Float?) -> void
192
+ def initialize: (input: Float, output: Float, ?cache_read: Float?, ?cache_write: Float?) -> void
193
+
194
+ # Returns the cost for the given token counts.
195
+ # --
196
+ # : (input_tokens: Integer, output_tokens: Integer, ?cache_read_tokens: Integer?, ?cache_write_tokens: Integer?) -> Float
197
+ def cost_for: (input_tokens: Integer, output_tokens: Integer, ?cache_read_tokens: Integer?, ?cache_write_tokens: Integer?) -> Float
198
+ end
199
+
200
+ # --
201
+ # : () -> void
202
+ def initialize: () -> void
203
+
204
+ # Registers per-million-token rates for a +provider/model+ id/s. Raises
205
+ # Riffer::ArgumentError on a malformed id or a negative/non-numeric rate.
206
+ # --
207
+ # : ((String | Array[String]), input: Numeric, output: Numeric, ?cache_read: Numeric?, ?cache_write: Numeric?) -> void
208
+ def set: (String | Array[String], input: Numeric, output: Numeric, ?cache_read: Numeric?, ?cache_write: Numeric?) -> void
209
+
210
+ # Returns the rates registered for a +provider/model+ id.
211
+ # --
212
+ # : (String) -> Riffer::Config::Pricing::Rates?
213
+ def rates_for: (String) -> Riffer::Config::Pricing::Rates?
214
+
215
+ # Returns true when no rates are registered.
216
+ # --
217
+ # : () -> bool
218
+ def empty?: () -> bool
219
+
220
+ private
221
+
222
+ # --
223
+ # : (String) -> void
224
+ def validate_model!: (String) -> void
225
+
226
+ # --
227
+ # : (untyped, String) -> Float
228
+ def coerce_rate: (untyped, String) -> Float
229
+
230
+ # --
231
+ # : (untyped, String) -> Float?
232
+ def coerce_optional_rate: (untyped, String) -> Float?
233
+ end
234
+
98
235
  VALID_MESSAGE_ID_STRATEGIES: untyped
99
236
 
100
237
  # Amazon Bedrock configuration.
@@ -136,6 +273,15 @@ class Riffer::Config
136
273
  # Skills-related global configuration.
137
274
  attr_reader skills: Riffer::Config::Skills
138
275
 
276
+ # Tracing-related global configuration.
277
+ attr_reader tracing: Riffer::Config::Tracing
278
+
279
+ # Metrics-related global configuration.
280
+ attr_reader metrics: Riffer::Config::Metrics
281
+
282
+ # Consumer-configured per-model token pricing.
283
+ attr_reader pricing: Riffer::Config::Pricing
284
+
139
285
  # Strategy for auto-generating message ids: +:none+ (default), +:uuid+, or
140
286
  # +:uuidv7+. When not +:none+, messages get an +id+ at construction, and
141
287
  # seeded messages passed to +Riffer::Agent#generate+ must carry their own.
@@ -26,6 +26,12 @@ class Riffer::Guardrail
26
26
  # : (Riffer::Messages::Assistant, messages: Array[Riffer::Messages::Base], context: untyped) -> Riffer::Guardrails::Result
27
27
  def process_output: (Riffer::Messages::Assistant, messages: Array[Riffer::Messages::Base], context: untyped) -> Riffer::Guardrails::Result
28
28
 
29
+ # Returns the guardrail's identifier, used as the tracing span suffix and the
30
+ # <tt>riffer.guardrail.name</tt> attribute; override to label the span.
31
+ # --
32
+ # : () -> String
33
+ def name: () -> String
34
+
29
35
  # Creates a pass result that continues with unchanged data.
30
36
  # --
31
37
  # : (untyped) -> Riffer::Guardrails::Result
@@ -36,4 +36,26 @@ class Riffer::Guardrails::Runner
36
36
  # --
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
+
40
+ # --
41
+ # : (Riffer::Guardrail) { () -> Riffer::Guardrails::Result } -> Riffer::Guardrails::Result
42
+ def instrument_guardrail: (Riffer::Guardrail) { () -> Riffer::Guardrails::Result } -> Riffer::Guardrails::Result
43
+
44
+ # --
45
+ # : (Riffer::Guardrail, untyped, messages: Array[Riffer::Messages::Base]?) -> Riffer::Guardrails::Result
46
+ def run_guardrail_phase: (Riffer::Guardrail, untyped, messages: Array[Riffer::Messages::Base]?) -> Riffer::Guardrails::Result
47
+
48
+ # --
49
+ # : (Riffer::Guardrail) -> Hash[String, untyped]
50
+ def guardrail_span_attributes: (Riffer::Guardrail) -> Hash[String, untyped]
51
+
52
+ # --
53
+ # : (Riffer::Guardrail, String?) -> Hash[String, untyped]
54
+ def guardrail_metric_attributes: (Riffer::Guardrail, String?) -> Hash[String, untyped]
55
+
56
+ # A block is a handled outcome, so its span status stays unset — an error
57
+ # span status is reserved for a raised exception.
58
+ # --
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
39
61
  end
@@ -0,0 +1,11 @@
1
+ # Generated from lib/riffer/helpers/boolean.rb with RBS::Inline
2
+
3
+ # Coercion for boolean-ish configuration values.
4
+ module Riffer::Helpers::Boolean
5
+ # Coerces +value+ to a boolean so an env-var +"false"+ (truthy in Ruby)
6
+ # doesn't silently read as +true+. Raises Riffer::ArgumentError on an
7
+ # unrecognized value, naming +attribute+ in the message.
8
+ # --
9
+ # : (untyped, attribute: String) -> bool
10
+ def coerce: (untyped, attribute: String) -> bool
11
+ end
@@ -1,18 +1,16 @@
1
1
  # Generated from lib/riffer/mcp/authenticated_tool.rb with RBS::Inline
2
2
 
3
3
  # Wraps MCP-generated tool classes so +tools/call+ resolves
4
- # +Riffer.config.mcp.credentials+ per invocation, delegating metadata to the
5
- # inner class.
4
+ # +Riffer.config.mcp.credentials+ per invocation, copying metadata from the
5
+ # inner class at wrap time.
6
6
  module Riffer::Mcp::AuthenticatedTool
7
7
  # Returns one wrapper class per inner tool, sharing +manifest+ and +matched_tags+.
8
8
  #
9
9
  # --
10
- # : (Array[singleton(Riffer::Tool)], Riffer::Mcp::Manifest, Array[Symbol]) -> Array[singleton(Riffer::Tool)]
11
- def wrap_all: (Array[singleton(Riffer::Tool)], Riffer::Mcp::Manifest, Array[Symbol]) -> Array[singleton(Riffer::Tool)]
10
+ # : (Array[singleton(Riffer::Mcp::Tool)], Riffer::Mcp::Manifest, Array[Symbol]) -> Array[singleton(Riffer::Mcp::Tool)]
11
+ def wrap_all: (Array[singleton(Riffer::Mcp::Tool)], Riffer::Mcp::Manifest, Array[Symbol]) -> Array[singleton(Riffer::Mcp::Tool)]
12
12
 
13
13
  # --
14
- # : (singleton(Riffer::Tool), Riffer::Mcp::Manifest, Array[Symbol]) -> singleton(Riffer::Tool)
15
- # Class.new(Riffer::Tool) is typed as ::Class by steep — it cannot verify the subtype
16
- # relationship for dynamically created anonymous classes, so the ignore is required.
17
- def wrap_one: (singleton(Riffer::Tool), Riffer::Mcp::Manifest, Array[Symbol]) -> singleton(Riffer::Tool)
14
+ # : (singleton(Riffer::Mcp::Tool), Riffer::Mcp::Manifest, Array[Symbol]) -> singleton(Riffer::Mcp::Tool)
15
+ def wrap_one: (singleton(Riffer::Mcp::Tool), Riffer::Mcp::Manifest, Array[Symbol]) -> singleton(Riffer::Mcp::Tool)
18
16
  end
@@ -5,18 +5,18 @@
5
5
  class Riffer::Mcp::Registration
6
6
  @cancelled: bool
7
7
 
8
- @tools: Array[singleton(Riffer::Tool)]
8
+ @tools: Array[singleton(Riffer::Mcp::Tool)]
9
9
 
10
10
  @mutex: Thread::Mutex
11
11
 
12
12
  # The manifest that describes this server.
13
13
  attr_reader manifest: Riffer::Mcp::Manifest
14
14
 
15
- # Generated Riffer::Tool subclasses.
15
+ # Generated Riffer::Mcp::Tool subclasses.
16
16
  #
17
17
  # --
18
- # : () -> Array[singleton(Riffer::Tool)]
19
- def tools: () -> Array[singleton(Riffer::Tool)]
18
+ # : () -> Array[singleton(Riffer::Mcp::Tool)]
19
+ def tools: () -> Array[singleton(Riffer::Mcp::Tool)]
20
20
 
21
21
  # --
22
22
  # : (Riffer::Mcp::Manifest) -> void
@@ -0,0 +1,19 @@
1
+ # Generated from lib/riffer/mcp/tool.rb with RBS::Inline
2
+
3
+ # Base class for MCP-generated tools.
4
+ class Riffer::Mcp::Tool < Riffer::Tool
5
+ self.@mcp_server_tool_name: String?
6
+
7
+ self.@input_schema: Hash[Symbol, untyped]?
8
+
9
+ # Returns the unprefixed tool name used for +tools/call+ on the MCP server.
10
+ # --
11
+ # : () -> String
12
+ def self.mcp_server_tool_name: () -> String
13
+
14
+ # Returns the server-published input schema, falling back to the params DSL.
15
+ # MCP schemas are server-defined, so +strict+ is not applied to them.
16
+ # --
17
+ # : (?strict: bool) -> Hash[Symbol, untyped]
18
+ def self.parameters_schema: (?strict: bool) -> Hash[Symbol, untyped]
19
+ end
@@ -1,20 +1,21 @@
1
1
  # Generated from lib/riffer/mcp/tool_factory.rb with RBS::Inline
2
2
 
3
- # Generates anonymous Riffer::Tool subclasses from MCP tool definitions.
3
+ # Generates anonymous Riffer::Mcp::Tool subclasses from MCP tool definitions.
4
4
  # Generated tools delegate +#call+ to the MCP client and skip Riffer's param
5
5
  # validation — the MCP server validates inputs.
6
6
  module Riffer::Mcp::ToolFactory
7
- # Builds one Riffer::Tool subclass per tool definition, prefixing names with
8
- # the manifest name to avoid cross-server collisions (e.g. +jira__search+);
9
- # the server-side name stays on +.mcp_server_tool_name+.
7
+ # Builds one Riffer::Mcp::Tool subclass per tool definition, prefixing names
8
+ # with the manifest name to avoid cross-server collisions (e.g.
9
+ # +jira__search+); the server-side name stays on +.mcp_server_tool_name+.
10
10
  # --
11
- # : (String, Riffer::Mcp::Client, Array[Hash[Symbol, untyped]]) -> Array[singleton(Riffer::Tool)]
12
- def build: (String, Riffer::Mcp::Client, Array[Hash[Symbol, untyped]]) -> Array[singleton(Riffer::Tool)]
11
+ # : (String, Riffer::Mcp::Client, Array[Hash[Symbol, untyped]]) -> Array[singleton(Riffer::Mcp::Tool)]
12
+ def build: (String, Riffer::Mcp::Client, Array[Hash[Symbol, untyped]]) -> Array[singleton(Riffer::Mcp::Tool)]
13
13
 
14
14
  private
15
15
 
16
16
  # : (String) -> String
17
17
  def sanitize_name_component: (String) -> String
18
18
 
19
- def build_tool_class: (untyped manifest_name, untyped client, untyped td) -> untyped
19
+ # : (String, Riffer::Mcp::Client, Hash[Symbol, untyped]) -> singleton(Riffer::Mcp::Tool)
20
+ def build_tool_class: (String, Riffer::Mcp::Client, Hash[Symbol, untyped]) -> singleton(Riffer::Mcp::Tool)
20
21
  end
@@ -10,8 +10,8 @@ class Riffer::Messages::Assistant < Riffer::Messages::Base
10
10
 
11
11
  attr_accessor arguments(): untyped
12
12
 
13
- def self.new: (?call_id: untyped, ?name: untyped, ?arguments: untyped) -> instance
14
- | ({ ?call_id: untyped, ?name: untyped, ?arguments: untyped }) -> instance
13
+ def self.new: (?untyped call_id, ?untyped name, ?untyped arguments) -> instance
14
+ | (?call_id: untyped, ?name: untyped, ?arguments: untyped) -> instance
15
15
  end
16
16
 
17
17
  # Array of tool calls requested by the assistant.
@@ -23,9 +23,15 @@ class Riffer::Messages::Assistant < Riffer::Messages::Base
23
23
  # Parsed structured output hash, or nil when not applicable.
24
24
  attr_reader structured_output: Hash[Symbol, untyped]?
25
25
 
26
+ # Normalized reason the provider finished this response, when reported (see
27
+ # <tt>Riffer::Providers::FinishReason::VALUES</tt>).
28
+ attr_reader finish_reason: Symbol?
29
+
30
+ # Raises Riffer::ArgumentError when +finish_reason+ is outside the
31
+ # normalized vocabulary.
26
32
  # --
27
- # : (String, ?id: String?, ?tool_calls: Array[Riffer::Messages::Assistant::ToolCall], ?token_usage: Riffer::Providers::TokenUsage?, ?structured_output: Hash[Symbol, untyped]?) -> void
28
- def initialize: (String, ?id: String?, ?tool_calls: Array[Riffer::Messages::Assistant::ToolCall], ?token_usage: Riffer::Providers::TokenUsage?, ?structured_output: Hash[Symbol, untyped]?) -> void
33
+ # : (String, ?id: String?, ?tool_calls: Array[Riffer::Messages::Assistant::ToolCall], ?token_usage: Riffer::Providers::TokenUsage?, ?structured_output: Hash[Symbol, untyped]?, ?finish_reason: Symbol?) -> void
34
+ def initialize: (String, ?id: String?, ?tool_calls: Array[Riffer::Messages::Assistant::ToolCall], ?token_usage: Riffer::Providers::TokenUsage?, ?structured_output: Hash[Symbol, untyped]?, ?finish_reason: Symbol?) -> void
29
35
 
30
36
  # --
31
37
  # : () -> Symbol
@@ -0,0 +1,15 @@
1
+ # Generated from lib/riffer/metrics/instruments.rb with RBS::Inline
2
+
3
+ # The catalog of metric instruments riffer records. Each handle is a constant
4
+ # that resolves its backend at record time, so it survives a meter-provider swap
5
+ # or a runtime +enabled+ flip.
6
+ module Riffer::Metrics::Instruments
7
+ # :nodoc: all
8
+ OPERATION_DURATION: Riffer::Metrics::Histogram
9
+
10
+ TOKEN_USAGE: Riffer::Metrics::Histogram
11
+
12
+ COST: Riffer::Metrics::Histogram
13
+
14
+ GUARDRAIL_DURATION: Riffer::Metrics::Histogram
15
+ end
@@ -0,0 +1,10 @@
1
+ # Generated from lib/riffer/metrics/no_op.rb with RBS::Inline
2
+
3
+ # No-op metrics backend, used when the OpenTelemetry metrics API is unavailable
4
+ # or metrics are disabled.
5
+ module Riffer::Metrics::NoOp
6
+ # Ignores the measurement; there is no meter without the OTEL metrics API.
7
+ # --
8
+ # : (String, Numeric, unit: String?, description: String?, attributes: Hash[String, untyped]?) -> void
9
+ def record_histogram: (String, Numeric, unit: String?, description: String?, attributes: Hash[String, untyped]?) -> void
10
+ end