riffer 0.6.1 → 0.8.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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/architecture.md +113 -0
  3. data/.agents/code-style.md +42 -0
  4. data/.agents/providers.md +46 -0
  5. data/.agents/rdoc.md +51 -0
  6. data/.agents/testing.md +56 -0
  7. data/.release-please-manifest.json +1 -1
  8. data/AGENTS.md +28 -0
  9. data/CHANGELOG.md +17 -0
  10. data/README.md +26 -36
  11. data/Rakefile +1 -1
  12. data/docs/01_OVERVIEW.md +106 -0
  13. data/docs/02_GETTING_STARTED.md +128 -0
  14. data/docs/03_AGENTS.md +226 -0
  15. data/docs/04_TOOLS.md +251 -0
  16. data/docs/05_MESSAGES.md +173 -0
  17. data/docs/06_STREAM_EVENTS.md +191 -0
  18. data/docs/07_CONFIGURATION.md +195 -0
  19. data/docs_providers/01_PROVIDERS.md +168 -0
  20. data/docs_providers/02_AMAZON_BEDROCK.md +196 -0
  21. data/docs_providers/03_ANTHROPIC.md +211 -0
  22. data/docs_providers/04_OPENAI.md +157 -0
  23. data/docs_providers/05_TEST_PROVIDER.md +163 -0
  24. data/docs_providers/06_CUSTOM_PROVIDERS.md +304 -0
  25. data/lib/riffer/agent.rb +220 -57
  26. data/lib/riffer/config.rb +20 -12
  27. data/lib/riffer/core.rb +7 -7
  28. data/lib/riffer/helpers/class_name_converter.rb +6 -3
  29. data/lib/riffer/helpers/dependencies.rb +18 -0
  30. data/lib/riffer/helpers/validations.rb +9 -0
  31. data/lib/riffer/messages/assistant.rb +23 -1
  32. data/lib/riffer/messages/base.rb +15 -0
  33. data/lib/riffer/messages/converter.rb +15 -5
  34. data/lib/riffer/messages/system.rb +8 -1
  35. data/lib/riffer/messages/tool.rb +58 -4
  36. data/lib/riffer/messages/user.rb +8 -1
  37. data/lib/riffer/messages.rb +7 -0
  38. data/lib/riffer/providers/amazon_bedrock.rb +128 -13
  39. data/lib/riffer/providers/anthropic.rb +209 -0
  40. data/lib/riffer/providers/base.rb +23 -18
  41. data/lib/riffer/providers/open_ai.rb +119 -39
  42. data/lib/riffer/providers/repository.rb +9 -4
  43. data/lib/riffer/providers/test.rb +78 -24
  44. data/lib/riffer/providers.rb +6 -0
  45. data/lib/riffer/stream_events/base.rb +13 -1
  46. data/lib/riffer/stream_events/reasoning_delta.rb +15 -1
  47. data/lib/riffer/stream_events/reasoning_done.rb +15 -1
  48. data/lib/riffer/stream_events/text_delta.rb +14 -1
  49. data/lib/riffer/stream_events/text_done.rb +14 -1
  50. data/lib/riffer/stream_events/tool_call_delta.rb +35 -0
  51. data/lib/riffer/stream_events/tool_call_done.rb +40 -0
  52. data/lib/riffer/stream_events.rb +9 -0
  53. data/lib/riffer/tool.rb +120 -0
  54. data/lib/riffer/tools/param.rb +68 -0
  55. data/lib/riffer/tools/params.rb +118 -0
  56. data/lib/riffer/tools.rb +9 -0
  57. data/lib/riffer/version.rb +1 -1
  58. data/lib/riffer.rb +23 -19
  59. metadata +41 -2
  60. data/CLAUDE.md +0 -73
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "timeout"
4
+
5
+ # Riffer::Tool is the base class for all tools in the Riffer framework.
6
+ #
7
+ # Provides a DSL for defining tool description and parameters.
8
+ # Subclasses must implement the +call+ method.
9
+ #
10
+ # See Riffer::Agent.
11
+ #
12
+ # class WeatherLookupTool < Riffer::Tool
13
+ # description "Provides current weather information for a specified city."
14
+ #
15
+ # params do
16
+ # required :city, String, description: "The city to look up"
17
+ # optional :units, String, default: "celsius"
18
+ # end
19
+ #
20
+ # def call(context:, city:, units: nil)
21
+ # # Implementation
22
+ # end
23
+ # end
24
+ #
25
+ class Riffer::Tool
26
+ DEFAULT_TIMEOUT = 10
27
+
28
+ class << self
29
+ include Riffer::Helpers::ClassNameConverter
30
+
31
+ # Gets or sets the tool description.
32
+ #
33
+ # value:: String or nil - the description to set, or nil to get
34
+ #
35
+ # Returns String or nil - the tool description.
36
+ def description(value = nil)
37
+ return @description if value.nil?
38
+ @description = value.to_s
39
+ end
40
+
41
+ # Gets or sets the tool identifier/name.
42
+ #
43
+ # value:: String or nil - the identifier to set, or nil to get
44
+ #
45
+ # Returns String - the tool identifier (defaults to snake_case class name).
46
+ def identifier(value = nil)
47
+ return @identifier || class_name_to_path(Module.instance_method(:name).bind_call(self)) if value.nil?
48
+ @identifier = value.to_s
49
+ end
50
+
51
+ # Alias for identifier - used by providers
52
+ alias_method :name, :identifier
53
+
54
+ # Gets or sets the tool timeout in seconds.
55
+ #
56
+ # value:: Numeric or nil - the timeout to set in seconds, or nil to get
57
+ #
58
+ # Returns Numeric - the tool timeout (defaults to 10).
59
+ def timeout(value = nil)
60
+ return @timeout || DEFAULT_TIMEOUT if value.nil?
61
+ @timeout = value.to_f
62
+ end
63
+
64
+ # Defines parameters using the Params DSL.
65
+ #
66
+ # Yields to the parameter definition block.
67
+ #
68
+ # Returns Riffer::Tools::Params or nil - the params builder.
69
+ def params(&block)
70
+ return @params_builder if block.nil?
71
+ @params_builder = Riffer::Tools::Params.new
72
+ @params_builder.instance_eval(&block)
73
+ end
74
+
75
+ # Returns the JSON Schema for the tool's parameters.
76
+ #
77
+ # Returns Hash - the JSON Schema.
78
+ def parameters_schema
79
+ @params_builder&.to_json_schema || empty_schema
80
+ end
81
+
82
+ private
83
+
84
+ def empty_schema
85
+ {type: "object", properties: {}, required: [], additionalProperties: false}
86
+ end
87
+ end
88
+
89
+ # Executes the tool with the given arguments.
90
+ #
91
+ # context:: Object or nil - optional context passed from the agent
92
+ # kwargs:: Hash - the tool arguments
93
+ #
94
+ # Returns Object - the tool result.
95
+ #
96
+ # Raises NotImplementedError if not implemented by subclass.
97
+ def call(context:, **kwargs)
98
+ raise NotImplementedError, "#{self.class} must implement #call"
99
+ end
100
+
101
+ # Executes the tool with validation and timeout (used by Agent).
102
+ #
103
+ # context:: Object or nil - context passed from the agent
104
+ # kwargs:: Hash - the tool arguments
105
+ #
106
+ # Returns Object - the tool result.
107
+ #
108
+ # Raises Riffer::ValidationError if validation fails.
109
+ # Raises Riffer::TimeoutError if execution exceeds the configured timeout.
110
+ def call_with_validation(context:, **kwargs)
111
+ params_builder = self.class.params
112
+ validated_args = params_builder ? params_builder.validate(kwargs) : kwargs
113
+
114
+ Timeout.timeout(self.class.timeout) do
115
+ call(context: context, **validated_args)
116
+ end
117
+ rescue Timeout::Error
118
+ raise Riffer::TimeoutError, "Tool execution timed out after #{self.class.timeout} seconds"
119
+ end
120
+ end
@@ -0,0 +1,68 @@
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
+ class Riffer::Tools::Param
7
+ # Maps Ruby types to JSON Schema type strings
8
+ TYPE_MAPPINGS = {
9
+ String => "string",
10
+ Integer => "integer",
11
+ Float => "number",
12
+ TrueClass => "boolean",
13
+ FalseClass => "boolean",
14
+ Array => "array",
15
+ Hash => "object"
16
+ }.freeze
17
+
18
+ attr_reader :name, :type, :required, :description, :enum, :default
19
+
20
+ # Creates a new parameter definition.
21
+ #
22
+ # name:: Symbol - the parameter name
23
+ # type:: Class - the expected Ruby type
24
+ # required:: Boolean - whether the parameter is required
25
+ # description:: String or nil - optional description for the parameter
26
+ # enum:: Array or nil - optional list of allowed values
27
+ # default:: Object or nil - optional default value for optional parameters
28
+ def initialize(name:, type:, required:, description: nil, enum: nil, default: nil)
29
+ @name = name.to_sym
30
+ @type = type
31
+ @required = required
32
+ @description = description
33
+ @enum = enum
34
+ @default = default
35
+ end
36
+
37
+ # Validates that a value matches the expected type.
38
+ #
39
+ # value:: Object - the value to validate
40
+ #
41
+ # Returns Boolean - true if valid, false otherwise.
42
+ def valid_type?(value)
43
+ return true if value.nil? && !required
44
+
45
+ if type == TrueClass || type == FalseClass
46
+ value == true || value == false
47
+ else
48
+ value.is_a?(type)
49
+ end
50
+ end
51
+
52
+ # Returns the JSON Schema type name for this parameter.
53
+ #
54
+ # Returns String - the JSON Schema type.
55
+ def type_name
56
+ TYPE_MAPPINGS[type] || type.to_s.downcase
57
+ end
58
+
59
+ # Converts this parameter to JSON Schema format.
60
+ #
61
+ # Returns Hash - the JSON Schema representation.
62
+ def to_json_schema
63
+ schema = {type: type_name}
64
+ schema[:description] = description if description
65
+ schema[:enum] = enum if enum
66
+ schema
67
+ end
68
+ end
@@ -0,0 +1,118 @@
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
+ # params do
8
+ # required :city, String, description: "The city name"
9
+ # optional :units, String, default: "celsius", enum: ["celsius", "fahrenheit"]
10
+ # end
11
+ #
12
+ class Riffer::Tools::Params
13
+ attr_reader :parameters
14
+
15
+ def initialize
16
+ @parameters = []
17
+ end
18
+
19
+ # Defines a required parameter.
20
+ #
21
+ # name:: Symbol - the parameter name
22
+ # type:: Class - the expected Ruby type
23
+ # description:: String or nil - optional description
24
+ # enum:: Array or nil - optional list of allowed values
25
+ #
26
+ # Returns 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
+ #
39
+ # name:: Symbol - the parameter name
40
+ # type:: Class - the expected Ruby type
41
+ # description:: String or nil - optional description
42
+ # enum:: Array or nil - optional list of allowed values
43
+ # default:: Object or nil - default value when not provided
44
+ #
45
+ # Returns void.
46
+ def optional(name, type, description: nil, enum: nil, default: nil)
47
+ @parameters << Riffer::Tools::Param.new(
48
+ name: name,
49
+ type: type,
50
+ required: false,
51
+ description: description,
52
+ enum: enum,
53
+ default: default
54
+ )
55
+ end
56
+
57
+ # Validates arguments against parameter definitions.
58
+ #
59
+ # arguments:: Hash - the arguments to validate
60
+ #
61
+ # Returns Hash - validated arguments with defaults applied.
62
+ #
63
+ # Raises Riffer::ValidationError if validation fails.
64
+ def validate(arguments)
65
+ validated = {}
66
+ errors = []
67
+
68
+ @parameters.each do |param|
69
+ value = arguments[param.name]
70
+
71
+ if value.nil? && param.required
72
+ errors << "#{param.name} is required"
73
+ next
74
+ end
75
+
76
+ if value.nil?
77
+ validated[param.name] = param.default
78
+ next
79
+ end
80
+
81
+ unless param.valid_type?(value)
82
+ errors << "#{param.name} must be a #{param.type_name}"
83
+ next
84
+ end
85
+
86
+ if param.enum && !param.enum.include?(value)
87
+ errors << "#{param.name} must be one of: #{param.enum.join(", ")}"
88
+ next
89
+ end
90
+
91
+ validated[param.name] = value
92
+ end
93
+
94
+ raise Riffer::ValidationError, errors.join("; ") if errors.any?
95
+
96
+ validated
97
+ end
98
+
99
+ # Converts all parameters to JSON Schema format.
100
+ #
101
+ # Returns Hash - the JSON Schema representation.
102
+ def to_json_schema
103
+ properties = {}
104
+ required_params = []
105
+
106
+ @parameters.each do |param|
107
+ properties[param.name.to_s] = param.to_json_schema
108
+ required_params << param.name.to_s if param.required
109
+ end
110
+
111
+ {
112
+ type: "object",
113
+ properties: properties,
114
+ required: required_params,
115
+ additionalProperties: false
116
+ }
117
+ end
118
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Namespace for tool-related classes in the Riffer framework.
4
+ #
5
+ # Contains:
6
+ # - Riffer::Tools::Params - DSL for defining tool parameters
7
+ # - Riffer::Tools::Param - Individual parameter definition
8
+ module Riffer::Tools
9
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Riffer
4
- VERSION = "0.6.1"
4
+ VERSION = "0.8.0"
5
5
  end
