fix: add diagnostic logging and increase scaffold max_tokens to 2048

The "Unterminated string" JSON parse error is likely caused by Gemini
output truncation at 1024 tokens. Increases scaffold max_tokens to 2048
and adds logging for: raw response text, finish_reason (truncation
detection), and JSON parse failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit was merged in pull request #93.
This commit is contained in:
chihlasm
2026-02-27 00:15:01 -05:00
parent 957f13b993
commit 6fc76187c0
2 changed files with 26 additions and 1 deletions

View File

@@ -5,10 +5,13 @@ Supports Gemini (google-genai) and Anthropic (anthropic) as interchangeable
backends for JSON generation used by the AI Flow Builder. backends for JSON generation used by the AI Flow Builder.
""" """
import logging
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from app.core.config import settings from app.core.config import settings
logger = logging.getLogger(__name__)
class AIProvider(ABC): class AIProvider(ABC):
"""Abstract base class for AI providers.""" """Abstract base class for AI providers."""
@@ -74,6 +77,16 @@ class GeminiProvider(AIProvider):
config=config, config=config,
) )
# Log finish reason to detect truncation
if response.candidates:
finish_reason = getattr(response.candidates[0], "finish_reason", None)
logger.info("Gemini finish_reason=%s model=%s", finish_reason, self._model)
if str(finish_reason) == "MAX_TOKENS":
logger.warning(
"Gemini output truncated (MAX_TOKENS). max_output_tokens=%d",
max_tokens,
)
text = response.text or "" text = response.text or ""
input_tokens = getattr(response.usage_metadata, "prompt_token_count", 0) or 0 input_tokens = getattr(response.usage_metadata, "prompt_token_count", 0) or 0
output_tokens = ( output_tokens = (

View File

@@ -154,15 +154,23 @@ async def scaffold_branches(
raw_text, input_tokens, output_tokens = await provider.generate_json( raw_text, input_tokens, output_tokens = await provider.generate_json(
system_prompt=SCAFFOLD_SYSTEM_PROMPT, system_prompt=SCAFFOLD_SYSTEM_PROMPT,
messages=[{"role": "user", "content": user_message}], messages=[{"role": "user", "content": user_message}],
max_tokens=1024, max_tokens=2048,
) )
logger.info(
"scaffold raw response (tokens in=%d out=%d, len=%d): %s",
input_tokens,
output_tokens,
len(raw_text),
raw_text[:500],
)
raw_text = _strip_markdown_fences(raw_text) raw_text = _strip_markdown_fences(raw_text)
cost = _estimate_cost(input_tokens, output_tokens) cost = _estimate_cost(input_tokens, output_tokens)
try: try:
data = json.loads(raw_text) data = json.loads(raw_text)
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
logger.error("scaffold JSON parse failed. Full text (%d chars): %s", len(raw_text), raw_text)
raise ValueError(f"AI returned invalid JSON: {e}") raise ValueError(f"AI returned invalid JSON: {e}")
branches = data.get("branches", []) branches = data.get("branches", [])
@@ -224,6 +232,10 @@ async def generate_branch_detail(
try: try:
branch_tree = json.loads(raw_text) branch_tree = json.loads(raw_text)
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
logger.error(
"branch_detail attempt=%d JSON parse failed (%d chars): %s",
attempt, len(raw_text), raw_text[:500],
)
if attempt < 2: if attempt < 2:
messages.append({"role": "assistant", "content": raw_text}) messages.append({"role": "assistant", "content": raw_text})
messages.append({ messages.append({