riffer 0.6.0 → 0.7.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/.release-please-manifest.json +1 -1
- data/AGENTS.md +315 -0
- data/CHANGELOG.md +14 -0
- data/README.md +82 -1
- data/lib/riffer/agent.rb +135 -26
- data/lib/riffer/messages/tool.rb +14 -3
- data/lib/riffer/providers/amazon_bedrock.rb +120 -9
- data/lib/riffer/providers/base.rb +8 -8
- data/lib/riffer/providers/open_ai.rb +112 -40
- data/lib/riffer/providers/test.rb +60 -24
- data/lib/riffer/stream_events/tool_call_delta.rb +28 -0
- data/lib/riffer/stream_events/tool_call_done.rb +30 -0
- data/lib/riffer/tool.rb +88 -0
- data/lib/riffer/tools/param.rb +65 -0
- data/lib/riffer/tools/params.rb +112 -0
- data/lib/riffer/tools.rb +4 -0
- data/lib/riffer/version.rb +1 -1
- data/lib/riffer.rb +4 -0
- metadata +8 -2
- data/CLAUDE.md +0 -73
data/lib/riffer/tool.rb
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Riffer::Tool is the base class for all tools in the Riffer framework.
|
|
4
|
+
#
|
|
5
|
+
# Provides a DSL for defining tool description and parameters.
|
|
6
|
+
#
|
|
7
|
+
# @abstract Subclasses must implement the `call` method.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# class WeatherLookupTool < Riffer::Tool
|
|
11
|
+
# description "Provides current weather information for a specified city."
|
|
12
|
+
#
|
|
13
|
+
# params do
|
|
14
|
+
# required :city, String, description: "The city to look up"
|
|
15
|
+
# optional :units, String, default: "celsius"
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# def call(context:, city:, units: nil)
|
|
19
|
+
# # Implementation
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# @see Riffer::Agent
|
|
24
|
+
class Riffer::Tool
|
|
25
|
+
class << self
|
|
26
|
+
include Riffer::Helpers::ClassNameConverter
|
|
27
|
+
|
|
28
|
+
# Gets or sets the tool description
|
|
29
|
+
# @param value [String, nil] the description to set, or nil to get
|
|
30
|
+
# @return [String, nil] the tool description
|
|
31
|
+
def description(value = nil)
|
|
32
|
+
return @description if value.nil?
|
|
33
|
+
@description = value.to_s
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Gets or sets the tool identifier/name
|
|
37
|
+
# @param value [String, nil] the identifier to set, or nil to get
|
|
38
|
+
# @return [String] the tool identifier (defaults to snake_case class name)
|
|
39
|
+
def identifier(value = nil)
|
|
40
|
+
return @identifier || class_name_to_path(Module.instance_method(:name).bind_call(self)) if value.nil?
|
|
41
|
+
@identifier = value.to_s
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Alias for identifier - used by providers
|
|
45
|
+
alias_method :name, :identifier
|
|
46
|
+
|
|
47
|
+
# Defines parameters using the Params DSL
|
|
48
|
+
# @yield the parameter definition block
|
|
49
|
+
# @return [Riffer::Tools::Params, nil] the params builder
|
|
50
|
+
def params(&block)
|
|
51
|
+
return @params_builder if block.nil?
|
|
52
|
+
@params_builder = Riffer::Tools::Params.new
|
|
53
|
+
@params_builder.instance_eval(&block)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Returns the JSON Schema for the tool's parameters
|
|
57
|
+
# @return [Hash] the JSON Schema
|
|
58
|
+
def parameters_schema
|
|
59
|
+
@params_builder&.to_json_schema || empty_schema
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def empty_schema
|
|
65
|
+
{type: "object", properties: {}, required: [], additionalProperties: false}
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Executes the tool with the given arguments
|
|
70
|
+
# @param context [Object, nil] optional context passed from the agent
|
|
71
|
+
# @param kwargs [Hash] the tool arguments
|
|
72
|
+
# @return [Object] the tool result
|
|
73
|
+
# @raise [NotImplementedError] if not implemented by subclass
|
|
74
|
+
def call(context:, **kwargs)
|
|
75
|
+
raise NotImplementedError, "#{self.class} must implement #call"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Executes the tool with validation (used by Agent)
|
|
79
|
+
# @param context [Object, nil] context passed from the agent
|
|
80
|
+
# @param kwargs [Hash] the tool arguments
|
|
81
|
+
# @return [Object] the tool result
|
|
82
|
+
# @raise [Riffer::ValidationError] if validation fails
|
|
83
|
+
def call_with_validation(context:, **kwargs)
|
|
84
|
+
params_builder = self.class.params
|
|
85
|
+
validated_args = params_builder ? params_builder.validate(kwargs) : kwargs
|
|
86
|
+
call(context: context, **validated_args)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Riffer::Tools::Param represents a single parameter definition for a tool.
|
|
4
|
+
#
|
|
5
|
+
# Handles type validation and JSON Schema generation for individual parameters.
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
8
|
+
class Riffer::Tools::Param
|
|
9
|
+
# Maps Ruby types to JSON Schema type strings
|
|
10
|
+
TYPE_MAPPINGS = {
|
|
11
|
+
String => "string",
|
|
12
|
+
Integer => "integer",
|
|
13
|
+
Float => "number",
|
|
14
|
+
TrueClass => "boolean",
|
|
15
|
+
FalseClass => "boolean",
|
|
16
|
+
Array => "array",
|
|
17
|
+
Hash => "object"
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
attr_reader :name, :type, :required, :description, :enum, :default
|
|
21
|
+
|
|
22
|
+
# Creates a new parameter definition
|
|
23
|
+
# @param name [Symbol] the parameter name
|
|
24
|
+
# @param type [Class] the expected Ruby type
|
|
25
|
+
# @param required [Boolean] whether the parameter is required
|
|
26
|
+
# @param description [String, nil] optional description for the parameter
|
|
27
|
+
# @param enum [Array, nil] optional list of allowed values
|
|
28
|
+
# @param default [Object, nil] optional default value for optional parameters
|
|
29
|
+
def initialize(name:, type:, required:, description: nil, enum: nil, default: nil)
|
|
30
|
+
@name = name.to_sym
|
|
31
|
+
@type = type
|
|
32
|
+
@required = required
|
|
33
|
+
@description = description
|
|
34
|
+
@enum = enum
|
|
35
|
+
@default = default
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Validates that a value matches the expected type
|
|
39
|
+
# @param value [Object] the value to validate
|
|
40
|
+
# @return [Boolean] true if valid, false otherwise
|
|
41
|
+
def valid_type?(value)
|
|
42
|
+
return true if value.nil? && !required
|
|
43
|
+
|
|
44
|
+
if type == TrueClass || type == FalseClass
|
|
45
|
+
value == true || value == false
|
|
46
|
+
else
|
|
47
|
+
value.is_a?(type)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Returns the JSON Schema type name for this parameter
|
|
52
|
+
# @return [String] the JSON Schema type
|
|
53
|
+
def type_name
|
|
54
|
+
TYPE_MAPPINGS[type] || type.to_s.downcase
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Converts this parameter to JSON Schema format
|
|
58
|
+
# @return [Hash] the JSON Schema representation
|
|
59
|
+
def to_json_schema
|
|
60
|
+
schema = {type: type_name}
|
|
61
|
+
schema[:description] = description if description
|
|
62
|
+
schema[:enum] = enum if enum
|
|
63
|
+
schema
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Riffer::Tools::Params provides a DSL for defining tool parameters.
|
|
4
|
+
#
|
|
5
|
+
# Used within a Tool's `params` block to define required and optional parameters.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# params do
|
|
9
|
+
# required :city, String, description: "The city name"
|
|
10
|
+
# optional :units, String, default: "celsius", enum: ["celsius", "fahrenheit"]
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# @api private
|
|
14
|
+
class Riffer::Tools::Params
|
|
15
|
+
attr_reader :parameters
|
|
16
|
+
|
|
17
|
+
def initialize
|
|
18
|
+
@parameters = []
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Defines a required parameter
|
|
22
|
+
# @param name [Symbol] the parameter name
|
|
23
|
+
# @param type [Class] the expected Ruby type
|
|
24
|
+
# @param description [String, nil] optional description
|
|
25
|
+
# @param enum [Array, nil] optional list of allowed values
|
|
26
|
+
# @return [void]
|
|
27
|
+
def required(name, type, description: nil, enum: nil)
|
|
28
|
+
@parameters << Riffer::Tools::Param.new(
|
|
29
|
+
name: name,
|
|
30
|
+
type: type,
|
|
31
|
+
required: true,
|
|
32
|
+
description: description,
|
|
33
|
+
enum: enum
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Defines an optional parameter
|
|
38
|
+
# @param name [Symbol] the parameter name
|
|
39
|
+
# @param type [Class] the expected Ruby type
|
|
40
|
+
# @param description [String, nil] optional description
|
|
41
|
+
# @param enum [Array, nil] optional list of allowed values
|
|
42
|
+
# @param default [Object, nil] default value when not provided
|
|
43
|
+
# @return [void]
|
|
44
|
+
def optional(name, type, description: nil, enum: nil, default: nil)
|
|
45
|
+
@parameters << Riffer::Tools::Param.new(
|
|
46
|
+
name: name,
|
|
47
|
+
type: type,
|
|
48
|
+
required: false,
|
|
49
|
+
description: description,
|
|
50
|
+
enum: enum,
|
|
51
|
+
default: default
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Validates arguments against parameter definitions
|
|
56
|
+
# @param arguments [Hash] the arguments to validate
|
|
57
|
+
# @return [Hash] validated arguments with defaults applied
|
|
58
|
+
# @raise [Riffer::ValidationError] if validation fails
|
|
59
|
+
def validate(arguments)
|
|
60
|
+
validated = {}
|
|
61
|
+
errors = []
|
|
62
|
+
|
|
63
|
+
@parameters.each do |param|
|
|
64
|
+
value = arguments[param.name]
|
|
65
|
+
|
|
66
|
+
if value.nil? && param.required
|
|
67
|
+
errors << "#{param.name} is required"
|
|
68
|
+
next
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
if value.nil?
|
|
72
|
+
validated[param.name] = param.default
|
|
73
|
+
next
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
unless param.valid_type?(value)
|
|
77
|
+
errors << "#{param.name} must be a #{param.type_name}"
|
|
78
|
+
next
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if param.enum && !param.enum.include?(value)
|
|
82
|
+
errors << "#{param.name} must be one of: #{param.enum.join(", ")}"
|
|
83
|
+
next
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
validated[param.name] = value
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
raise Riffer::ValidationError, errors.join("; ") if errors.any?
|
|
90
|
+
|
|
91
|
+
validated
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Converts all parameters to JSON Schema format
|
|
95
|
+
# @return [Hash] the JSON Schema representation
|
|
96
|
+
def to_json_schema
|
|
97
|
+
properties = {}
|
|
98
|
+
required_params = []
|
|
99
|
+
|
|
100
|
+
@parameters.each do |param|
|
|
101
|
+
properties[param.name.to_s] = param.to_json_schema
|
|
102
|
+
required_params << param.name.to_s if param.required
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
{
|
|
106
|
+
type: "object",
|
|
107
|
+
properties: properties,
|
|
108
|
+
required: required_params,
|
|
109
|
+
additionalProperties: false
|
|
110
|
+
}
|
|
111
|
+
end
|
|
112
|
+
end
|
data/lib/riffer/tools.rb
ADDED
data/lib/riffer/version.rb
CHANGED
data/lib/riffer.rb
CHANGED
|
@@ -26,6 +26,10 @@ module Riffer
|
|
|
26
26
|
# @api public
|
|
27
27
|
class ArgumentError < ::ArgumentError; end
|
|
28
28
|
|
|
29
|
+
# Validation error for tool parameter validation
|
|
30
|
+
# @api public
|
|
31
|
+
class ValidationError < Error; end
|
|
32
|
+
|
|
29
33
|
# Provides configuration and versioning methods for Riffer
|
|
30
34
|
#
|
|
31
35
|
# @!group Configuration
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: riffer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jake Bottrall
|
|
@@ -143,8 +143,8 @@ files:
|
|
|
143
143
|
- ".release-please-manifest.json"
|
|
144
144
|
- ".ruby-version"
|
|
145
145
|
- ".standard.yml"
|
|
146
|
+
- AGENTS.md
|
|
146
147
|
- CHANGELOG.md
|
|
147
|
-
- CLAUDE.md
|
|
148
148
|
- CODE_OF_CONDUCT.md
|
|
149
149
|
- LICENSE.txt
|
|
150
150
|
- README.md
|
|
@@ -175,6 +175,12 @@ files:
|
|
|
175
175
|
- lib/riffer/stream_events/reasoning_done.rb
|
|
176
176
|
- lib/riffer/stream_events/text_delta.rb
|
|
177
177
|
- lib/riffer/stream_events/text_done.rb
|
|
178
|
+
- lib/riffer/stream_events/tool_call_delta.rb
|
|
179
|
+
- lib/riffer/stream_events/tool_call_done.rb
|
|
180
|
+
- lib/riffer/tool.rb
|
|
181
|
+
- lib/riffer/tools.rb
|
|
182
|
+
- lib/riffer/tools/param.rb
|
|
183
|
+
- lib/riffer/tools/params.rb
|
|
178
184
|
- lib/riffer/version.rb
|
|
179
185
|
- sig/riffer.rbs
|
|
180
186
|
homepage: https://riffer.ai
|
data/CLAUDE.md
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
# CLAUDE.md
|
|
2
|
-
|
|
3
|
-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
-
|
|
5
|
-
## Project Overview
|
|
6
|
-
|
|
7
|
-
Riffer is a Ruby gem framework for building AI-powered applications and agents. It uses Zeitwerk for autoloading.
|
|
8
|
-
|
|
9
|
-
## Commands
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
# Install dependencies
|
|
13
|
-
bin/setup
|
|
14
|
-
|
|
15
|
-
# Run tests
|
|
16
|
-
bundle exec rake test
|
|
17
|
-
|
|
18
|
-
# Run a single test file
|
|
19
|
-
bundle exec ruby -Ilib:test test/riffer/agent_test.rb
|
|
20
|
-
|
|
21
|
-
# Run a specific test by name
|
|
22
|
-
bundle exec ruby -Ilib:test test/riffer/agent_test.rb --name "test_something"
|
|
23
|
-
|
|
24
|
-
# Check code style
|
|
25
|
-
bundle exec rake standard
|
|
26
|
-
|
|
27
|
-
# Auto-fix style issues
|
|
28
|
-
bundle exec rake standard:fix
|
|
29
|
-
|
|
30
|
-
# Run both tests and linting (default task)
|
|
31
|
-
bundle exec rake
|
|
32
|
-
|
|
33
|
-
# Interactive console
|
|
34
|
-
bin/console
|
|
35
|
-
|
|
36
|
-
# Generate documentation
|
|
37
|
-
bundle exec rake docs
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Architecture
|
|
41
|
-
|
|
42
|
-
### Core Components
|
|
43
|
-
|
|
44
|
-
- **Agent** (`lib/riffer/agent.rb`): Base class for AI agents. Subclass and use DSL methods `model` and `instructions` to configure. Orchestrates message flow, LLM calls, and tool execution via a generate/stream loop.
|
|
45
|
-
|
|
46
|
-
- **Providers** (`lib/riffer/providers/`): Adapters for LLM APIs. Each provider extends `Riffer::Providers::Base` and implements `perform_generate_text` and `perform_stream_text`. Registered in `Repository` with identifier (e.g., `openai`).
|
|
47
|
-
|
|
48
|
-
- **Messages** (`lib/riffer/messages/`): Typed message objects (`System`, `User`, `Assistant`, `Tool`). All extend `Base`. The `Converter` module handles hash-to-object conversion.
|
|
49
|
-
|
|
50
|
-
- **StreamEvents** (`lib/riffer/stream_events/`): Structured events for streaming (`TextDelta`, `TextDone`).
|
|
51
|
-
|
|
52
|
-
### Key Patterns
|
|
53
|
-
|
|
54
|
-
- Model strings use `provider/model` format (e.g., `openai/gpt-4`)
|
|
55
|
-
- Configuration via `Riffer.configure { |c| c.openai.api_key = "..." }`
|
|
56
|
-
- Providers use `depends_on` helper for runtime dependency checking
|
|
57
|
-
- Tests use VCR cassettes in `test/fixtures/vcr_cassettes/`
|
|
58
|
-
|
|
59
|
-
### Adding a New Provider
|
|
60
|
-
|
|
61
|
-
1. Create `lib/riffer/providers/your_provider.rb` extending `Riffer::Providers::Base`
|
|
62
|
-
2. Implement `perform_generate_text(messages, model:)` returning `Riffer::Messages::Assistant`
|
|
63
|
-
3. Implement `perform_stream_text(messages, model:)` returning an `Enumerator` yielding stream events
|
|
64
|
-
4. Register in `Riffer::Providers::Repository::REPO`
|
|
65
|
-
5. Add provider config to `Riffer::Config` if needed
|
|
66
|
-
|
|
67
|
-
## Code Style
|
|
68
|
-
|
|
69
|
-
- Ruby 3.2+ required
|
|
70
|
-
- All files must have `# frozen_string_literal: true`
|
|
71
|
-
- StandardRB for formatting (double quotes, 2-space indent)
|
|
72
|
-
- Minitest with spec DSL for tests
|
|
73
|
-
- YARD-style documentation for public APIs
|