riffer 0.28.0 → 0.29.1
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/.agents/architecture.md +18 -11
- data/.agents/code-style.md +1 -1
- data/.agents/rbs-inline.md +53 -2
- data/.agents/testing.md +9 -5
- data/.release-please-manifest.json +1 -1
- data/AGENTS.md +17 -10
- data/CHANGELOG.md +26 -0
- data/README.md +17 -18
- data/Steepfile +8 -1
- data/docs/03_AGENTS.md +34 -3
- data/docs/04_AGENT_LIFECYCLE.md +87 -86
- data/docs/05_AGENT_LOOP.md +2 -2
- data/docs/06_TOOLS.md +9 -4
- data/docs/07_TOOL_ADVANCED.md +17 -17
- data/docs/08_MESSAGES.md +25 -32
- data/docs/09_STREAM_EVENTS.md +1 -1
- data/docs/10_CONFIGURATION.md +7 -18
- data/docs/providers/01_PROVIDERS.md +6 -0
- data/docs/providers/06_MOCK_PROVIDER.md +2 -1
- data/docs/providers/07_CUSTOM_PROVIDERS.md +4 -4
- data/docs/providers/08_GEMINI.md +2 -2
- data/docs/providers/09_OPENROUTER.md +242 -0
- data/lib/riffer/agent/config.rb +173 -0
- data/lib/riffer/agent/context.rb +127 -0
- data/lib/riffer/agent/response.rb +2 -0
- data/lib/riffer/agent/run.rb +308 -0
- data/lib/riffer/agent/session/repair.rb +112 -0
- data/lib/riffer/agent/session.rb +270 -0
- data/lib/riffer/{structured_output → agent/structured_output}/result.rb +1 -1
- data/lib/riffer/{structured_output.rb → agent/structured_output.rb} +4 -4
- data/lib/riffer/agent.rb +236 -923
- data/lib/riffer/config.rb +14 -7
- data/lib/riffer/evals/evaluator.rb +18 -3
- data/lib/riffer/evals/judge.rb +7 -2
- data/lib/riffer/evals/run_result.rb +2 -1
- data/lib/riffer/evals/scenario_result.rb +2 -1
- data/lib/riffer/guardrails/runner.rb +3 -2
- data/lib/riffer/helpers/call_or_value.rb +16 -0
- data/lib/riffer/helpers.rb +0 -1
- data/lib/riffer/mcp/authenticated_tool.rb +4 -0
- data/lib/riffer/mcp/client.rb +3 -1
- data/lib/riffer/mcp/registration.rb +6 -3
- data/lib/riffer/mcp/registry.rb +6 -1
- data/lib/riffer/mcp/tool_factory.rb +5 -0
- data/lib/riffer/messages/assistant.rb +9 -3
- data/lib/riffer/messages/base.rb +22 -0
- data/lib/riffer/messages/converter.rb +6 -6
- data/lib/riffer/{file_part.rb → messages/file_part.rb} +7 -5
- data/lib/riffer/messages/tool.rb +1 -1
- data/lib/riffer/messages/user.rb +4 -4
- data/lib/riffer/{boolean.rb → params/boolean.rb} +3 -3
- data/lib/riffer/{param.rb → params/param.rb} +6 -6
- data/lib/riffer/params.rb +27 -21
- data/lib/riffer/providers/amazon_bedrock.rb +37 -31
- data/lib/riffer/providers/anthropic.rb +39 -36
- data/lib/riffer/providers/base.rb +12 -9
- data/lib/riffer/providers/gemini.rb +19 -12
- data/lib/riffer/providers/mock.rb +45 -13
- data/lib/riffer/providers/open_ai.rb +34 -29
- data/lib/riffer/providers/open_router.rb +325 -0
- data/lib/riffer/providers/repository.rb +1 -0
- data/lib/riffer/{token_usage.rb → providers/token_usage.rb} +4 -4
- data/lib/riffer/providers.rb +1 -0
- data/lib/riffer/runner/fibers.rb +6 -3
- data/lib/riffer/runner/sequential.rb +1 -1
- data/lib/riffer/runner/threaded.rb +3 -1
- data/lib/riffer/runner.rb +1 -1
- data/lib/riffer/skills/activate_tool.rb +4 -3
- data/lib/riffer/skills/config.rb +6 -1
- data/lib/riffer/skills/context.rb +6 -3
- data/lib/riffer/skills/filesystem_backend.rb +10 -5
- data/lib/riffer/skills/markdown_adapter.rb +1 -1
- data/lib/riffer/skills/xml_adapter.rb +1 -1
- data/lib/riffer/stream_events/interrupt.rb +1 -1
- data/lib/riffer/stream_events/token_usage_done.rb +2 -2
- data/lib/riffer/stream_events/web_search_status.rb +1 -1
- data/lib/riffer/tool.rb +3 -3
- data/lib/riffer/tools/response.rb +2 -0
- data/lib/riffer/{tool_runtime → tools/runtime}/fibers.rb +2 -2
- data/lib/riffer/{tool_runtime → tools/runtime}/inline.rb +1 -1
- data/lib/riffer/{tool_runtime → tools/runtime}/threaded.rb +2 -2
- data/lib/riffer/{tool_runtime.rb → tools/runtime.rb} +11 -9
- data/lib/riffer/{toolable.rb → tools/toolable.rb} +19 -9
- data/lib/riffer/version.rb +1 -1
- data/lib/riffer.rb +4 -1
- data/sig/_private/anthropic.rbs +16 -0
- data/sig/_private/async.rbs +24 -0
- data/sig/_private/aws-sdk-core/seahorse_request_context.rbs +7 -0
- data/sig/_private/aws-sdk-core/static_token_provider.rbs +5 -0
- data/sig/_private/mcp.rbs +22 -0
- data/sig/_private/openai.rbs +29 -0
- data/sig/_private/riffer/providers/amazon_bedrock.rbs +4 -0
- data/sig/_private/riffer/providers/anthropic.rbs +4 -0
- data/sig/_private/riffer/providers/open_ai.rbs +4 -0
- data/sig/_private/riffer/providers/open_router.rbs +4 -0
- data/sig/_private/zeitwerk.rbs +12 -0
- data/sig/generated/riffer/agent/config.rbs +119 -0
- data/sig/generated/riffer/agent/context.rbs +93 -0
- data/sig/generated/riffer/agent/response.rbs +2 -0
- data/sig/generated/riffer/agent/run.rbs +144 -0
- data/sig/generated/riffer/agent/session/repair.rbs +51 -0
- data/sig/generated/riffer/agent/session.rbs +147 -0
- data/sig/generated/riffer/{structured_output → agent/structured_output}/result.rbs +2 -2
- data/sig/generated/riffer/{structured_output.rbs → agent/structured_output.rbs} +6 -6
- data/sig/generated/riffer/agent.rbs +145 -342
- data/sig/generated/riffer/config.rbs +17 -5
- data/sig/generated/riffer/evals/evaluator.rbs +8 -0
- data/sig/generated/riffer/evals/judge.rbs +10 -2
- data/sig/generated/riffer/helpers/call_or_value.rbs +9 -0
- data/sig/generated/riffer/helpers.rbs +0 -1
- data/sig/generated/riffer/mcp/client.rbs +2 -0
- data/sig/generated/riffer/mcp/registration.rbs +6 -0
- data/sig/generated/riffer/mcp/registry.rbs +4 -0
- data/sig/generated/riffer/messages/assistant.rbs +7 -3
- data/sig/generated/riffer/messages/base.rbs +18 -0
- data/sig/generated/riffer/messages/converter.rbs +4 -4
- data/sig/generated/riffer/{file_part.rbs → messages/file_part.rbs} +7 -5
- data/sig/generated/riffer/messages/user.rbs +4 -4
- data/sig/generated/riffer/params/boolean.rbs +10 -0
- data/sig/generated/riffer/{param.rbs → params/param.rbs} +3 -3
- data/sig/generated/riffer/params.rbs +15 -15
- data/sig/generated/riffer/providers/amazon_bedrock.rbs +22 -22
- data/sig/generated/riffer/providers/anthropic.rbs +12 -12
- data/sig/generated/riffer/providers/base.rbs +12 -10
- data/sig/generated/riffer/providers/gemini.rbs +10 -4
- data/sig/generated/riffer/providers/mock.rbs +31 -5
- data/sig/generated/riffer/providers/open_ai.rbs +10 -10
- data/sig/generated/riffer/providers/open_router.rbs +85 -0
- data/sig/generated/riffer/{token_usage.rbs → providers/token_usage.rbs} +5 -5
- data/sig/generated/riffer/providers.rbs +1 -0
- data/sig/generated/riffer/runner/fibers.rbs +4 -2
- data/sig/generated/riffer/runner/sequential.rbs +2 -2
- data/sig/generated/riffer/runner/threaded.rbs +4 -2
- data/sig/generated/riffer/runner.rbs +2 -2
- data/sig/generated/riffer/skills/activate_tool.rbs +4 -3
- data/sig/generated/riffer/skills/config.rbs +9 -1
- data/sig/generated/riffer/skills/context.rbs +6 -2
- data/sig/generated/riffer/skills/filesystem_backend.rbs +4 -0
- data/sig/generated/riffer/stream_events/token_usage_done.rbs +3 -3
- data/sig/generated/riffer/tool.rbs +5 -5
- data/sig/generated/riffer/tools/response.rbs +2 -0
- data/sig/generated/riffer/{tool_runtime → tools/runtime}/fibers.rbs +3 -3
- data/sig/generated/riffer/{tool_runtime → tools/runtime}/inline.rbs +2 -2
- data/sig/generated/riffer/{tool_runtime → tools/runtime}/threaded.rbs +3 -3
- data/sig/generated/riffer/{tool_runtime.rbs → tools/runtime.rbs} +14 -12
- data/sig/generated/riffer/{toolable.rbs → tools/toolable.rbs} +18 -6
- data/sig/generated/riffer.rbs +2 -0
- data/sig/manifest.yaml +3 -0
- data/sig/manual/riffer/agent/run.rbs +5 -0
- data/sig/manual/riffer/helpers/call_or_value.rbs +5 -0
- data/sig/manual/riffer/tools/toolable.rbs +6 -0
- metadata +59 -33
- data/lib/riffer/core.rb +0 -28
- data/lib/riffer/helpers/validations.rb +0 -18
- data/sig/generated/riffer/boolean.rbs +0 -10
- data/sig/generated/riffer/core.rbs +0 -19
- data/sig/generated/riffer/helpers/validations.rbs +0 -12
data/lib/riffer/params.rb
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# end
|
|
13
13
|
#
|
|
14
14
|
class Riffer::Params
|
|
15
|
-
attr_reader :parameters #: Array[Riffer::Param]
|
|
15
|
+
attr_reader :parameters #: Array[Riffer::Params::Param]
|
|
16
16
|
|
|
17
17
|
#--
|
|
18
18
|
#: () -> void
|
|
@@ -23,10 +23,10 @@ class Riffer::Params
|
|
|
23
23
|
# Defines a required parameter.
|
|
24
24
|
#
|
|
25
25
|
#--
|
|
26
|
-
#: (Symbol, Class, ?description: String?, ?enum: Array[untyped]?, ?of: Class?) ?{ () -> void } -> void
|
|
26
|
+
#: (Symbol, Class, ?description: String?, ?enum: Array[untyped]?, ?of: Class?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> void
|
|
27
27
|
def required(name, type, description: nil, enum: nil, of: nil, &block)
|
|
28
28
|
nested = build_nested(type, of, &block)
|
|
29
|
-
@parameters << Riffer::Param.new(
|
|
29
|
+
@parameters << Riffer::Params::Param.new(
|
|
30
30
|
name: name,
|
|
31
31
|
type: type,
|
|
32
32
|
required: true,
|
|
@@ -40,10 +40,10 @@ class Riffer::Params
|
|
|
40
40
|
# Defines an optional parameter.
|
|
41
41
|
#
|
|
42
42
|
#--
|
|
43
|
-
#: (Symbol, Class, ?description: String?, ?enum: Array[untyped]?, ?default: untyped, ?of: Class?) ?{ () -> void } -> void
|
|
43
|
+
#: (Symbol, Class, ?description: String?, ?enum: Array[untyped]?, ?default: untyped, ?of: Class?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> void
|
|
44
44
|
def optional(name, type, description: nil, enum: nil, default: nil, of: nil, &block)
|
|
45
45
|
nested = build_nested(type, of, &block)
|
|
46
|
-
@parameters << Riffer::Param.new(
|
|
46
|
+
@parameters << Riffer::Params::Param.new(
|
|
47
47
|
name: name,
|
|
48
48
|
type: type,
|
|
49
49
|
required: false,
|
|
@@ -62,8 +62,8 @@ class Riffer::Params
|
|
|
62
62
|
#--
|
|
63
63
|
#: (Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
|
|
64
64
|
def validate(arguments)
|
|
65
|
-
validated = {}
|
|
66
|
-
errors = []
|
|
65
|
+
validated = {} #: Hash[Symbol, untyped]
|
|
66
|
+
errors = [] #: Array[String]
|
|
67
67
|
|
|
68
68
|
@parameters.each do |param|
|
|
69
69
|
value = arguments[param.name]
|
|
@@ -107,8 +107,8 @@ class Riffer::Params
|
|
|
107
107
|
#--
|
|
108
108
|
#: (?strict: bool) -> Hash[Symbol, untyped]
|
|
109
109
|
def to_json_schema(strict: false)
|
|
110
|
-
properties = {}
|
|
111
|
-
required_params = []
|
|
110
|
+
properties = {} #: Hash[String, untyped]
|
|
111
|
+
required_params = [] #: Array[String]
|
|
112
112
|
|
|
113
113
|
@parameters.each do |param|
|
|
114
114
|
properties[param.name.to_s] = param.to_json_schema(strict: strict)
|
|
@@ -126,7 +126,7 @@ class Riffer::Params
|
|
|
126
126
|
private
|
|
127
127
|
|
|
128
128
|
#--
|
|
129
|
-
#: (Class, Class?) ?{ () -> void } -> Riffer::Params?
|
|
129
|
+
#: (Class, Class?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> Riffer::Params?
|
|
130
130
|
def build_nested(type, of, &block)
|
|
131
131
|
if of && block
|
|
132
132
|
raise Riffer::ArgumentError, "cannot use both of: and a block"
|
|
@@ -136,9 +136,9 @@ class Riffer::Params
|
|
|
136
136
|
unless type == Array
|
|
137
137
|
raise Riffer::ArgumentError, "of: can only be used with Array type, got #{type}"
|
|
138
138
|
end
|
|
139
|
-
unless Riffer::Param::PRIMITIVE_TYPES.include?(of)
|
|
139
|
+
unless Riffer::Params::Param::PRIMITIVE_TYPES.include?(of)
|
|
140
140
|
raise Riffer::ArgumentError,
|
|
141
|
-
"of: must be a primitive type (#{Riffer::Param::PRIMITIVE_TYPES.map(&:name).join(", ")}), got #{of}"
|
|
141
|
+
"of: must be a primitive type (#{Riffer::Params::Param::PRIMITIVE_TYPES.map(&:name).join(", ")}), got #{of}"
|
|
142
142
|
end
|
|
143
143
|
return nil
|
|
144
144
|
end
|
|
@@ -154,7 +154,7 @@ class Riffer::Params
|
|
|
154
154
|
end
|
|
155
155
|
|
|
156
156
|
#--
|
|
157
|
-
#: (Riffer::Param, untyped, Array[String]) -> untyped
|
|
157
|
+
#: (Riffer::Params::Param, untyped, Array[String]) -> untyped
|
|
158
158
|
def validate_nested(param, value, errors)
|
|
159
159
|
if param.type == Hash && param.nested_params
|
|
160
160
|
validate_nested_hash(param, value, errors)
|
|
@@ -169,9 +169,11 @@ class Riffer::Params
|
|
|
169
169
|
end
|
|
170
170
|
|
|
171
171
|
#--
|
|
172
|
-
#: (Riffer::Param, Hash[Symbol, untyped], Array[String]) -> Hash[Symbol, untyped]
|
|
172
|
+
#: (Riffer::Params::Param, Hash[Symbol, untyped], Array[String]) -> Hash[Symbol, untyped]
|
|
173
173
|
def validate_nested_hash(param, value, errors)
|
|
174
|
-
param.nested_params
|
|
174
|
+
nested = param.nested_params
|
|
175
|
+
return value unless nested
|
|
176
|
+
nested.validate(value)
|
|
175
177
|
rescue Riffer::ValidationError => e
|
|
176
178
|
e.message.split("; ").each do |msg|
|
|
177
179
|
errors << "#{param.name}.#{msg}"
|
|
@@ -180,14 +182,16 @@ class Riffer::Params
|
|
|
180
182
|
end
|
|
181
183
|
|
|
182
184
|
#--
|
|
183
|
-
#: (Riffer::Param, Array[untyped], Array[String]) -> Array[untyped]
|
|
185
|
+
#: (Riffer::Params::Param, Array[untyped], Array[String]) -> Array[untyped]
|
|
184
186
|
def validate_nested_array_of_objects(param, value, errors)
|
|
187
|
+
nested = param.nested_params
|
|
188
|
+
return value unless nested
|
|
185
189
|
value.map.with_index do |item, i|
|
|
186
190
|
unless item.is_a?(Hash)
|
|
187
191
|
errors << "#{param.name}[#{i}] must be an object"
|
|
188
192
|
next item
|
|
189
193
|
end
|
|
190
|
-
|
|
194
|
+
nested.validate(item)
|
|
191
195
|
rescue Riffer::ValidationError => e
|
|
192
196
|
e.message.split("; ").each do |msg|
|
|
193
197
|
errors << "#{param.name}[#{i}].#{msg}"
|
|
@@ -197,14 +201,16 @@ class Riffer::Params
|
|
|
197
201
|
end
|
|
198
202
|
|
|
199
203
|
#--
|
|
200
|
-
#: (Riffer::Param, Array[untyped], Array[String]) -> void
|
|
204
|
+
#: (Riffer::Params::Param, Array[untyped], Array[String]) -> void
|
|
201
205
|
def validate_typed_array(param, value, errors)
|
|
202
|
-
|
|
206
|
+
item_type = param.item_type
|
|
207
|
+
return unless item_type
|
|
208
|
+
type_name = Riffer::Params::Param::TYPE_MAPPINGS[item_type]
|
|
203
209
|
value.each_with_index do |item, i|
|
|
204
|
-
valid = if
|
|
210
|
+
valid = if item_type == Riffer::Params::Boolean || item_type == TrueClass || item_type == FalseClass
|
|
205
211
|
item == true || item == false
|
|
206
212
|
else
|
|
207
|
-
item.is_a?(
|
|
213
|
+
item.is_a?(item_type)
|
|
208
214
|
end
|
|
209
215
|
errors << "#{param.name}[#{i}] must be a #{type_name}" unless valid
|
|
210
216
|
end
|
|
@@ -63,7 +63,7 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
63
63
|
system: partitioned_messages[:system],
|
|
64
64
|
messages: partitioned_messages[:conversation],
|
|
65
65
|
**options.except(:tools, :structured_output)
|
|
66
|
-
}
|
|
66
|
+
} #: Hash[Symbol, untyped]
|
|
67
67
|
|
|
68
68
|
if tools && !tools.empty?
|
|
69
69
|
params[:tool_config] = {
|
|
@@ -92,18 +92,18 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
#--
|
|
95
|
-
#: (Hash[Symbol, untyped]) ->
|
|
95
|
+
#: (Hash[Symbol, untyped]) -> untyped
|
|
96
96
|
def execute_generate(params)
|
|
97
97
|
@client.converse(**params)
|
|
98
98
|
end
|
|
99
99
|
|
|
100
100
|
#--
|
|
101
|
-
#: (
|
|
101
|
+
#: (untyped) -> Riffer::Providers::TokenUsage?
|
|
102
102
|
def extract_token_usage(response)
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
typed_response = response #: Aws::BedrockRuntime::Client::_ConverseResponseSuccess
|
|
104
|
+
usage = typed_response.usage
|
|
105
105
|
|
|
106
|
-
Riffer::TokenUsage.new(
|
|
106
|
+
Riffer::Providers::TokenUsage.new(
|
|
107
107
|
input_tokens: usage.input_tokens,
|
|
108
108
|
output_tokens: usage.output_tokens,
|
|
109
109
|
cache_creation_tokens: usage.cache_write_input_tokens,
|
|
@@ -112,9 +112,10 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
#--
|
|
115
|
-
#: (
|
|
115
|
+
#: (untyped) -> String
|
|
116
116
|
def extract_content(response)
|
|
117
|
-
|
|
117
|
+
typed_response = response #: Aws::BedrockRuntime::Client::_ConverseResponseSuccess
|
|
118
|
+
content_blocks = typed_response.output&.message&.content
|
|
118
119
|
return "" if content_blocks.nil? || content_blocks.empty?
|
|
119
120
|
|
|
120
121
|
text_content = ""
|
|
@@ -127,12 +128,13 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
127
128
|
end
|
|
128
129
|
|
|
129
130
|
#--
|
|
130
|
-
#: (
|
|
131
|
+
#: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
|
|
131
132
|
def extract_tool_calls(response)
|
|
132
|
-
|
|
133
|
+
typed_response = response #: Aws::BedrockRuntime::Client::_ConverseResponseSuccess
|
|
134
|
+
content_blocks = typed_response.output&.message&.content
|
|
133
135
|
return [] if content_blocks.nil? || content_blocks.empty?
|
|
134
136
|
|
|
135
|
-
tool_calls = []
|
|
137
|
+
tool_calls = [] #: Array[Riffer::Messages::Assistant::ToolCall]
|
|
136
138
|
|
|
137
139
|
content_blocks.each do |block|
|
|
138
140
|
if block.respond_to?(:tool_use) && block.tool_use
|
|
@@ -153,7 +155,7 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
153
155
|
current_state = {
|
|
154
156
|
text: nil,
|
|
155
157
|
tool_call: nil
|
|
156
|
-
}
|
|
158
|
+
} #: Hash[Symbol, untyped]
|
|
157
159
|
|
|
158
160
|
@client.converse_stream(**params) do |stream|
|
|
159
161
|
stream.on_event do |event|
|
|
@@ -196,28 +198,31 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
196
198
|
end
|
|
197
199
|
|
|
198
200
|
#--
|
|
199
|
-
#: (
|
|
201
|
+
#: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
|
|
200
202
|
def handle_content_block_start_tool_use(event, state:, yielder:)
|
|
203
|
+
typed_event = event #: Aws::BedrockRuntime::Types::ContentBlockStartEvent
|
|
201
204
|
state[:tool_call] = {
|
|
202
|
-
id:
|
|
203
|
-
name: decode_tool_name(
|
|
205
|
+
id: typed_event.start.tool_use.tool_use_id,
|
|
206
|
+
name: decode_tool_name(typed_event.start.tool_use.name, tools: @current_tools),
|
|
204
207
|
arguments: ""
|
|
205
208
|
}
|
|
206
209
|
end
|
|
207
210
|
|
|
208
211
|
#--
|
|
209
|
-
#: (
|
|
212
|
+
#: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
|
|
210
213
|
def handle_content_block_delta_text_delta(event, state:, yielder:)
|
|
211
|
-
|
|
214
|
+
typed_event = event #: Aws::BedrockRuntime::Types::ContentBlockDeltaEvent
|
|
215
|
+
delta_text = typed_event.delta.text
|
|
212
216
|
state[:text] ||= ""
|
|
213
217
|
state[:text] += delta_text
|
|
214
218
|
yielder << Riffer::StreamEvents::TextDelta.new(delta_text)
|
|
215
219
|
end
|
|
216
220
|
|
|
217
221
|
#--
|
|
218
|
-
#: (
|
|
222
|
+
#: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
|
|
219
223
|
def handle_content_block_delta_tool_use(event, state:, yielder:)
|
|
220
|
-
|
|
224
|
+
typed_event = event #: Aws::BedrockRuntime::Types::ContentBlockDeltaEvent
|
|
225
|
+
input_delta = typed_event.delta.tool_use.input
|
|
221
226
|
|
|
222
227
|
state[:tool_call][:arguments] += input_delta
|
|
223
228
|
|
|
@@ -229,14 +234,14 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
229
234
|
end
|
|
230
235
|
|
|
231
236
|
#--
|
|
232
|
-
#: (
|
|
237
|
+
#: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
|
|
233
238
|
def handle_content_block_stop_text_delta(_event, state:, yielder:)
|
|
234
239
|
yielder << Riffer::StreamEvents::TextDone.new(state[:text])
|
|
235
240
|
state[:text] = nil
|
|
236
241
|
end
|
|
237
242
|
|
|
238
243
|
#--
|
|
239
|
-
#: (
|
|
244
|
+
#: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
|
|
240
245
|
def handle_content_block_stop_tool_use(_event, state:, yielder:)
|
|
241
246
|
tool_call = state[:tool_call]
|
|
242
247
|
yielder << Riffer::StreamEvents::ToolCallDone.new(
|
|
@@ -249,14 +254,15 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
249
254
|
end
|
|
250
255
|
|
|
251
256
|
#--
|
|
252
|
-
#: (
|
|
257
|
+
#: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
|
|
253
258
|
def handle_metadata_usage(event, state:, yielder:)
|
|
259
|
+
typed_event = event #: Aws::BedrockRuntime::Types::ConverseStreamMetadataEvent
|
|
254
260
|
yielder << Riffer::StreamEvents::TokenUsageDone.new(
|
|
255
|
-
token_usage: Riffer::TokenUsage.new(
|
|
256
|
-
input_tokens:
|
|
257
|
-
output_tokens:
|
|
258
|
-
cache_creation_tokens:
|
|
259
|
-
cache_read_tokens:
|
|
261
|
+
token_usage: Riffer::Providers::TokenUsage.new(
|
|
262
|
+
input_tokens: typed_event.usage.input_tokens,
|
|
263
|
+
output_tokens: typed_event.usage.output_tokens,
|
|
264
|
+
cache_creation_tokens: typed_event.usage.cache_write_input_tokens,
|
|
265
|
+
cache_read_tokens: typed_event.usage.cache_read_input_tokens
|
|
260
266
|
)
|
|
261
267
|
)
|
|
262
268
|
end
|
|
@@ -264,8 +270,8 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
264
270
|
#--
|
|
265
271
|
#: (Array[Riffer::Messages::Base]) -> Hash[Symbol, untyped]
|
|
266
272
|
def partition_messages(messages)
|
|
267
|
-
system_prompts = []
|
|
268
|
-
conversation_messages = []
|
|
273
|
+
system_prompts = [] #: Array[Hash[Symbol, untyped]]
|
|
274
|
+
conversation_messages = [] #: Array[Hash[Symbol, untyped]]
|
|
269
275
|
|
|
270
276
|
messages.each do |message|
|
|
271
277
|
case message
|
|
@@ -291,7 +297,7 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
291
297
|
#--
|
|
292
298
|
#: (Riffer::Messages::Assistant) -> Hash[Symbol, untyped]
|
|
293
299
|
def convert_assistant_to_bedrock_format(message)
|
|
294
|
-
content = []
|
|
300
|
+
content = [] #: Array[Hash[Symbol, untyped]]
|
|
295
301
|
content << {text: message.content} if message.content && !message.content.empty?
|
|
296
302
|
|
|
297
303
|
message.tool_calls.each do |tc|
|
|
@@ -326,7 +332,7 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
326
332
|
end
|
|
327
333
|
|
|
328
334
|
#--
|
|
329
|
-
#: (Riffer::FilePart) -> Hash[Symbol, untyped]
|
|
335
|
+
#: (Riffer::Messages::FilePart) -> Hash[Symbol, untyped]
|
|
330
336
|
def convert_file_part_to_bedrock_format(file)
|
|
331
337
|
format = bedrock_format(file.media_type)
|
|
332
338
|
|
|
@@ -26,7 +26,7 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
26
26
|
|
|
27
27
|
api_key ||= Riffer.config.anthropic.api_key
|
|
28
28
|
|
|
29
|
-
@client = Anthropic::Client.new(api_key: api_key, **options)
|
|
29
|
+
@client = ::Anthropic::Client.new(api_key: api_key, **options)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
private
|
|
@@ -46,11 +46,11 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
46
46
|
messages: partitioned_messages[:conversation],
|
|
47
47
|
max_tokens: max_tokens,
|
|
48
48
|
**options.except(:tools, :max_tokens, :structured_output, :web_search)
|
|
49
|
-
}
|
|
49
|
+
} #: Hash[Symbol, untyped]
|
|
50
50
|
|
|
51
51
|
params[:system] = partitioned_messages[:system] if partitioned_messages[:system]
|
|
52
52
|
|
|
53
|
-
anthropic_tools = []
|
|
53
|
+
anthropic_tools = [] #: Array[Hash[Symbol, untyped]]
|
|
54
54
|
anthropic_tools.concat(tools.map { |t| convert_tool_to_anthropic_format(t) }) if tools && !tools.empty?
|
|
55
55
|
|
|
56
56
|
if web_search
|
|
@@ -77,18 +77,18 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
#--
|
|
80
|
-
#: (Hash[Symbol, untyped]) ->
|
|
80
|
+
#: (Hash[Symbol, untyped]) -> untyped
|
|
81
81
|
def execute_generate(params)
|
|
82
82
|
@client.messages.create(**params)
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
#--
|
|
86
|
-
#: (
|
|
86
|
+
#: (untyped) -> Riffer::Providers::TokenUsage?
|
|
87
87
|
def extract_token_usage(response)
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
message = response #: Anthropic::Models::Message
|
|
89
|
+
usage = message.usage
|
|
90
90
|
|
|
91
|
-
Riffer::TokenUsage.new(
|
|
91
|
+
Riffer::Providers::TokenUsage.new(
|
|
92
92
|
input_tokens: usage.input_tokens,
|
|
93
93
|
output_tokens: usage.output_tokens,
|
|
94
94
|
cache_creation_tokens: usage.cache_creation_input_tokens,
|
|
@@ -97,30 +97,32 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
#--
|
|
100
|
-
#: (
|
|
100
|
+
#: (untyped) -> String
|
|
101
101
|
def extract_content(response)
|
|
102
|
-
|
|
102
|
+
message = response #: Anthropic::Models::Message
|
|
103
|
+
content_blocks = message.content
|
|
103
104
|
return "" if content_blocks.nil? || content_blocks.empty?
|
|
104
105
|
|
|
105
106
|
text_content = ""
|
|
106
107
|
|
|
107
108
|
content_blocks.each do |block|
|
|
108
|
-
text_content = block.text if block.
|
|
109
|
+
text_content = block.text if block.is_a?(::Anthropic::Models::TextBlock)
|
|
109
110
|
end
|
|
110
111
|
|
|
111
112
|
text_content
|
|
112
113
|
end
|
|
113
114
|
|
|
114
115
|
#--
|
|
115
|
-
#: (
|
|
116
|
+
#: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
|
|
116
117
|
def extract_tool_calls(response)
|
|
117
|
-
|
|
118
|
+
message = response #: Anthropic::Models::Message
|
|
119
|
+
content_blocks = message.content
|
|
118
120
|
return [] if content_blocks.nil? || content_blocks.empty?
|
|
119
121
|
|
|
120
|
-
tool_calls = []
|
|
122
|
+
tool_calls = [] #: Array[Riffer::Messages::Assistant::ToolCall]
|
|
121
123
|
|
|
122
124
|
content_blocks.each do |block|
|
|
123
|
-
if block.
|
|
125
|
+
if block.is_a?(::Anthropic::Models::ToolUseBlock)
|
|
124
126
|
tool_calls << Riffer::Messages::Assistant::ToolCall.new(
|
|
125
127
|
call_id: block.id,
|
|
126
128
|
name: decode_tool_name(block.name, tools: @current_tools),
|
|
@@ -142,7 +144,7 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
142
144
|
web_search_index: nil,
|
|
143
145
|
web_search_json: nil,
|
|
144
146
|
web_search_query: nil
|
|
145
|
-
}
|
|
147
|
+
} #: Hash[Symbol, untyped]
|
|
146
148
|
|
|
147
149
|
# Workaround for anthropics/anthropic-sdk-ruby#182: force identity
|
|
148
150
|
# encoding so Net::HTTP/Zlib doesn't buffer SSE chunks until EOF.
|
|
@@ -154,24 +156,24 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
154
156
|
begin
|
|
155
157
|
stream.each do |event|
|
|
156
158
|
case event
|
|
157
|
-
when Anthropic::Models::RawContentBlockStartEvent
|
|
159
|
+
when ::Anthropic::Models::RawContentBlockStartEvent
|
|
158
160
|
handle_raw_content_block_start(event, state: current_state)
|
|
159
|
-
when Anthropic::Models::RawContentBlockDeltaEvent
|
|
161
|
+
when ::Anthropic::Models::RawContentBlockDeltaEvent
|
|
160
162
|
handle_raw_content_block_delta(event, state: current_state)
|
|
161
|
-
when Anthropic::Streaming::TextEvent
|
|
163
|
+
when ::Anthropic::Helpers::Streaming::TextEvent
|
|
162
164
|
handle_text_event(event, state: current_state, yielder: yielder)
|
|
163
|
-
when Anthropic::Streaming::ThinkingEvent
|
|
165
|
+
when ::Anthropic::Helpers::Streaming::ThinkingEvent
|
|
164
166
|
handle_thinking_event(event, state: current_state, yielder: yielder)
|
|
165
|
-
when Anthropic::Streaming::InputJsonEvent
|
|
167
|
+
when ::Anthropic::Helpers::Streaming::InputJsonEvent
|
|
166
168
|
handle_input_json_event(event, state: current_state, yielder: yielder)
|
|
167
|
-
when Anthropic::Streaming::ContentBlockStopEvent
|
|
168
|
-
|
|
169
|
-
handle_content_block_stop_text(event, state: current_state, yielder: yielder) if
|
|
170
|
-
handle_content_block_stop_tool_use(event, state: current_state, yielder: yielder) if
|
|
171
|
-
handle_content_block_stop_thinking(event, state: current_state, yielder: yielder) if
|
|
172
|
-
handle_content_block_stop_server_tool_use(event, state: current_state, yielder: yielder) if
|
|
173
|
-
handle_content_block_stop_web_search_result(event, state: current_state, yielder: yielder) if
|
|
174
|
-
when Anthropic::Streaming::MessageStopEvent
|
|
169
|
+
when ::Anthropic::Helpers::Streaming::ContentBlockStopEvent
|
|
170
|
+
block = event.content_block
|
|
171
|
+
handle_content_block_stop_text(event, state: current_state, yielder: yielder) if block.is_a?(::Anthropic::Models::TextBlock) && current_state[:text]
|
|
172
|
+
handle_content_block_stop_tool_use(event, state: current_state, yielder: yielder) if block.is_a?(::Anthropic::Models::ToolUseBlock)
|
|
173
|
+
handle_content_block_stop_thinking(event, state: current_state, yielder: yielder) if block.is_a?(::Anthropic::Models::ThinkingBlock) && current_state[:reasoning]
|
|
174
|
+
handle_content_block_stop_server_tool_use(event, state: current_state, yielder: yielder) if block.is_a?(::Anthropic::Models::ServerToolUseBlock)
|
|
175
|
+
handle_content_block_stop_web_search_result(event, state: current_state, yielder: yielder) if block.is_a?(::Anthropic::Models::WebSearchToolResultBlock)
|
|
176
|
+
when ::Anthropic::Helpers::Streaming::MessageStopEvent
|
|
175
177
|
handle_message_stop(event, accumulated_message: stream.accumulated_message, yielder: yielder)
|
|
176
178
|
end
|
|
177
179
|
end
|
|
@@ -286,13 +288,14 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
286
288
|
end
|
|
287
289
|
|
|
288
290
|
#--
|
|
289
|
-
#: (untyped, accumulated_message:
|
|
291
|
+
#: (untyped, accumulated_message: untyped, yielder: Enumerator::Yielder) -> void
|
|
290
292
|
def handle_message_stop(_event, accumulated_message:, yielder:)
|
|
291
|
-
|
|
293
|
+
message = accumulated_message #: Anthropic::Models::Message?
|
|
294
|
+
usage = message&.usage
|
|
292
295
|
return unless usage
|
|
293
296
|
|
|
294
297
|
yielder << Riffer::StreamEvents::TokenUsageDone.new(
|
|
295
|
-
token_usage: Riffer::TokenUsage.new(
|
|
298
|
+
token_usage: Riffer::Providers::TokenUsage.new(
|
|
296
299
|
input_tokens: usage.input_tokens,
|
|
297
300
|
output_tokens: usage.output_tokens,
|
|
298
301
|
cache_creation_tokens: usage.cache_creation_input_tokens,
|
|
@@ -304,8 +307,8 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
304
307
|
#--
|
|
305
308
|
#: (Array[Riffer::Messages::Base]) -> Hash[Symbol, untyped]
|
|
306
309
|
def partition_messages(messages)
|
|
307
|
-
system_prompts = []
|
|
308
|
-
conversation_messages = []
|
|
310
|
+
system_prompts = [] #: Array[Hash[Symbol, untyped]]
|
|
311
|
+
conversation_messages = [] #: Array[Hash[Symbol, untyped]]
|
|
309
312
|
|
|
310
313
|
messages.each do |message|
|
|
311
314
|
case message
|
|
@@ -342,7 +345,7 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
342
345
|
#--
|
|
343
346
|
#: (Riffer::Messages::Assistant) -> Hash[Symbol, untyped]
|
|
344
347
|
def convert_assistant_to_anthropic_format(message)
|
|
345
|
-
content = []
|
|
348
|
+
content = [] #: Array[Hash[Symbol, untyped]]
|
|
346
349
|
content << {type: "text", text: message.content} if message.content && !message.content.empty?
|
|
347
350
|
|
|
348
351
|
message.tool_calls.each do |tc|
|
|
@@ -358,7 +361,7 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
358
361
|
end
|
|
359
362
|
|
|
360
363
|
#--
|
|
361
|
-
#: (Riffer::FilePart) -> Hash[Symbol, untyped]
|
|
364
|
+
#: (Riffer::Messages::FilePart) -> Hash[Symbol, untyped]
|
|
362
365
|
def convert_file_part_to_anthropic_format(file)
|
|
363
366
|
type = file.image? ? "image" : "document"
|
|
364
367
|
|
|
@@ -17,6 +17,8 @@ require "json"
|
|
|
17
17
|
# [extract_content] extract text content from the SDK response
|
|
18
18
|
# [extract_tool_calls] extract tool calls from the SDK response
|
|
19
19
|
class Riffer::Providers::Base
|
|
20
|
+
# @rbs @current_tools: Array[singleton(Riffer::Tool)]
|
|
21
|
+
|
|
20
22
|
include Riffer::Helpers::Dependencies
|
|
21
23
|
include Riffer::Messages::Converter
|
|
22
24
|
|
|
@@ -39,10 +41,10 @@ class Riffer::Providers::Base
|
|
|
39
41
|
# Generates text using the provider.
|
|
40
42
|
#
|
|
41
43
|
#--
|
|
42
|
-
#: (?prompt: String?, ?system: String?, ?messages: Array[Hash[Symbol, untyped] | Riffer::Messages::Base]?, ?model: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::FilePart]?, **untyped) -> Riffer::Messages::Assistant
|
|
44
|
+
#: (?prompt: String?, ?system: String?, ?messages: Array[Hash[Symbol, untyped] | Riffer::Messages::Base]?, ?model: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, **untyped) -> Riffer::Messages::Assistant
|
|
43
45
|
def generate_text(prompt: nil, system: nil, messages: nil, model: nil, files: nil, **options)
|
|
44
46
|
validate_input!(prompt: prompt, system: system, messages: messages)
|
|
45
|
-
@current_tools = options[:tools] || []
|
|
47
|
+
@current_tools = options[:tools] || [] #: Array[singleton(Riffer::Tool)]
|
|
46
48
|
messages = normalize_messages(prompt: prompt, system: system, messages: messages, files: files)
|
|
47
49
|
validate_normalized_messages!(messages)
|
|
48
50
|
messages = merge_consecutive_messages(messages)
|
|
@@ -65,10 +67,10 @@ class Riffer::Providers::Base
|
|
|
65
67
|
# Streams text from the provider.
|
|
66
68
|
#
|
|
67
69
|
#--
|
|
68
|
-
#: (?prompt: String?, ?system: String?, ?messages: Array[Hash[Symbol, untyped] | Riffer::Messages::Base]?, ?model: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::FilePart]?, **untyped) -> Enumerator[Riffer::StreamEvents::Base, void]
|
|
70
|
+
#: (?prompt: String?, ?system: String?, ?messages: Array[Hash[Symbol, untyped] | Riffer::Messages::Base]?, ?model: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, **untyped) -> Enumerator[Riffer::StreamEvents::Base, void]
|
|
69
71
|
def stream_text(prompt: nil, system: nil, messages: nil, model: nil, files: nil, **options)
|
|
70
72
|
validate_input!(prompt: prompt, system: system, messages: messages)
|
|
71
|
-
@current_tools = options[:tools] || []
|
|
73
|
+
@current_tools = options[:tools] || [] #: Array[singleton(Riffer::Tool)]
|
|
72
74
|
messages = normalize_messages(prompt: prompt, system: system, messages: messages, files: files)
|
|
73
75
|
validate_normalized_messages!(messages)
|
|
74
76
|
messages = merge_consecutive_messages(messages)
|
|
@@ -87,7 +89,7 @@ class Riffer::Providers::Base
|
|
|
87
89
|
end
|
|
88
90
|
|
|
89
91
|
#--
|
|
90
|
-
#: (String, tools: Array[Riffer::Tool]) -> String
|
|
92
|
+
#: (String, tools: Array[singleton(Riffer::Tool)]) -> String
|
|
91
93
|
def decode_tool_name(wire_name, tools:)
|
|
92
94
|
tool = tools.find { |t| encode_tool_name(t.name) == wire_name }
|
|
93
95
|
tool ? tool.name : wire_name
|
|
@@ -112,7 +114,7 @@ class Riffer::Providers::Base
|
|
|
112
114
|
end
|
|
113
115
|
|
|
114
116
|
#--
|
|
115
|
-
#: (untyped) -> Riffer::TokenUsage?
|
|
117
|
+
#: (untyped) -> Riffer::Providers::TokenUsage?
|
|
116
118
|
def extract_token_usage(response)
|
|
117
119
|
raise NotImplementedError, "Subclasses must implement #extract_token_usage"
|
|
118
120
|
end
|
|
@@ -166,7 +168,7 @@ class Riffer::Providers::Base
|
|
|
166
168
|
end
|
|
167
169
|
|
|
168
170
|
#--
|
|
169
|
-
#: (prompt: String?, system: String?, messages: Array[Hash[Symbol, untyped] | Riffer::Messages::Base]?, ?files: Array[Hash[Symbol, untyped] | Riffer::FilePart]?) -> Array[Riffer::Messages::Base]
|
|
171
|
+
#: (prompt: String?, system: String?, messages: Array[Hash[Symbol, untyped] | Riffer::Messages::Base]?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?) -> Array[Riffer::Messages::Base]
|
|
170
172
|
def normalize_messages(prompt:, system:, messages:, files: nil)
|
|
171
173
|
if messages && files && !files.empty?
|
|
172
174
|
raise Riffer::ArgumentError, "cannot provide both files and messages; attach files to individual messages instead"
|
|
@@ -176,10 +178,11 @@ class Riffer::Providers::Base
|
|
|
176
178
|
return messages.map { |msg| convert_to_message_object(msg) }
|
|
177
179
|
end
|
|
178
180
|
|
|
179
|
-
result = []
|
|
181
|
+
result = [] #: Array[Riffer::Messages::Base]
|
|
180
182
|
result << Riffer::Messages::System.new(system) if system
|
|
181
183
|
file_parts = (files || []).map { |f| convert_to_file_part(f) }
|
|
182
|
-
|
|
184
|
+
prompt_text = prompt #: String
|
|
185
|
+
result << Riffer::Messages::User.new(prompt_text, files: file_parts)
|
|
183
186
|
result
|
|
184
187
|
end
|
|
185
188
|
|