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,48 @@
1
+ # Generated from lib/riffer/metrics/otel.rb with RBS::Inline
2
+
3
+ # OTEL-backed metrics backend. <tt>::OpenTelemetry</tt> constants appear only
4
+ # inside method bodies here, so the gem loads and eager-loads cleanly when the
5
+ # OpenTelemetry metrics API is absent.
6
+ class Riffer::Metrics::Otel
7
+ # :nodoc: all
8
+ SUPPORTED_API_VERSIONS: Gem::Requirement
9
+
10
+ # Builds a backend when the OpenTelemetry metrics API is loadable at a
11
+ # supported version; returns +nil+ so resolution falls back to NoOp.
12
+ # +provider+ defaults to the global <tt>OpenTelemetry.meter_provider</tt>.
13
+ # --
14
+ # : (?provider: untyped) -> Riffer::Metrics::Otel?
15
+ def self.build: (?provider: untyped) -> Riffer::Metrics::Otel?
16
+
17
+ # Whether the OpenTelemetry metrics API gem is loadable at a supported
18
+ # version.
19
+ # --
20
+ # : () -> bool
21
+ def self.available?: () -> bool
22
+
23
+ # Whether the given opentelemetry-metrics-api version is one riffer codes
24
+ # against. The gem is undeclared, so this guard is the only protection
25
+ # against an incompatible, still-pre-1.0 API.
26
+ # --
27
+ # : (Gem::Version) -> bool
28
+ def self.supported?: (Gem::Version) -> bool
29
+
30
+ # --
31
+ # : () -> Gem::Version?
32
+ private def self.api_version: () -> Gem::Version?
33
+
34
+ @meter: untyped
35
+
36
+ @instruments: Hash[String, untyped]
37
+
38
+ @mutex: Mutex
39
+
40
+ # --
41
+ # : (provider: untyped) -> void
42
+ def initialize: (provider: untyped) -> void
43
+
44
+ # Records a value onto the named histogram.
45
+ # --
46
+ # : (String, Numeric, unit: String?, description: String?, attributes: Hash[String, untyped]?) -> void
47
+ def record_histogram: (String, Numeric, unit: String?, description: String?, attributes: Hash[String, untyped]?) -> void
48
+ end
@@ -0,0 +1,72 @@
1
+ # Generated from lib/riffer/metrics.rb with RBS::Inline
2
+
3
+ # Internal metrics port — records OTEL metric instruments when the host bundles
4
+ # the OpenTelemetry metrics API and no-ops otherwise, so riffer never declares
5
+ # an OTEL dependency.
6
+ module Riffer::Metrics
7
+ @backend: untyped
8
+
9
+ MUTEX: Mutex
10
+
11
+ # The Ruby API cannot attach a schema URL to a meter, so the semconv pin
12
+ # lives here as the documented contract version.
13
+ SCHEMA_URL: String
14
+
15
+ # A handle to a named histogram, safe to hold as a constant: it defers backend
16
+ # resolution to record time, so it survives a meter-provider swap or a runtime
17
+ # +enabled+ flip.
18
+ class Histogram
19
+ @name: String
20
+
21
+ @unit: String?
22
+
23
+ @description: String?
24
+
25
+ # --
26
+ # : (String, ?unit: String?, ?description: String?) -> void
27
+ def initialize: (String, ?unit: String?, ?description: String?) -> void
28
+
29
+ # --
30
+ # : (Numeric, ?attributes: Hash[String, untyped]?) -> void
31
+ def record: (Numeric, ?attributes: Hash[String, untyped]?) -> void
32
+ end
33
+
34
+ # Returns a handle to the named histogram.
35
+ # --
36
+ # : (String, ?unit: String?, ?description: String?) -> Riffer::Metrics::Histogram
37
+ def create_histogram: (String, ?unit: String?, ?description: String?) -> Riffer::Metrics::Histogram
38
+
39
+ # Records a value onto the named histogram.
40
+ # --
41
+ # : (String, Numeric, ?unit: String?, ?description: String?, ?attributes: Hash[String, untyped]?) -> void
42
+ def record_histogram: (String, Numeric, ?unit: String?, ?description: String?, ?attributes: Hash[String, untyped]?) -> void
43
+
44
+ # Mirrors a span's +recording?+ so a caller can skip work that exists only to
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.
47
+ # --
48
+ # : () -> bool
49
+ def recording?: () -> bool
50
+
51
+ # Reads the monotonic clock in seconds — the time source for duration metrics,
52
+ # immune to wall-clock adjustments.
53
+ # --
54
+ # : () -> Float
55
+ def monotonic_now: () -> Float
56
+
57
+ # Discards the resolved backend so the next record re-resolves it; cached
58
+ # instruments live on that backend, so this clears them too.
59
+ # --
60
+ # : () -> void
61
+ def reset!: () -> void
62
+
63
+ private
64
+
65
+ # --
66
+ # : () -> untyped
67
+ def backend: () -> untyped
68
+
69
+ # --
70
+ # : () -> untyped
71
+ def resolve_backend: () -> untyped
72
+ end
@@ -7,12 +7,19 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
7
7
  # cross-region (+us.anthropic.claude-...+) ids.
