dspy 0.34.4 → 1.0.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: 5d5296e0130d0550156345659e6703451bd6ca6fb9ffbac33d8584ee203f2a84
4
- data.tar.gz: 339772eb768a2babbb8b700b868fc772183473b7cf4f8a6867e55945f75a3655
3
+ metadata.gz: 85ae2299dbddc20bfd710c68e9e8dfbeeb201b742e3fd18b768669ddc4443261
4
+ data.tar.gz: ba0ee2d5f637f499448e456fb58f0513aa58b5b258e754bf1b32839ea8c49b63
5
5
  SHA512:
6
- metadata.gz: 827360cba1ad8d03373d40d9b1b9ce3acf966e896261b918537150568cf4635904e591c238cdea7d8e8bf45980851ce3c581f709639215c8d7e4c7e1ea78dc08
7
- data.tar.gz: e8d91df3e7204ac0d0db830c5839a4cbdd9683a00be2a77dec38de156052f0a0b3956e25c8b7026fcce23880e9806fc9af023fc2a4fe830926f288f82474a85b
6
+ metadata.gz: 310de5ce11b23d29bd168069eae4f5ce3ac154ba91974c96b9d70e5d02a0dcb45122dc4c83f80097a409ff448a0a30d45ebec3ff0883d80b2cdd8f1215d2dfff
7
+ data.tar.gz: 1ce5ff1bfe900bcedabab28efba1e8352fed81c6cbf458d5b7c54e8f7eea29fa6b7ee33a62dcc73b456ede6181a85bbf055541e4fb70037ac4de05e3be2b8ce9
data/README.md CHANGED
@@ -9,6 +9,7 @@
9
9
  **Build reliable LLM applications in idiomatic Ruby using composable, type-safe modules.**
10
10
 
11
11
  DSPy.rb is the Ruby port of Stanford's [DSPy](https://dspy.ai). Instead of wrestling with brittle prompt strings, you define typed signatures and let the framework handle the rest. Prompts become functions. LLM calls become predictable.
12
+ The `1.x` line is the stable release track for production Ruby LLM applications.
12
13
 
