riffer 0.1.0 → 0.2.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.
@@ -2,103 +2,100 @@
2
2
 
3
3
  require "openai"
4
4
 
5
- module Riffer::Providers
6
- class OpenAI < Base
7
- identifier "openai"
5
+ class Riffer::Providers::OpenAI < Riffer::Providers::Base
6
+ identifier "openai"
8
7
 
9
- def initialize(**options)
10
- depends_on "openai"
8
+ # Initializes the OpenAI provider.
9
+ # @param options [Hash] optional client options. Use `:api_key` to override `Riffer.config.openai.api_key`.
10
+ # @raise [Riffer::ArgumentError] if an API key is not provided either via `:api_key` or `Riffer.config.openai.api_key`.
11
+ def initialize(**options)
12
+ depends_on "openai"
11
13
 
12
- api_key = options.fetch(:api_key, Riffer.config.openai.api_key)
13
- raise ArgumentError, "OpenAI API key is required. Set it via Riffer.configure or pass :api_key option" if api_key.nil? || api_key.empty?
14
+ api_key = options.fetch(:api_key, Riffer.config.openai.api_key)
15
+ raise Riffer::ArgumentError, "OpenAI API key is required. Set it via Riffer.configure or pass :api_key option" if api_key.nil? || api_key.empty?
14
16
 
15
- @client = ::OpenAI::Client.new(api_key: api_key, **options.except(:api_key))
16
- end
17
-
18
- private
17
+ @client = ::OpenAI::Client.new(api_key: api_key, **options.except(:api_key))
18
+ end
19
19
 
20
- def perform_generate_text(messages, model:)
21
- params = build_request_params(messages, model)
22
- response = @client.responses.create(params)
20
+ private
23
21
 
24
- output = response.output.find { |o| o.type == :message }
22
+ def perform_generate_text(messages, model:)
23
+ params = build_request_params(messages, model)
24
+ response = @client.responses.create(params)
25
25
 
26
- if output.nil?
27
- raise Riffer::Providers::Error, "No output returned from OpenAI API"
28
- end
26
+ output = response.output.find { |o| o.type == :message }
29
27
 
30
- content = output.content.find { |c| c.type == :output_text }
28
+ if output.nil?
29
+ raise Riffer::Error, "No output returned from OpenAI API"
30
+ end
31
31
 
32
- if content.nil?
33
- raise Riffer::Providers::Error, "No content returned from OpenAI API"
34
- end
32
+ content = output.content.find { |c| c.type == :output_text }
35
33
 
36
- if content.type == :refusal
37
- raise Riffer::Providers::Error, "Request was refused: #{content.refusal}"
38
- end
34
+ if content.nil?
35
+ raise Riffer::Error, "No content returned from OpenAI API"
36
+ end
39
37
 
40
- if content.type != :output_text
41
- raise Riffer::Providers::Error, "Unexpected content type: #{content.type}"
42
- end
38
+ if content.type == :refusal
39
+ raise Riffer::Error, "Request was refused: #{content.refusal}"
40
+ end
43
41
 
44
- Riffer::Messages::Assistant.new(content.text)
42
+ if content.type != :output_text
43
+ raise Riffer::Error, "Unexpected content type: #{content.type}"
45
44
  end
46
45
 
47
- def perform_stream_text(messages, model:)
48
- Enumerator.new do |yielder|
49
- params = build_request_params(messages, model)
50
- stream = @client.responses.stream(params)
46
+ Riffer::Messages::Assistant.new(content.text)
47
+ end
48
+
49
+ def perform_stream_text(messages, model:)
50
+ Enumerator.new do |yielder|
51
+ params = build_request_params(messages, model)
52
+ stream = @client.responses.stream(params)
51
53
 
52
- process_stream_events(stream, yielder)
53
- end
54
+ process_stream_events(stream, yielder)
54
55
  end
56
+ end
55
57
 
56
- def build_request_params(messages, model)
57
- {
58
- model: model,
59
- input: convert_message_to_openai_format(messages)
60
- }
61
- end
58
+ def build_request_params(messages, model)
59
+ {
60
+ model: model,
61
+ input: convert_message_to_openai_format(messages)
62
+ }
63
+ end
62
64
 
