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.
@@ -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
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Riffer::Tools
4
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Riffer
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.0"
5
5
  end
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.6.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