13
14
  ```ruby
14
15
  require 'dspy'
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+ require 'stringio'
5
+ require 'uri'
6
+
7
+ module DSPy
8
+ class Document
9
+ class RubyLLMInlineAttachment < StringIO
10
+ attr_reader :path
11
+
12
+ def initialize(content, path:)
13
+ super(content)
14
+ @path = path
15
+ binmode
16
+ end
17
+ end
18
+
19
+ private_constant :RubyLLMInlineAttachment
20
+
21
+ attr_reader :url, :base64, :data, :content_type
22
+
23
+ SUPPORTED_FORMATS = %w[application/pdf].freeze
24
+ MAX_SIZE_BYTES = 32 * 1024 * 1024 # 32MB limit
25
+
26
+ def initialize(url: nil, base64: nil, data: nil, content_type: nil)
27
+ validate_input!(url, base64, data)
28
+
29
+ if url
30
+ @url = url
31
+ @content_type = content_type || infer_content_type_from_url(url)
32
+ elsif base64
33
+ raise ArgumentError, "content_type is required when using base64" unless content_type
34
+
35
+ @base64 = base64
36
+ @content_type = content_type
37
+ validate_size!(Base64.decode64(base64).bytesize)
38
+ elsif data
39
+ raise ArgumentError, "content_type is required when using data" unless content_type
40
+
41
+ @data = data
42
+ @content_type = content_type
43
+ validate_size!(data.size)
44
+ end
45
+
46
+ validate_content_type!
47
+ end
48
+
49
+ def to_openai_format
50
+ raise DSPy::LM::IncompatibleDocumentFeatureError,
51
+ "OpenAI document inputs are not supported in this release. Use Anthropic directly or Anthropic via RubyLLM."
52
+ end
53
+
54
+ def to_anthropic_format
55
+ if url
56
+ {
57
+ type: 'document',
58
+ source: {
59
+ type: 'url',
60
+ url: url
61
+ }
62
+ }
63
+ else
64
+ {
65
+ type: 'document',
66
+ source: {
67
+ type: 'base64',
68
+ media_type: content_type,
69
+ data: to_base64
70
+ }
71
+ }
72
+ end
73
+ end
74
+
75
+ def to_gemini_format
76
+ raise DSPy::LM::IncompatibleDocumentFeatureError,
77
+ "Gemini document inputs are not supported in this release. Use Anthropic directly or Anthropic via RubyLLM."
78
+ end
79
+
80
+ def to_ruby_llm_attachment
81
+ if url
82
+ url
83
+ else
84
+ RubyLLMInlineAttachment.new(to_binary, path: 'document.pdf')
85
+ end
86
+ end
87
+
88
+ def to_base64
89
+ return base64 if base64
90
+ return Base64.strict_encode64(data.pack('C*')) if data
91
+
92
+ nil
93
+ end
94
+
95
+ def validate_for_provider!(provider)
96
+ case provider
97
+ when 'anthropic'
98
+ true
99
+ when 'openai'
100
+ raise DSPy::LM::IncompatibleDocumentFeatureError,
101
+ "OpenAI document inputs are not supported in this release. Use Anthropic directly or Anthropic via RubyLLM."
102
+ when 'gemini'
103
+ raise DSPy::LM::IncompatibleDocumentFeatureError,
104
+ "Gemini document inputs are not supported in this release. Use Anthropic directly or Anthropic via RubyLLM."
105
+ else
106
+ raise DSPy::LM::IncompatibleDocumentFeatureError,
107
+ "Unknown provider '#{provider}'. Document inputs are currently supported only for Anthropic."
108
+ end
109
+ end
110
+
111
+ private
112
+
113
+ def validate_input!(url, base64, data)
114
+ inputs = [url, base64, data].compact
115
+
116
+ if inputs.empty?
117
+ raise ArgumentError, "Must provide either url, base64, or data"
118
+ elsif inputs.size > 1
119
+ raise ArgumentError, "Only one of url, base64, or data can be provided"
120
+ end
121
+ end
122
+
123
+ def validate_content_type!
124
+ unless SUPPORTED_FORMATS.include?(content_type)
125
+ raise ArgumentError, "Unsupported document format: #{content_type}. Supported formats: #{SUPPORTED_FORMATS.join(', ')}"
126
+ end
127
+ end
128
+
129
+ def validate_size!(size_bytes)
130
+ if size_bytes > MAX_SIZE_BYTES
131
+ raise ArgumentError, "Document size exceeds 32MB limit (got #{size_bytes} bytes)"
132
+ end
133
+ end
134
+
135
+ def infer_content_type_from_url(url)
136
+ extension = File.extname(URI.parse(url).path).downcase
137
+
138
+ case extension
139
+ when '.pdf'
140
+ 'application/pdf'
141
+ else
142
+ raise ArgumentError, "Document URL must point to a PDF (.pdf): #{url}"
143
+ end
144
+ end
145
+
146
+ def to_binary
147
+ return Base64.decode64(base64) if base64
148
+ return data.pack('C*') if data
149
+
150
+ raise ArgumentError, "Document has no binary content"
151
+ end
152
+ end
153
+ end
@@ -58,6 +58,17 @@ module DSPy
58
58
  end
59
59
  end
60
60
 
61
+ def contains_documents?(messages)
62
+ messages.any? do |msg|
63
+ content = msg[:content] || msg.content
64
+ content.is_a?(Array) && content.any? { |item| item[:type] == 'document' }
65
+ end
66
+ end
67
+
68
+ def contains_media?(messages)
69
+ contains_images?(messages) || contains_documents?(messages)
70
+ end
71
+
61
72
  # Format multimodal messages for a specific provider
62
73
  # @param messages [Array<Hash>] Array of message hashes
63
74
  # @param provider_name [String] Provider name for image validation and formatting
@@ -71,6 +82,8 @@ module DSPy
71
82
  { type: 'text', text: item[:text] }
72
83
  when 'image'
73
84
  format_image_for_provider(item[:image], provider_name)
85
+ when 'document'
86
+ format_document_for_provider(item[:document], provider_name)
74
87
  else
75
88
  item
76
89
  end
@@ -96,6 +109,16 @@ module DSPy
96
109
  { type: 'image', image: image }
97
110
  end
98
111
  end
112
+
113
+ def format_document_for_provider(document, provider_name)
114
+ document.validate_for_provider!(provider_name)
115
+ format_method = "to_#{provider_name}_format"
116
+ if document.respond_to?(format_method)
117
+ document.send(format_method)
118
+ else
119
+ { type: 'document', document: document }
120
+ end
121
+ end
99
122
  end
100
123
  end
101
124
  end
@@ -7,8 +7,6 @@ module DSPy
7
7
  class UnsupportedProviderError < Error; end
8
8
  class ConfigurationError < Error; end
9
9
  class MissingAdapterError < Error; end
10
- class UnsupportedVersionError < Error; end
11
- class MissingOfficialSDKError < Error; end
12
10
 
13
11
  # Raised when API key is missing or invalid
14
12
  class MissingAPIKeyError < Error
@@ -29,5 +27,12 @@ module DSPy
29
27
  super(message)
30
28
  end
31
29
  end
30
+
31
+ # Raised when document features are incompatible with the target provider
32
+ class IncompatibleDocumentFeatureError < AdapterError
33
+ def initialize(message)
34
+ super(message)
35
+ end
36
+ end
32
37
  end
33
38
  end
@@ -136,59 +136,96 @@ module DSPy
136
136
  def extract_json_from_content(content)
137
137
  return content if content.nil? || content.empty?
138
138
 
139
- # Fix Anthropic Beta API bug with optional fields producing invalid JSON
140
- # When some output fields are optional and not returned, Anthropic's structured outputs
141
- # can produce trailing comma+brace: {"field1": {...},} instead of {"field1": {...}}
142
- # This workaround removes the invalid trailing syntax before JSON parsing
143
- if content =~ /,\s*\}\s*$/
144
- content = content.sub(/,(\s*\}\s*)$/, '\1')
145
- end
146
-
147
139
  # Try 1: Check for ```json code block (with or without preceding text)