8
8
  ANTHROPIC_MODEL_PATTERN: Regexp
9
9
 
10
+ FINISH_REASONS: Hash[String, Symbol]
11
+
10
12
  # Returns the skill adapter for the Bedrock model — XML for Anthropic models
11
13
  # (which Bedrock hosts alongside other vendors'), else Markdown.
12
14
  # --
13
15
  # : (?String?) -> singleton(Riffer::Skills::Adapter)
14
16
  def self.skills_adapter: (?String?) -> singleton(Riffer::Skills::Adapter)
15
17
 
18
+ # The GenAI semconv well-known provider name.
19
+ # --
20
+ # : () -> String
21
+ def self.semconv_provider_name: () -> String
22
+
16
23
  # --
17
24
  # : (?api_token: String?, ?region: String?, **untyped) -> void
18
25
  def initialize: (?api_token: String?, ?region: String?, **untyped) -> void
@@ -42,6 +49,20 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
42
49
  # : (untyped) -> Riffer::Providers::TokenUsage?
43
50
  def extract_token_usage: (untyped) -> Riffer::Providers::TokenUsage?
44
51
 
52
+ # Converse's +input_tokens+ excludes the cache buckets; TokenUsage's
53
+ # input includes them.
54
+ # --
55
+ # : (untyped) -> Riffer::Providers::TokenUsage
56
+ def build_token_usage: (untyped) -> Riffer::Providers::TokenUsage
57
+
58
+ # --
59
+ # : (untyped) -> Riffer::Providers::FinishReason?
60
+ def extract_finish_reason: (untyped) -> Riffer::Providers::FinishReason?
61
+
62
+ # --
63
+ # : (untyped) -> Riffer::Providers::FinishReason?
64
+ def build_finish_reason: (untyped) -> Riffer::Providers::FinishReason?
65
+
45
66
  # --
46
67
  # : (untyped) -> String
47
68
  def extract_content: (untyped) -> String
@@ -51,8 +72,8 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
51
72
  def extract_tool_calls: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
52
73
 
53
74
  # --
54
- # : (Hash[Symbol, untyped], Enumerator::Yielder) -> void
55
- def execute_stream: (Hash[Symbol, untyped], Enumerator::Yielder) -> void
75
+ # : (Hash[Symbol, untyped], Riffer::Providers::_EventSink) -> void
76
+ def execute_stream: (Hash[Symbol, untyped], Riffer::Providers::_EventSink) -> void
56
77
 
57
78
  # Re-raises a Bedrock stream-exception event as the matching
58
79
  # +Aws::BedrockRuntime::Errors+ class. ConverseStream delivers API errors on
@@ -63,28 +84,28 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
63
84
  def raise_if_stream_exception!: (untyped) -> void
64
85
 
65
86
  # --
66
- # : (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
67
- def handle_content_block_start_tool_use: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
87
+ # : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
88
+ def handle_content_block_start_tool_use: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
68
89
 
69
90
  # --
70
- # : (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
71
- def handle_content_block_delta_text_delta: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
91
+ # : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
92
+ def handle_content_block_delta_text_delta: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
72
93
 
73
94
  # --
74
- # : (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
75
- def handle_content_block_delta_tool_use: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
95
+ # : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
96
+ def handle_content_block_delta_tool_use: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
76
97
 
77
98
  # --
78
- # : (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
79
- def handle_content_block_stop_text_delta: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
99
+ # : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
100
+ def handle_content_block_stop_text_delta: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
80
101
 
81
102
  # --
82
- # : (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
83
- def handle_content_block_stop_tool_use: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
103
+ # : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
104
+ def handle_content_block_stop_tool_use: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
84
105
 
