ruby_llm-instructor 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.
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ad714a8bf1c0fdec80e73420156ddc0d93fa9e48a82b81078918a2ab19e34651
|
|
4
|
+
data.tar.gz: 539d055dbb5dba7937fdd040b740329edcf1f794259963438ef777538a79c7db
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 36ef7c3fcbf4bedc8eb415e57d0858691e2a8710f558642d04a4fa5c367ef1c6ca41bf85b15dfd82ebf2d45e088ab45069bc8cdf0b5b3182f64b9d1b936de24f
|
|
7
|
+
data.tar.gz: d3bd594ecdecadc5c603f5eb70d8ca79e15a08de79a29765664a7fa36eb57000afb17dcd55a0b0c9a530ad2c7c6861165df28a58e558e825d96863cc179e67f5
|
data/README.md
CHANGED
|
@@ -189,9 +189,15 @@ person.frozen? # => true
|
|
|
189
189
|
|
|
190
190
|
### Struct
|
|
191
191
|
|
|
192
|
+
Both `keyword_init: true` and positional structs are supported:
|
|
193
|
+
|
|
192
194
|
```ruby
|
|
195
|
+
# keyword_init (recommended)
|
|
193
196
|
Address = Struct.new(:street, :city, :zip, keyword_init: true)
|
|
194
197
|
|
|
198
|
+
# positional — also works
|
|
199
|
+
Point = Struct.new(:x, :y)
|
|
200
|
+
|
|
195
201
|
address = instructor.chat(
|
|
196
202
|
model: "gpt-4o",
|
|
197
203
|
response_model: Address,
|
|
@@ -246,6 +252,7 @@ instructor.chat(
|
|
|
246
252
|
By default `ruby_llm-instructor` uses `mode: :schema` — structured output via the
|
|
247
253
|
provider's native JSON schema constraint. Pass `mode: :tools` to use function
|
|
248
254
|
calling instead, which works with older models that pre-date structured output.
|
|
255
|
+
Passing any other value raises `ArgumentError` immediately.
|
|
249
256
|
|
|
250
257
|
```ruby
|
|
251
258
|
# Default — structured output (recommended for modern models)
|
|
@@ -258,9 +265,8 @@ instructor.chat(model: "gpt-3.5-turbo", response_model: MyModel, prompt: "...",
|
|
|
258
265
|
## Auto-retry on validation failure
|
|
259
266
|
|
|
260
267
|
When the LLM returns data that fails `valid?`, `ruby_llm-instructor` feeds the
|
|
261
|
-
error messages back to the model
|
|
262
|
-
`max_retries` times (default: 3).
|
|
263
|
-
is raised.
|
|
268
|
+
error messages back to the model — along with the original task — and asks for a
|
|
269
|
+
corrected response. This repeats up to `max_retries` times (default: 3).
|
|
264
270
|
|
|
265
271
|
```ruby
|
|
266
272
|
instructor.chat(
|
|
@@ -271,6 +277,18 @@ instructor.chat(
|
|
|
271
277
|
)
|
|
272
278
|
```
|
|
273
279
|
|
|
280
|
+
If all retries are exhausted a `RubyLLM::Instructor::ValidationError` is raised
|
|
281
|
+
(a `StandardError` subclass), carrying the final validation message:
|
|
282
|
+
|
|
283
|
+
```ruby
|
|
284
|
+
begin
|
|
285
|
+
instructor.chat(model: "gpt-4o", response_model: LeadCapture, prompt: "...")
|
|
286
|
+
rescue RubyLLM::Instructor::ValidationError => e
|
|
287
|
+
# e.message => "ruby_llm-instructor failed validation after 3 attempts. Errors: ..."
|
|
288
|
+
Rails.logger.warn("LLM extraction failed: #{e.message}")
|
|
289
|
+
end
|
|
290
|
+
```
|
|
291
|
+
|
|
274
292
|
## One model, any provider
|
|
275
293
|
|
|
276
294
|
The `model:` string is passed straight through to `ruby_llm`:
|
|
@@ -286,13 +304,15 @@ instructor.chat(model: "claude-3-5-sonnet", ...)
|
|
|
286
304
|
instructor.chat(model: "llama3", ...)
|
|
287
305
|
```
|
|
288
306
|
|
|
289
|
-
## What's in v0.
|
|
307
|
+
## What's in v0.2
|
|
290
308
|
|
|
291
309
|
- All `ruby_llm`-supported providers (OpenAI, Anthropic, Gemini, Ollama, …)
|
|
292
|
-
- Response models: PORO, ActiveModel, native dry-validation contract, duck-typed dry-v bridge, `Data.define`, `Struct
|
|
310
|
+
- Response models: PORO, ActiveModel, native dry-validation contract, duck-typed dry-v bridge, `Data.define`, `Struct` (keyword and positional), custom `to_json_schema`
|
|
293
311
|
- Type inference from `ActiveModel::Attributes` (integer, number, boolean)
|
|
294
312
|
- Required vs. optional fields from presence validators
|
|
295
|
-
- Automatic retry-on-validation-failure with corrective prompt
|
|
313
|
+
- Automatic retry-on-validation-failure with corrective prompt (original task preserved on each retry)
|
|
314
|
+
- `RubyLLM::Instructor::ValidationError` raised on exhaustion — rescueable by type
|
|
315
|
+
- `mode:` validation — `ArgumentError` on unknown values
|
|
296
316
|
- Streaming via `stream:` proc
|
|
297
317
|
- Function-calling fallback via `mode: :tools`
|
|
298
318
|
|
|
@@ -54,11 +54,7 @@ module RubyLLM
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
def dry_contract?
|
|
57
|
-
|
|
58
|
-
@klass.is_a?(Class) &&
|
|
59
|
-
@klass < Dry::Validation::Contract
|
|
60
|
-
rescue TypeError
|
|
61
|
-
false
|
|
57
|
+
Utils.dry_contract?(@klass)
|
|
62
58
|
end
|
|
63
59
|
|
|
64
60
|
def build_dry_contract_schema
|
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
module RubyLLM
|
|
4
4
|
module Instructor
|
|
5
5
|
class Client
|
|
6
|
+
VALID_MODES = %i[schema tools].freeze
|
|
7
|
+
|
|
6
8
|
def chat(model:, response_model:, prompt:, max_retries: 3, stream: nil, mode: :schema)
|
|
9
|
+
unless VALID_MODES.include?(mode)
|
|
10
|
+
raise ArgumentError, "Unknown mode #{mode.inspect}. Valid modes are: #{VALID_MODES.join(', ')}"
|
|
11
|
+
end
|
|
12
|
+
|
|
7
13
|
compiled_schema = Adapters::RubyLlmSchemaAdapter.new(response_model).build_schema
|
|
8
14
|
current_prompt = prompt
|
|
9
15
|
retries = 0
|
|
@@ -28,10 +34,12 @@ module RubyLLM
|
|
|
28
34
|
rescue ValidationError => e
|
|
29
35
|
if retries < max_retries
|
|
30
36
|
retries += 1
|
|
31
|
-
current_prompt = "
|
|
37
|
+
current_prompt = "Original task: #{prompt}\n\n" \
|
|
38
|
+
"Your previous response failed validation: #{e.message}. " \
|
|
39
|
+
"Please fix the data to match the schema exactly."
|
|
32
40
|
retry
|
|
33
41
|
else
|
|
34
|
-
raise "ruby_llm-instructor failed validation after #{max_retries} attempts. Errors: #{e.message}"
|
|
42
|
+
raise ValidationError, "ruby_llm-instructor failed validation after #{max_retries} attempts. Errors: #{e.message}"
|
|
35
43
|
end
|
|
36
44
|
end
|
|
37
45
|
end
|
|
@@ -58,11 +66,7 @@ module RubyLLM
|
|
|
58
66
|
end
|
|
59
67
|
|
|
60
68
|
def dry_contract?(klass)
|
|
61
|
-
|
|
62
|
-
klass.is_a?(Class) &&
|
|
63
|
-
klass < Dry::Validation::Contract
|
|
64
|
-
rescue TypeError
|
|
65
|
-
false
|
|
69
|
+
Utils.dry_contract?(klass)
|
|
66
70
|
end
|
|
67
71
|
|
|
68
72
|
def validate_payload(response_model, parsed_data)
|
|
@@ -91,7 +95,13 @@ module RubyLLM
|
|
|
91
95
|
end
|
|
92
96
|
|
|
93
97
|
if response_model.respond_to?(:members)
|
|
94
|
-
|
|
98
|
+
kwargs = parsed_data.transform_keys(&:to_sym)
|
|
99
|
+
begin
|
|
100
|
+
response_model.new(**kwargs)
|
|
101
|
+
rescue ArgumentError
|
|
102
|
+
# Positional Struct (no keyword_init:true) — fall back to positional args
|
|
103
|
+
response_model.new(*response_model.members.map { |m| kwargs[m] })
|
|
104
|
+
end
|
|
95
105
|
else
|
|
96
106
|
instance = response_model.new
|
|
97
107
|
parsed_data.each do |key, value|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Instructor
|
|
5
|
+
module Utils
|
|
6
|
+
# Returns true when +klass+ is a Dry::Validation::Contract subclass.
|
|
7
|
+
# Safely returns false when dry-validation is not loaded or klass is not a
|
|
8
|
+
# class (e.g. an instance, a module, or a Data object).
|
|
9
|
+
def dry_contract?(klass)
|
|
10
|
+
!!(defined?(Dry::Validation::Contract) &&
|
|
11
|
+
klass.is_a?(Class) &&
|
|
12
|
+
klass < Dry::Validation::Contract)
|
|
13
|
+
rescue TypeError
|
|
14
|
+
false
|
|
15
|
+
end
|
|
16
|
+
module_function :dry_contract?
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
data/lib/ruby_llm/instructor.rb
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "ruby_llm"
|
|
2
4
|
require "ruby_llm/schema"
|
|
3
5
|
require "active_model"
|
|
4
6
|
require_relative "instructor/version"
|
|
7
|
+
require_relative "instructor/utils"
|
|
5
8
|
require_relative "instructor/adapters/ruby_llm_schema"
|
|
6
9
|
require_relative "instructor/client"
|
|
7
10
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_llm-instructor
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sal Scotto Di Luzio
|
|
@@ -30,6 +30,9 @@ dependencies:
|
|
|
30
30
|
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
32
|
version: 0.4.0
|
|
33
|
+
- - "<"
|
|
34
|
+
- !ruby/object:Gem::Version
|
|
35
|
+
version: '2'
|
|
33
36
|
type: :runtime
|
|
34
37
|
prerelease: false
|
|
35
38
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -37,6 +40,9 @@ dependencies:
|
|
|
37
40
|
- - ">="
|
|
38
41
|
- !ruby/object:Gem::Version
|
|
39
42
|
version: 0.4.0
|
|
43
|
+
- - "<"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '2'
|
|
40
46
|
- !ruby/object:Gem::Dependency
|
|
41
47
|
name: rspec
|
|
42
48
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -132,10 +138,15 @@ files:
|
|
|
132
138
|
- lib/ruby_llm/instructor.rb
|
|
133
139
|
- lib/ruby_llm/instructor/adapters/ruby_llm_schema.rb
|
|
134
140
|
- lib/ruby_llm/instructor/client.rb
|
|
141
|
+
- lib/ruby_llm/instructor/utils.rb
|
|
135
142
|
- lib/ruby_llm/instructor/version.rb
|
|
143
|
+
homepage: https://github.com/washu/ruby_llm-instructor
|
|
136
144
|
licenses:
|
|
137
145
|
- MIT
|
|
138
|
-
metadata:
|
|
146
|
+
metadata:
|
|
147
|
+
source_code_uri: https://github.com/washu/ruby_llm-instructor
|
|
148
|
+
changelog_uri: https://github.com/washu/ruby_llm-instructor/blob/main/CHANGELOG.md
|
|
149
|
+
bug_tracker_uri: https://github.com/washu/ruby_llm-instructor/issues
|
|
139
150
|
rdoc_options: []
|
|
140
151
|
require_paths:
|
|
141
152
|
- lib
|