Extract JSON from LLM output. Handles trailing commas, missing closing braces, literal newlines inside strings, and Python triple-quotes.
(raw)
| 95 | return text.strip() |
| 96 | |
| 97 | def extract_json(raw): |
| 98 | """ |
| 99 | Extract JSON from LLM output. Handles trailing commas, missing closing |
| 100 | braces, literal newlines inside strings, and Python triple-quotes. |
| 101 | """ |
| 102 | raw = raw.strip() |
| 103 | |
| 104 | # Fix Python triple-quotes → JSON strings (common 7B model error). |
| 105 | # The model writes """content""" instead of a proper JSON string. |
| 106 | # Handles nested docstrings inside the code content. |
| 107 | def _fix_triple_quotes(s): |
| 108 | # The original s.find('"""') always matched the FIRST triple-quote |
| 109 | # found after the opening — which is the docstring opener inside the |
| 110 | # code content, not the real closing delimiter. Fix: scan all """ |
| 111 | # positions and pick the LAST one followed by } or , (JSON context), |
| 112 | # so nested docstrings in the code are captured as part of the content. |
| 113 | result = [] |
| 114 | i = 0 |
| 115 | while i < len(s): |
| 116 | if s[i:i+3] == '"""': |
| 117 | rest = s[i + 3:] |
| 118 | positions = [m.start() for m in re.finditer(r'"""', rest)] |
| 119 | closing_pos = -1 |
| 120 | for pos in reversed(positions): |
| 121 | after = rest[pos + 3:].lstrip() |
| 122 | if not after or after[0] in ',}': |
| 123 | closing_pos = pos |
| 124 | break |
| 125 | if closing_pos == -1 and positions: |
| 126 | closing_pos = positions[-1] |
| 127 | if closing_pos != -1: |
| 128 | inner = rest[:closing_pos] |
| 129 | i = i + 3 + closing_pos + 3 |
| 130 | else: |
| 131 | inner = rest |
| 132 | i = len(s) |
| 133 | # Encode raw content as a proper JSON string |
| 134 | inner = inner.replace('\\', '\\\\') |
| 135 | inner = inner.replace('"', '\\"') |
| 136 | inner = inner.replace('\n', '\\n') |
| 137 | inner = inner.replace('\t', '\\t') |
| 138 | inner = inner.replace('\r', '\\r') |
| 139 | result.append('"' + inner + '"') |
| 140 | else: |
| 141 | result.append(s[i]) |
| 142 | i += 1 |
| 143 | return ''.join(result) |
| 144 | |
| 145 | if '"""' in raw: |
| 146 | raw = _fix_triple_quotes(raw) |
| 147 | |
| 148 | if not raw.startswith('{'): |
| 149 | # Try to find the start of a JSON block |
| 150 | idx = raw.find('{') |
| 151 | if idx != -1: |
| 152 | raw = raw[idx:] |
| 153 | else: |
| 154 | return None |