rager 0.5.0 → 0.6.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 +50 -7
- data/lib/rager/chat/options.rb +9 -5
- data/lib/rager/chat/providers/openai.rb +27 -26
- data/lib/rager/chat/schema.rb +3 -2
- data/lib/rager/config.rb +5 -1
- data/lib/rager/context.rb +168 -55
- data/lib/rager/embed/options.rb +1 -0
- data/lib/rager/embed/providers/openai.rb +8 -4
- data/lib/rager/errors/credentials_error.rb +24 -0
- data/lib/rager/errors/dependency_error.rb +23 -0
- data/lib/rager/errors/http_error.rb +12 -5
- data/lib/rager/errors/options_error.rb +10 -5
- data/lib/rager/errors/parse_error.rb +9 -5
- data/lib/rager/errors/template_error.rb +10 -4
- data/lib/rager/errors/timeout_error.rb +25 -0
- data/lib/rager/http/adapters/async_http.rb +67 -13
- data/lib/rager/http/adapters/mock.rb +43 -45
- data/lib/rager/http/adapters/net_http.rb +145 -0
- data/lib/rager/http/request.rb +2 -0
- data/lib/rager/image_gen/options.rb +1 -0
- data/lib/rager/image_gen/providers/replicate.rb +5 -4
- data/lib/rager/mesh_gen/options.rb +1 -0
- data/lib/rager/mesh_gen/providers/replicate.rb +7 -5
- data/lib/rager/providers.rb +49 -0
- data/lib/rager/rerank/options.rb +1 -0
- data/lib/rager/rerank/{query.rb → output.rb} +2 -2
- data/lib/rager/rerank/providers/abstract.rb +3 -2
- data/lib/rager/rerank/providers/cohere.rb +17 -14
- data/lib/rager/result.rb +49 -29
- data/lib/rager/search/options.rb +3 -1
- data/lib/rager/search/output.rb +14 -0
- data/lib/rager/search/providers/jina.rb +67 -0
- data/lib/rager/template/providers/abstract.rb +3 -2
- data/lib/rager/template/providers/erb.rb +6 -5
- data/lib/rager/types.rb +11 -7
- data/lib/rager/utils/http.rb +35 -28
- data/lib/rager/utils/replicate.rb +13 -16
- data/lib/rager/version.rb +1 -1
- metadata +10 -30
- data/lib/rager/chat.rb +0 -35
- data/lib/rager/embed.rb +0 -35
- data/lib/rager/errors/missing_credentials_error.rb +0 -19
- data/lib/rager/errors/unknown_provider_error.rb +0 -17
- data/lib/rager/image_gen.rb +0 -31
- data/lib/rager/mesh_gen.rb +0 -31
- data/lib/rager/rerank/result.rb +0 -13
- data/lib/rager/rerank.rb +0 -35
- data/lib/rager/search/providers/brave.rb +0 -59
- data/lib/rager/search/result.rb +0 -14
- data/lib/rager/search.rb +0 -35
- data/lib/rager/template/input.rb +0 -11
- data/lib/rager/template.rb +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2036814b6f6bbe713486b60918b0d75222416ecf90eeb2e0fbf139c44d2d8c1f
|
4
|
+
data.tar.gz: d6e112b1c3d1189df8957aa219a0cee7d86735b4198ec355680fab25215e33aa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f3c65b99f301a2e617a86c1bbd2ae3149cd1a7a473d688936f66e4321eaf1ead643f58e9d387dd7d9cb314e15188a4577f710188da3082c5753229a1ae8998bf
|
7
|
+
data.tar.gz: 80e6f853ccdee991e19ba3f921fc6b10023cc2c2620d92ccc40f9b796cd5037d2ddad694834d0df4e9ed5aa9fdd84554c97a8afc2b311c95f7cb94c565a56f95
|
data/README.md
CHANGED
@@ -4,23 +4,66 @@
|
|
4
4
|
[](https://github.com/mvkvc/rager_rb/actions/workflows/publish.yml)
|
5
5
|
[](https://github.com/mvkvc/rager_rb/actions/workflows/test.yml)
|
6
6
|
[](https://github.com/mvkvc/rager_rb/actions/workflows/lint.yml)
|
7
|
+
[](https://github.com/mvkvc/rager_rb/actions/workflows/docs.yml)
|
7
8
|
|
8
|
-
Build continuously improving
|
9
|
+
Build continuously improving generative workflows.
|
10
|
+
|
11
|
+
## Providers
|
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.
|
9
26
|
|
10
27
|
## Installation
|
11
28
|
|
12
|
-
|
29
|
+
Add this line to your application’s Gemfile:
|
13
30
|
|
14
|
-
```
|
15
|
-
|
31
|
+
```ruby
|
32
|
+
gem "rager", "~> 0.6.0"
|
16
33
|
```
|
17
34
|
|
18
|
-
|
35
|
+
Or use it in a standalone script (example uses OPENAI_API_KEY env var):
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
#!/usr/bin/env ruby
|
19
39
|
|
20
|
-
|
21
|
-
|
40
|
+
require "bundler/inline"
|
41
|
+
|
42
|
+
gemfile do
|
43
|
+
source "https://rubygems.org"
|
44
|
+
gem "async-http", "~> 0.88.0"
|
45
|
+
gem "rager", "~> 0.6.0"
|
46
|
+
end
|
47
|
+
|
48
|
+
require "rager"
|
49
|
+
|
50
|
+
Rager.configure do |config|
|
51
|
+
config.http_adapter = Rager::Http::Adapters::AsyncHttp.new
|
52
|
+
end
|
53
|
+
|
54
|
+
Async do
|
55
|
+
ctx = Rager::Context.new
|
56
|
+
prompt = ctx.template(
|
57
|
+
"Tell me about the history of <%= topic %>:\n",
|
58
|
+
{topic: "Ruby programming"}
|
59
|
+
)
|
60
|
+
chat = ctx.chat(prompt, stream: true)
|
61
|
+
chat.out.each { |d| print d.content }
|
62
|
+
end
|
22
63
|
```
|
23
64
|
|
65
|
+
This library makes extensive use of `sorbet-runtime` for runtime type checking. To learn more, including how to disable it, please see the official Sorbet [documentation](https://sorbet.org/docs/overview).
|
66
|
+
|
24
67
|
## License
|
25
68
|
|
26
69
|
[MIT](./LICENSE.md)
|
data/lib/rager/chat/options.rb
CHANGED
@@ -15,13 +15,15 @@ module Rager
|
|
15
15
|
const :url, T.nilable(String)
|
16
16
|
const :api_key, T.nilable(String)
|
17
17
|
const :model, T.nilable(String)
|
18
|
-
const :stream, T.nilable(T::Boolean)
|
19
18
|
const :n, T.nilable(Integer)
|
19
|
+
const :max_tokens, T.nilable(Integer)
|
20
20
|
const :temperature, T.nilable(Float)
|
21
21
|
const :system_prompt, T.nilable(String)
|
22
22
|
const :schema, T.nilable(Dry::Schema::JSON)
|
23
23
|
const :schema_name, T.nilable(String)
|
24
|
+
const :stream, T.nilable(T::Boolean)
|
24
25
|
const :seed, T.nilable(Integer)
|
26
|
+
const :timeout, T.nilable(Numeric)
|
25
27
|
|
26
28
|
sig { override.returns(T::Hash[String, T.untyped]) }
|
27
29
|
def serialize_safe
|
@@ -35,15 +37,17 @@ module Rager
|
|
35
37
|
def validate
|
36
38
|
if stream && schema
|
37
39
|
raise Rager::Errors::OptionsError.new(
|
38
|
-
|
39
|
-
|
40
|
+
self,
|
41
|
+
["stream", "schema"],
|
42
|
+
details: "You cannot use streaming with structured outputs"
|
40
43
|
)
|
41
44
|
end
|
42
45
|
|
43
46
|
if schema && schema_name.nil?
|
44
47
|
raise Rager::Errors::OptionsError.new(
|
45
|
-
|
46
|
-
|
48
|
+
self,
|
49
|
+
["schema", "schema_name"],
|
50
|
+
details: "You must provide a schema name when using structured outputs"
|
47
51
|
)
|
48
52
|
end
|
49
53
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "json"
|
5
|
+
|
5
6
|
require "sorbet-runtime"
|
6
7
|
|
7
8
|
module Rager
|
@@ -20,14 +21,17 @@ module Rager
|
|
20
21
|
end
|
21
22
|
def chat(messages, options)
|
22
23
|
api_key = options.api_key || ENV["OPENAI_API_KEY"]
|
23
|
-
raise Rager::Errors::
|
24
|
+
raise Rager::Errors::CredentialsError.new("OpenAI", env_var: ["OPENAI_API_KEY"]) if api_key.nil?
|
25
|
+
|
26
|
+
base_url = options.url || ENV["OPENAI_URL"] || "https://api.openai.com/v1"
|
24
27
|
|
25
28
|
body = {
|
26
29
|
model: options.model || "gpt-4.1",
|
27
30
|
messages: build_openai_messages(messages, options.history, options.system_prompt)
|
28
31
|
}.tap do |b|
|
29
|
-
b[:temperature] = options.temperature if options.temperature
|
30
32
|
b[:n] = options.n if options.n
|
33
|
+
b[:max_tokens] = options.max_tokens if options.max_tokens
|
34
|
+
b[:temperature] = options.temperature if options.temperature
|
31
35
|
b[:stream] = options.stream if options.stream
|
32
36
|
b[:seed] = options.seed if options.seed
|
33
37
|
|
@@ -48,15 +52,17 @@ module Rager
|
|
48
52
|
|
49
53
|
request = Rager::Http::Request.new(
|
50
54
|
verb: Rager::Http::Verb::Post,
|
51
|
-
url:
|
55
|
+
url: "#{base_url}/chat/completions",
|
52
56
|
headers: headers,
|
53
|
-
body: body.to_json
|
57
|
+
body: body.to_json,
|
58
|
+
streaming: options.stream || false,
|
59
|
+
timeout: options.timeout || Rager.config.timeout
|
54
60
|
)
|
55
61
|
|
56
62
|
response = Rager.config.http_adapter.make_request(request)
|
57
63
|
response_body = T.must(response.body)
|
58
64
|
|
59
|
-
raise Rager::Errors::HttpError.new(Rager.config.http_adapter, response.status, T.cast(response_body, String)) if response.status != 200
|
65
|
+
raise Rager::Errors::HttpError.new(Rager.config.http_adapter, request.url, response.status, body: T.cast(response_body, String)) if response.status != 200
|
60
66
|
|
61
67
|
case response_body
|
62
68
|
when String then handle_non_stream_body(response_body)
|
@@ -113,48 +119,43 @@ module Rager
|
|
113
119
|
end
|
114
120
|
end
|
115
121
|
|
116
|
-
sig { params(body: String).returns(
|
122
|
+
sig { params(body: String).returns(Rager::Types::ChatNonStream) }
|
117
123
|
def handle_non_stream_body(body)
|
118
124
|
response_data = JSON.parse(body)
|
119
125
|
return [] unless response_data.key?("choices") && response_data["choices"].is_a?(Array)
|
120
126
|
|
121
|
-
response_data["choices"].filter_map do |choice|
|
127
|
+
result = response_data["choices"].filter_map do |choice|
|
122
128
|
text = choice.dig("message", "content").to_s
|
123
129
|
text unless text.empty?
|
124
130
|
end
|
131
|
+
|
132
|
+
result.one? ? result.first : result
|
125
133
|
rescue JSON::ParserError
|
126
|
-
raise Rager::Errors::ParseError.new("OpenAI response body is not valid JSON"
|
134
|
+
raise Rager::Errors::ParseError.new(body, details: "OpenAI response body is not valid JSON")
|
127
135
|
end
|
128
136
|
|
129
137
|
sig { params(body: T::Enumerator[String]).returns(T::Enumerator[Rager::Chat::MessageDelta]) }
|
130
138
|
def handle_stream_body(body)
|
131
139
|
Enumerator.new do |yielder|
|
132
140
|
buffer = +""
|
133
|
-
|
134
|
-
process_chunk = ->(chunk) do
|
141
|
+
body.each do |chunk|
|
135
142
|
buffer << chunk
|
136
|
-
while (
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
next if data_line.nil? || data_line.strip.empty? || data_line.strip == "[DONE]"
|
143
|
+
while (line = buffer.slice!(/.*\n/))
|
144
|
+
line.delete_prefix!("data: ") or next
|
145
|
+
line.strip!
|
146
|
+
next if line.empty? || line == "[DONE]"
|
141
147
|
|
142
148
|
begin
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
delta = choice.dig("delta", "content")
|
148
|
-
yielder << Rager::Chat::MessageDelta.new(index: choice.dig("index") || 0, content: delta) if delta
|
149
|
+
JSON.parse(line).dig("choices")&.each do |choice|
|
150
|
+
if (content = choice.dig("delta", "content"))
|
151
|
+
yielder << Rager::Chat::MessageDelta.new(index: choice["index"] || 0, content: content)
|
152
|
+
end
|
149
153
|
end
|
150
|
-
rescue
|
151
|
-
|
154
|
+
rescue
|
155
|
+
nil
|
152
156
|
end
|
153
157
|
end
|
154
158
|
end
|
155
|
-
|
156
|
-
body.each(&process_chunk)
|
157
|
-
process_chunk.call("\n") unless buffer.empty?
|
158
159
|
end
|
159
160
|
end
|
160
161
|
end
|
data/lib/rager/chat/schema.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "dry-schema"
|
5
4
|
require "json"
|
5
|
+
|
6
|
+
require "dry-schema"
|
6
7
|
require "sorbet-runtime"
|
7
8
|
|
8
9
|
Dry::Schema.load_extensions(:json_schema)
|
@@ -15,7 +16,7 @@ module Rager
|
|
15
16
|
sig { params(schema: Dry::Schema::JSON).returns(T::Hash[Symbol, T.untyped]) }
|
16
17
|
def self.dry_schema_to_json_schema(schema)
|
17
18
|
json_schema_original = schema.json_schema
|
18
|
-
json_schema = JSON.parse(JSON.generate(json_schema_original)
|
19
|
+
json_schema = JSON.parse(JSON.generate(json_schema_original))
|
19
20
|
|
20
21
|
make_strict_recursive!(json_schema)
|
21
22
|
|
data/lib/rager/config.rb
CHANGED
@@ -19,6 +19,9 @@ module Rager
|
|
19
19
|
sig { returns(T.nilable(String)) }
|
20
20
|
attr_accessor :api_key
|
21
21
|
|
22
|
+
sig { returns(T.nilable(Numeric)) }
|
23
|
+
attr_accessor :timeout
|
24
|
+
|
22
25
|
sig { void }
|
23
26
|
def initialize
|
24
27
|
@http_adapter = T.let(nil, T.nilable(Rager::Http::Adapters::Abstract))
|
@@ -26,6 +29,7 @@ module Rager
|
|
26
29
|
@logger = T.let(::Logger.new($stdout), ::Logger)
|
27
30
|
@url = T.let(nil, T.nilable(String))
|
28
31
|
@api_key = T.let(nil, T.nilable(String))
|
32
|
+
@timeout = T.let(nil, T.nilable(Numeric))
|
29
33
|
end
|
30
34
|
|
31
35
|
sig { returns(Rager::Http::Adapters::Abstract) }
|
@@ -40,7 +44,7 @@ module Rager
|
|
40
44
|
|
41
45
|
sig { returns(Rager::Http::Adapters::Abstract) }
|
42
46
|
def default_http_adapter
|
43
|
-
Rager::Http::Adapters::
|
47
|
+
Rager::Http::Adapters::NetHttp.new
|
44
48
|
end
|
45
49
|
end
|
46
50
|
end
|
data/lib/rager/context.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "securerandom"
|
5
|
+
|
5
6
|
require "sorbet-runtime"
|
6
7
|
|
7
8
|
module Rager
|
@@ -14,58 +15,76 @@ module Rager
|
|
14
15
|
sig { returns(T.nilable(String)) }
|
15
16
|
attr_reader :name
|
16
17
|
|
17
|
-
sig {
|
18
|
-
|
18
|
+
sig { returns(Integer) }
|
19
|
+
attr_reader :max_retries
|
20
|
+
|
21
|
+
sig { returns(Float) }
|
22
|
+
attr_reader :backoff
|
23
|
+
|
24
|
+
sig { params(id: T.nilable(String), name: T.nilable(String), max_retries: T.nilable(Integer), backoff: T.nilable(Float)).void }
|
25
|
+
def initialize(id: nil, name: nil, max_retries: nil, backoff: nil)
|
19
26
|
@id = T.let(id || SecureRandom.uuid, String)
|
20
27
|
@name = T.let(name, T.nilable(String))
|
28
|
+
@max_retries = T.let(max_retries || 0, Integer)
|
29
|
+
@backoff = T.let(backoff || 1.0, Float)
|
21
30
|
end
|
22
31
|
|
23
32
|
sig do
|
24
33
|
params(
|
25
|
-
messages: T.any(String, Rager::Types::ChatInput),
|
34
|
+
messages: T.any(String, Rager::Types::ChatInput, Rager::Result),
|
26
35
|
kwargs: T.untyped
|
27
36
|
).returns(Rager::Result)
|
28
37
|
end
|
29
38
|
def chat(messages, **kwargs)
|
30
|
-
if messages.is_a?(String)
|
31
|
-
messages = [
|
32
|
-
Rager::Chat::Message.new(
|
33
|
-
role: Rager::Chat::MessageRole::User,
|
34
|
-
content: messages
|
35
|
-
)
|
36
|
-
]
|
37
|
-
end
|
38
|
-
|
39
39
|
execute(
|
40
40
|
Rager::Operation::Chat,
|
41
41
|
Rager::Chat::Options,
|
42
42
|
kwargs,
|
43
43
|
messages
|
44
|
-
) { |options
|
44
|
+
) { |options, normalized_input|
|
45
|
+
final_input = if normalized_input.is_a?(String)
|
46
|
+
[
|
47
|
+
Rager::Chat::Message.new(
|
48
|
+
role: Rager::Chat::MessageRole::User,
|
49
|
+
content: normalized_input
|
50
|
+
)
|
51
|
+
]
|
52
|
+
else
|
53
|
+
normalized_input
|
54
|
+
end
|
55
|
+
|
56
|
+
provider = Rager::Providers.get_provider(:chat, options.provider, options)
|
57
|
+
provider.chat(final_input, options)
|
58
|
+
}
|
45
59
|
end
|
46
60
|
|
47
61
|
sig do
|
48
62
|
params(
|
49
|
-
text: T.any(String, Rager::Types::EmbedInput),
|
63
|
+
text: T.any(String, Rager::Types::EmbedInput, Rager::Result),
|
50
64
|
kwargs: T.untyped
|
51
65
|
).returns(Rager::Result)
|
52
66
|
end
|
53
67
|
def embed(text, **kwargs)
|
54
|
-
if text.is_a?(String)
|
55
|
-
text = [text]
|
56
|
-
end
|
57
|
-
|
58
68
|
execute(
|
59
69
|
Rager::Operation::Embed,
|
60
70
|
Rager::Embed::Options,
|
61
71
|
kwargs,
|
62
72
|
text
|
63
|
-
) { |options
|
73
|
+
) { |options, normalized_input|
|
74
|
+
final_input = if normalized_input.is_a?(String)
|
75
|
+
[normalized_input]
|
76
|
+
else
|
77
|
+
normalized_input
|
78
|
+
end
|
79
|
+
|
80
|
+
provider = Rager::Providers.get_provider(:embed, options.provider, options)
|
81
|
+
provider.embed(final_input, options)
|
82
|
+
}
|
64
83
|
end
|
65
84
|
|
66
85
|
sig do
|
67
86
|
params(
|
68
|
-
prompt: Rager::Types::ImageGenInput,
|
87
|
+
prompt: T.any(Rager::Types::ImageGenInput, Rager::Result),
|
69
88
|
kwargs: T.untyped
|
70
89
|
).returns(Rager::Result)
|
71
90
|
end
|
@@ -75,12 +94,15 @@ module Rager
|
|
75
94
|
Rager::ImageGen::Options,
|
76
95
|
kwargs,
|
77
96
|
prompt
|
78
|
-
) { |options
|
97
|
+
) { |options, normalized_input|
|
98
|
+
provider = Rager::Providers.get_provider(:image_gen, options.provider, options)
|
99
|
+
provider.image_gen(T.cast(normalized_input, Rager::Types::ImageGenInput), options)
|
100
|
+
}
|
79
101
|
end
|
80
102
|
|
81
103
|
sig do
|
82
104
|
params(
|
83
|
-
prompt: Rager::Types::MeshGenInput,
|
105
|
+
prompt: T.any(Rager::Types::MeshGenInput, Rager::Result),
|
84
106
|
kwargs: T.untyped
|
85
107
|
).returns(Rager::Result)
|
86
108
|
end
|
@@ -90,27 +112,35 @@ module Rager
|
|
90
112
|
Rager::MeshGen::Options,
|
91
113
|
kwargs,
|
92
114
|
prompt
|
93
|
-
) { |options
|
115
|
+
) { |options, normalized_input|
|
116
|
+
provider = Rager::Providers.get_provider(:mesh_gen, options.provider, options)
|
117
|
+
provider.mesh_gen(T.cast(normalized_input, Rager::Types::MeshGenInput), options)
|
118
|
+
}
|
94
119
|
end
|
95
120
|
|
96
121
|
sig do
|
97
122
|
params(
|
98
|
-
query: Rager::
|
123
|
+
query: T.any(String, Rager::Result),
|
124
|
+
documents: T.any(T::Array[String], Rager::Result),
|
99
125
|
kwargs: T.untyped
|
100
126
|
).returns(Rager::Result)
|
101
127
|
end
|
102
|
-
def rerank(query, **kwargs)
|
128
|
+
def rerank(query, documents, **kwargs)
|
103
129
|
execute(
|
104
130
|
Rager::Operation::Rerank,
|
105
131
|
Rager::Rerank::Options,
|
106
132
|
kwargs,
|
107
|
-
query
|
108
|
-
) { |options
|
133
|
+
{query: query, documents: documents}
|
134
|
+
) { |options, normalized_input|
|
135
|
+
provider = Rager::Providers.get_provider(:rerank, options.provider, options)
|
136
|
+
input_hash = T.cast(normalized_input, T::Hash[Symbol, T.untyped])
|
137
|
+
provider.rerank(input_hash[:query], input_hash[:documents], options)
|
138
|
+
}
|
109
139
|
end
|
110
140
|
|
111
141
|
sig do
|
112
142
|
params(
|
113
|
-
query: Rager::Types::SearchInput,
|
143
|
+
query: T.any(Rager::Types::SearchInput, Rager::Result),
|
114
144
|
kwargs: T.untyped
|
115
145
|
).returns(Rager::Result)
|
116
146
|
end
|
@@ -120,22 +150,30 @@ module Rager
|
|
120
150
|
Rager::Search::Options,
|
121
151
|
kwargs,
|
122
152
|
query
|
123
|
-
) { |options
|
153
|
+
) { |options, normalized_input|
|
154
|
+
provider = Rager::Providers.get_provider(:search, options.provider, options)
|
155
|
+
provider.search(T.cast(normalized_input, Rager::Types::SearchInput), options)
|
156
|
+
}
|
124
157
|
end
|
125
158
|
|
126
159
|
sig do
|
127
160
|
params(
|
128
|
-
|
161
|
+
template: T.any(String, Rager::Result),
|
162
|
+
variables: T.any(T::Hash[Symbol, T.untyped], Rager::Result),
|
129
163
|
kwargs: T.untyped
|
130
164
|
).returns(Rager::Result)
|
131
165
|
end
|
132
|
-
def template(
|
166
|
+
def template(template, variables, **kwargs)
|
133
167
|
execute(
|
134
168
|
Rager::Operation::Template,
|
135
169
|
Rager::Template::Options,
|
136
170
|
kwargs,
|
137
|
-
|
138
|
-
) { |options
|
171
|
+
{template: template, variables: variables}
|
172
|
+
) { |options, normalized_input|
|
173
|
+
provider = Rager::Providers.get_provider(:template, options.provider, options)
|
174
|
+
input_hash = T.cast(normalized_input, T::Hash[Symbol, T.untyped])
|
175
|
+
provider.template(input_hash[:template], input_hash[:variables], options)
|
176
|
+
}
|
139
177
|
end
|
140
178
|
|
141
179
|
private
|
@@ -145,53 +183,128 @@ module Rager
|
|
145
183
|
operation: Rager::Operation,
|
146
184
|
options_struct: T::Class[Rager::Options],
|
147
185
|
kwargs: T.untyped,
|
148
|
-
input: Rager::Types::Input,
|
149
|
-
block: T.proc.params(options: T.untyped).returns(T.untyped)
|
186
|
+
input: T.any(Rager::Types::Input, Rager::Result, T::Hash[Symbol, T.untyped]),
|
187
|
+
block: T.proc.params(options: T.untyped, normalized_input: Rager::Types::Input).returns(T.untyped)
|
150
188
|
).returns(Rager::Result)
|
151
189
|
end
|
152
190
|
def execute(operation, options_struct, kwargs, input, &block)
|
153
191
|
name = kwargs.delete(:name)
|
154
|
-
|
192
|
+
tags = kwargs.delete(:tags) || []
|
193
|
+
|
194
|
+
existing_input_ids = kwargs.delete(:input_ids) || []
|
195
|
+
normalized_input, new_input_ids = normalize_input(input)
|
196
|
+
input_ids = (existing_input_ids + new_input_ids).uniq
|
155
197
|
|
156
198
|
options = options_struct.new(**kwargs)
|
157
199
|
options.validate
|
158
200
|
|
159
201
|
start_time = Time.now
|
202
|
+
errors = []
|
203
|
+
attempt = 0
|
160
204
|
|
161
205
|
begin
|
162
|
-
output = yield(options)
|
206
|
+
output = yield(options, normalized_input)
|
163
207
|
|
164
208
|
Result.new(
|
165
209
|
id: SecureRandom.uuid,
|
166
210
|
context_id: @id,
|
167
211
|
operation: operation,
|
168
|
-
input:
|
212
|
+
input: normalized_input,
|
169
213
|
output: output,
|
170
214
|
options: options,
|
171
215
|
start_time: start_time.to_i,
|
172
216
|
end_time: Time.now.to_i,
|
173
217
|
name: name,
|
174
218
|
context_name: @name,
|
175
|
-
|
176
|
-
|
219
|
+
tags: tags,
|
220
|
+
input_ids: input_ids,
|
221
|
+
errors: errors,
|
222
|
+
attempt: attempt
|
177
223
|
).tap(&:log)
|
178
224
|
rescue => e
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
225
|
+
errors << e.message
|
226
|
+
attempt += 1
|
227
|
+
|
228
|
+
if attempt < @max_retries
|
229
|
+
delay = @backoff * 2**attempt
|
230
|
+
Rager::Utils::Http.sleep(delay)
|
231
|
+
retry
|
232
|
+
else
|
233
|
+
Result.new(
|
234
|
+
id: SecureRandom.uuid,
|
235
|
+
context_id: @id,
|
236
|
+
operation: operation,
|
237
|
+
input: normalized_input,
|
238
|
+
output: nil,
|
239
|
+
options: options,
|
240
|
+
start_time: start_time.to_i,
|
241
|
+
end_time: Time.now.to_i,
|
242
|
+
name: name,
|
243
|
+
context_name: @name,
|
244
|
+
tags: tags,
|
245
|
+
input_ids: input_ids,
|
246
|
+
errors: errors,
|
247
|
+
attempt: attempt
|
248
|
+
).tap(&:log)
|
249
|
+
|
250
|
+
raise e
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
sig { params(input: T.untyped, ids: T::Array[String]).returns([T.untyped, T::Array[String]]) }
|
256
|
+
def normalize_input(input, ids: [])
|
257
|
+
case input
|
258
|
+
when Rager::Result
|
259
|
+
ids << input.id unless ids.include?(input.id)
|
260
|
+
|
261
|
+
materialized_output = input.mat
|
262
|
+
normalized_output = case materialized_output
|
263
|
+
when Rager::Search::Output
|
264
|
+
materialized_output.contents
|
265
|
+
when Rager::Rerank::Output
|
266
|
+
materialized_output.documents
|
267
|
+
else
|
268
|
+
materialized_output
|
269
|
+
end
|
270
|
+
|
271
|
+
[normalized_output, ids]
|
272
|
+
when Hash
|
273
|
+
normalized_hash = {}
|
274
|
+
input.each do |key, value|
|
275
|
+
normalized_value, ids = normalize_input(value, ids: ids)
|
276
|
+
normalized_hash[key] = normalized_value
|
277
|
+
end
|
278
|
+
[normalized_hash, ids]
|
279
|
+
when Array
|
280
|
+
normalized_array = []
|
281
|
+
input.each do |item|
|
282
|
+
normalized_item, ids = normalize_input(item, ids: ids)
|
283
|
+
normalized_array << normalized_item
|
284
|
+
end
|
285
|
+
[normalized_array, ids]
|
286
|
+
else
|
287
|
+
[input, ids]
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
sig { returns(String) }
|
292
|
+
def to_json
|
293
|
+
{
|
294
|
+
id: @id,
|
295
|
+
name: @name,
|
296
|
+
max_retries: @max_retries,
|
297
|
+
backoff: @backoff
|
298
|
+
}.to_json
|
299
|
+
end
|
193
300
|
|
194
|
-
|
301
|
+
sig { params(json: String, id: T.nilable(String), name: T.nilable(String), max_retries: T.nilable(Integer), backoff: T.nilable(Float)).returns(Rager::Context) }
|
302
|
+
def from_json(json, id: nil, name: nil, max_retries: nil, backoff: nil)
|
303
|
+
JSON.parse(json, symbolize_names: true).tap do |data|
|
304
|
+
@id = id || data[:id]
|
305
|
+
@name = name || data[:name]
|
306
|
+
@max_retries = max_retries || data[:max_retries]
|
307
|
+
@backoff = backoff || data[:backoff]
|
195
308
|
end
|
196
309
|
end
|
197
310
|
end
|