From fe43f5cb46a64c18876ebde4b33f09853374364d Mon Sep 17 00:00:00 2001 From: chihlasm Date: Mon, 23 Feb 2026 23:45:39 -0500 Subject: [PATCH] fix: improve AI corrective prompt clarity and add global next_node_id validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rewrote CORRECTIVE_PROMPT_TEMPLATE to clearly distinguish option→child vs action→sibling next_node_id semantics with concrete examples - Added global check in ai_tree_validator that action next_node_ids actually reference existing nodes in the tree (was silently unchecked) - Added max_tokens truncation warning to branch_detail logger - Added test for action next_node_id referencing nonexistent node Co-Authored-By: Claude Sonnet 4.6 --- backend/app/core/ai_tree_generator_service.py | 17 ++++++++++++++++- backend/app/core/ai_tree_validator.py | 11 ++++++++++- backend/tests/test_ai_tree_validator.py | 7 +++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/backend/app/core/ai_tree_generator_service.py b/backend/app/core/ai_tree_generator_service.py index 01db3bf9..4d40e257 100644 --- a/backend/app/core/ai_tree_generator_service.py +++ b/backend/app/core/ai_tree_generator_service.py @@ -97,7 +97,16 @@ CORRECTIVE_PROMPT_TEMPLATE = """Your previous JSON was invalid for ResolutionFlo Validation errors: {error_list} -IMPORTANT: If any error mentions a next_node_id referencing a non-existent child, you must ensure every option's next_node_id exactly matches the "id" field of one of the node's direct children. The child node must exist in the "children" array of the same parent node. +CRITICAL RULES TO FIX THESE ERRORS: + +1. Decision node options → next_node_id MUST match the "id" of a direct child in that SAME decision node's "children" array. + Example: if decision node has children [A, B, C], then option next_node_id must be "A", "B", or "C". + +2. Action node → next_node_id MUST match the "id" of a SIBLING node — another child of the SAME parent decision node. + Example: if parent decision has children [action-1, solution-1, solution-2], then action-1's next_node_id must be "solution-1" or "solution-2". + The next node must ALREADY EXIST in the parent's children array — do NOT nest the next node inside the action node. + +3. Every referenced ID must physically exist somewhere in the tree as a node with that exact "id" value. 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.""" @@ -224,6 +233,12 @@ async def generate_branch_detail( len(response.content), response.usage.output_tokens, ) + if response.stop_reason == "max_tokens": + logger.warning( + "branch_detail attempt=%d hit max_tokens limit (%d output tokens) — response may be truncated", + attempt, + response.usage.output_tokens, + ) raw_text = _strip_markdown_fences(response.content[0].text) if response.content else "" if not raw_text: logger.warning("branch_detail attempt=%d returned empty text, stop_reason=%s", attempt, response.stop_reason) diff --git a/backend/app/core/ai_tree_validator.py b/backend/app/core/ai_tree_validator.py index 7e1df9a9..37941066 100644 --- a/backend/app/core/ai_tree_validator.py +++ b/backend/app/core/ai_tree_validator.py @@ -40,7 +40,8 @@ def validate_generated_tree(tree: dict[str, Any]) -> list[str]: # Collect all node IDs and validate structure all_ids: set[str] = set() - all_referenced_ids: set[str] = set() + all_referenced_ids: set[str] = set() # option next_node_ids (already checked locally) + action_next_ids: set[str] = set() # action next_node_ids (checked globally below) node_count = 0 solution_count = 0 @@ -121,6 +122,7 @@ def validate_generated_tree(tree: dict[str, Any]) -> list[str]: ) else: all_referenced_ids.add(next_id) + action_next_ids.add(next_id) elif node_type == "solution": solution_count += 1 @@ -131,6 +133,13 @@ def validate_generated_tree(tree: dict[str, Any]) -> list[str]: _validate_node(tree, "root") + # Check that all action next_node_ids actually exist in the tree + for ref_id in action_next_ids: + if ref_id not in all_ids: + errors.append( + f"Action next_node_id '{ref_id}' references a node that does not exist in the tree" + ) + # Global checks if node_count < 5: errors.append( diff --git a/backend/tests/test_ai_tree_validator.py b/backend/tests/test_ai_tree_validator.py index 1a9d7186..f8f3f4d7 100644 --- a/backend/tests/test_ai_tree_validator.py +++ b/backend/tests/test_ai_tree_validator.py @@ -124,6 +124,13 @@ class TestReferenceIntegrity: errors = validate_generated_tree(tree) assert any("non-existent child" in e for e in errors) + def test_action_next_node_id_references_nonexistent_node(self): + """Action next_node_id pointing to a node that doesn't exist anywhere in the tree.""" + tree = _make_valid_tree() + tree["children"][1]["next_node_id"] = "ghost-node" + errors = validate_generated_tree(tree) + assert any("ghost-node" in e for e in errors) + def test_duplicate_option_ids(self): tree = _make_valid_tree() tree["options"][0]["id"] = "same"