rager 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/README.md +26 -27
- data/lib/rager/chat/message.rb +10 -0
- data/lib/rager/chat/message_content.rb +7 -0
- data/lib/rager/chat/message_delta.rb +7 -0
- data/lib/rager/chat/options.rb +6 -7
- data/lib/rager/chat/providers/openai.rb +49 -34
- data/lib/rager/chat/schema.rb +0 -2
- data/lib/rager/config.rb +12 -20
- data/lib/rager/context.rb +302 -186
- data/lib/rager/context_options.rb +23 -0
- data/lib/rager/embed/options.rb +3 -3
- data/lib/rager/embed/providers/openai.rb +7 -3
- data/lib/rager/errors/options_error.rb +1 -1
- data/lib/rager/http/adapters/async_http.rb +0 -2
- data/lib/rager/http/adapters/mock.rb +0 -2
- data/lib/rager/http/adapters/net_http.rb +18 -19
- data/lib/rager/{image_gen → image}/options.rb +4 -4
- data/lib/rager/{image_gen → image}/output_format.rb +1 -1
- data/lib/rager/{image_gen → image}/providers/abstract.rb +4 -4
- data/lib/rager/{image_gen → image}/providers/replicate.rb +14 -10
- data/lib/rager/{logger.rb → log_strategy.rb} +2 -1
- data/lib/rager/{mesh_gen → mesh}/options.rb +3 -3
- data/lib/rager/{mesh_gen → mesh}/providers/abstract.rb +4 -4
- data/lib/rager/{mesh_gen → mesh}/providers/replicate.rb +13 -9
- data/lib/rager/operation.rb +2 -2
- data/lib/rager/options.rb +1 -1
- data/lib/rager/outcome.rb +25 -0
- data/lib/rager/providers.rb +37 -25
- data/lib/rager/rerank/input.rb +20 -0
- data/lib/rager/rerank/options.rb +2 -2
- data/lib/rager/rerank/providers/abstract.rb +2 -3
- data/lib/rager/rerank/providers/cohere.rb +19 -13
- data/lib/rager/rerank/result.rb +20 -0
- data/lib/rager/result.rb +92 -122
- data/lib/rager/search/options.rb +3 -2
- data/lib/rager/search/providers/jina.rb +16 -15
- data/lib/rager/search/result.rb +21 -0
- data/lib/rager/template/input.rb +20 -0
- data/lib/rager/template/options.rb +1 -1
- data/lib/rager/template/providers/abstract.rb +2 -3
- data/lib/rager/template/providers/erb.rb +4 -5
- data/lib/rager/template/providers/mustache.rb +30 -0
- data/lib/rager/types.rb +31 -24
- data/lib/rager/utils/http.rb +73 -15
- data/lib/rager/utils/replicate.rb +28 -6
- data/lib/rager/utils/runtime.rb +21 -0
- data/lib/rager/version.rb +1 -1
- metadata +18 -12
- data/lib/rager/rerank/output.rb +0 -13
- data/lib/rager/search/output.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a240f204b0677aee45009f9357cea5e13b85eaa77dd9696db6e12e065f0144b4
|
4
|
+
data.tar.gz: 8d31692e3d8fe5d962ff4f2a9a94817a8886dd20f01eb72cbb24189deb490365
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 381d1ee7c8202be21536d209d3845039223709e7dd4e93c974f1970ce4b019f8538cac0bcb6cae7c8c15209e86bde9d6adb0e10d9e9b4546734744d4be7133dd
|
7
|
+
data.tar.gz: 34341e107e1018810723d9bdbd5f6e0b6fc69f66daf166860aec9f4513b29827479754ba84afe07cf5e9ece45cacf23c0e34f70f8fe896fd14c2b99d86a9f878
|
data/README.md
CHANGED
@@ -8,41 +8,25 @@
|
|
8
8
|
|
9
9
|
Build continuously improving generative workflows.
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
| Feature | Providers |
|
14
|
-
| ---------------------- | ------------------------------ |
|
15
|
-
| **Chat** | `openai` (and compatible APIs) |
|
16
|
-
| **Embedding** | `openai` (and compatible APIs) |
|
17
|
-
| **Image generation** | `replicate` |
|
18
|
-
| **3D mesh generation** | `replicate` |
|
19
|
-
| **Rerank** | `cohere` (and compatible APIs) |
|
20
|
-
| **Search** | `jina` |
|
21
|
-
| **Templating** | `erb` (built-in) |
|
22
|
-
|
23
|
-
## Logging
|
24
|
-
|
25
|
-
The main reason for developing yet another library is to have an out-of-the-box compatible logging server that tracks AI workflows and outcomes. This data can be used to generate high-quality examples for few-shot prompting and fine-tuning. That server will be released soon, check back here for updates.
|
11
|
+
Examples are available in the [`examples/`](./examples/) folder.
|
26
12
|
|
27
13
|
## Installation
|
28
14
|
|
29
|
-
Add this line to your application
|
15
|
+
Add this line to your application's Gemfile:
|
30
16
|
|
31
17
|
```ruby
|
32
|
-
gem "rager", "~> 0.
|
18
|
+
gem "rager", "~> 0.7.0"
|
33
19
|
```
|
34
20
|
|
35
|
-
Or use it in a standalone script (
|
21
|
+
Or use it in a standalone script (requires OPENAI_API_KEY set):
|
36
22
|
|
37
23
|
```ruby
|
38
|
-
#!/usr/bin/env ruby
|
39
|
-
|
40
24
|
require "bundler/inline"
|
41
25
|
|
42
26
|
gemfile do
|
43
27
|
source "https://rubygems.org"
|
44
28
|
gem "async-http", "~> 0.88.0"
|
45
|
-
gem "rager", "~> 0.
|
29
|
+
gem "rager", "~> 0.7.0"
|
46
30
|
end
|
47
31
|
|
48
32
|
require "rager"
|
@@ -51,18 +35,33 @@ Rager.configure do |config|
|
|
51
35
|
config.http_adapter = Rager::Http::Adapters::AsyncHttp.new
|
52
36
|
end
|
53
37
|
|
38
|
+
PROMPT = ARGV[0] || "Ruby programming"
|
39
|
+
|
54
40
|
Async do
|
55
41
|
ctx = Rager::Context.new
|
56
|
-
prompt = ctx.template(
|
57
|
-
"Tell me about the history of <%= topic %>:\n",
|
58
|
-
{topic: "Ruby programming"}
|
59
|
-
)
|
42
|
+
prompt = ctx.template("Tell me about the history of <%= topic %>",{topic: PROMPT})
|
60
43
|
chat = ctx.chat(prompt, stream: true)
|
61
|
-
chat.
|
44
|
+
chat.stream.each { |d| print d.content }
|
62
45
|
end
|
63
46
|
```
|
64
47
|
|
65
|
-
|
48
|
+
## Providers
|
49
|
+
|
50
|
+
Modalities that take a URL parameter (chat, embedding, rerank) support compatible services, allowing you to use alternative providers that support the same interface.
|
51
|
+
|
52
|
+
| Feature | Providers |
|
53
|
+
| ---------------------- | ---------------- |
|
54
|
+
| **Chat** | `openai` |
|
55
|
+
| **Embedding** | `openai` |
|
56
|
+
| **Image generation** | `replicate` |
|
57
|
+
| **3D mesh generation** | `replicate` |
|
58
|
+
| **Reranking** | `cohere` |
|
59
|
+
| **Search** | `jina` |
|
60
|
+
| **Templating** | `erb`,`mustache` |
|
61
|
+
|
62
|
+
## Logging
|
63
|
+
|
64
|
+
The main reason for creating yet another library is to have out-of-the-box logging for workflows and outcomes. The logged data can then be used for few-shot prompting and fine-tuning. This logging server is being developed at [`rager.cloud`](https://rager.cloud) and is closed while I work on it. Contact me if you would like access.
|
66
65
|
|
67
66
|
## License
|
68
67
|
|
data/lib/rager/chat/message.rb
CHANGED
@@ -6,8 +6,18 @@ require "sorbet-runtime"
|
|
6
6
|
module Rager
|
7
7
|
module Chat
|
8
8
|
class Message < T::Struct
|
9
|
+
extend T::Sig
|
10
|
+
|
9
11
|
const :role, MessageRole
|
10
12
|
const :content, T.any(String, T::Array[MessageContent])
|
13
|
+
|
14
|
+
sig { params(options: T.untyped).returns(String) }
|
15
|
+
def to_json(options = nil)
|
16
|
+
{
|
17
|
+
role: role,
|
18
|
+
content: content
|
19
|
+
}.to_json(options)
|
20
|
+
end
|
11
21
|
end
|
12
22
|
end
|
13
23
|
end
|
@@ -6,9 +6,16 @@ require "sorbet-runtime"
|
|
6
6
|
module Rager
|
7
7
|
module Chat
|
8
8
|
class MessageContent < T::Struct
|
9
|
+
extend T::Sig
|
10
|
+
|
9
11
|
const :type, MessageContentType, default: MessageContentType::Text
|
10
12
|
const :image_type, T.nilable(MessageContentImageType)
|
11
13
|
const :content, String
|
14
|
+
|
15
|
+
sig { params(options: T.untyped).returns(String) }
|
16
|
+
def to_json(options = nil)
|
17
|
+
serialize.to_json(options)
|
18
|
+
end
|
12
19
|
end
|
13
20
|
end
|
14
21
|
end
|
@@ -6,8 +6,15 @@ require "sorbet-runtime"
|
|
6
6
|
module Rager
|
7
7
|
module Chat
|
8
8
|
class MessageDelta < T::Struct
|
9
|
+
extend T::Sig
|
10
|
+
|
9
11
|
const :index, Integer, default: 0
|
10
12
|
const :content, String
|
13
|
+
|
14
|
+
sig { params(options: T.untyped).returns(String) }
|
15
|
+
def to_json(options = nil)
|
16
|
+
serialize.to_json(options)
|
17
|
+
end
|
11
18
|
end
|
12
19
|
end
|
13
20
|
end
|
data/lib/rager/chat/options.rb
CHANGED
@@ -10,22 +10,21 @@ module Rager
|
|
10
10
|
extend T::Sig
|
11
11
|
include Rager::Options
|
12
12
|
|
13
|
-
const :provider,
|
14
|
-
const :history, T::Array[Message], default: []
|
15
|
-
const :url, T.nilable(String)
|
16
|
-
const :api_key, T.nilable(String)
|
13
|
+
const :provider, Symbol, default: :openai
|
17
14
|
const :model, T.nilable(String)
|
15
|
+
const :stream, T.nilable(T::Boolean)
|
18
16
|
const :n, T.nilable(Integer)
|
19
|
-
const :max_tokens, T.nilable(Integer)
|
20
17
|
const :temperature, T.nilable(Float)
|
18
|
+
const :max_tokens, T.nilable(Integer)
|
21
19
|
const :system_prompt, T.nilable(String)
|
22
20
|
const :schema, T.nilable(Dry::Schema::JSON)
|
23
21
|
const :schema_name, T.nilable(String)
|
24
|
-
const :stream, T.nilable(T::Boolean)
|
25
22
|
const :seed, T.nilable(Integer)
|
23
|
+
const :url, T.nilable(String)
|
24
|
+
const :api_key, T.nilable(String)
|
26
25
|
const :timeout, T.nilable(Numeric)
|
27
26
|
|
28
|
-
sig { override.returns(T::Hash[
|
27
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
29
28
|
def serialize_safe
|
30
29
|
result = serialize
|
31
30
|
result["api_key"] = "[REDACTED]" if result.key?("api_key")
|
@@ -11,7 +11,8 @@ module Rager
|
|
11
11
|
class Openai < Rager::Chat::Providers::Abstract
|
12
12
|
extend T::Sig
|
13
13
|
|
14
|
-
|
14
|
+
OpenaiContentItem = T.type_alias { T::Hash[String, T.any(String, T::Hash[String, String])] }
|
15
|
+
OpenaiMessages = T.type_alias { T::Array[T::Hash[String, T.any(String, T::Array[OpenaiContentItem])]] }
|
15
16
|
|
16
17
|
sig do
|
17
18
|
override.params(
|
@@ -24,10 +25,17 @@ module Rager
|
|
24
25
|
raise Rager::Errors::CredentialsError.new("OpenAI", env_var: ["OPENAI_API_KEY"]) if api_key.nil?
|
25
26
|
|
26
27
|
base_url = options.url || ENV["OPENAI_URL"] || "https://api.openai.com/v1"
|
28
|
+
url = "#{base_url}/chat/completions"
|
29
|
+
|
30
|
+
headers = {
|
31
|
+
"Content-Type" => "application/json"
|
32
|
+
}.tap do |h|
|
33
|
+
h["Authorization"] = "Bearer #{api_key}" if api_key
|
34
|
+
end
|
27
35
|
|
28
36
|
body = {
|
29
37
|
model: options.model || "gpt-4.1",
|
30
|
-
messages: build_openai_messages(messages, options.
|
38
|
+
messages: build_openai_messages(messages, options.system_prompt)
|
31
39
|
}.tap do |b|
|
32
40
|
b[:n] = options.n if options.n
|
33
41
|
b[:max_tokens] = options.max_tokens if options.max_tokens
|
@@ -47,12 +55,9 @@ module Rager
|
|
47
55
|
end
|
48
56
|
end
|
49
57
|
|
50
|
-
headers = {"Content-Type" => "application/json"}
|
51
|
-
headers["Authorization"] = "Bearer #{api_key}" if api_key
|
52
|
-
|
53
58
|
request = Rager::Http::Request.new(
|
54
59
|
verb: Rager::Http::Verb::Post,
|
55
|
-
url:
|
60
|
+
url: url,
|
56
61
|
headers: headers,
|
57
62
|
body: body.to_json,
|
58
63
|
streaming: options.stream || false,
|
@@ -70,39 +75,36 @@ module Rager
|
|
70
75
|
end
|
71
76
|
end
|
72
77
|
|
73
|
-
private
|
74
|
-
|
75
78
|
sig do
|
76
79
|
params(
|
77
80
|
messages: T::Array[Rager::Chat::Message],
|
78
|
-
history: T::Array[Rager::Chat::Message],
|
79
81
|
system_prompt: T.nilable(String)
|
80
82
|
).returns(OpenaiMessages)
|
81
83
|
end
|
82
|
-
def build_openai_messages(messages,
|
83
|
-
|
84
|
+
def build_openai_messages(messages, system_prompt)
|
85
|
+
output = T.let([], OpenaiMessages)
|
84
86
|
|
85
|
-
if
|
86
|
-
|
87
|
+
if system_prompt
|
88
|
+
output << {"role" => "system", "content" => system_prompt}
|
87
89
|
end
|
88
90
|
|
89
|
-
|
91
|
+
messages.each do |message|
|
90
92
|
role = message.role.is_a?(String) ? message.role : message.role.serialize
|
91
93
|
content = message.content
|
92
94
|
|
93
95
|
case content
|
94
96
|
when String
|
95
|
-
|
97
|
+
output << {"role" => role, "content" => content}
|
96
98
|
when Array
|
97
99
|
formatted_content = content.map { |item| format_content_item(item) }
|
98
|
-
|
100
|
+
output << {"role" => role, "content" => formatted_content}
|
99
101
|
end
|
100
102
|
end
|
101
103
|
|
102
|
-
|
104
|
+
output
|
103
105
|
end
|
104
106
|
|
105
|
-
sig { params(item: Rager::Chat::MessageContent).returns(
|
107
|
+
sig { params(item: Rager::Chat::MessageContent).returns(OpenaiContentItem) }
|
106
108
|
def format_content_item(item)
|
107
109
|
case item.type
|
108
110
|
when Rager::Chat::MessageContentType::Text
|
@@ -119,7 +121,7 @@ module Rager
|
|
119
121
|
end
|
120
122
|
end
|
121
123
|
|
122
|
-
sig { params(body: String).returns(Rager::Types::
|
124
|
+
sig { params(body: String).returns(Rager::Types::ChatNonStreamOutput) }
|
123
125
|
def handle_non_stream_body(body)
|
124
126
|
response_data = JSON.parse(body)
|
125
127
|
return [] unless response_data.key?("choices") && response_data["choices"].is_a?(Array)
|
@@ -137,25 +139,38 @@ module Rager
|
|
137
139
|
sig { params(body: T::Enumerator[String]).returns(T::Enumerator[Rager::Chat::MessageDelta]) }
|
138
140
|
def handle_stream_body(body)
|
139
141
|
Enumerator.new do |yielder|
|
140
|
-
|
142
|
+
buffer_parts = []
|
141
143
|
body.each do |chunk|
|
142
|
-
|
144
|
+
buffer_parts << chunk.force_encoding(Encoding::UTF_8)
|
145
|
+
buffer = buffer_parts.join
|
143
146
|
while (line = buffer.slice!(/.*\n/))
|
144
|
-
line
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
147
|
+
process_stream_line(line, yielder)
|
148
|
+
end
|
149
|
+
buffer_parts = buffer.empty? ? [] : [buffer]
|
150
|
+
end
|
151
|
+
|
152
|
+
unless buffer_parts.empty?
|
153
|
+
process_stream_line(buffer_parts.join, yielder)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
sig { params(line: String, yielder: Enumerator::Yielder).void.checked(:never) }
|
161
|
+
def process_stream_line(line, yielder)
|
162
|
+
line.delete_prefix!("data: ") or return
|
163
|
+
line.strip!
|
164
|
+
return if line.empty? || line == "[DONE]"
|
165
|
+
|
166
|
+
begin
|
167
|
+
JSON.parse(line).dig("choices")&.each do |choice|
|
168
|
+
if (content = choice.dig("delta", "content"))
|
169
|
+
yielder << Rager::Chat::MessageDelta.new(index: choice["index"] || 0, content: content)
|
157
170
|
end
|
158
171
|
end
|
172
|
+
rescue JSON::ParserError
|
173
|
+
nil
|
159
174
|
end
|
160
175
|
end
|
161
176
|
end
|
data/lib/rager/chat/schema.rb
CHANGED
data/lib/rager/config.rb
CHANGED
@@ -7,12 +7,18 @@ module Rager
|
|
7
7
|
class Config
|
8
8
|
extend T::Sig
|
9
9
|
|
10
|
-
sig { returns(T.nilable(Rager::Logger)) }
|
11
|
-
attr_accessor :logger_type
|
12
|
-
|
13
10
|
sig { returns(::Logger) }
|
14
11
|
attr_accessor :logger
|
15
12
|
|
13
|
+
sig { returns(T::Boolean) }
|
14
|
+
attr_accessor :log_raise
|
15
|
+
|
16
|
+
sig { returns(Rager::LogStrategy) }
|
17
|
+
attr_accessor :log_strategy
|
18
|
+
|
19
|
+
sig { returns(Rager::Http::Adapters::Abstract) }
|
20
|
+
attr_accessor :http_adapter
|
21
|
+
|
16
22
|
sig { returns(T.nilable(String)) }
|
17
23
|
attr_accessor :url
|
18
24
|
|
@@ -24,27 +30,13 @@ module Rager
|
|
24
30
|
|
25
31
|
sig { void }
|
26
32
|
def initialize
|
27
|
-
@http_adapter = T.let(nil, T.nilable(Rager::Http::Adapters::Abstract))
|
28
|
-
@logger_type = T.let(nil, T.nilable(Rager::Logger))
|
29
33
|
@logger = T.let(::Logger.new($stdout), ::Logger)
|
34
|
+
@log_raise = T.let(false, T::Boolean)
|
35
|
+
@log_strategy = T.let(Rager::LogStrategy::None, Rager::LogStrategy)
|
36
|
+
@http_adapter = T.let(Rager::Http::Adapters::NetHttp.new, Rager::Http::Adapters::Abstract)
|
30
37
|
@url = T.let(nil, T.nilable(String))
|
31
38
|
@api_key = T.let(nil, T.nilable(String))
|
32
39
|
@timeout = T.let(nil, T.nilable(Numeric))
|
33
40
|
end
|
34
|
-
|
35
|
-
sig { returns(Rager::Http::Adapters::Abstract) }
|
36
|
-
def http_adapter
|
37
|
-
@http_adapter ||= default_http_adapter
|
38
|
-
end
|
39
|
-
|
40
|
-
sig { params(http_adapter: Rager::Http::Adapters::Abstract).void }
|
41
|
-
attr_writer :http_adapter
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
sig { returns(Rager::Http::Adapters::Abstract) }
|
46
|
-
def default_http_adapter
|
47
|
-
Rager::Http::Adapters::NetHttp.new
|
48
|
-
end
|
49
41
|
end
|
50
42
|
end
|