riffer 0.4.1 → 0.5.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/CHANGELOG.md +14 -0
- data/README.md +13 -30
- data/lib/riffer/agent.rb +30 -1
- data/lib/riffer/providers/base.rb +0 -26
- data/lib/riffer/providers/open_ai.rb +6 -11
- data/lib/riffer/providers/repository.rb +15 -0
- data/lib/riffer/providers/test.rb +6 -5
- data/lib/riffer/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 12e6843cdf4824294d9c9470c2dad55125bfa490b49389530145bd334041ce15
|
|
4
|
+
data.tar.gz: 636133b4f5b2a046311a33a3b294ce2eb893bcb3a16292bd83198fc24f3dc500
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a26010e36c164851b3a9cbd6d3b7eebd9dcd935d4fdd405793998d2760b871b01a487724a8628eddc6bf366db164f924990ee12a2c93f99602e2c0f99c341d88
|
|
7
|
+
data.tar.gz: 18428555a37e27ee29c35d21c7b1f6f00f988a22e02e6a25141d6a3d6b9d0c49da43a7bf408886d28734e485fd7ac32015643ac7d1c9fe2406bffd6d9a6602d7
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.5.0](https://github.com/bottrall/riffer/compare/riffer/v0.4.2...riffer/v0.5.0) (2026-01-06)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* streaming via agents ([#63](https://github.com/bottrall/riffer/issues/63)) ([b4171c2](https://github.com/bottrall/riffer/commit/b4171c20f64a7ada1264ce90ab5278c19ff8a47a))
|
|
14
|
+
|
|
15
|
+
## [0.4.2](https://github.com/bottrall/riffer/compare/riffer/v0.4.1...riffer/v0.4.2) (2025-12-29)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* update README for clarity on provider usage and examples ([#60](https://github.com/bottrall/riffer/issues/60)) ([b12835c](https://github.com/bottrall/riffer/commit/b12835ce71c29e02074a0897551db50283ac8be6))
|
|
21
|
+
|
|
8
22
|
## [0.4.1](https://github.com/bottrall/riffer/compare/riffer/v0.4.0...riffer/v0.4.1) (2025-12-29)
|
|
9
23
|
|
|
10
24
|
|
data/README.md
CHANGED
|
@@ -17,7 +17,7 @@ Key concepts:
|
|
|
17
17
|
## Features
|
|
18
18
|
|
|
19
19
|
- Minimal, well-documented core for building AI agents
|
|
20
|
-
- Provider abstraction (OpenAI
|
|
20
|
+
- Provider abstraction (OpenAI) for pluggable providers
|
|
21
21
|
- Streaming support and structured stream events
|
|
22
22
|
- Message converters and helpers for robust message handling
|
|
23
23
|
|
|
@@ -47,46 +47,33 @@ gem 'riffer', git: 'https://github.com/bottrall/riffer.git'
|
|
|
47
47
|
|
|
48
48
|
## Quick Start
|
|
49
49
|
|
|
50
|
-
Basic usage with the
|
|
50
|
+
Basic usage with the OpenAI provider:
|
|
51
51
|
|
|
52
52
|
```ruby
|
|
53
53
|
require 'riffer'
|
|
54
54
|
|
|
55
|
+
# Configure OpenAI API key (recommended to use ENV)
|
|
56
|
+
Riffer.configure do |config|
|
|
57
|
+
config.openai.api_key = ENV['OPENAI_API_KEY']
|
|
58
|
+
end
|
|
59
|
+
|
|
55
60
|
class EchoAgent < Riffer::Agent
|
|
56
61
|
identifier 'echo'
|
|
57
|
-
model '
|
|
62
|
+
model 'openai/gpt-5-mini' # provider/model
|
|
58
63
|
instructions 'You are an assistant that repeats what the user says.'
|
|
59
64
|
end
|
|
60
65
|
|
|
61
66
|
agent = EchoAgent.new
|
|
62
67
|
puts agent.generate('Hello world')
|
|
63
|
-
# => "
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
Using the test provider directly (useful for unit tests):
|
|
67
|
-
|
|
68
|
-
```ruby
|
|
69
|
-
provider = Riffer::Providers::Test.new
|
|
70
|
-
provider.stub_response('Hello from test provider!')
|
|
71
|
-
assistant = provider.generate_text(prompt: 'Say hi')
|
|
72
|
-
puts assistant.content # => "Hello from test provider!"
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Streaming example (provider-dependent):
|
|
76
|
-
|
|
77
|
-
```ruby
|
|
78
|
-
provider = Riffer::Providers::Test.new
|
|
79
|
-
enum = provider.stream_text(prompt: 'Stream something')
|
|
80
|
-
enum.each do |chunk|
|
|
81
|
-
puts chunk[:content]
|
|
82
|
-
end
|
|
68
|
+
# => "Hello world"
|
|
83
69
|
```
|
|
84
70
|
|
|
85
|
-
|
|
71
|
+
Streaming example:
|
|
86
72
|
|
|
87
73
|
```ruby
|
|
88
|
-
|
|
89
|
-
|
|
74
|
+
agent = EchoAgent.new
|
|
75
|
+
agent.stream('Tell me a story').each do |event|
|
|
76
|
+
print event.content
|
|
90
77
|
end
|
|
91
78
|
```
|
|
92
79
|
|
|
@@ -136,7 +123,3 @@ Licensed under the MIT License. See `LICENSE.txt` for details.
|
|
|
136
123
|
## Maintainers
|
|
137
124
|
|
|
138
125
|
- Jake Bottrall — https://github.com/bottrall
|
|
139
|
-
|
|
140
|
-
---
|
|
141
|
-
|
|
142
|
-
If you'd like, I can add short usage examples to the documentation site or update the gemspec metadata (authors, homepage, summary).
|
data/lib/riffer/agent.rb
CHANGED
|
@@ -91,6 +91,31 @@ class Riffer::Agent
|
|
|
91
91
|
extract_final_response
|
|
92
92
|
end
|
|
93
93
|
|
|
94
|
+
# Streams a response from the agent
|
|
95
|
+
# @param prompt_or_messages [String, Array<Hash, Riffer::Messages::Base>]
|
|
96
|
+
# @return [Enumerator] an enumerator yielding stream events
|
|
97
|
+
def stream(prompt_or_messages)
|
|
98
|
+
initialize_messages(prompt_or_messages)
|
|
99
|
+
|
|
100
|
+
Enumerator.new do |yielder|
|
|
101
|
+
accumulated_content = ""
|
|
102
|
+
|
|
103
|
+
call_llm_stream.each do |event|
|
|
104
|
+
yielder << event
|
|
105
|
+
|
|
106
|
+
case event
|
|
107
|
+
when Riffer::StreamEvents::TextDelta
|
|
108
|
+
accumulated_content += event.content
|
|
109
|
+
when Riffer::StreamEvents::TextDone
|
|
110
|
+
accumulated_content = event.content
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
response = Riffer::Messages::Assistant.new(accumulated_content)
|
|
115
|
+
@messages << response
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
94
119
|
private
|
|
95
120
|
|
|
96
121
|
def initialize_messages(prompt_or_messages)
|
|
@@ -110,9 +135,13 @@ class Riffer::Agent
|
|
|
110
135
|
provider_instance.generate_text(messages: @messages, model: @model_name)
|
|
111
136
|
end
|
|
112
137
|
|
|
138
|
+
def call_llm_stream
|
|
139
|
+
provider_instance.stream_text(messages: @messages, model: @model_name)
|
|
140
|
+
end
|
|
141
|
+
|
|
113
142
|
def provider_instance
|
|
114
143
|
@provider_instance ||= begin
|
|
115
|
-
provider_class = Riffer::Providers::
|
|
144
|
+
provider_class = Riffer::Providers::Repository.find(@provider_name)
|
|
116
145
|
raise Riffer::ArgumentError, "Provider not found: #{@provider_name}" unless provider_class
|
|
117
146
|
provider_class.new
|
|
118
147
|
end
|
|
@@ -4,32 +4,6 @@ class Riffer::Providers::Base
|
|
|
4
4
|
include Riffer::Helpers::Dependencies
|
|
5
5
|
include Riffer::Messages::Converter
|
|
6
6
|
|
|
7
|
-
class << self
|
|
8
|
-
def identifier(value = nil)
|
|
9
|
-
return @identifier if value.nil?
|
|
10
|
-
|
|
11
|
-
@identifier = value
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
# Finds a provider class by identifier
|
|
15
|
-
# @param identifier [String, Symbol] the identifier to search for
|
|
16
|
-
# @return [Class, nil] the provider class, or nil if not found
|
|
17
|
-
def find(identifier)
|
|
18
|
-
ensure_providers_loaded
|
|
19
|
-
subclasses.find { |provider_class| provider_class.identifier == identifier }
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
def ensure_providers_loaded
|
|
25
|
-
return if @providers_loaded
|
|
26
|
-
|
|
27
|
-
Zeitwerk::Loader.eager_load_namespace(Riffer::Providers)
|
|
28
|
-
|
|
29
|
-
@providers_loaded = true
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
7
|
# Generates text using the provider.
|
|
34
8
|
#
|
|
35
9
|
# @param prompt [String, nil] the user prompt (required when `messages` is not provided)
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
require "openai"
|
|
4
4
|
|
|
5
5
|
class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
6
|
-
identifier "openai"
|
|
7
|
-
|
|
8
6
|
# Initializes the OpenAI provider.
|
|
9
7
|
# @param options [Hash] optional client options. Use `:api_key` to override `Riffer.config.openai.api_key`.
|
|
10
8
|
# @raise [Riffer::ArgumentError] if an API key is not provided either via `:api_key` or `Riffer.config.openai.api_key`.
|
|
@@ -78,19 +76,16 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
78
76
|
end
|
|
79
77
|
|
|
80
78
|
def process_stream_events(stream, yielder)
|
|
81
|
-
stream.each do |
|
|
82
|
-
|
|
79
|
+
stream.each do |raw_event|
|
|
80
|
+
event = convert_event(raw_event)
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
yielder << content if content
|
|
86
|
-
end
|
|
87
|
-
end
|
|
82
|
+
next unless event
|
|
88
83
|
|
|
89
|
-
|
|
90
|
-
|
|
84
|
+
yielder << event if event
|
|
85
|
+
end
|
|
91
86
|
end
|
|
92
87
|
|
|
93
|
-
def
|
|
88
|
+
def convert_event(event)
|
|
94
89
|
case event.type
|
|
95
90
|
when :"response.output_text.delta"
|
|
96
91
|
Riffer::StreamEvents::TextDelta.new(event.delta)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class Riffer::Providers::Repository
|
|
2
|
+
REPO = {
|
|
3
|
+
openai: -> { Riffer::Providers::OpenAI },
|
|
4
|
+
test: -> { Riffer::Providers::Test }
|
|
5
|
+
}.freeze
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
# Finds a provider class by identifier
|
|
9
|
+
# @param identifier [String, Symbol] the identifier to search for
|
|
10
|
+
# @return [Class, nil] the provider class, or nil if not found
|
|
11
|
+
def find(identifier)
|
|
12
|
+
REPO.fetch(identifier.to_sym, nil)&.call
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class Riffer::Providers::Test < Riffer::Providers::Base
|
|
4
|
-
identifier "test"
|
|
5
|
-
|
|
6
4
|
attr_reader :calls
|
|
7
5
|
|
|
8
6
|
def initialize(**options)
|
|
@@ -35,11 +33,14 @@ class Riffer::Providers::Test < Riffer::Providers::Base
|
|
|
35
33
|
response = @stubbed_response || @responses[@current_index] || {role: "assistant", content: "Test response"}
|
|
36
34
|
@current_index += 1
|
|
37
35
|
Enumerator.new do |yielder|
|
|
38
|
-
|
|
36
|
+
full_content = response[:content]
|
|
37
|
+
content_parts = full_content.split(". ").map { |part| part + (part.end_with?(".") ? "" : ".") }
|
|
38
|
+
|
|
39
39
|
content_parts.each do |part|
|
|
40
|
-
yielder <<
|
|
41
|
-
sleep 0.5
|
|
40
|
+
yielder << Riffer::StreamEvents::TextDelta.new(part + " ")
|
|
42
41
|
end
|
|
42
|
+
|
|
43
|
+
yielder << Riffer::StreamEvents::TextDone.new(full_content)
|
|
43
44
|
end
|
|
44
45
|
end
|
|
45
46
|
end
|
data/lib/riffer/version.rb
CHANGED
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.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jake Bottrall
|
|
@@ -150,6 +150,7 @@ files:
|
|
|
150
150
|
- lib/riffer/providers.rb
|
|
151
151
|
- lib/riffer/providers/base.rb
|
|
152
152
|
- lib/riffer/providers/open_ai.rb
|
|
153
|
+
- lib/riffer/providers/repository.rb
|
|
153
154
|
- lib/riffer/providers/test.rb
|
|
154
155
|
- lib/riffer/stream_events.rb
|
|
155
156
|
- lib/riffer/stream_events/base.rb
|