85
106
  # --
86
- # : (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
87
- def handle_metadata_usage: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
107
+ # : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
108
+ def handle_metadata_usage: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
88
109
 
89
110
  # --
90
111
  # : (Array[Riffer::Messages::Base]) -> Hash[Symbol, untyped]
@@ -5,12 +5,19 @@
5
5
  class Riffer::Providers::Anthropic < Riffer::Providers::Base
6
6
  WEB_SEARCH_TOOL_TYPE: String
7
7
 
8
+ FINISH_REASONS: Hash[String, Symbol]
9
+
8
10
  # Returns the XML skill adapter for Anthropic/Claude.
9
11
  #
10
12
  # --
11
13
  # : (?String?) -> singleton(Riffer::Skills::Adapter)
12
14
  def self.skills_adapter: (?String?) -> singleton(Riffer::Skills::Adapter)
13
15
 
16
+ # The GenAI semconv well-known provider name.
17
+ # --
18
+ # : () -> String
19
+ def self.semconv_provider_name: () -> String
20
+
14
21
  # --
15
22
  # : (?api_key: String?, **untyped) -> void
16
23
  def initialize: (?api_key: String?, **untyped) -> void
@@ -29,6 +36,20 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
29
36
  # : (untyped) -> Riffer::Providers::TokenUsage?
30
37
  def extract_token_usage: (untyped) -> Riffer::Providers::TokenUsage?
31
38
 
39
+ # --
40
+ # : (untyped) -> Riffer::Providers::FinishReason?
41
+ def extract_finish_reason: (untyped) -> Riffer::Providers::FinishReason?
42
+
43
+ # --
44
+ # : (untyped) -> Riffer::Providers::FinishReason?
45
+ def build_finish_reason: (untyped) -> Riffer::Providers::FinishReason?
46
+
47
+ # Anthropic's +input_tokens+ excludes the cache buckets; TokenUsage's
48
+ # input includes them.
49
+ # --
50
+ # : (untyped) -> Riffer::Providers::TokenUsage
51
+ def build_token_usage: (untyped) -> Riffer::Providers::TokenUsage
52
+
32
53
  # --
33
54
  # : (untyped) -> String
34
55
  def extract_content: (untyped) -> String
@@ -38,8 +59,8 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
38
59
  def extract_tool_calls: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
39
60
 
40
61
  # --
41
- # : (Hash[Symbol, untyped], Enumerator::Yielder) -> void
42
- def execute_stream: (Hash[Symbol, untyped], Enumerator::Yielder) -> void
62
+ # : (Hash[Symbol, untyped], Riffer::Providers::_EventSink) -> void
63
+ def execute_stream: (Hash[Symbol, untyped], Riffer::Providers::_EventSink) -> void
43
64
 
44
65
  # --
45
66
  # : (untyped, state: Hash[Symbol, untyped]) -> void
@@ -50,40 +71,40 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
50
71
  def handle_raw_content_block_delta: (untyped, state: Hash[Symbol, untyped]) -> void
51
72
 
52
73
  # --
53
- # : (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
54
- def handle_text_event: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
74
+ # : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
75
+ def handle_text_event: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
55
76
 
56
77
  # --
57
- # : (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
58
- def handle_thinking_event: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
78
+ # : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
79
+ def handle_thinking_event: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
59
80
 
60
81
  # --
61
- # : (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
62
- def handle_input_json_event: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
82
+ # : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
83
+ def handle_input_json_event: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
63
84
 
64
85
  # --
65
- # : (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
66
- def handle_content_block_stop_tool_use: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
86
+ # : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
87
+ def handle_content_block_stop_tool_use: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
67
88
 
68
89
  # --
69
- # : (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
70
- def handle_content_block_stop_thinking: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
90
+ # : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
91
+ def handle_content_block_stop_thinking: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
71
92
 
72
93
  # --
73
- # : (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
74
- def handle_content_block_stop_text: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
94
+ # : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
95
+ def handle_content_block_stop_text: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
75
96
 
76
97
  # --
77
- # : (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
78
- def handle_content_block_stop_server_tool_use: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
98
+ # : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
99
+ def handle_content_block_stop_server_tool_use: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
79
100
 
80
101
  # --
81
- # : (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
82
- def handle_content_block_stop_web_search_result: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
102
+ # : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
103
+ def handle_content_block_stop_web_search_result: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
83
104
 
