Module: Llm::ResponseParser

Defined in:
lib/llm/response_parser.rb

Overview

Extracts JSON from LLM responses, handling markdown wrapping and malformed output. Returns nil on parse failure — callers should fall back to non-AI path.

Class Method Summary collapse

Class Method Details

.extract_elements(response) ⇒ Object



21
22
23
24
25
26
# File 'lib/llm/response_parser.rb', line 21

def extract_elements(response)
  parsed = parse_json(response)
  return nil unless parsed && parsed[:elements].is_a?(Array)

  parsed[:elements].map { |el| normalize_element(el) }.compact
end

.extract_json(text) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/llm/response_parser.rb', line 51

def extract_json(text)
  # Try raw JSON first
  stripped = text.strip
  return stripped if stripped.start_with?('{')

  # Try markdown code block
  match = text.match(/```(?:json)?\s*\n?(.*?)\n?\s*```/m)
  return match[1].strip if match

  # Try finding first { ... } block
  brace_match = text.match(/(\{.*\})/m)
  return brace_match[1] if brace_match

  text
end

.extract_scenarios(response) ⇒ Object



28
29
30
31
32
33
# File 'lib/llm/response_parser.rb', line 28

def extract_scenarios(response)
  parsed = parse_json(response)
  return nil unless parsed && parsed[:scenarios].is_a?(Array)

  parsed[:scenarios]
end

.normalize_element(element) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/llm/response_parser.rb', line 35

def normalize_element(element)
  return nil unless element[:name] && element[:type] && element[:locator]

  locator = element[:locator]
  locator = { type: locator[:type]&.to_sym, value: locator[:value] } if locator.is_a?(Hash)

  {
    name: element[:name].to_s.gsub(/[^a-z0-9_]/i, '_').downcase,
    type: element[:type].to_sym,
    locator:,
    purpose: element[:purpose],
    input_type: element[:input_type],
    text: element[:text]
  }.compact
end

.parse_json(response) ⇒ Object



11
12
13
14
15
16
17
18
19
# File 'lib/llm/response_parser.rb', line 11

def parse_json(response)
  return nil if response.nil? || response.strip.empty?

  json_str = extract_json(response)
  result = JSON.parse(json_str, symbolize_names: true)
  result.is_a?(Hash) ? result : nil
rescue JSON::ParserError
  nil
end