data/lib/riffer.rb CHANGED
@@ -2,15 +2,11 @@
2
2
 
3
3
  require "zeitwerk"
4
4
 
5
- # :nodoc:
6
5
  # Riffer is the main module for the Riffer AI framework.
7
6
  #
8
7
  # Provides configuration, error classes, and versioning for the gem.
9
8
  #
10
- # @see Riffer::Config
11
- # @see Riffer::Agent
12
- # @see Riffer::Providers
13
- # @see Riffer::Messages
9
+ # See Riffer::Config, Riffer::Agent, Riffer::Providers, and Riffer::Messages.
14
10
  loader = Zeitwerk::Loader.for_gem
15
11
  loader.inflector.inflect(
16
12
  "open_ai" => "OpenAI"
@@ -18,33 +14,41 @@ loader.inflector.inflect(
18
14
  loader.setup
19
15
 
20
16
  module Riffer
21
- # Base error for Riffer
22
- # @api public
17
+ # Base error class for Riffer.
23
18
  class Error < StandardError; end
24
19
 
25
- # Argument error for Riffer
26
- # @api public
20
+ # Raised when invalid arguments are provided.
27
21
  class ArgumentError < ::ArgumentError; end
28
22
 
29
- # Provides configuration and versioning methods for Riffer
30
- #
31
- # @!group Configuration
23
+ # Raised when tool parameter validation fails.
24
+ class ValidationError < Error; end
25
+
26
+ # Raised when tool execution times out.
27
+ class TimeoutError < Error; end
28
+
32
29
  class << self
33
- # Returns the Riffer configuration
34
- # @return [Riffer::Config]
30
+ # Returns the Riffer configuration.
31
+ #
32
+ # Returns Riffer::Config.
35
33
  def config
36
34
  @config ||= Config.new
37
35
  end
38
36
 
39
- # Yields the configuration for block-based setup
40
- # @yieldparam config [Riffer::Config] the configuration object
41
- # @return [void]
37
+ # Yields the configuration for block-based setup.
38
+ #
39
+ # Yields config (Riffer::Config) to the block.
40
+ #
41
+ # Riffer.configure do |config|
42
+ # config.openai.api_key = ENV['OPENAI_API_KEY']
43
+ # end
44
+ #
42
45
  def configure
43
46
  yield config if block_given?
44
47
  end
45
48
 
46
- # Returns the gem version
47
- # @return [String] the version string
49
+ # Returns the gem version.
50
+ #
51
+ # Returns String.
48
52
  def version
49
53
  VERSION
50
54
  end
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.1
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jake Bottrall
@@ -29,6 +29,20 @@ dependencies:
29
29
  - - ">="
30
30
  - !ruby/object:Gem::Version
31
31
  version: 2.6.0
32
+ - !ruby/object:Gem::Dependency
33
+ name: anthropic
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: 1.16.3
39
+ type: :development
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - "~>"
44
+ - !ruby/object:Gem::Version
45
+ version: 1.16.3
32
46
  - !ruby/object:Gem::Dependency
33
47
  name: aws-sdk-bedrockruntime
34
48
  requirement: !ruby/object:Gem::Requirement
@@ -139,16 +153,34 @@ extra_rdoc_files:
139
153
  - LICENSE.txt
140
154
  - README.md
141
155
  files:
156
+ - ".agents/architecture.md"
157
+ - ".agents/code-style.md"
158
+ - ".agents/providers.md"
159
+ - ".agents/rdoc.md"
160
+ - ".agents/testing.md"
142
161
  - ".release-please-config.json"
143
162
  - ".release-please-manifest.json"
144
163
  - ".ruby-version"
145
164
  - ".standard.yml"
165
+ - AGENTS.md
146
166
  - CHANGELOG.md
147
- - CLAUDE.md
148
167
  - CODE_OF_CONDUCT.md
149
168
  - LICENSE.txt
150
169
  - README.md
151
170
  - Rakefile
171
+ - docs/01_OVERVIEW.md
172
+ - docs/02_GETTING_STARTED.md
173
+ - docs/03_AGENTS.md
174
+ - docs/04_TOOLS.md
175
+ - docs/05_MESSAGES.md
176
+ - docs/06_STREAM_EVENTS.md
177
+ - docs/07_CONFIGURATION.md
178
+ - docs_providers/01_PROVIDERS.md
179
+ - docs_providers/02_AMAZON_BEDROCK.md
180
+ - docs_providers/03_ANTHROPIC.md
181
+ - docs_providers/04_OPENAI.md
182
+ - docs_providers/05_TEST_PROVIDER.md
183
+ - docs_providers/06_CUSTOM_PROVIDERS.md
152
184
  - lib/riffer.rb
153
185
  - lib/riffer/agent.rb
154
186
  - lib/riffer/config.rb
@@ -165,6 +197,7 @@ files:
165
197
  - lib/riffer/messages/user.rb
166
198
  - lib/riffer/providers.rb
167
199
  - lib/riffer/providers/amazon_bedrock.rb
200
+ - lib/riffer/providers/anthropic.rb
168
201
  - lib/riffer/providers/base.rb
169
202
  - lib/riffer/providers/open_ai.rb
170
203
  - lib/riffer/providers/repository.rb
@@ -175,6 +208,12 @@ files:
175
208
  - lib/riffer/stream_events/reasoning_done.rb
176
209
  - lib/riffer/stream_events/text_delta.rb
177
210
  - lib/riffer/stream_events/text_done.rb
211
+ - lib/riffer/stream_events/tool_call_delta.rb
212
+ - lib/riffer/stream_events/tool_call_done.rb
213
+ - lib/riffer/tool.rb
214
+ - lib/riffer/tools.rb
215
+ - lib/riffer/tools/param.rb
216
+ - lib/riffer/tools/params.rb
178
217
  - lib/riffer/version.rb
179
218
  - sig/riffer.rbs
180
219
  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