84
105
  # --
85
- # : (untyped, accumulated_message: untyped, yielder: Enumerator::Yielder) -> void
86
- def handle_message_stop: (untyped, accumulated_message: untyped, yielder: Enumerator::Yielder) -> void
106
+ # : (untyped, accumulated_message: untyped, yielder: Riffer::Providers::_EventSink) -> void
107
+ def handle_message_stop: (untyped, accumulated_message: untyped, yielder: Riffer::Providers::_EventSink) -> void
87
108
 
88
109
  # --
89
110
  # : (Array[Riffer::Messages::Base]) -> Hash[Symbol, untyped]
@@ -4,6 +4,11 @@
4
4
  # gem. Credentials resolve from kwargs, then config, then
5
5
  # +AZURE_OPENAI_API_KEY+ / +AZURE_OPENAI_ENDPOINT+.
6
6
  class Riffer::Providers::AzureOpenAI < Riffer::Providers::OpenAI
7
+ # The GenAI semconv well-known provider name.
8
+ # --
9
+ # : () -> String
10
+ def self.semconv_provider_name: () -> String
11
+
7
12
  # --
8
13
  # : (**untyped) -> void
9
14
  def initialize: (**untyped) -> void
@@ -5,6 +5,8 @@
5
5
  # +extract_token_usage+, +extract_content+, +extract_tool_calls+) and the base
6
6
  # class orchestrates them.
7
7
  class Riffer::Providers::Base
8
+ @current_model: String?
9
+
8
10
  @current_tools: Array[singleton(Riffer::Tool)]
9
11
 
10
12
  WIRE_SEPARATOR: String
@@ -15,6 +17,14 @@ class Riffer::Providers::Base
15
17
  # : (?String?) -> singleton(Riffer::Skills::Adapter)
16
18
  def self.skills_adapter: (?String?) -> singleton(Riffer::Skills::Adapter)
17
19
 
20
+ # Returns the provider name stamped as <tt>gen_ai.provider.name</tt> on trace
21
+ # spans, ideally a GenAI semconv well-known value. Defaults to the snake_cased
22
+ # class name rather than raising like the abstract provider methods, so
23
+ # enabling tracing never breaks an otherwise-working custom provider.
24
+ # --
25
+ # : () -> String
26
+ def self.semconv_provider_name: () -> String
27
+
18
28
  # Generates text using the provider.
19
29
  #
20
30
  # --
@@ -49,13 +59,26 @@ class Riffer::Providers::Base
49
59
  def execute_generate: (Hash[Symbol, untyped]) -> untyped
50
60
 
51
61
  # --
52
- # : (Hash[Symbol, untyped], Enumerator::Yielder) -> void
53
- def execute_stream: (Hash[Symbol, untyped], Enumerator::Yielder) -> void
62
+ # : (Hash[Symbol, untyped], Riffer::Providers::_EventSink) -> void
63
+ def execute_stream: (Hash[Symbol, untyped], Riffer::Providers::_EventSink) -> void
54
64
 
55
65
  # --
56
66
  # : (untyped) -> Riffer::Providers::TokenUsage?
57
67
  def extract_token_usage: (untyped) -> Riffer::Providers::TokenUsage?
58
68
 
69
+ # : (Riffer::Providers::TokenUsage) -> Riffer::Providers::TokenUsage
70
+ def apply_pricing: (Riffer::Providers::TokenUsage) -> Riffer::Providers::TokenUsage
71
+
72
+ # --
73
+ # : () -> Riffer::Config::Pricing::Rates?
74
+ def pricing_rates: () -> Riffer::Config::Pricing::Rates?
75
+
76
+ # Defaults to nil rather than raising — finish reasons are optional, so
77
+ # providers that don't report one stay valid.
78
+ # --
79
+ # : (untyped) -> Riffer::Providers::FinishReason?
80
+ def extract_finish_reason: (untyped) -> Riffer::Providers::FinishReason?
81
+
59
82
  # --
60
83
  # : (untyped) -> String
61
84
  def extract_content: (untyped) -> String
@@ -64,6 +87,59 @@ class Riffer::Providers::Base
64
87
  # : (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
65
88
  def extract_tool_calls: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
66
89
 
