fix: improve AI corrective prompt clarity and add global next_node_id validation
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user