148
140
  if content.include?('```json')
149
141
  json_match = content.match(/```json\s*\n(.*?)\n```/m)
150
- return json_match[1].strip if json_match
142
+ if json_match
143
+ normalized = normalize_json_candidate(json_match[1].strip)
144
+ return normalized if valid_json?(normalized)
145
+ end
151
146
  end
152
147
 
153
148
  # Try 2: Check for generic ``` code block
154
149
  if content.include?('```')
155
150
  code_match = content.match(/```\s*\n(.*?)\n```/m)
156
151
  if code_match
157
- potential_json = code_match[1].strip
158
- # Verify it's JSON
159
- begin
160
- JSON.parse(potential_json)
161
- return potential_json
162
- rescue JSON::ParserError
163
- # Not valid JSON, continue
164
- end
152
+ potential_json = normalize_json_candidate(code_match[1].strip)
153
+ return potential_json if valid_json?(potential_json)
165
154
  end
166
155
  end
167
156
 
168
157
  # Try 3: Try parsing entire content as JSON
169
- begin
170
- JSON.parse(content)
171
- return content
172
- rescue JSON::ParserError
173
- # Not pure JSON, try extracting
174
- end
158
+ normalized_content = normalize_json_candidate(content)
159
+ return normalized_content if valid_json?(normalized_content)
175
160
 
176
161
  # Try 4: Look for JSON object pattern in text (greedy match for nested objects)
177
162
  json_pattern = /\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\}/m
178
163
  json_match = content.match(json_pattern)
179
164
  if json_match
180
- potential_json = json_match[0]
181
- begin
182
- JSON.parse(potential_json)
183
- return potential_json
184
- rescue JSON::ParserError
185
- # Not valid JSON
186
- end
165
+ potential_json = normalize_json_candidate(json_match[0])
166
+ return potential_json if valid_json?(potential_json)
187
167
  end
188
168
 
189
169
  # Return content as-is if no JSON found
190
170
  content
191
171
  end
172
+
173
+ sig { params(content: String).returns(String) }
174
+ def normalize_json_candidate(content)
175
+ escape_control_characters_in_strings(remove_trailing_object_commas(content))
176
+ end
177
+
178
+ sig { params(content: String).returns(String) }
179
+ def remove_trailing_object_commas(content)
180
+ content.sub(/,(\s*\}\s*)$/, '\1')
181
+ end
182
+
183
+ sig { params(content: String).returns(T::Boolean) }
184
+ def valid_json?(content)
185
+ JSON.parse(content)
186
+ true
187
+ rescue JSON::ParserError
188
+ false
189
+ end
190
+
191
+ sig { params(content: String).returns(String) }
192
+ def escape_control_characters_in_strings(content)
193
+ escaped = +""
194
+ in_string = false
195
+ escaping = false
196
+
197
+ content.each_char do |char|
198
+ if in_string
199
+ if escaping
200
+ escaped << char
201
+ escaping = false
202
+ next
203
+ end
204
+
205
+ case char
206
+ when '\\'
207
+ escaped << char
208
+ escaping = true
209
+ when '"'
210
+ escaped << char
211
+ in_string = false
212
+ when "\n"
213
+ escaped << '\n'
214
+ when "\r"
215
+ escaped << '\r'
216
+ when "\t"
217
+ escaped << '\t'
218
+ else
219
+ escaped << (char.ord < 0x20 ? "" : char)
220
+ end
221
+ else
222
+ escaped << char
223
+ in_string = true if char == '"'
224
+ end
225
+ end
226
+
227
+ escaped
228
+ end
192
229
  end