90
+ # A deliberate whitelist — caller options outside it stay off spans.
91
+ REQUEST_PARAM_ATTRIBUTES: Hash[Symbol, String]
92
+
93
+ # --
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
+
97
+ # --
98
+ # : (String?, Hash[Symbol, untyped]) -> Hash[String, untyped]
99
+ def chat_span_attributes: (String?, Hash[Symbol, untyped]) -> Hash[String, untyped]
100
+
101
+ # --
102
+ # : (String?) -> Hash[String, untyped]
103
+ def chat_metric_base_attributes: (String?) -> Hash[String, untyped]
104
+
105
+ # --
106
+ # : (String?, String?) -> Hash[String, untyped]
107
+ def chat_metric_attributes: (String?, String?) -> Hash[String, untyped]
108
+
109
+ # Per-call only — the run level would double-count an aggregate.
110
+ # --
111
+ # : (String?, Riffer::Providers::TokenUsage?) -> void
112
+ def record_token_usage_metric: (String?, Riffer::Providers::TokenUsage?) -> void
113
+
114
+ # Per-call only — the run level would double-count an aggregate.
115
+ # --
116
+ # : (String?, Riffer::Providers::TokenUsage?) -> void
117
+ def record_cost_metric: (String?, Riffer::Providers::TokenUsage?) -> void
118
+
119
+ # --
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
+
123
+ # --
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
+
127
+ # --
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
+
131
+ # --
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
+
135
+ # --
136
+ # : ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span)) -> bool
137
+ def capture_messages?: (Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span) -> bool
138
+
139
+ # --
140
+ # : (Riffer::Providers::_EventSink, Riffer::Providers::FinishReason?) -> void
141
+ def yield_finish_reason: (Riffer::Providers::_EventSink, Riffer::Providers::FinishReason?) -> void
142
+
67
143
  # --
68
144
  # : (String) -> Hash[Symbol, untyped]?
69
145
  def parse_structured_output: (String) -> Hash[Symbol, untyped]?
@@ -0,0 +1,19 @@
1
+ # Generated from lib/riffer/providers/finish_reason.rb with RBS::Inline
2
+
3
+ # Normalized reason an LLM call finished, paired with the provider's raw
4
+ # wire value. +reason+ carries the same meaning for every provider.
5
+ class Riffer::Providers::FinishReason
6
+ # The normalized vocabulary every provider maps into.
7
+ VALUES: Array[Symbol]
8
+
9
+ # The normalized reason.
10
+ attr_reader reason: Symbol
11
+
12
+ # The provider's raw finish-reason value, when one exists on the wire.
13
+ attr_reader raw: String?
14
+
15
+ # Raises Riffer::ArgumentError when +reason+ is outside VALUES.
16
+ # --
17
+ # : (reason: Symbol, ?raw: String?) -> void
18
+ def initialize: (reason: Symbol, ?raw: String?) -> void
19
+ end
@@ -16,6 +16,13 @@ class Riffer::Providers::Gemini < Riffer::Providers::Base
16
16
 
17
17
  DEFAULT_READ_TIMEOUT: Integer
18
18
 
19
+ FINISH_REASONS: Hash[String, Symbol]
20
+
21
+ # The GenAI semconv well-known provider name.
22
+ # --
23
+ # : () -> String
24
+ def self.semconv_provider_name: () -> String
25
+
19
26
  # --
20
27
  # : (?api_key: String?, ?open_timeout: Integer?, ?read_timeout: Integer?, **untyped) -> void
21
28
  def initialize: (?api_key: String?, ?open_timeout: Integer?, ?read_timeout: Integer?, **untyped) -> void
@@ -43,8 +50,24 @@ class Riffer::Providers::Gemini < Riffer::Providers::Base
43
50
  def extract_token_usage: (Hash[Symbol, untyped]) -> Riffer::Providers::TokenUsage?
44
51
 
45
52
  # --
