From 21f542694ee73e2b96a65aa6039c27d095971e1d Mon Sep 17 00:00:00 2001 From: chihlasm Date: Sat, 21 Feb 2026 13:05:42 -0500 Subject: [PATCH] fix: increase branch retries to 3 and relax cross-reference validation on final attempt next_node_id mismatches are a common model hallucination that the retry prompt doesn't reliably fix. On the final (3rd) attempt, accept the branch with strict=False so only truly fatal errors (missing fields, dead ends, bad JSON) cause a hard failure. Cross-reference issues are minor and fixable in the tree editor. Co-Authored-By: Claude Sonnet 4.6 --- backend/app/core/ai_tree_generator_service.py | 15 ++++++++++----- backend/app/core/ai_tree_validator.py | 4 ++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/backend/app/core/ai_tree_generator_service.py b/backend/app/core/ai_tree_generator_service.py index c08f47a3..edc0a98f 100644 --- a/backend/app/core/ai_tree_generator_service.py +++ b/backend/app/core/ai_tree_generator_service.py @@ -199,7 +199,7 @@ async def generate_branch_detail( total_input = 0 total_output = 0 - for attempt in range(2): + for attempt in range(3): response = await client.messages.create( model=settings.AI_MODEL, max_tokens=8192, @@ -223,7 +223,7 @@ async def generate_branch_detail( try: branch_tree = json.loads(raw_text) except json.JSONDecodeError as e: - if attempt == 0: + if attempt < 2: messages.append({"role": "assistant", "content": raw_text}) messages.append({ "role": "user", @@ -234,13 +234,18 @@ async def generate_branch_detail( continue raise ValueError(f"AI returned invalid JSON after retry: {e}") - # Validate the branch structure - errors = validate_generated_tree(branch_tree) + # On the final attempt, use strict=False to accept cross-reference + # mismatches (next_node_id pointing to wrong child) — these are + # minor structural issues the user can fix in the editor. + is_final_attempt = attempt == 2 + errors = validate_generated_tree(branch_tree, strict=not is_final_attempt) if not errors: + if is_final_attempt: + logger.warning("branch_detail accepted on final attempt (lenient validation): branch=%s", branch_name) cost = _estimate_cost(total_input, total_output) return branch_tree, total_input, total_output, cost - if attempt == 0: + if attempt < 2: messages.append({"role": "assistant", "content": raw_text}) messages.append({ "role": "user", diff --git a/backend/app/core/ai_tree_validator.py b/backend/app/core/ai_tree_validator.py index fa6bc6dc..8a282d1e 100644 --- a/backend/app/core/ai_tree_validator.py +++ b/backend/app/core/ai_tree_validator.py @@ -24,7 +24,7 @@ class TreeValidationError(Exception): super().__init__(f"Tree validation failed: {'; '.join(errors)}") -def validate_generated_tree(tree: dict[str, Any]) -> list[str]: +def validate_generated_tree(tree: dict[str, Any], strict: bool = True) -> list[str]: """Validate an AI-generated tree structure. Returns a list of error strings. Empty list means valid. @@ -106,7 +106,7 @@ def validate_generated_tree(tree: dict[str, Any]) -> list[str]: next_id = opt.get("next_node_id") if next_id: all_referenced_ids.add(next_id) - if child_ids and next_id not in child_ids: + if strict and child_ids and next_id not in child_ids: errors.append( f"Option '{opt.get('label', '?')}' in node '{node_id}' " f"references non-existent child '{next_id}'"