193
230
  end
194
231
  end
@@ -59,6 +59,8 @@ module DSPy
59
59
  { type: 'text', text: item[:text] }
60
60
  when 'image'
61
61
  item[:image].to_openai_format
62
+ when 'document'
63
+ item[:document].to_openai_format
62
64
  else
63
65
  item
64
66
  end
@@ -83,6 +85,8 @@ module DSPy
83
85
  { type: 'text', text: item[:text] }
84
86
  when 'image'
85
87
  item[:image].to_anthropic_format
88
+ when 'document'
89
+ item[:document].to_anthropic_format
86
90
  else
87
91
  item
88
92
  end
@@ -160,4 +164,4 @@ module DSPy
160
164
  end
161
165
  end
162
166
  end
163
- end
167
+ end
@@ -68,6 +68,20 @@ module DSPy
68
68
  )
69
69
  self
70
70
  end
71
+
72
+ sig { params(text: String, document: DSPy::Document).returns(MessageBuilder) }
73
+ def user_with_document(text, document)
74
+ content_array = [
75
+ { type: 'text', text: text },
76
+ { type: 'document', document: document }
77
+ ]
78
+
79
+ @messages << Message.new(
80
+ role: Message::Role::User,
81
+ content: content_array
82
+ )
83
+ self
84
+ end
71
85
 
72
86
  # For backward compatibility, allow conversion to hash array
73
87
  sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
@@ -76,4 +90,4 @@ module DSPy
76
90
  end
77
91
  end
78
92
  end
79
- end
93
+ end
data/lib/dspy/lm.rb CHANGED
@@ -161,16 +161,78 @@ module DSPy
161
161
  )
162
162
  end
163
163
 
164
- # Add user message
165
- user_prompt = prompt.render_user_prompt(input_values)
166
- messages << Message.new(
167
- role: Message::Role::User,
168
- content: user_prompt
169
- )
164
+ document_inputs = extract_document_inputs(input_values)
165
+
166
+ if document_inputs.empty?
167
+ user_prompt = prompt.render_user_prompt(input_values)
168
+ messages << Message.new(
169
+ role: Message::Role::User,
170
+ content: user_prompt
171
+ )
172
+ else
173
+ validate_document_predict_support!(input_values, document_inputs)
174
+
175
+ placeholder_inputs = input_values.transform_values do |value|
176
+ value.is_a?(DSPy::Document) ? "[attached pdf document]" : value
177
+ end
178
+
179
+ user_prompt = prompt.render_user_prompt(placeholder_inputs)
180
+ content_array = [
181
+ { type: 'text', text: user_prompt },
182
+ { type: 'document', document: document_inputs.first.last }
183
+ ]
184
+
185
+ messages << Message.new(
186
+ role: Message::Role::User,
187
+ content: content_array
188
+ )
189
+ end
170
190
 
171
191
  messages
172
192
  end
173
193
 
194
+ def extract_document_inputs(input_values)
195
+ input_values.each_with_object([]) do |(key, value), inputs|
196
+ if value.is_a?(DSPy::Document)
197
+ inputs << [key, value]
198
+ elsif nested_document_input?(value)
199
+ raise DSPy::LM::IncompatibleDocumentFeatureError,
200
+ "Only one top-level DSPy::Document input is currently supported in Predict."
201
+ end
202
+ end
203
+ end
204
+
205
+ def nested_document_input?(value)
206
+ case value
207
+ when T::Struct
208
+ value.class.props.any? { |name, _| nested_document_input?(value.public_send(name)) }
209
+ when Array
210
+ value.any? { |item| nested_document_input?(item) }
211
+ when Hash
212
+ value.values.any? { |item| nested_document_input?(item) }
213
+ else
214
+ value.is_a?(DSPy::Document)
215
+ end
216
+ end
217
+
218
+ def validate_document_predict_support!(input_values, document_inputs)
219
+ if document_inputs.length > 1
220
+ raise DSPy::LM::IncompatibleDocumentFeatureError,
221
+ "Only one top-level DSPy::Document input is currently supported in Predict."
222
+ end
223
+
224
+ if input_values.values.any? { |value| value.is_a?(DSPy::Image) }
225
+ raise DSPy::LM::IncompatibleDocumentFeatureError,
226
+ "Predict does not support mixing DSPy::Document and DSPy::Image inputs in this release."
227
+ end
228
+
229
+ return if provider == 'anthropic'
230
+ return if adapter.class.name.include?('RubyLLMAdapter') && adapter.provider == 'anthropic'
231
+
232
+ raise DSPy::LM::IncompatibleDocumentFeatureError,
233
+ "Document inputs are currently supported only for Anthropic models and Anthropic via RubyLLM."
234
+ end
235
+
174
236
  def will_use_structured_outputs?(signature_class, data_format: nil)