63
- def convert_message_to_openai_format(messages)
64
- messages.map do |message|
65
- case message
66
- when Riffer::Messages::System
67
- {role: "developer", content: message.content}
68
- when Riffer::Messages::User
69
- {role: "user", content: message.content}
70
- when Riffer::Messages::Assistant
71
- {role: "assistant", content: message.content}
72
- when Riffer::Messages::Tool
73
- raise Riffer::Providers::InvalidInputError, "Tool messages are not supported by OpenAI provider yet"
74
- when Hash
75
- message
76
- else
77
- raise Riffer::Providers::InvalidInputError, "Unsupported message type: #{message.class}"
78
- end
65
+ def convert_message_to_openai_format(messages)
66
+ messages.map do |message|
67
+ case message
68
+ when Riffer::Messages::System
69
+ {role: "developer", content: message.content}
70
+ when Riffer::Messages::User
71
+ {role: "user", content: message.content}
72
+ when Riffer::Messages::Assistant
73
+ {role: "assistant", content: message.content}
74
+ when Riffer::Messages::Tool
75
+ raise NotImplementedError, "Tool messages are not supported by OpenAI provider yet"
79
76
  end
80
77
  end
78
+ end
81
79
 
82
- def process_stream_events(stream, yielder)
83
- stream.each do |event|
84
- next unless should_process_event?(event)
80
+ def process_stream_events(stream, yielder)
81
+ stream.each do |event|
82
+ next unless should_process_event?(event)
85
83
 
86
- content = extract_event_content(event)
87
- yielder << content if content
88
- end
84
+ content = extract_event_content(event)
85
+ yielder << content if content
89
86
  end
87
+ end
90
88
 
91
- def should_process_event?(event)
92
- [:"response.output_text.delta", :"response.output_text.done"].include?(event.type)
93
- end
89
+ def should_process_event?(event)
90
+ [:"response.output_text.delta", :"response.output_text.done"].include?(event.type)
91
+ end
94
92
 
95
- def extract_event_content(event)
96
- case event.type
97
- when :"response.output_text.delta"
98
- Riffer::StreamEvents::TextDelta.new(event.delta)
99
- when :"response.output_text.done"
100
- Riffer::StreamEvents::TextDone.new(event.text)
101
- end
93
+ def extract_event_content(event)
94
+ case event.type
95
+ when :"response.output_text.delta"
96
+ Riffer::StreamEvents::TextDelta.new(event.delta)
97
+ when :"response.output_text.done"
98
+ Riffer::StreamEvents::TextDone.new(event.text)
102
99
  end
103
100
  end
104
101
  end
@@ -1,46 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Riffer::Providers
4
- class Test < Base
5
- identifier "test"
3
+ class Riffer::Providers::Test < Riffer::Providers::Base
4
+ identifier "test"
6
5
 
7
- attr_reader :calls
6
+ attr_reader :calls
8
7
 
9
- def initialize(**options)
10
- @responses = options[:responses] || []
11
- @current_index = 0
12
- @calls = []
13
- @stubbed_response = nil
14
- end
8
+ def initialize(**options)
9
+ @responses = options[:responses] || []
10
+ @current_index = 0
11
+ @calls = []
12
+ @stubbed_response = nil
13
+ end
15
14
 
16
- def stub_response(content, tool_calls: [])
17
- @stubbed_response = {role: "assistant", content: content, tool_calls: tool_calls}
18
- end
15
+ def stub_response(content, tool_calls: [])
16
+ @stubbed_response = {role: "assistant", content: content, tool_calls: tool_calls}
17
+ end
19
18
 
20
- private
19
+ private
21
20
 
22
- def perform_generate_text(messages, model: nil)
23
- @calls << {messages: messages.map(&:to_h)}
24
- response = @stubbed_response || @responses[@current_index] || {role: "assistant", content: "Test response"}
25
- @current_index += 1
21
+ def perform_generate_text(messages, model: nil)
22
+ @calls << {messages: messages.map(&:to_h)}
23
+ response = @stubbed_response || @responses[@current_index] || {role: "assistant", content: "Test response"}
24
+ @current_index += 1
26
25
 
27
- if response.is_a?(Hash)
28
- Riffer::Messages::Assistant.new(response[:content], tool_calls: response[:tool_calls] || [])
29
- else
30
- response
31
- end
26
+ if response.is_a?(Hash)
27
+ Riffer::Messages::Assistant.new(response[:content], tool_calls: response[:tool_calls] || [])
28
+ else
29
+ response
32
30
  end
31
+ end
33
32
 
