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 <noreply@anthropic.com>
This commit is contained in:
@@ -199,7 +199,7 @@ async def generate_branch_detail(
|
|||||||
total_input = 0
|
total_input = 0
|
||||||
total_output = 0
|
total_output = 0
|
||||||
|
|
||||||
for attempt in range(2):
|
for attempt in range(3):
|
||||||
response = await client.messages.create(
|
response = await client.messages.create(
|
||||||
model=settings.AI_MODEL,
|
model=settings.AI_MODEL,
|
||||||
max_tokens=8192,
|
max_tokens=8192,
|
||||||
@@ -223,7 +223,7 @@ 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:
|
||||||
if attempt == 0:
|
if attempt < 2:
|
||||||
messages.append({"role": "assistant", "content": raw_text})
|
messages.append({"role": "assistant", "content": raw_text})
|
||||||
messages.append({
|
messages.append({
|
||||||
"role": "user",
|
"role": "user",
|
||||||
@@ -234,13 +234,18 @@ async def generate_branch_detail(
|
|||||||
continue
|
continue
|
||||||
raise ValueError(f"AI returned invalid JSON after retry: {e}")
|
raise ValueError(f"AI returned invalid JSON after retry: {e}")
|
||||||
|
|
||||||
# Validate the branch structure
|
# On the final attempt, use strict=False to accept cross-reference
|
||||||
errors = validate_generated_tree(branch_tree)
|
# 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 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)
|
cost = _estimate_cost(total_input, total_output)
|
||||||
return branch_tree, total_input, total_output, cost
|
return branch_tree, total_input, total_output, cost
|
||||||
|
|
||||||
if attempt == 0:
|
if attempt < 2:
|
||||||
messages.append({"role": "assistant", "content": raw_text})
|
messages.append({"role": "assistant", "content": raw_text})
|
||||||
messages.append({
|
messages.append({
|
||||||
"role": "user",
|
"role": "user",
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class TreeValidationError(Exception):
|
|||||||
super().__init__(f"Tree validation failed: {'; '.join(errors)}")
|
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.
|
"""Validate an AI-generated tree structure.
|
||||||
|
|
||||||
Returns a list of error strings. Empty list means valid.
|
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")
|
next_id = opt.get("next_node_id")
|
||||||
if next_id:
|
if next_id:
|
||||||
all_referenced_ids.add(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(
|
errors.append(
|
||||||
f"Option '{opt.get('label', '?')}' in node '{node_id}' "
|
f"Option '{opt.get('label', '?')}' in node '{node_id}' "
|
||||||
f"references non-existent child '{next_id}'"
|
f"references non-existent child '{next_id}'"
|
||||||
|
|||||||
Reference in New Issue
Block a user