175
237
  return false unless signature_class
176
238
  return false if data_format == :toon
@@ -5,9 +5,6 @@ require 'ruby_llm'
5
5
  require 'dspy/lm/adapter'
6
6
  require 'dspy/lm/vision_models'
7
7
 
8
- require 'dspy/ruby_llm/guardrails'
9
- DSPy::RubyLLM::Guardrails.ensure_ruby_llm_installed!
10
-
11
8
  module DSPy
12
9
  module RubyLLM
13
10
  module LM
@@ -49,6 +46,8 @@ module DSPy
49
46
  def chat(messages:, signature: nil, &block)
50
47
  normalized_messages = normalize_messages(messages)
51
48
 
49
+ validate_document_support!(normalized_messages)
50
+
52
51
  # Validate vision support if images are present
53
52
  if contains_images?(normalized_messages)
54
53
  validate_vision_support!
@@ -255,6 +254,9 @@ module DSPy
255
254
  elsif item[:image_url]
256
255
  attachments << item[:image_url][:url]
257
256
  end
257
+ when 'document'
258
+ document = item[:document]
259
+ attachments << document.to_ruby_llm_attachment if document
258
260
  end
259
261
  end
260
262
  content = text_parts.join("\n")
@@ -263,6 +265,14 @@ module DSPy
263
265
  [content.to_s, attachments]
264
266
  end
265
267
 
268
+ def validate_document_support!(messages)
269
+ return unless contains_documents?(messages)
270
+ return if provider == 'anthropic'
271
+
272
+ raise DSPy::LM::IncompatibleDocumentFeatureError,
273
+ "RubyLLM document inputs are currently supported only when the underlying provider is Anthropic."
274
+ end
275
+
266
276
  def map_response(ruby_llm_response)
