Class: DSPy::ErrorFormatter

Inherits:
Object
  • Object
show all
Extended by:
T::Sig
Defined in:
lib/dspy/error_formatter.rb

Overview

Utility class for formatting complex error messages into human-readable format

Class Method Summary collapse

Class Method Details

.argument_error?(message) ⇒ Boolean

Returns:

  • (Boolean)


34
35
36
# File 'lib/dspy/error_formatter.rb', line 34

def self.argument_error?(message)
  message.match?(/missing keyword/i) || message.match?(/unknown keyword/i)
end

.clean_error_message(message) ⇒ Object



201
202
203
204
205
206
207
# File 'lib/dspy/error_formatter.rb', line 201

def self.clean_error_message(message)
  # Remove caller information that's not useful to end users
  cleaned = message.gsub(/\nCaller:.*$/m, '')
  
  # Remove excessive newlines and clean up formatting
  cleaned.gsub(/\n+/, "\n").strip
end

.format_argument_error(message) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
# File 'lib/dspy/error_formatter.rb', line 74

def self.format_argument_error(message)
  if message.match(/missing keyword: (.+)/i)
    missing_fields = $1.split(', ')
    format_missing_fields_error(missing_fields)
  elsif message.match(/unknown keyword: (.+)/i)
    unknown_fields = $1.split(', ')
    format_unknown_fields_error(unknown_fields)
  else
    clean_error_message(message)
  end
end

.format_error(error_message, context = nil) ⇒ Object



12
13
14
15
16
17
18
19
20
21
22
# File 'lib/dspy/error_formatter.rb', line 12

def self.format_error(error_message, context = nil)
  # Try different error patterns in order of specificity
  if sorbet_type_error?(error_message)
    format_sorbet_type_error(error_message)
  elsif argument_error?(error_message)
    format_argument_error(error_message)
  else
    # Fallback to original message with minor cleanup
    clean_error_message(error_message)
  end
end

.format_missing_fields_error(fields) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/dspy/error_formatter.rb', line 88

def self.format_missing_fields_error(fields)
  field_list = fields.map { |f| "• #{f}" }.join("\n")
  
  "    Missing Required Fields\n\n    The following required fields were not provided:\n    \#{field_list}\n\n    Suggestions:\n    \u2022 Check your signature definition - these fields should be marked as optional if they're not always provided\n    \u2022 Ensure your LLM prompt asks for all required information\n    \u2022 Consider providing default values in your signature\n\n    This usually happens when the LLM response doesn't include all expected fields.\n  ERROR\nend\n".strip

.format_sorbet_type_error(message) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/dspy/error_formatter.rb', line 40

def self.format_sorbet_type_error(message)
  # Parse the Sorbet error pattern
  match = message.match(/Can't set \.(\w+) to (.+?) \(instance of (\w+)\) - need a (.+?)(?:\n|$)/)
  return clean_error_message(message) unless match

  field_name = match[1]
  raw_value = match[2]
  actual_type = match[3]
  expected_type = match[4]

  # Extract sample data for display (truncate if too long)
  sample_data = if raw_value.length > 100
    "#{raw_value[0..100]}..."
  else
    raw_value
  end

  "    Type Mismatch in '\#{field_name}'\n\n    Expected: \#{expected_type}\n    Received: \#{actual_type} (plain Ruby \#{actual_type.downcase})\n\n    \#{generate_type_specific_explanation(actual_type, expected_type)}\n\n    Suggestions:\n    \#{generate_suggestions(field_name, actual_type, expected_type)}\n\n    Sample data received: \#{sample_data}\n  ERROR\nend\n".strip

.format_unknown_fields_error(fields) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/dspy/error_formatter.rb', line 108

def self.format_unknown_fields_error(fields)
  field_list = fields.map { |f| "• #{f}" }.join("\n")
  
  "    Unknown Fields in Response\n\n    The LLM response included unexpected fields:\n    \#{field_list}\n\n    Suggestions:\n    \u2022 Check if these fields should be added to your signature definition\n    \u2022 Review your prompt to ensure it only asks for expected fields\n    \u2022 Consider if the LLM is hallucinating extra information\n\n    Extra fields are ignored, but this might indicate a prompt or signature mismatch.\n  ERROR\nend\n".strip

.generate_suggestions(field_name, actual_type, expected_type) ⇒ Object



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/dspy/error_formatter.rb', line 156

def self.generate_suggestions(field_name, actual_type, expected_type)
  suggestions = []
  
  # Type-specific suggestions
  case actual_type
  when 'Array'
    if expected_type.match(/T::Array\[(.+)\]/)
      struct_type = $1
      suggestions << "Check your signature uses proper T::Array[#{struct_type}] typing"
      suggestions << "Verify the LLM response format matches your expected structure"
      suggestions << "Ensure your struct definitions are correct and accessible"
    else
      suggestions << "Check your signature definition for the '#{field_name}' field"
      suggestions << "Verify the LLM prompt asks for the correct data type"
    end
  when 'Hash'
    if expected_type != 'Hash' && !expected_type.include?('T::Hash')
      suggestions << "Check your signature uses proper #{expected_type} typing"
      suggestions << "Verify the LLM response contains the expected fields"
    else
      suggestions << "Check your signature definition for the '#{field_name}' field"
      suggestions << "Verify the LLM prompt asks for the correct data type"
    end
  when 'String'
    if expected_type.include?('T::Enum')
      suggestions << "Check your enum definition and available values"
      suggestions << "Ensure your prompt specifies valid enum options"
    else
      suggestions << "Check your signature definition for the '#{field_name}' field"
      suggestions << "Verify the LLM prompt asks for the correct data type"
    end
  else
    suggestions << "Check your signature definition for the '#{field_name}' field"
    suggestions << "Verify the LLM prompt asks for the correct data type"
  end
  
  # General suggestions
  suggestions << "Consider if your prompt needs clearer type instructions"
  suggestions << "Check if the LLM model supports structured output for complex types"
  
  suggestions.map { |s| "• #{s}" }.join("\n")
end

.generate_type_specific_explanation(actual_type, expected_type) ⇒ Object



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/dspy/error_formatter.rb', line 128

def self.generate_type_specific_explanation(actual_type, expected_type)
  case actual_type
  when 'Array'
    if expected_type.match(/T::Array\[(.+)\]/)
      struct_type = $1
      "The LLM returned a plain Ruby array with hash elements, but your signature requires an array of #{struct_type} struct objects."
    else
      "The LLM returned a #{actual_type}, but your signature requires #{expected_type}."
    end
  when 'Hash'
    if expected_type != 'Hash' && !expected_type.include?('T::Hash')
      "The LLM returned a plain Ruby hash, but your signature requires a #{expected_type} struct object."
    else
      "The LLM returned a #{actual_type}, but your signature requires #{expected_type}."
    end
  when 'String'
    if expected_type.include?('T::Enum')
      "The LLM returned a string, but your signature requires an enum value."
    else
      "The LLM returned a #{actual_type}, but your signature requires #{expected_type}."
    end
  else
    "The LLM returned a #{actual_type}, but your signature requires #{expected_type}."
  end
end

.sorbet_type_error?(message) ⇒ Boolean

Returns:

  • (Boolean)


28
29
30
# File 'lib/dspy/error_formatter.rb', line 28

def self.sorbet_type_error?(message)
  message.match?(/Can't set \.(\w+) to .* \(instance of (\w+)\) - need a (.+)/)
end