Class: RubyLLM::Contract::Parser
- Inherits:
-
Object
- Object
- RubyLLM::Contract::Parser
- Extended by:
- Concerns::DeepSymbolize
- Defined in:
- lib/ruby_llm/contract/contract/parser.rb
Constant Summary collapse
- UTF8_BOM =
Strip UTF-8 BOM (Byte Order Mark) that some LLMs/APIs prepend to output
"\xEF\xBB\xBF"- CODE_FENCE_PATTERN =
Strip markdown code fences that LLMs commonly wrap around JSON output Handles “‘json … “`, “` … “`, with optional trailing whitespace
/\A\s*```(?:json|JSON)?\s*\n(.*?)\n\s*```\s*\z/m- JSON_START_PATTERN =
Extract the first JSON object or array from text that may contain prose. Uses bracket-matching to find the outermost balanced { } or [ ] block.
/[{\[]/
Class Method Summary collapse
- .extract_json(text) ⇒ Object
- .parse(raw_output, strategy:) ⇒ Object
- .parse_json(raw_output) ⇒ Object
- .strip_bom(text) ⇒ Object
- .strip_code_fences(text) ⇒ Object
- .symbolize_keys(obj) ⇒ Object
Methods included from Concerns::DeepSymbolize
Class Method Details
.extract_json(text) ⇒ Object
79 80 81 82 83 84 85 86 |
# File 'lib/ruby_llm/contract/contract/parser.rb', line 79 def self.extract_json(text) return nil unless text.is_a?(String) start_idx = text.index(JSON_START_PATTERN) return nil unless start_idx scan_for_balanced_json(text, start_idx) end |
.parse(raw_output, strategy:) ⇒ Object
14 15 16 17 18 19 20 |
# File 'lib/ruby_llm/contract/contract/parser.rb', line 14 def self.parse(raw_output, strategy:) case strategy when :json then parse_json(raw_output) when :text then raw_output else raise ArgumentError, "Unknown parse strategy: #{strategy}" end end |
.parse_json(raw_output) ⇒ Object
22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/ruby_llm/contract/contract/parser.rb', line 22 def self.parse_json(raw_output) return deep_symbolize(raw_output) if raw_output.is_a?(Hash) || raw_output.is_a?(Array) # Coerce non-String scalars (boolean, numeric) to their JSON representation # to prevent TypeError from JSON.parse on non-string input. coerced = raw_output.is_a?(String) ? raw_output : raw_output&.to_s text = strip_code_fences(strip_bom(coerced)) raise RubyLLM::Contract::ParseError.new("Failed to parse JSON: nil content", details: raw_output) if text.nil? parse_json_text(text, raw_output) end |
.strip_bom(text) ⇒ Object
58 59 60 61 62 |
# File 'lib/ruby_llm/contract/contract/parser.rb', line 58 def self.strip_bom(text) return text unless text.is_a?(String) text.delete_prefix(UTF8_BOM) end |
.strip_code_fences(text) ⇒ Object
68 69 70 71 72 73 |
# File 'lib/ruby_llm/contract/contract/parser.rb', line 68 def self.strip_code_fences(text) return text unless text.is_a?(String) match = text.match(CODE_FENCE_PATTERN) match ? match[1] : text end |
.symbolize_keys(obj) ⇒ Object
10 11 12 |
# File 'lib/ruby_llm/contract/contract/parser.rb', line 10 def self.symbolize_keys(obj) deep_symbolize(obj) end |