34
- def perform_stream_text(messages, model: nil)
35
- @calls << {messages: messages.map(&:to_h)}
36
- response = @stubbed_response || @responses[@current_index] || {role: "assistant", content: "Test response"}
37
- @current_index += 1
38
- Enumerator.new do |yielder|
39
- content_parts = response[:content].split(". ").map { |part| part + (part.end_with?(".") ? "" : ".") }
40
- content_parts.each do |part|
41
- yielder << {role: "assistant", content: part + " "}
42
- sleep 0.5
43
- end
33
+ def perform_stream_text(messages, model: nil)
34
+ @calls << {messages: messages.map(&:to_h)}
35
+ response = @stubbed_response || @responses[@current_index] || {role: "assistant", content: "Test response"}
36
+ @current_index += 1
37
+ Enumerator.new do |yielder|
38
+ content_parts = response[:content].split(". ").map { |part| part + (part.end_with?(".") ? "" : ".") }
39
+ content_parts.each do |part|
40
+ yielder << {role: "assistant", content: part + " "}
41
+ sleep 0.5
44
42
  end
45
43
  end
46
44
  end
@@ -1,6 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Riffer::Providers
4
- class Error < StandardError; end
5
- class InvalidInputError < Error; end
6
4
  end
@@ -1,15 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Riffer::StreamEvents
4
- class Base
5
- attr_reader :role
3
+ class Riffer::StreamEvents::Base
4
+ attr_reader :role
6
5
 
7
- def initialize(role: "assistant")
8
- @role = role
9
- end
6
+ def initialize(role: "assistant")
7
+ @role = role
8
+ end
10
9
 
11
- def to_h
12
- raise NotImplementedError, "Subclasses must implement #to_h"
13
- end
10
+ def to_h
11
+ raise NotImplementedError, "Subclasses must implement #to_h"
14
12
  end
15
13
  end
@@ -1,16 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Riffer::StreamEvents
4
- class TextDelta < Base
5
- attr_reader :content
3
+ class Riffer::StreamEvents::TextDelta < Riffer::StreamEvents::Base
4
+ attr_reader :content
6
5
 
7
- def initialize(content, role: "assistant")
8
- super(role: role)
9
- @content = content
10
- end
6
+ def initialize(content, role: "assistant")
7
+ super(role: role)
8
+ @content = content
9
+ end
11
10
 
12
- def to_h
13
- {role: @role, content: @content}
14
- end
11
+ def to_h
12
+ {role: @role, content: @content}
15
13
  end
16
14
  end
@@ -1,16 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Riffer::StreamEvents
4
- class TextDone < Base
5
- attr_reader :content
3
+ class Riffer::StreamEvents::TextDone < Riffer::StreamEvents::Base
4
+ attr_reader :content
6
5
 
7
- def initialize(content, role: "assistant")
8
- super(role: role)
9
- @content = content
10
- end
6
+ def initialize(content, role: "assistant")
7
+ super(role: role)
8
+ @content = content
9
+ end
11
10
 
12
- def to_h
13
- {role: @role, content: @content}
14
- end
11
+ def to_h
12
+ {role: @role, content: @content}
15
13
  end
16
14
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Riffer
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/riffer.rb CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  require "zeitwerk"
4
4
 
5
+ # :nodoc:
6
+ # Riffer is the main module for the Riffer AI framework.
7
+ #
8
+ # Provides configuration, error classes, and versioning for the gem.
9
+ #
10
+ # @see Riffer::Config
11
+ # @see Riffer::Agent
12
+ # @see Riffer::Providers
13
+ # @see Riffer::Messages
5
14
  loader = Zeitwerk::Loader.for_gem
