riffer 0.47.1 → 0.48.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/.claude/rules/rbs-inline.md +5 -1
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +31 -0
- data/Rakefile +1 -1
- data/Steepfile +1 -15
- data/docs/AGENTS.md +37 -3
- data/docs/AGENT_LIFECYCLE.md +1 -0
- data/docs/CONFIGURATION.md +1 -1
- data/docs/EVALS.md +2 -1
- data/docs/MCP.md +0 -4
- data/docs/MESSAGES.md +85 -17
- data/docs/STREAM_EVENTS.md +31 -4
- data/docs/TRACING.md +1 -1
- data/docs/providers/AMAZON_BEDROCK.md +1 -1
- data/docs/providers/CUSTOM_PROVIDERS.md +58 -4
- data/docs/providers/GEMINI.md +1 -1
- data/docs/providers/MOCK_PROVIDER.md +17 -0
- data/lib/riffer/agent/config.rb +40 -16
- data/lib/riffer/agent/outcome.rb +2 -2
- data/lib/riffer/agent/response.rb +16 -9
- data/lib/riffer/agent/run.rb +24 -9
- data/lib/riffer/agent/session.rb +2 -1
- data/lib/riffer/agent/structured_output/result.rb +2 -2
- data/lib/riffer/agent/structured_output.rb +1 -1
- data/lib/riffer/agent.rb +40 -16
- data/lib/riffer/config.rb +31 -31
- data/lib/riffer/evals/evaluator.rb +29 -2
- data/lib/riffer/evals/judge.rb +13 -8
- data/lib/riffer/evals/result.rb +6 -6
- data/lib/riffer/evals/run_result.rb +1 -1
- data/lib/riffer/evals/scenario_result.rb +6 -6
- data/lib/riffer/files/resolver.rb +3 -3
- data/lib/riffer/guardrails/modification.rb +3 -3
- data/lib/riffer/guardrails/result.rb +3 -3
- data/lib/riffer/guardrails/runner.rb +4 -4
- data/lib/riffer/guardrails/tripwire.rb +4 -4
- data/lib/riffer/helpers/deep_dup.rb +42 -0
- data/lib/riffer/mcp/manifest.rb +5 -5
- data/lib/riffer/mcp/registration.rb +1 -1
- data/lib/riffer/mcp/search_tool.rb +1 -1
- data/lib/riffer/messages/assistant/reasoning_part.rb +90 -0
- data/lib/riffer/messages/assistant/tool_call.rb +63 -0
- data/lib/riffer/messages/assistant.rb +55 -7
- data/lib/riffer/messages/base.rb +7 -25
- data/lib/riffer/messages/system.rb +10 -0
- data/lib/riffer/messages/tool.rb +21 -4
- data/lib/riffer/messages/{file_part.rb → user/file_part.rb} +18 -17
- data/lib/riffer/messages/user.rb +13 -2
- data/lib/riffer/params/param.rb +20 -8
- data/lib/riffer/params.rb +10 -1
- data/lib/riffer/providers/amazon_bedrock.rb +34 -6
- data/lib/riffer/providers/anthropic.rb +9 -2
- data/lib/riffer/providers/base.rb +28 -10
- data/lib/riffer/providers/finish_reason.rb +2 -2
- data/lib/riffer/providers/gemini.rb +2 -2
- data/lib/riffer/providers/mock.rb +19 -3
- data/lib/riffer/providers/open_ai.rb +14 -5
- data/lib/riffer/providers/open_router.rb +6 -5
- data/lib/riffer/providers/repository.rb +0 -4
- data/lib/riffer/providers/token_usage.rb +21 -5
- data/lib/riffer/skills/adapter.rb +1 -1
- data/lib/riffer/skills/config.rb +12 -0
- data/lib/riffer/skills/context.rb +4 -7
- data/lib/riffer/skills/filesystem_backend.rb +1 -2
- data/lib/riffer/skills/frontmatter.rb +4 -4
- data/lib/riffer/skills/xml_adapter.rb +1 -1
- data/lib/riffer/stream_events/base.rb +1 -1
- data/lib/riffer/stream_events/finish_reason_done.rb +2 -2
- data/lib/riffer/stream_events/guardrail_modification.rb +1 -1
- data/lib/riffer/stream_events/guardrail_tripwire.rb +1 -1
- data/lib/riffer/stream_events/interrupt.rb +2 -2
- data/lib/riffer/stream_events/reasoning_delta.rb +1 -1
- data/lib/riffer/stream_events/reasoning_done.rb +10 -8
- data/lib/riffer/stream_events/skill_activation.rb +1 -1
- data/lib/riffer/stream_events/text_delta.rb +1 -1
- data/lib/riffer/stream_events/text_done.rb +1 -1
- data/lib/riffer/stream_events/token_usage_done.rb +1 -1
- data/lib/riffer/stream_events/tool_call_delta.rb +3 -3
- data/lib/riffer/stream_events/tool_call_done.rb +4 -4
- data/lib/riffer/stream_events/web_search_done.rb +2 -2
- data/lib/riffer/stream_events/web_search_status.rb +3 -3
- data/lib/riffer/tools/response.rb +4 -4
- data/lib/riffer/tools/runtime.rb +3 -4
- data/lib/riffer/tracing/capture.rb +2 -4
- data/lib/riffer/tracing/stream_recorder.rb +6 -6
- data/lib/riffer/version.rb +1 -1
- data/lib/riffer.rb +7 -0
- data/rbs_collection.lock.yaml +320 -0
- data/rbs_collection.yaml +12 -0
- data/sig/_private/anthropic.rbs +5 -4
- data/sig/_private/aws-sdk-core/event_error.rbs +15 -0
- data/sig/generated/riffer/agent/config.rbs +19 -5
- data/sig/generated/riffer/agent/response.rbs +6 -1
- data/sig/generated/riffer/agent/run.rbs +15 -8
- data/sig/generated/riffer/agent.rbs +25 -9
- data/sig/generated/riffer/config.rbs +1 -1
- data/sig/generated/riffer/evals/evaluator.rbs +20 -1
- data/sig/generated/riffer/evals/judge.rbs +5 -2
- data/sig/generated/riffer/files/resolver.rbs +6 -6
- data/sig/generated/riffer/helpers/deep_dup.rbs +21 -0
- data/sig/generated/riffer/messages/assistant/reasoning_part.rbs +58 -0
- data/sig/generated/riffer/messages/assistant/tool_call.rbs +42 -0
- data/sig/generated/riffer/messages/assistant.rbs +24 -10
- data/sig/generated/riffer/messages/system.rbs +6 -0
- data/sig/generated/riffer/messages/tool.rbs +6 -0
- data/sig/generated/riffer/messages/{file_part.rbs → user/file_part.rbs} +11 -6
- data/sig/generated/riffer/messages/user.rbs +9 -3
- data/sig/generated/riffer/params/param.rbs +7 -0
- data/sig/generated/riffer/params.rbs +6 -0
- data/sig/generated/riffer/providers/amazon_bedrock.rbs +14 -5
- data/sig/generated/riffer/providers/anthropic.rbs +2 -2
- data/sig/generated/riffer/providers/base.rbs +22 -10
- data/sig/generated/riffer/providers/gemini.rbs +4 -4
- data/sig/generated/riffer/providers/mock.rbs +7 -2
- data/sig/generated/riffer/providers/open_ai.rbs +4 -4
- data/sig/generated/riffer/providers/open_router.rbs +4 -4
- data/sig/generated/riffer/providers/repository.rbs +0 -2
- data/sig/generated/riffer/providers/token_usage.rbs +6 -0
- data/sig/generated/riffer/skills/config.rbs +9 -0
- data/sig/generated/riffer/stream_events/reasoning_done.rbs +8 -6
- data/sig/generated/riffer/tools/runtime.rbs +2 -2
- data/sig/generated/riffer/tracing/capture.rbs +4 -4
- data/sig/generated/riffer.rbs +8 -0
- data/sig/manual/riffer/helpers/deep_dup.rbs +5 -0
- metadata +13 -5
- data/sig/_private/async.rbs +0 -28
- data/sig/_private/minitest.rbs +0 -9
|
@@ -35,7 +35,7 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
#--
|
|
38
|
-
#: (Riffer::Messages::FilePart) -> Symbol
|
|
38
|
+
#: (Riffer::Messages::User::FilePart) -> Symbol
|
|
39
39
|
def file_delivery(file)
|
|
40
40
|
file.image? ? :url : :base64
|
|
41
41
|
end
|
|
@@ -80,7 +80,8 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
80
80
|
} #: Hash[Symbol, untyped]
|
|
81
81
|
|
|
82
82
|
unless tags.empty?
|
|
83
|
-
|
|
83
|
+
# Merged over any metadata set in model_options; a tag wins on a shared key.
|
|
84
|
+
params[:metadata] = (params[:metadata] || {}).merge(tags)
|
|
84
85
|
# The reserved user_id also maps to the native safety identifier while
|
|
85
86
|
# staying in metadata as an ordinary tag.
|
|
86
87
|
user_id = tags["user_id"]
|
|
@@ -218,6 +219,8 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
218
219
|
tool_info: {},
|
|
219
220
|
} #: Hash[Symbol, untyped]
|
|
220
221
|
|
|
222
|
+
stream_completed = false
|
|
223
|
+
|
|
221
224
|
stream = client.responses.stream(params)
|
|
222
225
|
begin
|
|
223
226
|
stream.each do |event|
|
|
@@ -247,6 +250,7 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
247
250
|
when :"response.output_item.done"
|
|
248
251
|
handle_output_item_done_web_search(event, yielder: yielder) if event.item&.type == :web_search_call
|
|
249
252
|
when :"response.completed", :"response.incomplete", :"response.failed"
|
|
253
|
+
stream_completed = true
|
|
250
254
|
handle_response_finished(event, state: current_state, yielder: yielder)
|
|
251
255
|
end
|
|
252
256
|
end
|
|
@@ -256,6 +260,11 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
256
260
|
# socket leaks until GC. close is idempotent and a no-op after EOF.
|
|
257
261
|
stream.close
|
|
258
262
|
end
|
|
263
|
+
|
|
264
|
+
return if stream_completed
|
|
265
|
+
|
|
266
|
+
raise Riffer::IncompleteStreamError,
|
|
267
|
+
"OpenAI Responses stream ended without a response.completed, response.incomplete, or response.failed event"
|
|
259
268
|
end
|
|
260
269
|
|
|
261
270
|
#--
|
|
@@ -288,7 +297,7 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
288
297
|
#--
|
|
289
298
|
#: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
290
299
|
def handle_reasoning_summary_text_done(event, state:, yielder:)
|
|
291
|
-
yielder
|
|
300
|
+
yield_reasoning_done(yielder, event.text)
|
|
292
301
|
end
|
|
293
302
|
|
|
294
303
|
#--
|
|
@@ -394,7 +403,7 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
394
403
|
type: "function_call",
|
|
395
404
|
call_id: tc.call_id,
|
|
396
405
|
name: encode_tool_name(tc.name),
|
|
397
|
-
arguments: tc.arguments
|
|
406
|
+
arguments: tc.arguments,
|
|
398
407
|
}
|
|
399
408
|
end
|
|
400
409
|
items
|
|
@@ -402,7 +411,7 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
402
411
|
end
|
|
403
412
|
|
|
404
413
|
#--
|
|
405
|
-
#: (Riffer::Messages::FilePart) -> Hash[Symbol, untyped]
|
|
414
|
+
#: (Riffer::Messages::User::FilePart) -> Hash[Symbol, untyped]
|
|
406
415
|
def convert_file_part_to_openai_format(file)
|
|
407
416
|
if file.image?
|
|
408
417
|
image_url = file.url? ? file.url : "data:#{file.media_type};base64,#{file.data}"
|
|
@@ -34,7 +34,7 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
#--
|
|
37
|
-
#: (Riffer::Messages::FilePart) -> Symbol
|
|
37
|
+
#: (Riffer::Messages::User::FilePart) -> Symbol
|
|
38
38
|
def file_delivery(file)
|
|
39
39
|
file.image? ? :url : :base64
|
|
40
40
|
end
|
|
@@ -74,7 +74,8 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
|
|
|
74
74
|
} #: Hash[Symbol, untyped]
|
|
75
75
|
|
|
76
76
|
unless tags.empty?
|
|
77
|
-
|
|
77
|
+
# Merged over any metadata set in model_options; a tag wins on a shared key.
|
|
78
|
+
params[:metadata] = (params[:metadata] || {}).merge(tags)
|
|
78
79
|
# OpenRouter exposes the legacy Chat Completions user field rather than
|
|
79
80
|
# safety_identifier; the reserved user_id maps there and stays in metadata.
|
|
80
81
|
user = tags["user_id"]
|
|
@@ -223,7 +224,7 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
|
|
|
223
224
|
emit_tool_call_done_events(state: state, yielder: yielder) unless state[:tool_calls].empty?
|
|
224
225
|
|
|
225
226
|
yielder << Riffer::StreamEvents::TextDone.new(state[:text]) unless state[:text].empty?
|
|
226
|
-
yielder
|
|
227
|
+
yield_reasoning_done(yielder, state[:reasoning]) unless state[:reasoning].empty?
|
|
227
228
|
yield_finish_reason(yielder, build_finish_reason(state[:finish_reason], native: state[:native_finish_reason]))
|
|
228
229
|
end
|
|
229
230
|
|
|
@@ -362,7 +363,7 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
|
|
|
362
363
|
type: "function",
|
|
363
364
|
function: {
|
|
364
365
|
name: encode_tool_name(tc.name),
|
|
365
|
-
arguments: tc.arguments
|
|
366
|
+
arguments: tc.arguments,
|
|
366
367
|
},
|
|
367
368
|
}
|
|
368
369
|
end
|
|
@@ -372,7 +373,7 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
|
|
|
372
373
|
end
|
|
373
374
|
|
|
374
375
|
#--
|
|
375
|
-
#: (Riffer::Messages::FilePart) -> Hash[Symbol, untyped]
|
|
376
|
+
#: (Riffer::Messages::User::FilePart) -> Hash[Symbol, untyped]
|
|
376
377
|
def convert_file_part_to_chat_completions_format(file)
|
|
377
378
|
if file.image?
|
|
378
379
|
image_url = file.url? ? file.url : "data:#{file.media_type};base64,#{file.data}"
|
|
@@ -27,13 +27,9 @@ module Riffer::Providers::Repository
|
|
|
27
27
|
#
|
|
28
28
|
# Riffer::Providers::Repository.register(:jane) { MyApp::JaneProvider }
|
|
29
29
|
#
|
|
30
|
-
# Raises Riffer::ArgumentError when called without a block.
|
|
31
|
-
#
|
|
32
30
|
#--
|
|
33
31
|
#: ((String | Symbol)) { () -> singleton(Riffer::Providers::Base) } -> void
|
|
34
32
|
def register(identifier, &factory)
|
|
35
|
-
raise Riffer::ArgumentError, "register requires a block returning a provider class" unless factory
|
|
36
|
-
|
|
37
33
|
@registrations[identifier.to_sym] = factory
|
|
38
34
|
@key_for = nil
|
|
39
35
|
end
|
|
@@ -4,20 +4,36 @@
|
|
|
4
4
|
# Normalized token usage for an LLM API call. Buckets carry the same
|
|
5
5
|
# meaning for every provider.
|
|
6
6
|
class Riffer::Providers::TokenUsage
|
|
7
|
+
# Builds a TokenUsage from a hash, or returns +usage+ unchanged when it is
|
|
8
|
+
# already a TokenUsage.
|
|
9
|
+
#--
|
|
10
|
+
#: ((Hash[Symbol, untyped] | Riffer::Providers::TokenUsage)) -> Riffer::Providers::TokenUsage
|
|
11
|
+
def self.from_hash(usage)
|
|
12
|
+
return usage if usage.is_a?(Riffer::Providers::TokenUsage)
|
|
13
|
+
|
|
14
|
+
new(
|
|
15
|
+
input_tokens: usage[:input_tokens],
|
|
16
|
+
output_tokens: usage[:output_tokens],
|
|
17
|
+
cache_write_tokens: usage[:cache_write_tokens],
|
|
18
|
+
cache_read_tokens: usage[:cache_read_tokens],
|
|
19
|
+
cost: usage[:cost],
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
7
23
|
# Number of tokens entering the context window, including cache reads and writes.
|
|
8
|
-
attr_reader :input_tokens #: Integer
|
|
24
|
+
attr_reader :input_tokens #: Integer # @dynamic input_tokens
|
|
9
25
|
|
|
10
26
|
# Number of tokens generated by the model, including reasoning/thinking tokens.
|
|
11
|
-
attr_reader :output_tokens #: Integer
|
|
27
|
+
attr_reader :output_tokens #: Integer # @dynamic output_tokens
|
|
12
28
|
|
|
13
29
|
# Subset of +input_tokens+ written to the provider's prompt cache, when the provider reports it.
|
|
14
|
-
attr_reader :cache_write_tokens #: Integer?
|
|
30
|
+
attr_reader :cache_write_tokens #: Integer? # @dynamic cache_write_tokens
|
|
15
31
|
|
|
16
32
|
# Subset of +input_tokens+ read from the provider's prompt cache, when the provider reports it.
|
|
17
|
-
attr_reader :cache_read_tokens #: Integer?
|
|
33
|
+
attr_reader :cache_read_tokens #: Integer? # @dynamic cache_read_tokens
|
|
18
34
|
|
|
19
35
|
# Cost of the call, set when the model is priced. For observability, not billing.
|
|
20
|
-
attr_reader :cost #: Float?
|
|
36
|
+
attr_reader :cost #: Float? # @dynamic cost
|
|
21
37
|
|
|
22
38
|
#--
|
|
23
39
|
#: (input_tokens: Integer, output_tokens: Integer, ?cache_write_tokens: Integer?, ?cache_read_tokens: Integer?, ?cost: Float?) -> void
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# for the rendered output.
|
|
8
8
|
class Riffer::Skills::Adapter
|
|
9
9
|
# The activation tool class for this adapter.
|
|
10
|
-
attr_reader :skill_activate_tool #: singleton(Riffer::Tool)
|
|
10
|
+
attr_reader :skill_activate_tool #: singleton(Riffer::Tool) # @dynamic skill_activate_tool
|
|
11
11
|
|
|
12
12
|
#--
|
|
13
13
|
#: (skill_activate_tool: singleton(Riffer::Tool)) -> void
|
data/lib/riffer/skills/config.rb
CHANGED
|
@@ -67,4 +67,16 @@ class Riffer::Skills::Config
|
|
|
67
67
|
|
|
68
68
|
@activate_tool = value
|
|
69
69
|
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
# +dup+ would leave the copy sharing this one's activation list, so activating
|
|
74
|
+
# a skill on either would reach the other. DeepDup rebuilds the list and
|
|
75
|
+
# returns a Proc as-is, which has nothing to alias.
|
|
76
|
+
#--
|
|
77
|
+
#: (Riffer::Skills::Config) -> void
|
|
78
|
+
def initialize_copy(source)
|
|
79
|
+
super
|
|
80
|
+
@activate = Riffer::Helpers::DeepDup.call(source.activate)
|
|
81
|
+
end
|
|
70
82
|
end
|
|
@@ -11,13 +11,13 @@ class Riffer::Skills::Context
|
|
|
11
11
|
# @rbs @preactivated: Array[String]
|
|
12
12
|
|
|
13
13
|
# Skill catalog indexed by name.
|
|
14
|
-
attr_reader :skills #: Hash[String, Riffer::Skills::Frontmatter]
|
|
14
|
+
attr_reader :skills #: Hash[String, Riffer::Skills::Frontmatter] # @dynamic skills
|
|
15
15
|
|
|
16
16
|
# The skill adapter used for this context.
|
|
17
|
-
attr_reader :adapter #: Riffer::Skills::Adapter
|
|
17
|
+
attr_reader :adapter #: Riffer::Skills::Adapter # @dynamic adapter
|
|
18
18
|
|
|
19
19
|
# Optional callback invoked when a skill is first activated.
|
|
20
|
-
attr_accessor :on_activate #: (^(String) -> void)?
|
|
20
|
+
attr_accessor :on_activate #: (^(String) -> void)? # @dynamic on_activate, on_activate=
|
|
21
21
|
|
|
22
22
|
#--
|
|
23
23
|
#: (backend: Riffer::Skills::Backend, skills: Hash[String, Riffer::Skills::Frontmatter], adapter: Riffer::Skills::Adapter) -> void
|
|
@@ -106,10 +106,7 @@ class Riffer::Skills::Context
|
|
|
106
106
|
#--
|
|
107
107
|
#: (String) -> bool
|
|
108
108
|
def model_invocable?(name)
|
|
109
|
-
|
|
110
|
-
return false unless skill
|
|
111
|
-
|
|
112
|
-
!skill.disable_model_invocation
|
|
109
|
+
skills.key?(name) && !skills.fetch(name).disable_model_invocation
|
|
113
110
|
end
|
|
114
111
|
|
|
115
112
|
# Returns whether any skill is available for the model to activate.
|
|
@@ -53,8 +53,7 @@ class Riffer::Skills::FilesystemBackend < Riffer::Skills::Backend
|
|
|
53
53
|
def read_skill(name)
|
|
54
54
|
list_skills unless @skills_cache
|
|
55
55
|
cache = @skills_cache #: Hash[String, String]
|
|
56
|
-
dir = cache
|
|
57
|
-
raise Riffer::ArgumentError, "Skill not found: '#{name}'" unless dir
|
|
56
|
+
dir = cache.fetch(name) { raise Riffer::ArgumentError, "Skill not found: '#{name}'" }
|
|
58
57
|
|
|
59
58
|
_, body = Riffer::Skills::Frontmatter.parse(File.read(File.join(dir, SKILL_FILENAME)))
|
|
60
59
|
body
|
|
@@ -13,19 +13,19 @@ class Riffer::Skills::Frontmatter
|
|
|
13
13
|
MAX_DESCRIPTION_LENGTH = 1024 #: Integer
|
|
14
14
|
|
|
15
15
|
# The skill name (1-64 chars, lowercase alphanumeric and hyphens).
|
|
16
|
-
attr_reader :name #: String
|
|
16
|
+
attr_reader :name #: String # @dynamic name
|
|
17
17
|
|
|
18
18
|
# The skill description (1-1024 chars).
|
|
19
|
-
attr_reader :description #: String
|
|
19
|
+
attr_reader :description #: String # @dynamic description
|
|
20
20
|
|
|
21
21
|
# Whether the skill opts out of model-driven activation. Hidden from the
|
|
22
22
|
# catalog and rejected at model activation; still reachable via programmatic
|
|
23
23
|
# activation.
|
|
24
|
-
attr_reader :disable_model_invocation #: bool
|
|
24
|
+
attr_reader :disable_model_invocation #: bool # @dynamic disable_model_invocation
|
|
25
25
|
|
|
26
26
|
# Metadata from the spec's +metadata+ field plus any unrecognized top-level
|
|
27
27
|
# keys.
|
|
28
|
-
attr_reader :metadata #: Hash[Symbol, untyped]
|
|
28
|
+
attr_reader :metadata #: Hash[Symbol, untyped] # @dynamic metadata
|
|
29
29
|
|
|
30
30
|
# Parses a raw SKILL.md string into a +[Frontmatter, body]+ pair — public so
|
|
31
31
|
# custom backends needn't reimplement parsing. Raises Riffer::ArgumentError
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# Base class for all streaming events. Subclasses must implement +to_h+.
|
|
5
5
|
class Riffer::StreamEvents::Base
|
|
6
6
|
# The message role (typically :assistant).
|
|
7
|
-
attr_reader :role #: Symbol
|
|
7
|
+
attr_reader :role #: Symbol # @dynamic role
|
|
8
8
|
|
|
9
9
|
#--
|
|
10
10
|
#: (?role: Symbol) -> void
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
# stream; no ordering guarantee relative to TokenUsageDone.
|
|
6
6
|
class Riffer::StreamEvents::FinishReasonDone < Riffer::StreamEvents::Base
|
|
7
7
|
# The normalized finish reason (see <tt>Riffer::Providers::FinishReason::VALUES</tt>).
|
|
8
|
-
attr_reader :finish_reason #: Symbol
|
|
8
|
+
attr_reader :finish_reason #: Symbol # @dynamic finish_reason
|
|
9
9
|
|
|
10
10
|
# The provider's raw finish-reason value, when one exists on the wire.
|
|
11
|
-
attr_reader :raw_finish_reason #: String?
|
|
11
|
+
attr_reader :raw_finish_reason #: String? # @dynamic raw_finish_reason
|
|
12
12
|
|
|
13
13
|
# Raises Riffer::ArgumentError when +finish_reason+ is outside the
|
|
14
14
|
# normalized vocabulary.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# Emitted when a guardrail transforms data during streaming.
|
|
5
5
|
class Riffer::StreamEvents::GuardrailModification < Riffer::StreamEvents::Base
|
|
6
6
|
# The modification record.
|
|
7
|
-
attr_reader :modification #: Riffer::Guardrails::Modification
|
|
7
|
+
attr_reader :modification #: Riffer::Guardrails::Modification # @dynamic modification
|
|
8
8
|
|
|
9
9
|
#--
|
|
10
10
|
#: (Riffer::Guardrails::Modification, ?role: Symbol) -> void
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# Emitted when a guardrail blocks execution during streaming.
|
|
5
5
|
class Riffer::StreamEvents::GuardrailTripwire < Riffer::StreamEvents::Base
|
|
6
6
|
# The tripwire containing block details.
|
|
7
|
-
attr_reader :tripwire #: Riffer::Guardrails::Tripwire
|
|
7
|
+
attr_reader :tripwire #: Riffer::Guardrails::Tripwire # @dynamic tripwire
|
|
8
8
|
|
|
9
9
|
#--
|
|
10
10
|
#: (Riffer::Guardrails::Tripwire, ?role: Symbol) -> void
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
# +:riffer_interrupt+.
|
|
6
6
|
class Riffer::StreamEvents::Interrupt < Riffer::StreamEvents::Base
|
|
7
7
|
# The reason provided with the interrupt, if any.
|
|
8
|
-
attr_reader :reason #: (String | Symbol)?
|
|
8
|
+
attr_reader :reason #: (String | Symbol)? # @dynamic reason
|
|
9
9
|
|
|
10
10
|
# Call ids of tool_use blocks riffer filled with placeholder results when the
|
|
11
11
|
# interrupt fired (only when history healing is on).
|
|
12
|
-
attr_reader :healed_tool_call_ids #: Array[String]
|
|
12
|
+
attr_reader :healed_tool_call_ids #: Array[String] # @dynamic healed_tool_call_ids
|
|
13
13
|
|
|
14
14
|
#--
|
|
15
15
|
#: (?reason: (String | Symbol)?, ?healed_tool_call_ids: Array[String]) -> void
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# providers that support reasoning (e.g. OpenAI with the reasoning option).
|
|
6
6
|
class Riffer::StreamEvents::ReasoningDelta < Riffer::StreamEvents::Base
|
|
7
7
|
# The incremental reasoning content.
|
|
8
|
-
attr_reader :content #: String
|
|
8
|
+
attr_reader :content #: String # @dynamic content
|
|
9
9
|
|
|
10
10
|
#--
|
|
11
11
|
#: (String, ?role: Symbol) -> void
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Represents completed reasoning during streaming; only emitted by
|
|
5
|
-
# that support reasoning (e.g. OpenAI with the reasoning option).
|
|
4
|
+
# Represents one completed reasoning block during streaming; only emitted by
|
|
5
|
+
# providers that support reasoning (e.g. OpenAI with the reasoning option).
|
|
6
6
|
class Riffer::StreamEvents::ReasoningDone < Riffer::StreamEvents::Base
|
|
7
|
-
# The
|
|
8
|
-
|
|
7
|
+
# The reasoning block, which the agent loop accumulates onto the assistant
|
|
8
|
+
# message. Its +text+ is the content the preceding ReasoningDelta events
|
|
9
|
+
# added up to.
|
|
10
|
+
attr_reader :part #: Riffer::Messages::Assistant::ReasoningPart # @dynamic part
|
|
9
11
|
|
|
10
12
|
#--
|
|
11
|
-
#: (
|
|
12
|
-
def initialize(
|
|
13
|
+
#: (Riffer::Messages::Assistant::ReasoningPart, ?role: Symbol) -> void
|
|
14
|
+
def initialize(part, role: :assistant)
|
|
13
15
|
super(role: role)
|
|
14
|
-
@
|
|
16
|
+
@part = part
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
#--
|
|
18
20
|
#: () -> Hash[Symbol, untyped]
|
|
19
21
|
def to_h
|
|
20
|
-
{ role: @role,
|
|
22
|
+
{ role: @role, part: part.to_h }
|
|
21
23
|
end
|
|
22
24
|
end
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# callback when the LLM calls the activation tool.
|
|
6
6
|
class Riffer::StreamEvents::SkillActivation < Riffer::StreamEvents::Base
|
|
7
7
|
# The activated skill name.
|
|
8
|
-
attr_reader :name #: String
|
|
8
|
+
attr_reader :name #: String # @dynamic name
|
|
9
9
|
|
|
10
10
|
#--
|
|
11
11
|
#: (String, ?role: Symbol) -> void
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# Represents an incremental text chunk during streaming.
|
|
5
5
|
class Riffer::StreamEvents::TextDelta < Riffer::StreamEvents::Base
|
|
6
6
|
# The incremental text content.
|
|
7
|
-
attr_reader :content #: String
|
|
7
|
+
attr_reader :content #: String # @dynamic content
|
|
8
8
|
|
|
9
9
|
#--
|
|
10
10
|
#: (String, ?role: Symbol) -> void
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# Represents completed text generation during streaming.
|
|
5
5
|
class Riffer::StreamEvents::TextDone < Riffer::StreamEvents::Base
|
|
6
6
|
# The complete text content.
|
|
7
|
-
attr_reader :content #: String
|
|
7
|
+
attr_reader :content #: String # @dynamic content
|
|
8
8
|
|
|
9
9
|
#--
|
|
10
10
|
#: (String, ?role: Symbol) -> void
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# Final token usage for the response, emitted when the LLM finishes.
|
|
5
5
|
class Riffer::StreamEvents::TokenUsageDone < Riffer::StreamEvents::Base
|
|
6
6
|
# The token usage data for this response.
|
|
7
|
-
attr_reader :token_usage #: Riffer::Providers::TokenUsage
|
|
7
|
+
attr_reader :token_usage #: Riffer::Providers::TokenUsage # @dynamic token_usage
|
|
8
8
|
|
|
9
9
|
#--
|
|
10
10
|
#: (token_usage: Riffer::Providers::TokenUsage, ?role: Symbol) -> void
|
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
# streaming.
|
|
6
6
|
class Riffer::StreamEvents::ToolCallDelta < Riffer::StreamEvents::Base
|
|
7
7
|
# The tool call item identifier.
|
|
8
|
-
attr_reader :item_id #: String
|
|
8
|
+
attr_reader :item_id #: String # @dynamic item_id
|
|
9
9
|
|
|
10
10
|
# The tool name (may only be present in first delta).
|
|
11
|
-
attr_reader :name #: String?
|
|
11
|
+
attr_reader :name #: String? # @dynamic name
|
|
12
12
|
|
|
13
13
|
# The incremental arguments JSON fragment.
|
|
14
|
-
attr_reader :arguments_delta #: String
|
|
14
|
+
attr_reader :arguments_delta #: String # @dynamic arguments_delta
|
|
15
15
|
|
|
16
16
|
#--
|
|
17
17
|
#: (item_id: String, arguments_delta: String, ?name: String?, ?role: Symbol) -> void
|
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
# Represents a completed tool call during streaming.
|
|
5
5
|
class Riffer::StreamEvents::ToolCallDone < Riffer::StreamEvents::Base
|
|
6
6
|
# The tool call item identifier.
|
|
7
|
-
attr_reader :item_id #: String
|
|
7
|
+
attr_reader :item_id #: String # @dynamic item_id
|
|
8
8
|
|
|
9
9
|
# The call identifier for response matching.
|
|
10
|
-
attr_reader :call_id #: String
|
|
10
|
+
attr_reader :call_id #: String # @dynamic call_id
|
|
11
11
|
|
|
12
12
|
# The tool name.
|
|
13
|
-
attr_reader :name #: String
|
|
13
|
+
attr_reader :name #: String # @dynamic name
|
|
14
14
|
|
|
15
15
|
# The complete arguments JSON string.
|
|
16
|
-
attr_reader :arguments #: String
|
|
16
|
+
attr_reader :arguments #: String # @dynamic arguments
|
|
17
17
|
|
|
18
18
|
#--
|
|
19
19
|
#: (item_id: String, call_id: String, name: String, arguments: String, ?role: Symbol) -> void
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
# The result of a completed server-side web search during streaming.
|
|
5
5
|
class Riffer::StreamEvents::WebSearchDone < Riffer::StreamEvents::Base
|
|
6
6
|
# The search query used.
|
|
7
|
-
attr_reader :query #: String
|
|
7
|
+
attr_reader :query #: String # @dynamic query
|
|
8
8
|
|
|
9
9
|
# The search result sources with title and url.
|
|
10
|
-
attr_reader :sources #: Array[Hash[Symbol, String?]]
|
|
10
|
+
attr_reader :sources #: Array[Hash[Symbol, String?]] # @dynamic sources
|
|
11
11
|
|
|
12
12
|
#--
|
|
13
13
|
#: (String, ?sources: Array[Hash[Symbol, String?]], ?role: Symbol) -> void
|
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
# progresses.
|
|
6
6
|
class Riffer::StreamEvents::WebSearchStatus < Riffer::StreamEvents::Base
|
|
7
7
|
# The web search status ("in_progress", "searching", "completed", "open_page").
|
|
8
|
-
attr_reader :status #: String
|
|
8
|
+
attr_reader :status #: String # @dynamic status
|
|
9
9
|
|
|
10
10
|
# The URL being fetched (present for "open_page" status).
|
|
11
|
-
attr_reader :url #: String?
|
|
11
|
+
attr_reader :url #: String? # @dynamic url
|
|
12
12
|
|
|
13
13
|
# The search query (present when available during status changes).
|
|
14
|
-
attr_reader :query #: String?
|
|
14
|
+
attr_reader :query #: String? # @dynamic query
|
|
15
15
|
|
|
16
16
|
#--
|
|
17
17
|
#: (String, ?url: String?, ?query: String?, ?role: Symbol) -> void
|
|
@@ -20,17 +20,17 @@ class Riffer::Tools::Response
|
|
|
20
20
|
VALID_FORMATS = %i[text json].freeze #: Array[Symbol]
|
|
21
21
|
|
|
22
22
|
# The response content.
|
|
23
|
-
attr_reader :content #: String
|
|
23
|
+
attr_reader :content #: String # @dynamic content
|
|
24
24
|
|
|
25
25
|
# The error message, or +nil+ on success.
|
|
26
|
-
attr_reader :error_message #: String?
|
|
26
|
+
attr_reader :error_message #: String? # @dynamic error_message
|
|
27
27
|
|
|
28
28
|
# The error type, or +nil+ on success.
|
|
29
|
-
attr_reader :error_type #: Symbol?
|
|
29
|
+
attr_reader :error_type #: Symbol? # @dynamic error_type
|
|
30
30
|
|
|
31
31
|
# The exception an unhandled failure was folded from, or +nil+. Kept out of
|
|
32
32
|
# every serialized form so it never reaches an LLM or a message payload.
|
|
33
|
-
attr_reader :exception #: Exception?
|
|
33
|
+
attr_reader :exception #: Exception? # @dynamic exception
|
|
34
34
|
|
|
35
35
|
# Creates a success response.
|
|
36
36
|
#
|
data/lib/riffer/tools/runtime.rb
CHANGED
|
@@ -101,9 +101,9 @@ class Riffer::Tools::Runtime
|
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
#--
|
|
104
|
-
#: (String
|
|
104
|
+
#: (String) -> untyped
|
|
105
105
|
def parse_arguments(arguments)
|
|
106
|
-
return {} if arguments.
|
|
106
|
+
return {} if arguments.empty?
|
|
107
107
|
|
|
108
108
|
JSON.parse(arguments, symbolize_names: true)
|
|
109
109
|
end
|
|
@@ -168,8 +168,7 @@ class Riffer::Tools::Runtime
|
|
|
168
168
|
def capture_tool_arguments(span, tool_call)
|
|
169
169
|
return unless capture_tool_content?(span)
|
|
170
170
|
|
|
171
|
-
arguments
|
|
172
|
-
span.set_attribute("gen_ai.tool.call.arguments", arguments) if arguments
|
|
171
|
+
span.set_attribute("gen_ai.tool.call.arguments", tool_call.arguments)
|
|
173
172
|
end
|
|
174
173
|
|
|
175
174
|
#--
|
|
@@ -71,7 +71,7 @@ module Riffer::Tracing::Capture # :nodoc: all
|
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
#--
|
|
74
|
-
#: (Riffer::Messages::FilePart) -> Hash[Symbol, untyped]
|
|
74
|
+
#: (Riffer::Messages::User::FilePart) -> Hash[Symbol, untyped]
|
|
75
75
|
def file_part(file)
|
|
76
76
|
part = { type: "file", media_type: file.media_type } #: Hash[Symbol, untyped]
|
|
77
77
|
part[:name] = file.filename if file.filename
|
|
@@ -81,10 +81,8 @@ module Riffer::Tracing::Capture # :nodoc: all
|
|
|
81
81
|
# Semconv's tool_call part carries arguments as a JSON object; riffer holds
|
|
82
82
|
# them as a string — parse so the captured payload isn't double-encoded.
|
|
83
83
|
#--
|
|
84
|
-
#: (
|
|
84
|
+
#: (String) -> untyped
|
|
85
85
|
def parse_arguments(arguments)
|
|
86
|
-
return arguments unless arguments.is_a?(String)
|
|
87
|
-
|
|
88
86
|
JSON.parse(arguments)
|
|
89
87
|
rescue JSON::ParserError
|
|
90
88
|
arguments
|
|
@@ -8,17 +8,17 @@ class Riffer::Tracing::StreamRecorder # :nodoc: all
|
|
|
8
8
|
# @rbs @clock: ^() -> Float
|
|
9
9
|
# @rbs @started_at: Float
|
|
10
10
|
|
|
11
|
-
attr_reader :token_usage #: Riffer::Providers::TokenUsage?
|
|
11
|
+
attr_reader :token_usage #: Riffer::Providers::TokenUsage? # @dynamic token_usage
|
|
12
12
|
|
|
13
|
-
attr_reader :time_to_first_chunk #: Float?
|
|
13
|
+
attr_reader :time_to_first_chunk #: Float? # @dynamic time_to_first_chunk
|
|
14
14
|
|
|
15
|
-
attr_reader :finish_reason #: Symbol?
|
|
15
|
+
attr_reader :finish_reason #: Symbol? # @dynamic finish_reason
|
|
16
16
|
|
|
17
|
-
attr_reader :raw_finish_reason #: String?
|
|
17
|
+
attr_reader :raw_finish_reason #: String? # @dynamic raw_finish_reason
|
|
18
18
|
|
|
19
|
-
attr_reader :content #: String?
|
|
19
|
+
attr_reader :content #: String? # @dynamic content
|
|
20
20
|
|
|
21
|
-
attr_reader :tool_calls #: Array[Riffer::Messages::Assistant::ToolCall]
|
|
21
|
+
attr_reader :tool_calls #: Array[Riffer::Messages::Assistant::ToolCall] # @dynamic tool_calls
|
|
22
22
|
|
|
23
23
|
#--
|
|
24
24
|
#: (Enumerator::Yielder, ?clock: ^() -> Float) -> void
|
data/lib/riffer/version.rb
CHANGED
data/lib/riffer.rb
CHANGED
|
@@ -41,6 +41,13 @@ module Riffer
|
|
|
41
41
|
# Raised when a tool encounters an expected execution error.
|
|
42
42
|
class ToolExecutionError < Error; end
|
|
43
43
|
|
|
44
|
+
# Raised when a provider's stream ends before its terminal event (Bedrock's
|
|
45
|
+
# messageStop, Anthropic's message_stop, OpenAI's response.completed /
|
|
46
|
+
# incomplete / failed), so a truncated or empty response is never returned
|
|
47
|
+
# as a complete message. Events already yielded were delivered; the
|
|
48
|
+
# request is safe to retry.
|
|
49
|
+
class IncompleteStreamError < Error; end
|
|
50
|
+
|
|
44
51
|
# Base error for a file attachment that can't be resolved for the provider.
|
|
45
52
|
class FileError < Error; end
|
|
46
53
|
|