riffer 0.29.0 → 0.30.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/.agents/rbs-inline.md +51 -0
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +18 -0
- data/README.md +1 -0
- data/Steepfile +2 -1
- data/docs/01_OVERVIEW.md +1 -0
- data/docs/03_AGENTS.md +1 -1
- data/docs/15_SERIALIZATION.md +103 -0
- data/lib/riffer/agent/config.rb +2 -2
- data/lib/riffer/agent/context.rb +2 -0
- data/lib/riffer/agent/response.rb +2 -0
- data/lib/riffer/agent/run.rb +2 -1
- data/lib/riffer/agent/serializer.rb +215 -0
- data/lib/riffer/agent/session.rb +2 -0
- data/lib/riffer/agent.rb +84 -18
- data/lib/riffer/evals/evaluator.rb +5 -0
- data/lib/riffer/evals/judge.rb +5 -0
- data/lib/riffer/mcp/client.rb +2 -0
- data/lib/riffer/mcp/registration.rb +4 -0
- data/lib/riffer/mcp/registry.rb +3 -0
- data/lib/riffer/messages/file_part.rb +2 -0
- data/lib/riffer/params/param.rb +84 -4
- data/lib/riffer/params.rb +34 -3
- data/lib/riffer/providers/amazon_bedrock.rb +28 -21
- data/lib/riffer/providers/anthropic.rb +13 -9
- data/lib/riffer/providers/base.rb +2 -0
- data/lib/riffer/providers/gemini.rb +4 -0
- data/lib/riffer/providers/mock.rb +4 -0
- data/lib/riffer/providers/open_ai.rb +10 -7
- data/lib/riffer/providers/open_router.rb +25 -18
- data/lib/riffer/runner/fibers.rb +2 -0
- data/lib/riffer/runner/threaded.rb +2 -0
- data/lib/riffer/skills/config.rb +5 -0
- data/lib/riffer/skills/context.rb +3 -0
- data/lib/riffer/skills/filesystem_backend.rb +3 -0
- data/lib/riffer/tools/response.rb +2 -0
- data/lib/riffer/tools/runtime.rb +2 -0
- data/lib/riffer/tools/toolable.rb +7 -0
- data/lib/riffer/version.rb +1 -1
- data/lib/riffer.rb +2 -0
- data/sig/_private/anthropic.rbs +16 -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/generated/riffer/agent/config.rbs +3 -3
- data/sig/generated/riffer/agent/context.rbs +2 -0
- data/sig/generated/riffer/agent/response.rbs +2 -0
- data/sig/generated/riffer/agent/serializer.rbs +132 -0
- data/sig/generated/riffer/agent/session.rbs +2 -0
- data/sig/generated/riffer/agent.rbs +67 -11
- data/sig/generated/riffer/evals/evaluator.rbs +8 -0
- data/sig/generated/riffer/evals/judge.rbs +8 -0
- 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/file_part.rbs +2 -0
- data/sig/generated/riffer/params/param.rbs +46 -5
- data/sig/generated/riffer/params.rbs +25 -6
- data/sig/generated/riffer/providers/amazon_bedrock.rbs +20 -20
- data/sig/generated/riffer/providers/anthropic.rbs +10 -10
- data/sig/generated/riffer/providers/base.rbs +2 -0
- data/sig/generated/riffer/providers/gemini.rbs +6 -0
- data/sig/generated/riffer/providers/mock.rbs +6 -0
- data/sig/generated/riffer/providers/open_ai.rbs +8 -8
- data/sig/generated/riffer/providers/open_router.rbs +16 -16
- data/sig/generated/riffer/runner/fibers.rbs +2 -0
- data/sig/generated/riffer/runner/threaded.rbs +2 -0
- data/sig/generated/riffer/skills/config.rbs +8 -0
- data/sig/generated/riffer/skills/context.rbs +4 -0
- data/sig/generated/riffer/skills/filesystem_backend.rbs +4 -0
- data/sig/generated/riffer/tools/response.rbs +2 -0
- data/sig/generated/riffer/tools/runtime.rbs +2 -0
- data/sig/generated/riffer/tools/toolable.rbs +12 -0
- 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/agent/serializer.rbs +5 -0
- data/sig/manual/riffer/helpers/call_or_value.rbs +5 -0
- data/sig/manual/riffer/tools/toolable.rbs +6 -0
- metadata +20 -11
- data/sig/stubs/agent_ivars.rbs +0 -7
- data/sig/stubs/extend_self.rbs +0 -11
- data/sig/stubs/lib_ivars.rbs +0 -101
- data/sig/stubs/provider_ivars.rbs +0 -36
- data/sig/stubs/provider_sdk_methods.rbs +0 -50
- /data/sig/{stubs → _private}/async.rbs +0 -0
- /data/sig/{stubs → _private}/aws-sdk-core/seahorse_request_context.rbs +0 -0
- /data/sig/{stubs → _private}/aws-sdk-core/static_token_provider.rbs +0 -0
- /data/sig/{stubs/mcp_sdk.rbs → _private/mcp.rbs} +0 -0
- /data/sig/{stubs → _private}/zeitwerk.rbs +0 -0
data/lib/riffer/agent.rb
CHANGED
|
@@ -19,6 +19,8 @@ require "json"
|
|
|
19
19
|
# agent.generate('Hello!')
|
|
20
20
|
#
|
|
21
21
|
class Riffer::Agent
|
|
22
|
+
# @rbs self.@config: Riffer::Agent::Config?
|
|
23
|
+
|
|
22
24
|
include Riffer::Messages::Converter
|
|
23
25
|
extend Riffer::Helpers::ClassNameConverter
|
|
24
26
|
|
|
@@ -100,13 +102,19 @@ class Riffer::Agent
|
|
|
100
102
|
|
|
101
103
|
# Gets or sets the maximum number of LLM call steps in the tool-use loop.
|
|
102
104
|
#
|
|
103
|
-
# Defaults to Riffer::Agent::Config::DEFAULT_MAX_STEPS (16). Set to
|
|
104
|
-
#
|
|
105
|
+
# Defaults to Riffer::Agent::Config::DEFAULT_MAX_STEPS (16). Set to +nil+
|
|
106
|
+
# for unlimited steps. The splat distinguishes a getter call (no argument)
|
|
107
|
+
# from setting the limit to +nil+.
|
|
108
|
+
#
|
|
109
|
+
# max_steps # reads the current limit
|
|
110
|
+
# max_steps 8 # cap the loop at 8 steps
|
|
111
|
+
# max_steps nil # unlimited
|
|
105
112
|
#
|
|
106
113
|
#--
|
|
107
|
-
#: (
|
|
108
|
-
def self.max_steps(value
|
|
109
|
-
|
|
114
|
+
#: (*Numeric?) -> Numeric?
|
|
115
|
+
def self.max_steps(*value)
|
|
116
|
+
return config.max_steps if value.empty?
|
|
117
|
+
config.max_steps = value.first
|
|
110
118
|
end
|
|
111
119
|
|
|
112
120
|
# Gets or sets the tools used by this agent.
|
|
@@ -204,6 +212,30 @@ class Riffer::Agent
|
|
|
204
212
|
new(context: context).stream(prompt, files: files)
|
|
205
213
|
end
|
|
206
214
|
|
|
215
|
+
# Reconstructs a runnable agent from a wire dict produced by +#to_h+.
|
|
216
|
+
#
|
|
217
|
+
# Delegates to Riffer::Agent::Serializer.from_h. See it for the
|
|
218
|
+
# +tool_resolver+ / +tool_runtime+ injection points and what does not
|
|
219
|
+
# transfer.
|
|
220
|
+
#
|
|
221
|
+
#--
|
|
222
|
+
#: (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
223
|
+
def self.from_h(hash, context: nil, tool_resolver: Riffer::Agent::Serializer::DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
|
|
224
|
+
Riffer::Agent::Serializer.from_h(hash, context: context, tool_resolver: tool_resolver, tool_runtime: tool_runtime)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Reconstructs a runnable agent from a JSON string produced by +#to_json+.
|
|
228
|
+
#
|
|
229
|
+
# Delegates to Riffer::Agent::Serializer.from_json, which parses the JSON
|
|
230
|
+
# (with symbol keys) for you. See Riffer::Agent::Serializer.from_h for the
|
|
231
|
+
# +tool_resolver+ / +tool_runtime+ injection points.
|
|
232
|
+
#
|
|
233
|
+
#--
|
|
234
|
+
#: (String, ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
235
|
+
def self.from_json(json, context: nil, tool_resolver: Riffer::Agent::Serializer::DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
|
|
236
|
+
Riffer::Agent::Serializer.from_json(json, context: context, tool_resolver: tool_resolver, tool_runtime: tool_runtime)
|
|
237
|
+
end
|
|
238
|
+
|
|
207
239
|
# Registers a guardrail for input, output, or both phases.
|
|
208
240
|
#
|
|
209
241
|
# [phase] :before, :after, or :around.
|
|
@@ -259,6 +291,11 @@ class Riffer::Agent
|
|
|
259
291
|
# reserved and cannot be passed by the caller.
|
|
260
292
|
attr_reader :context #: Riffer::Agent::Context
|
|
261
293
|
|
|
294
|
+
# The resolved provider name (the part before "provider/"), e.g. +"openai"+.
|
|
295
|
+
# Resolved eagerly at +Agent.new+ alongside +model_name+; together they
|
|
296
|
+
# form the provider-neutral model identifier the agent serializes.
|
|
297
|
+
attr_reader :provider_name #: String
|
|
298
|
+
|
|
262
299
|
# The resolved model name (the part after "provider/"), used as the model
|
|
263
300
|
# argument on every LLM call. Resolved eagerly at +Agent.new+.
|
|
264
301
|
attr_reader :model_name #: String
|
|
@@ -306,10 +343,10 @@ class Riffer::Agent
|
|
|
306
343
|
@config = config || self.class.config
|
|
307
344
|
@context = Riffer::Agent::Context.new(context || {})
|
|
308
345
|
|
|
309
|
-
|
|
310
|
-
@provider =
|
|
346
|
+
@provider_name, @model_name = resolve_provider_and_model
|
|
347
|
+
@provider = build_provider
|
|
311
348
|
|
|
312
|
-
@context.skills = resolve_skills
|
|
349
|
+
@context.skills = resolve_skills
|
|
313
350
|
|
|
314
351
|
@structured_output = resolve_structured_output
|
|
315
352
|
@tools = resolve_tools
|
|
@@ -374,6 +411,25 @@ class Riffer::Agent
|
|
|
374
411
|
throw :riffer_interrupt, reason
|
|
375
412
|
end
|
|
376
413
|
|
|
414
|
+
# Snapshots this resolved agent into a self-contained, provider-neutral
|
|
415
|
+
# wire dict. Delegates to Riffer::Agent::Serializer.to_h.
|
|
416
|
+
#
|
|
417
|
+
#--
|
|
418
|
+
#: () -> Hash[Symbol, untyped]
|
|
419
|
+
def to_h
|
|
420
|
+
Riffer::Agent::Serializer.to_h(agent: self)
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
# Snapshots this resolved agent into a wire JSON string. Delegates to
|
|
424
|
+
# Riffer::Agent::Serializer.to_json. The +*+ absorbs the JSON generator
|
|
425
|
+
# state argument so <tt>JSON.generate(agent)</tt> works too.
|
|
426
|
+
#
|
|
427
|
+
#--
|
|
428
|
+
#: (*untyped) -> String
|
|
429
|
+
def to_json(*)
|
|
430
|
+
Riffer::Agent::Serializer.to_json(agent: self)
|
|
431
|
+
end
|
|
432
|
+
|
|
377
433
|
private
|
|
378
434
|
|
|
379
435
|
#--
|
|
@@ -393,13 +449,13 @@ class Riffer::Agent
|
|
|
393
449
|
end
|
|
394
450
|
|
|
395
451
|
# Resolves +Config#model+ to a "provider/model" string (calling the Proc
|
|
396
|
-
# form against +@context+)
|
|
452
|
+
# form against +@context+) and parses it.
|
|
397
453
|
#
|
|
398
|
-
# Returns +[
|
|
399
|
-
#
|
|
454
|
+
# Returns +[provider_name, model_name]+. Raises Riffer::ArgumentError on an
|
|
455
|
+
# invalid model string.
|
|
400
456
|
#
|
|
401
457
|
#--
|
|
402
|
-
#: () -> [
|
|
458
|
+
#: () -> [String, String]
|
|
403
459
|
def resolve_provider_and_model
|
|
404
460
|
model_string = Riffer::Helpers::CallOrValue.resolve(@config.model, context: @context)
|
|
405
461
|
raise Riffer::ArgumentError, "Invalid model string: #{model_string}" unless model_string.is_a?(String)
|
|
@@ -410,18 +466,28 @@ class Riffer::Agent
|
|
|
410
466
|
raise Riffer::ArgumentError, "Invalid model string: #{model_string}"
|
|
411
467
|
end
|
|
412
468
|
|
|
413
|
-
|
|
414
|
-
|
|
469
|
+
[provider_name, model_name]
|
|
470
|
+
end
|
|
415
471
|
|
|
416
|
-
|
|
472
|
+
# Builds the provider client from the resolved +@provider_name+ and the
|
|
473
|
+
# configured +provider_options+.
|
|
474
|
+
#
|
|
475
|
+
# Raises Riffer::ArgumentError on an unregistered provider.
|
|
476
|
+
#
|
|
477
|
+
#--
|
|
478
|
+
#: () -> Riffer::Providers::Base
|
|
479
|
+
def build_provider
|
|
480
|
+
provider_class = Riffer::Providers::Repository.find(@provider_name)
|
|
481
|
+
raise Riffer::ArgumentError, "Provider not found: #{@provider_name}" unless provider_class
|
|
482
|
+
provider_class.new(**@config.provider_options)
|
|
417
483
|
end
|
|
418
484
|
|
|
419
485
|
# Resolves the skills backend, lists skills, and selects an adapter.
|
|
420
486
|
# Returns nil if skills are unconfigured or the backend is empty.
|
|
421
487
|
#
|
|
422
488
|
#--
|
|
423
|
-
#: (
|
|
424
|
-
def resolve_skills
|
|
489
|
+
#: () -> Riffer::Skills::Context?
|
|
490
|
+
def resolve_skills
|
|
425
491
|
skills_config = @config.skills_config
|
|
426
492
|
return nil unless skills_config
|
|
427
493
|
|
|
@@ -432,7 +498,7 @@ class Riffer::Agent
|
|
|
432
498
|
return nil if backend.list_skills.empty?
|
|
433
499
|
|
|
434
500
|
skills = backend.list_skills.to_h { |s| [s.name, s] }
|
|
435
|
-
adapter_class = skills_config.adapter ||
|
|
501
|
+
adapter_class = skills_config.adapter || @provider.class.skills_adapter(@model_name)
|
|
436
502
|
skill_activate_tool_class = skills_config.activate_tool || Riffer.config.skills.default_activate_tool
|
|
437
503
|
|
|
438
504
|
skills_context = Riffer::Skills::Context.new(
|
|
@@ -16,6 +16,11 @@
|
|
|
16
16
|
# end
|
|
17
17
|
#
|
|
18
18
|
class Riffer::Evals::Evaluator
|
|
19
|
+
# @rbs self.@instructions: String?
|
|
20
|
+
# @rbs self.@higher_is_better: bool?
|
|
21
|
+
# @rbs self.@judge_model: String?
|
|
22
|
+
# @rbs @judge: Riffer::Evals::Judge?
|
|
23
|
+
|
|
19
24
|
class << self
|
|
20
25
|
# Gets or sets the evaluation instructions (criteria and scoring rubric).
|
|
21
26
|
#
|
data/lib/riffer/evals/judge.rb
CHANGED
|
@@ -19,6 +19,11 @@ require "json"
|
|
|
19
19
|
# result[:reason] # => "The response is relevant..."
|
|
20
20
|
#
|
|
21
21
|
class Riffer::Evals::Judge
|
|
22
|
+
# @rbs @provider_options: Hash[Symbol, untyped]
|
|
23
|
+
# @rbs @provider_instance: Riffer::Providers::Base?
|
|
24
|
+
# @rbs @provider_name: String?
|
|
25
|
+
# @rbs @model_name: String?
|
|
26
|
+
|
|
22
27
|
# Internal tool for structured evaluation output.
|
|
23
28
|
class EvaluationTool < Riffer::Tool
|
|
24
29
|
identifier "evaluation"
|
data/lib/riffer/mcp/client.rb
CHANGED
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
# +tools/list+ call, then generates tool classes.
|
|
8
8
|
#
|
|
9
9
|
class Riffer::Mcp::Registration
|
|
10
|
+
# @rbs @cancelled: bool
|
|
11
|
+
# @rbs @tools: Array[singleton(Riffer::Tool)]
|
|
12
|
+
# @rbs @mutex: Thread::Mutex
|
|
13
|
+
|
|
10
14
|
# The manifest that describes this server.
|
|
11
15
|
attr_reader :manifest #: Riffer::Mcp::Manifest
|
|
12
16
|
|
data/lib/riffer/mcp/registry.rb
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
# Keyed by manifest name. All public methods are mutex-guarded.
|
|
7
7
|
#
|
|
8
8
|
module Riffer::Mcp::Registry
|
|
9
|
+
# @rbs self.@mutex: Thread::Mutex
|
|
10
|
+
# @rbs self.@store: Hash[String, Riffer::Mcp::Registration]
|
|
11
|
+
|
|
9
12
|
@mutex = Mutex.new
|
|
10
13
|
@store = {} #: Hash[String, Riffer::Mcp::Registration]
|
|
11
14
|
|
data/lib/riffer/params/param.rb
CHANGED
|
@@ -18,19 +18,90 @@ class Riffer::Params::Param
|
|
|
18
18
|
}.freeze #: Hash[Module, String]
|
|
19
19
|
|
|
20
20
|
# Primitive types allowed for the <tt>of:</tt> keyword on Array params
|
|
21
|
-
PRIMITIVE_TYPES = (TYPE_MAPPINGS.keys - [Array, Hash]).freeze #: Array[
|
|
21
|
+
PRIMITIVE_TYPES = (TYPE_MAPPINGS.keys - [Array, Hash]).freeze #: Array[Module]
|
|
22
|
+
|
|
23
|
+
# Maps JSON Schema type strings back to Ruby types. The inverse of
|
|
24
|
+
# TYPE_MAPPINGS, collapsing the three boolean spellings onto
|
|
25
|
+
# Riffer::Params::Boolean. Used by +from_json_schema+.
|
|
26
|
+
JSON_TYPE_MAPPINGS = {
|
|
27
|
+
"string" => String,
|
|
28
|
+
"integer" => Integer,
|
|
29
|
+
"number" => Float,
|
|
30
|
+
"boolean" => Riffer::Params::Boolean,
|
|
31
|
+
"array" => Array,
|
|
32
|
+
"object" => Hash
|
|
33
|
+
}.freeze #: Hash[String, Module]
|
|
22
34
|
|
|
23
35
|
attr_reader :name #: Symbol
|
|
24
|
-
attr_reader :type #:
|
|
36
|
+
attr_reader :type #: Module
|
|
25
37
|
attr_reader :required #: bool
|
|
26
38
|
attr_reader :description #: String?
|
|
27
39
|
attr_reader :enum #: Array[untyped]?
|
|
28
40
|
attr_reader :default #: untyped
|
|
29
|
-
attr_reader :item_type #:
|
|
41
|
+
attr_reader :item_type #: Module?
|
|
30
42
|
attr_reader :nested_params #: Riffer::Params?
|
|
31
43
|
|
|
32
44
|
#--
|
|
33
|
-
|
|
45
|
+
# Reconstructs a Param from a single JSON Schema property.
|
|
46
|
+
#
|
|
47
|
+
# [name] the parameter name (Symbol).
|
|
48
|
+
# [schema] the property's JSON Schema (Symbol-keyed).
|
|
49
|
+
# [required] whether the property appeared in the parent's +required+ list.
|
|
50
|
+
#
|
|
51
|
+
# Raises Riffer::ArgumentError on a type outside the Params-expressible subset.
|
|
52
|
+
#
|
|
53
|
+
#--
|
|
54
|
+
#: (Symbol, Hash[Symbol, untyped], required: bool) -> Riffer::Params::Param
|
|
55
|
+
def self.from_json_schema(name, schema, required:)
|
|
56
|
+
ruby_type = json_type_to_ruby(schema[:type])
|
|
57
|
+
item_type, nested = resolve_nesting(ruby_type, schema)
|
|
58
|
+
|
|
59
|
+
new(
|
|
60
|
+
name: name,
|
|
61
|
+
type: ruby_type,
|
|
62
|
+
required: required,
|
|
63
|
+
description: schema[:description],
|
|
64
|
+
enum: schema[:enum],
|
|
65
|
+
default: schema[:default],
|
|
66
|
+
item_type: item_type,
|
|
67
|
+
nested_params: nested
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Resolves the +[item_type, nested_params]+ pair for a reconstructed Param:
|
|
72
|
+
# a nested Params for object / array-of-object schemas, an +item_type+ for
|
|
73
|
+
# typed primitive arrays, and +nil+ for everything else.
|
|
74
|
+
#
|
|
75
|
+
#--
|
|
76
|
+
#: (Module, Hash[Symbol, untyped]) -> [Module?, Riffer::Params?]
|
|
77
|
+
def self.resolve_nesting(ruby_type, schema)
|
|
78
|
+
return [nil, Riffer::Params.from_json_schema(schema)] if ruby_type == Hash && schema[:properties]
|
|
79
|
+
return [nil, nil] unless ruby_type == Array
|
|
80
|
+
|
|
81
|
+
items = schema[:items]
|
|
82
|
+
return [nil, nil] unless items.is_a?(Hash)
|
|
83
|
+
return [nil, Riffer::Params.from_json_schema(items)] if items[:properties]
|
|
84
|
+
|
|
85
|
+
[json_type_to_ruby(items[:type]), nil]
|
|
86
|
+
end
|
|
87
|
+
private_class_method :resolve_nesting
|
|
88
|
+
|
|
89
|
+
# Resolves a JSON Schema +type+ (a String, or a <tt>[type, "null"]</tt>
|
|
90
|
+
# union) back to its Ruby type. Returns a Module because
|
|
91
|
+
# Riffer::Params::Boolean is a Module, not a Class — the same widening the
|
|
92
|
+
# +type+ attribute uses. Raises Riffer::ArgumentError on a type outside the
|
|
93
|
+
# Params-expressible subset (the block runs only for an unmapped type).
|
|
94
|
+
#
|
|
95
|
+
#--
|
|
96
|
+
#: (untyped) -> Module
|
|
97
|
+
def self.json_type_to_ruby(type)
|
|
98
|
+
key = type.is_a?(Array) ? type.find { |t| t != "null" } : type
|
|
99
|
+
JSON_TYPE_MAPPINGS.fetch(key) { raise Riffer::ArgumentError, "Unsupported JSON Schema type: #{type.inspect}" }
|
|
100
|
+
end
|
|
101
|
+
private_class_method :json_type_to_ruby
|
|
102
|
+
|
|
103
|
+
#--
|
|
104
|
+
#: (name: Symbol, type: Module, required: bool, ?description: String?, ?enum: Array[untyped]?, ?default: untyped, ?item_type: Module?, ?nested_params: Riffer::Params?) -> void
|
|
34
105
|
def initialize(name:, type:, required:, description: nil, enum: nil, default: nil, item_type: nil, nested_params: nil)
|
|
35
106
|
@name = name.to_sym
|
|
36
107
|
@type = type
|
|
@@ -74,6 +145,11 @@ class Riffer::Params::Param
|
|
|
74
145
|
# constraint from the null type, since providers like Anthropic reject
|
|
75
146
|
# <tt>{"type": ["string", "null"], "enum": [...]}</tt>.
|
|
76
147
|
#
|
|
148
|
+
# In non-strict mode a +default+ is emitted when set (a standard JSON
|
|
149
|
+
# Schema keyword), making the schema a lossless source for
|
|
150
|
+
# +Riffer::Params.from_json_schema+. Strict mode omits it, since strict
|
|
151
|
+
# providers reject the keyword.
|
|
152
|
+
#
|
|
77
153
|
#--
|
|
78
154
|
#: (?strict: bool) -> Hash[Symbol, untyped]
|
|
79
155
|
def to_json_schema(strict: false)
|
|
@@ -91,6 +167,10 @@ class Riffer::Params::Param
|
|
|
91
167
|
schema = {type: type} #: Hash[Symbol, untyped]
|
|
92
168
|
schema[:description] = description if description
|
|
93
169
|
schema[:enum] = enum if enum
|
|
170
|
+
# Strict providers reject the +default+ keyword; emit it only in
|
|
171
|
+
# non-strict mode, where it makes the schema a lossless round-trip
|
|
172
|
+
# source for +from_json_schema+.
|
|
173
|
+
schema[:default] = default unless strict || default.nil?
|
|
94
174
|
|
|
95
175
|
if self.type == Array && nested_params
|
|
96
176
|
schema[:items] = nested_params.to_json_schema(strict: strict)
|
data/lib/riffer/params.rb
CHANGED
|
@@ -20,10 +20,41 @@ class Riffer::Params
|
|
|
20
20
|
@parameters = []
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
# Reconstructs a Params from a JSON Schema object (the inverse of
|
|
24
|
+
# +to_json_schema(strict: false)+).
|
|
25
|
+
#
|
|
26
|
+
# Accepts the Symbol-keyed object schema produced by +to_json_schema+
|
|
27
|
+
# (property-name keys may be String or Symbol — both are normalized).
|
|
28
|
+
# Reconstructs types, +required+, +description+, +enum+, +default+,
|
|
29
|
+
# typed-array +item_type+, and nested object/array Params recursively.
|
|
30
|
+
#
|
|
31
|
+
# Round-trips losslessly with +to_json_schema(strict: false)+ over the
|
|
32
|
+
# Params-expressible subset of JSON Schema. Raises Riffer::ArgumentError
|
|
33
|
+
# on a schema using features outside that subset.
|
|
34
|
+
#
|
|
35
|
+
# schema = params.to_json_schema(strict: false)
|
|
36
|
+
# Riffer::Params.from_json_schema(schema) # => equivalent Riffer::Params
|
|
37
|
+
#
|
|
38
|
+
#--
|
|
39
|
+
#: (Hash[Symbol, untyped]) -> Riffer::Params
|
|
40
|
+
def self.from_json_schema(schema)
|
|
41
|
+
params = new
|
|
42
|
+
properties = schema[:properties] || {}
|
|
43
|
+
required = (schema[:required] || []).map { |key| key.to_s }
|
|
44
|
+
|
|
45
|
+
properties.each do |name, property_schema|
|
|
46
|
+
params.parameters << Riffer::Params::Param.from_json_schema(
|
|
47
|
+
name.to_sym, property_schema, required: required.include?(name.to_s)
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
params
|
|
52
|
+
end
|
|
53
|
+
|
|
23
54
|
# Defines a required parameter.
|
|
24
55
|
#
|
|
25
56
|
#--
|
|
26
|
-
#: (Symbol,
|
|
57
|
+
#: (Symbol, Module, ?description: String?, ?enum: Array[untyped]?, ?of: Module?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> void
|
|
27
58
|
def required(name, type, description: nil, enum: nil, of: nil, &block)
|
|
28
59
|
nested = build_nested(type, of, &block)
|
|
29
60
|
@parameters << Riffer::Params::Param.new(
|
|
@@ -40,7 +71,7 @@ class Riffer::Params
|
|
|
40
71
|
# Defines an optional parameter.
|
|
41
72
|
#
|
|
42
73
|
#--
|
|
43
|
-
#: (Symbol,
|
|
74
|
+
#: (Symbol, Module, ?description: String?, ?enum: Array[untyped]?, ?default: untyped, ?of: Module?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> void
|
|
44
75
|
def optional(name, type, description: nil, enum: nil, default: nil, of: nil, &block)
|
|
45
76
|
nested = build_nested(type, of, &block)
|
|
46
77
|
@parameters << Riffer::Params::Param.new(
|
|
@@ -126,7 +157,7 @@ class Riffer::Params
|
|
|
126
157
|
private
|
|
127
158
|
|
|
128
159
|
#--
|
|
129
|
-
#: (
|
|
160
|
+
#: (Module, Module?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> Riffer::Params?
|
|
130
161
|
def build_nested(type, of, &block)
|
|
131
162
|
if of && block
|
|
132
163
|
raise Riffer::ArgumentError, "cannot use both of: and a block"
|
|
@@ -92,15 +92,16 @@ 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
|
-
|
|
103
|
+
typed_response = response #: Aws::BedrockRuntime::Client::_ConverseResponseSuccess
|
|
104
|
+
usage = typed_response.usage
|
|
104
105
|
|
|
105
106
|
Riffer::Providers::TokenUsage.new(
|
|
106
107
|
input_tokens: usage.input_tokens,
|
|
@@ -111,9 +112,10 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
111
112
|
end
|
|
112
113
|
|
|
113
114
|
#--
|
|
114
|
-
#: (
|
|
115
|
+
#: (untyped) -> String
|
|
115
116
|
def extract_content(response)
|
|
116
|
-
|
|
117
|
+
typed_response = response #: Aws::BedrockRuntime::Client::_ConverseResponseSuccess
|
|
118
|
+
content_blocks = typed_response.output&.message&.content
|
|
117
119
|
return "" if content_blocks.nil? || content_blocks.empty?
|
|
118
120
|
|
|
119
121
|
text_content = ""
|
|
@@ -126,9 +128,10 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
126
128
|
end
|
|
127
129
|
|
|
128
130
|
#--
|
|
129
|
-
#: (
|
|
131
|
+
#: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
|
|
130
132
|
def extract_tool_calls(response)
|
|
131
|
-
|
|
133
|
+
typed_response = response #: Aws::BedrockRuntime::Client::_ConverseResponseSuccess
|
|
134
|
+
content_blocks = typed_response.output&.message&.content
|
|
132
135
|
return [] if content_blocks.nil? || content_blocks.empty?
|
|
133
136
|
|
|
134
137
|
tool_calls = [] #: Array[Riffer::Messages::Assistant::ToolCall]
|
|
@@ -195,28 +198,31 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
195
198
|
end
|
|
196
199
|
|
|
197
200
|
#--
|
|
198
|
-
#: (
|
|
201
|
+
#: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
|
|
199
202
|
def handle_content_block_start_tool_use(event, state:, yielder:)
|
|
203
|
+
typed_event = event #: Aws::BedrockRuntime::Types::ContentBlockStartEvent
|
|
200
204
|
state[:tool_call] = {
|
|
201
|
-
id:
|
|
202
|
-
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),
|
|
203
207
|
arguments: ""
|
|
204
208
|
}
|
|
205
209
|
end
|
|
206
210
|
|
|
207
211
|
#--
|
|
208
|
-
#: (
|
|
212
|
+
#: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
|
|
209
213
|
def handle_content_block_delta_text_delta(event, state:, yielder:)
|
|
210
|
-
|
|
214
|
+
typed_event = event #: Aws::BedrockRuntime::Types::ContentBlockDeltaEvent
|
|
215
|
+
delta_text = typed_event.delta.text
|
|
211
216
|
state[:text] ||= ""
|
|
212
217
|
state[:text] += delta_text
|
|
213
218
|
yielder << Riffer::StreamEvents::TextDelta.new(delta_text)
|
|
214
219
|
end
|
|
215
220
|
|
|
216
221
|
#--
|
|
217
|
-
#: (
|
|
222
|
+
#: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
|
|
218
223
|
def handle_content_block_delta_tool_use(event, state:, yielder:)
|
|
219
|
-
|
|
224
|
+
typed_event = event #: Aws::BedrockRuntime::Types::ContentBlockDeltaEvent
|
|
225
|
+
input_delta = typed_event.delta.tool_use.input
|
|
220
226
|
|
|
221
227
|
state[:tool_call][:arguments] += input_delta
|
|
222
228
|
|
|
@@ -228,14 +234,14 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
228
234
|
end
|
|
229
235
|
|
|
230
236
|
#--
|
|
231
|
-
#: (
|
|
237
|
+
#: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
|
|
232
238
|
def handle_content_block_stop_text_delta(_event, state:, yielder:)
|
|
233
239
|
yielder << Riffer::StreamEvents::TextDone.new(state[:text])
|
|
234
240
|
state[:text] = nil
|
|
235
241
|
end
|
|
236
242
|
|
|
237
243
|
#--
|
|
238
|
-
#: (
|
|
244
|
+
#: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
|
|
239
245
|
def handle_content_block_stop_tool_use(_event, state:, yielder:)
|
|
240
246
|
tool_call = state[:tool_call]
|
|
241
247
|
yielder << Riffer::StreamEvents::ToolCallDone.new(
|
|
@@ -248,14 +254,15 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
248
254
|
end
|
|
249
255
|
|
|
250
256
|
#--
|
|
251
|
-
#: (
|
|
257
|
+
#: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
|
|
252
258
|
def handle_metadata_usage(event, state:, yielder:)
|
|
259
|
+
typed_event = event #: Aws::BedrockRuntime::Types::ConverseStreamMetadataEvent
|
|
253
260
|
yielder << Riffer::StreamEvents::TokenUsageDone.new(
|
|
254
261
|
token_usage: Riffer::Providers::TokenUsage.new(
|
|
255
|
-
input_tokens:
|
|
256
|
-
output_tokens:
|
|
257
|
-
cache_creation_tokens:
|
|
258
|
-
cache_read_tokens:
|
|
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
|
|
259
266
|
)
|
|
260
267
|
)
|
|
261
268
|
end
|
|
@@ -77,15 +77,16 @@ 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
|
-
|
|
88
|
+
message = response #: Anthropic::Models::Message
|
|
89
|
+
usage = message.usage
|
|
89
90
|
|
|
90
91
|
Riffer::Providers::TokenUsage.new(
|
|
91
92
|
input_tokens: usage.input_tokens,
|
|
@@ -96,9 +97,10 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
96
97
|
end
|
|
97
98
|
|
|
98
99
|
#--
|
|
99
|
-
#: (
|
|
100
|
+
#: (untyped) -> String
|
|
100
101
|
def extract_content(response)
|
|
101
|
-
|
|
102
|
+
message = response #: Anthropic::Models::Message
|
|
103
|
+
content_blocks = message.content
|
|
102
104
|
return "" if content_blocks.nil? || content_blocks.empty?
|
|
103
105
|
|
|
104
106
|
text_content = ""
|
|
@@ -111,9 +113,10 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
111
113
|
end
|
|
112
114
|
|
|
113
115
|
#--
|
|
114
|
-
#: (
|
|
116
|
+
#: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
|
|
115
117
|
def extract_tool_calls(response)
|
|
116
|
-
|
|
118
|
+
message = response #: Anthropic::Models::Message
|
|
119
|
+
content_blocks = message.content
|
|
117
120
|
return [] if content_blocks.nil? || content_blocks.empty?
|
|
118
121
|
|
|
119
122
|
tool_calls = [] #: Array[Riffer::Messages::Assistant::ToolCall]
|
|
@@ -285,9 +288,10 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
285
288
|
end
|
|
286
289
|
|
|
287
290
|
#--
|
|
288
|
-
#: (untyped, accumulated_message:
|
|
291
|
+
#: (untyped, accumulated_message: untyped, yielder: Enumerator::Yielder) -> void
|
|
289
292
|
def handle_message_stop(_event, accumulated_message:, yielder:)
|
|
290
|
-
|
|
293
|
+
message = accumulated_message #: Anthropic::Models::Message?
|
|
294
|
+
usage = message&.usage
|
|
291
295
|
return unless usage
|
|
292
296
|
|
|
293
297
|
yielder << Riffer::StreamEvents::TokenUsageDone.new(
|
|
@@ -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
|
|
|
@@ -8,6 +8,10 @@ require "uri"
|
|
|
8
8
|
|
|
9
9
|
# Google Gemini provider for Gemini models via the Gemini REST API.
|
|
10
10
|
class Riffer::Providers::Gemini < Riffer::Providers::Base
|
|
11
|
+
# @rbs @api_key: String?
|
|
12
|
+
# @rbs @open_timeout: Integer
|
|
13
|
+
# @rbs @read_timeout: Integer
|
|
14
|
+
|
|
11
15
|
BASE_URI = URI("https://generativelanguage.googleapis.com") #: URI::Generic
|
|
12
16
|
VALID_MODEL_PATTERN = /\A[a-zA-Z0-9._-]+\z/ #: Regexp
|
|
13
17
|
DEFAULT_OPEN_TIMEOUT = 10 #: Integer
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
#
|
|
6
6
|
# No external gems required.
|
|
7
7
|
class Riffer::Providers::Mock < Riffer::Providers::Base
|
|
8
|
+
# @rbs @responses: Array[Hash[Symbol, untyped]]
|
|
9
|
+
# @rbs @current_index: Integer
|
|
10
|
+
# @rbs @stubbed_responses: Array[Hash[Symbol, untyped]]
|
|
11
|
+
|
|
8
12
|
# Returns the preferred skill adapter for the given mock model.
|
|
9
13
|
#
|
|
10
14
|
# Mock is used to stand in for any real provider in tests, so the model
|