267
277
  DSPy::LM::Response.new(
268
278
  content: ruby_llm_response.content.to_s,
@@ -2,6 +2,6 @@
2
2
 
3
3
  module DSPy
4
4
  module RubyLLM
5
- VERSION = '0.1.0'
5
+ VERSION = '0.1.1'
6
6
  end
7
7
  end
data/lib/dspy/ruby_llm.rb CHANGED
@@ -2,7 +2,4 @@
2
2
 
3
3
  require 'dspy/ruby_llm/version'
4
4
 
5
- require 'dspy/ruby_llm/guardrails'
6
- DSPy::RubyLLM::Guardrails.ensure_ruby_llm_installed!
7
-
8
5
  require 'dspy/ruby_llm/lm/adapters/ruby_llm_adapter'
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DSPy
4
+ module Support
5
+ module OpenAISDKWarning
6
+ WARNING_MESSAGE = <<~WARNING.freeze
7
+ WARNING: ruby-openai gem detected. This may cause conflicts with DSPy's OpenAI integration.
8
+
9
+ DSPy uses the official 'openai' gem. The community 'ruby-openai' gem uses the same
10
+ OpenAI namespace and will cause conflicts.
11
+
12
+ To fix this, remove 'ruby-openai' from your Gemfile and use the official gem instead:
13
+ - Remove: gem 'ruby-openai'
14
+ - Keep: gem 'openai' (official SDK that DSPy uses)
15
+
16
+ The official gem provides better compatibility and is actively maintained by OpenAI.
17
+ WARNING
18
+
19
+ def self.warn_if_community_gem_loaded!
20
+ return if @warned
21
+ return unless community_gem_loaded?
22
+
23
+ Kernel.warn WARNING_MESSAGE
24
+ @warned = true
25
+ end
26
+
27
+ def self.community_gem_loaded?
28
+ defined?(::OpenAI) && defined?(::OpenAI::Client) && !defined?(::OpenAI::Internal)
29
+ end
30
+ end
31
+ end
32
+ end
data/lib/dspy/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DSPy
4
- VERSION = "0.34.4"
4
+ VERSION = "1.0.0"
5
5
  end
data/lib/dspy.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require_relative 'dspy/support/warning_filters'
3
+ require_relative 'dspy/support/openai_sdk_warning'
3
4
  require 'sorbet-runtime'
4
5
  require 'dry-configurable'
5
6
  require 'dry/logger'
@@ -267,6 +268,7 @@ require_relative 'dspy/prompt'
267
268
  require_relative 'dspy/example'
268
269
  require_relative 'dspy/lm'
269
270
  require_relative 'dspy/image'
271
+ require_relative 'dspy/document'
270
272
  require_relative 'dspy/prediction'
271
273
  require_relative 'dspy/predict'
272
274
  require_relative 'dspy/chain_of_thought'
@@ -309,19 +311,4 @@ DSPy::Observability.configure!
309
311
 
310
312
  # LoggerSubscriber will be lazy-initialized when first accessed
311
313
 
312
- # Detect potential gem conflicts and warn users
313
- # DSPy uses the official openai gem, warn if ruby-openai (community version) is detected
314
- if defined?(OpenAI) && defined?(OpenAI::Client) && !defined?(OpenAI::Internal)
315
- warn <<~WARNING
316
- WARNING: ruby-openai gem detected. This may cause conflicts with DSPy's OpenAI integration.
317
-
318
- DSPy uses the official 'openai' gem. The community 'ruby-openai' gem uses the same
319
- OpenAI namespace and will cause conflicts.
320
-
321
- To fix this, remove 'ruby-openai' from your Gemfile and use the official gem instead:
322
- - Remove: gem 'ruby-openai'
323
- - Keep: gem 'openai' (official SDK that DSPy uses)
324
-
325
- The official gem provides better compatibility and is actively maintained by OpenAI.
326
- WARNING
327
- end
314
+ DSPy::Support::OpenAISDKWarning.warn_if_community_gem_loaded!
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dspy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.34.4
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vicente Reig Rincón de Arellano
@@ -157,6 +157,7 @@ files:
157
157
  - lib/dspy/callbacks.rb
158
158
  - lib/dspy/chain_of_thought.rb
159
159
  - lib/dspy/context.rb
160
+ - lib/dspy/document.rb
160
161
  - lib/dspy/error_formatter.rb
161
162
  - lib/dspy/errors.rb
162
163
  - lib/dspy/evals.rb
@@ -194,7 +195,6 @@ files:
194
195
  - lib/dspy/registry/registry_manager.rb
195
196
  - lib/dspy/registry/signature_registry.rb
196
197
  - lib/dspy/ruby_llm.rb
197
- - lib/dspy/ruby_llm/guardrails.rb
198
198
  - lib/dspy/ruby_llm/lm/adapters/ruby_llm_adapter.rb
199
199
  - lib/dspy/ruby_llm/version.rb
200
200
  - lib/dspy/schema.rb
@@ -210,6 +210,7 @@ files:
210
210
  - lib/dspy/storage/program_storage.rb
211
211
  - lib/dspy/storage/storage_manager.rb
212
212
  - lib/dspy/structured_outputs_prompt.rb
213
+ - lib/dspy/support/openai_sdk_warning.rb
213
214
  - lib/dspy/support/warning_filters.rb
214
215
  - lib/dspy/teleprompt/bootstrap_strategy.rb
215
216
  - lib/dspy/teleprompt/data_handler.rb
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'dspy/lm/errors'
4
-
5
- module DSPy
6
- module RubyLLM
7
- class Guardrails
8
- SUPPORTED_RUBY_LLM_VERSIONS = "~> 1.3".freeze
9
-
10
- def self.ensure_ruby_llm_installed!
11
- require 'ruby_llm'
12
-
13
- spec = Gem.loaded_specs["ruby_llm"]
14
- unless spec && Gem::Requirement.new(SUPPORTED_RUBY_LLM_VERSIONS).satisfied_by?(spec.version)
15
- msg = <<~MSG
16
- DSPy requires the `ruby_llm` gem #{SUPPORTED_RUBY_LLM_VERSIONS}.
17
- Please install or upgrade it with `bundle add ruby_llm --version "#{SUPPORTED_RUBY_LLM_VERSIONS}"`.
18
- MSG
19
- raise DSPy::LM::UnsupportedVersionError, msg
20
- end
21
- end
22
- end
23
- end
24
- end