46
- # : (Hash[Symbol, untyped], Enumerator::Yielder) -> void
47
- def execute_stream: (Hash[Symbol, untyped], Enumerator::Yielder) -> void
53
+ # : (Hash[Symbol, untyped]) -> Riffer::Providers::FinishReason?
54
+ def extract_finish_reason: (Hash[Symbol, untyped]) -> Riffer::Providers::FinishReason?
55
+
56
+ # Gemini reports STOP even when the candidate carries functionCall parts,
57
+ # so tool-call presence overrides the raw value.
58
+ # --
59
+ # : (String?, tool_calls: bool) -> Riffer::Providers::FinishReason?
60
+ def build_finish_reason: (String?, tool_calls: bool) -> Riffer::Providers::FinishReason?
61
+
62
+ # Gemini reports thinking tokens outside +candidatesTokenCount+;
63
+ # TokenUsage's output includes them.
64
+ # --
65
+ # : (Hash[Symbol, untyped]) -> Riffer::Providers::TokenUsage
66
+ def build_token_usage: (Hash[Symbol, untyped]) -> Riffer::Providers::TokenUsage
67
+
68
+ # --
69
+ # : (Hash[Symbol, untyped], Riffer::Providers::_EventSink) -> void
70
+ def execute_stream: (Hash[Symbol, untyped], Riffer::Providers::_EventSink) -> void
48
71
 
49
72
  # --
50
73
  # : (Array[Riffer::Messages::Base]) -> Hash[Symbol, untyped]
@@ -14,6 +14,11 @@ class Riffer::Providers::Mock < Riffer::Providers::Base
14
14
  # : (?String?) -> singleton(Riffer::Skills::Adapter)
15
15
  def self.skills_adapter: (?String?) -> singleton(Riffer::Skills::Adapter)
16
16
 
17
+ # The GenAI semconv well-known provider name.
18
+ # --
19
+ # : () -> String
20
+ def self.semconv_provider_name: () -> String
21
+
17
22
  # Array of recorded method calls for assertions.
18
23
  attr_reader calls: Array[Hash[Symbol, untyped]]
19
24
 
@@ -29,15 +34,17 @@ class Riffer::Providers::Mock < Riffer::Providers::Base
29
34
  # : (**untyped) -> void
30
35
  def initialize: (**untyped) -> void
31
36
 
32
- # Stubs the next response; call repeatedly to queue several.
37
+ # Stubs the next response; call repeatedly to queue several. +finish_reason+
38
+ # defaults to +:tool_calls+ when tool calls are present, else +:stop+.
33
39
  #
34
40
  # provider.stub_response("Hello")
35
41
  # provider.stub_response("", tool_calls: [{name: "my_tool", arguments: '{"key":"value"}'}])
36
42
  # provider.stub_response("Final response", token_usage: Riffer::Providers::TokenUsage.new(input_tokens: 10, output_tokens: 5))
43
+ # provider.stub_response("Truncated...", finish_reason: :length)
37
44
  #
38
45
  # --
39
- # : (String, ?tool_calls: Array[Hash[Symbol, untyped]], ?token_usage: Riffer::Providers::TokenUsage?) -> void
40
- def stub_response: (String, ?tool_calls: Array[Hash[Symbol, untyped]], ?token_usage: Riffer::Providers::TokenUsage?) -> void
46
+ # : (String, ?tool_calls: Array[Hash[Symbol, untyped]], ?token_usage: Riffer::Providers::TokenUsage?, ?finish_reason: Symbol?) -> void
47
+ def stub_response: (String, ?tool_calls: Array[Hash[Symbol, untyped]], ?token_usage: Riffer::Providers::TokenUsage?, ?finish_reason: Symbol?) -> void
41
48
 
42
49
  # Clears all stubbed responses.
43
50
  #
@@ -63,6 +70,10 @@ class Riffer::Providers::Mock < Riffer::Providers::Base
63
70
  # : (untyped) -> Riffer::Providers::TokenUsage?
64
71
  def extract_token_usage: (untyped) -> Riffer::Providers::TokenUsage?
65
72
 
73
+ # --
74
+ # : (untyped) -> Riffer::Providers::FinishReason?
75
+ def extract_finish_reason: (untyped) -> Riffer::Providers::FinishReason?
76
+
66
77
  # --
67
78
  # : (untyped) -> String
68
79
  def extract_content: (untyped) -> String
@@ -72,8 +83,8 @@ class Riffer::Providers::Mock < Riffer::Providers::Base
72
83
  def extract_tool_calls: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
73
84
 
74
85
  # --
75
- # : (Hash[Symbol, untyped], Enumerator::Yielder) -> void
76
- def execute_stream: (Hash[Symbol, untyped], Enumerator::Yielder) -> void
86
+ # : (Hash[Symbol, untyped], Riffer::Providers::_EventSink) -> void
87
+ def execute_stream: (Hash[Symbol, untyped], Riffer::Providers::_EventSink) -> void
77
88
 
78
89
  # --
79
90
  # : () -> Hash[Symbol, untyped]