| 1 | # Some chat models (observed with gemini-flash-lite via the OpenAI-compatible |
| 2 | # endpoint) frequently emit a trailing comma before a closing `}`/`]` in |
| 3 | # structured JSON responses (e.g. `"variation_reason": "...",\n}`). That is |
| 4 | # invalid JSON, so PydanticOutputParser raises OutputParserException even |
| 5 | # though the payload is otherwise well-formed and semantically complete -- |
| 6 | # and resampling burns LLM calls while often failing the same way again. |
| 7 | # Wrap PydanticOutputParser so a parse failure retries locally with trailing |
| 8 | # commas stripped before giving up. |
| 9 | import re |
| 10 | from typing import List, Optional |
| 11 | |
| 12 | from langchain_core.exceptions import OutputParserException |
| 13 | from langchain_core.output_parsers import PydanticOutputParser |
| 14 | from langchain_core.outputs import Generation |
| 15 | |
| 16 | _TRAILING_COMMA_RE = re.compile(r",(\s*[}\]])") |
| 17 | |
| 18 | |
| 19 | def strip_trailing_commas(text: str) -> str: |
| 20 | return _TRAILING_COMMA_RE.sub(r"\1", text) |
| 21 | |
| 22 | |
| 23 | class TrailingCommaTolerantPydanticOutputParser(PydanticOutputParser): |
| 24 | """PydanticOutputParser that retries once with trailing commas stripped.""" |
| 25 | |
| 26 | def parse_result(self, result: List[Generation], *, partial: bool = False): |
| 27 | try: |
| 28 | return super().parse_result(result, partial=partial) |
| 29 | except OutputParserException: |
| 30 | if not result: |
| 31 | raise |
| 32 | cleaned_text = strip_trailing_commas(result[0].text) |
| 33 | if cleaned_text == result[0].text: |
| 34 | raise |
| 35 | cleaned_result = [Generation(text=cleaned_text)] |
| 36 | return super().parse_result(cleaned_result, partial=partial) |
| 37 |