6
15
  loader.inflector.inflect(
7
16
  "open_ai" => "OpenAI"
@@ -9,19 +18,35 @@ loader.inflector.inflect(
9
18
  loader.setup
10
19
 
11
20
  module Riffer
21
+ # Base error for Riffer
22
+ # @api public
12
23
  class Error < StandardError; end
13
24
 
25
+ # Argument error for Riffer
26
+ # @api public
27
+ class ArgumentError < ::ArgumentError; end
28
+
29
+ # Provides configuration and versioning methods for Riffer
30
+ #
31
+ # @!group Configuration
14
32
  class << self
33
+ # Returns the Riffer configuration
34
+ # @return [Riffer::Config]
15
35
  def config
16
36
  @config ||= Config.new
17
37
  end
18
38
 
39
+ # Yields the configuration for block-based setup
40
+ # @yieldparam config [Riffer::Config] the configuration object
41
+ # @return [void]
19
42
  def configure
20
43
  yield config if block_given?
21
44
  end
22
- end
23
45
 
24
- Agent = Agents::Base
25
- Tool = Tools::Base
26
- Provider = Providers::Base
46
+ # Returns the gem version
47
+ # @return [String] the version string
48
+ def version
49
+ VERSION
50
+ end
51
+ end
27
52
  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.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jake Bottrall
@@ -44,61 +44,61 @@ dependencies:
44
44
  - !ruby/object:Gem::Version
45
45
  version: 0.42.0
46
46
  - !ruby/object:Gem::Dependency
47
- name: rspec
47
+ name: minitest
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  requirements:
50
50
  - - "~>"
51
51
  - !ruby/object:Gem::Version
52
- version: '3.0'
52
+ version: '6.0'
53
53
  type: :development
54
54
  prerelease: false
55
55
  version_requirements: !ruby/object:Gem::Requirement
56
56
  requirements:
57
57
  - - "~>"
58
58
  - !ruby/object:Gem::Version
59
- version: '3.0'
59
+ version: '6.0'
60
60
  - !ruby/object:Gem::Dependency
61
- name: vcr
61
+ name: rake
62
62
  requirement: !ruby/object:Gem::Requirement
63
63
  requirements:
64
64
  - - "~>"
65
65
  - !ruby/object:Gem::Version
66
- version: '6.0'
66
+ version: '13.0'
67
67
  type: :development
68
68
  prerelease: false
69
69
  version_requirements: !ruby/object:Gem::Requirement
70
70
  requirements:
71
71
  - - "~>"
72
72
  - !ruby/object:Gem::Version
73
- version: '6.0'
73
+ version: '13.0'
74
74
  - !ruby/object:Gem::Dependency
75
- name: webmock
75
+ name: vcr
76
76
  requirement: !ruby/object:Gem::Requirement
77
77
  requirements:
78
78
  - - "~>"
79
79
  - !ruby/object:Gem::Version
80
- version: '3.0'
80
+ version: '6.0'
81
81
  type: :development
82
82
  prerelease: false
83
83
  version_requirements: !ruby/object:Gem::Requirement
84
84
  requirements:
85
85
  - - "~>"
86
86
  - !ruby/object:Gem::Version
87
- version: '3.0'
87
+ version: '6.0'
88
88
  - !ruby/object:Gem::Dependency
89
- name: rake
89
+ name: webmock
90
90
  requirement: !ruby/object:Gem::Requirement
91
91
  requirements:
92
92
  - - "~>"
93
93
  - !ruby/object:Gem::Version
94
- version: '13.0'
94
+ version: '3.0'
95
95
  type: :development
96
96
  prerelease: false
97
97
  version_requirements: !ruby/object:Gem::Requirement
98
98
  requirements:
99
99
  - - "~>"
100
100
  - !ruby/object:Gem::Version
101
- version: '13.0'
101
+ version: '3.0'
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: standard
104
104
  requirement: !ruby/object:Gem::Requirement
@@ -113,20 +113,6 @@ dependencies:
113
113
  - - "~>"
114
114
  - !ruby/object:Gem::Version
115
115
  version: '1.3'
116
- - !ruby/object:Gem::Dependency
117
- name: rubocop-rspec
118
- requirement: !ruby/object:Gem::Requirement
119
- requirements:
120
- - - "~>"
121
- - !ruby/object:Gem::Version
122
- version: '3.8'
123
- type: :development
124
- prerelease: false
125
- version_requirements: !ruby/object:Gem::Requirement
126
- requirements:
127
- - - "~>"
128
- - !ruby/object:Gem::Version
129
- version: '3.8'
130
116
  description: Riffer is a comprehensive Ruby framework designed to simplify the development
131
117
  of AI-powered applications and agents. It provides a complete toolkit for integrating
132
118
  artificial intelligence capabilities into your Ruby projects.
@@ -136,25 +122,23 @@ executables: []
136
122
  extensions: []
137
123
  extra_rdoc_files: []
138
124
  files:
139
- - ".rspec"
140
- - ".rubocop_rspec.yml"
141
125
  - ".ruby-version"
142
126
  - ".standard.yml"
143
127
  - CHANGELOG.md
144
128
  - CODE_OF_CONDUCT.md
145
- - Guardfile
146
129
  - LICENSE.txt
147
130
  - README.md
148
131
  - Rakefile
149
132
  - lib/riffer.rb
150
- - lib/riffer/agents.rb
151
- - lib/riffer/agents/base.rb
133
+ - lib/riffer/agent.rb
152
134
  - lib/riffer/config.rb
153
135
  - lib/riffer/core.rb
154
- - lib/riffer/dependency_helper.rb
136
+ - lib/riffer/helpers/dependencies.rb
137
+ - lib/riffer/helpers/validations.rb
155
138
  - lib/riffer/messages.rb
156
139
  - lib/riffer/messages/assistant.rb
157
140
  - lib/riffer/messages/base.rb
141
+ - lib/riffer/messages/converter.rb
158
142
  - lib/riffer/messages/system.rb
159
143
  - lib/riffer/messages/tool.rb
160
144
  - lib/riffer/messages/user.rb
@@ -162,14 +146,10 @@ files:
162
146
  - lib/riffer/providers/base.rb
163
147
  - lib/riffer/providers/open_ai.rb
164
148
  - lib/riffer/providers/test.rb
165
- - lib/riffer/storage.rb
166
- - lib/riffer/storage/base.rb
167
149
  - lib/riffer/stream_events.rb
168
150
  - lib/riffer/stream_events/base.rb
169
151
  - lib/riffer/stream_events/text_delta.rb
170
152
  - lib/riffer/stream_events/text_done.rb
171
- - lib/riffer/tools.rb
172
- - lib/riffer/tools/base.rb
173
153
  - lib/riffer/version.rb
174
154
  - sig/riffer.rbs
175
155
  homepage: https://riffer.ai
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/.rubocop_rspec.yml DELETED
@@ -1,6 +0,0 @@
1
- plugins:
2
- - rubocop-rspec
3
-
4
- RSpec/MultipleExpectations:
5
- Enabled: true
6
- Max: 1
data/Guardfile DELETED
@@ -1,70 +0,0 @@
1
- # A sample Guardfile
2
- # More info at https://github.com/guard/guard#readme
3
-
4
- ## Uncomment and set this to only include directories you want to watch
5
- # directories %w(app lib config test spec features) \
6
- # .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
-
8
- ## Note: if you are using the `directories` clause above and you are not
9
- ## watching the project directory ('.'), then you will want to move
10
- ## the Guardfile to a watched dir and symlink it back, e.g.
11
- #
12
- # $ mkdir config
13
- # $ mv Guardfile config/
14
- # $ ln -s config/Guardfile .
15
- #
16
- # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
-
18
- # Note: The cmd option is now required due to the increasing number of ways
19
- # rspec may be run, below are examples of the most common uses.
20
- # * bundler: 'bundle exec rspec'
21
- # * bundler binstubs: 'bin/rspec'
22
- # * spring: 'bin/rspec' (This will use spring if running and you have
23
- # installed the spring binstubs per the docs)
24
- # * zeus: 'zeus rspec' (requires the server to be started separately)
25
- # * 'just' rspec: 'rspec'
26
-
27
- guard :rspec, cmd: "bundle exec rspec" do
28
- require "guard/rspec/dsl"
29
- dsl = Guard::RSpec::Dsl.new(self)
30
-
31
- # Feel free to open issues for suggestions and improvements
32
-
33
- # RSpec files
34
- rspec = dsl.rspec
35
- watch(rspec.spec_helper) { rspec.spec_dir }
36
- watch(rspec.spec_support) { rspec.spec_dir }
37
- watch(rspec.spec_files)
38
-
39
- # Ruby files
40
- ruby = dsl.ruby
41
- dsl.watch_spec_files_for(ruby.lib_files)
42
-
43
- # Rails files
44
- rails = dsl.rails(view_extensions: %w[erb haml slim])
45
- dsl.watch_spec_files_for(rails.app_files)
46
- dsl.watch_spec_files_for(rails.views)
47
-
48
- watch(rails.controllers) do |m|
49
- [
50
- rspec.spec.call("routing/#{m[1]}_routing"),
51
- rspec.spec.call("controllers/#{m[1]}_controller"),
52
- rspec.spec.call("acceptance/#{m[1]}")
53
- ]
54
- end
55
-
56
- # Rails config changes
57
- watch(rails.spec_helper) { rspec.spec_dir }
58
- watch(rails.routes) { "#{rspec.spec_dir}/routing" }
59
- watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
60
-
61
- # Capybara features specs
62
- watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
63
- watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
64
-
65
- # Turnip features and steps
66
- watch(%r{^spec/acceptance/(.+)\.feature$})
67
- watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
68
- Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
69
- end
70
- end