fix: strip markdown code fences from AI JSON responses

Haiku sometimes wraps its JSON in ```json ... ``` despite the prompt
instructing otherwise. Strip fences before parsing to avoid JSONDecodeError
at char 0.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-02-21 03:55:21 -05:00
parent 25df9421f9
commit 950f6750e0

View File

@@ -9,6 +9,7 @@ System prompts are static constants to enable Anthropic prompt caching.
""" """
import json import json
import logging import logging
import re
import uuid import uuid
from typing import Any from typing import Any
@@ -94,6 +95,15 @@ Return a corrected full JSON object only. No markdown, no prose, no code fences.
Fix ALL listed errors while maintaining the same troubleshooting/procedural logic.""" Fix ALL listed errors while maintaining the same troubleshooting/procedural logic."""
def _strip_markdown_fences(text: str) -> str:
"""Strip markdown code fences if the model wrapped its JSON response."""
text = text.strip()
match = re.match(r"^```(?:json)?\s*([\s\S]*?)```$", text)
if match:
return match.group(1).strip()
return text
def _get_client() -> anthropic.AsyncAnthropic: def _get_client() -> anthropic.AsyncAnthropic:
"""Get configured async Anthropic client.""" """Get configured async Anthropic client."""
if not settings.ANTHROPIC_API_KEY: if not settings.ANTHROPIC_API_KEY:
@@ -141,7 +151,7 @@ async def scaffold_branches(
messages=[{"role": "user", "content": user_message}], messages=[{"role": "user", "content": user_message}],
) )
raw_text = response.content[0].text raw_text = _strip_markdown_fences(response.content[0].text)
input_tokens = response.usage.input_tokens input_tokens = response.usage.input_tokens
output_tokens = response.usage.output_tokens output_tokens = response.usage.output_tokens
cost = _estimate_cost(input_tokens, output_tokens) cost = _estimate_cost(input_tokens, output_tokens)
@@ -197,7 +207,7 @@ async def generate_branch_detail(
messages=messages, messages=messages,
) )
raw_text = response.content[0].text raw_text = _strip_markdown_fences(response.content[0].text)
total_input += response.usage.input_tokens total_input += response.usage.input_tokens
total_output += response.usage.output_tokens total_output += response.usage.output_tokens