fix: the exception of plan validation (#714)

* fix: Missing Required Fields in Plan Validation

* fix: the exception of plan validation

* Fixed the test errors

* Addressed the comments of the PR reviews
This commit is contained in:
Willem Jiang
2025-11-27 19:39:25 +08:00
committed by GitHub
parent bec97f02ae
commit 667916959b
3 changed files with 228 additions and 12 deletions

View File

@@ -146,24 +146,29 @@ def validate_and_fix_plan(plan: dict, enforce_web_search: bool = False) -> dict:
# SECTION 2: Enforce web search requirements
# ============================================================
if enforce_web_search:
# Check if any step has need_search=true
has_search_step = any(step.get("need_search", False) for step in steps)
# Check if any step has need_search=true (only check dict steps)
has_search_step = any(
step.get("need_search", False)
for step in steps
if isinstance(step, dict)
)
if not has_search_step and steps:
# Ensure first research step has web search enabled
for idx, step in enumerate(steps):
if step.get("step_type") == "research":
if isinstance(step, dict) and step.get("step_type") == "research":
step["need_search"] = True
logger.info(f"Enforced web search on research step at index {idx}")
break
else:
# Fallback: If no research step exists, convert the first step to a research step with web search enabled.
# This ensures that at least one step will perform a web search as required.
steps[0]["step_type"] = "research"
steps[0]["need_search"] = True
logger.info(
"Converted first step to research with web search enforcement"
)
if isinstance(steps[0], dict):
steps[0]["step_type"] = "research"
steps[0]["need_search"] = True
logger.info(
"Converted first step to research with web search enforcement"
)
elif not has_search_step and not steps:
# Add a default research step if no steps exist
logger.warning("Plan has no steps. Adding default research step.")
@@ -176,6 +181,29 @@ def validate_and_fix_plan(plan: dict, enforce_web_search: bool = False) -> dict:
}
]
# ============================================================
# SECTION 3: Ensure required Plan fields are present (Issue #710 fix)
# ============================================================
# Set locale from state if not present
if "locale" not in plan or not plan.get("locale"):
plan["locale"] = "en-US" # Default locale
logger.info("Added missing locale field with default value 'en-US'")
# Ensure has_enough_context is present
if "has_enough_context" not in plan:
plan["has_enough_context"] = False # Default value
logger.info("Added missing has_enough_context field with default value 'False'")
# Ensure title is present
if "title" not in plan or not plan.get("title"):
# Try to infer title from steps or use a default
if steps and isinstance(steps[0], dict) and "title" in steps[0]:
plan["title"] = steps[0]["title"]
logger.info(f"Inferred missing title from first step: {plan['title']}")
else:
plan["title"] = "Research Plan" # Default title
logger.info("Added missing title field with default value 'Research Plan'")
return plan
@@ -381,8 +409,20 @@ def extract_plan_content(plan_data: str | dict | Any) -> str:
return plan_data.content
elif isinstance(plan_data, dict):
# If it's already a dictionary, convert to JSON string
logger.debug("Converting plan dictionary to JSON string")
return json.dumps(plan_data)
# Need to check if it's dict with content field (AIMessage-like)
if "content" in plan_data:
if isinstance(plan_data["content"], str):
logger.debug("Extracting plan content from dict with content field")
return plan_data["content"]
if isinstance(plan_data["content"], dict):
logger.debug("Converting content field dict to JSON string")
return json.dumps(plan_data["content"], ensure_ascii=False)
else:
logger.warning(f"Unexpected type for 'content' field in plan_data dict: {type(plan_data['content']).__name__}, converting to string")
return str(plan_data["content"])
else:
logger.debug("Converting plan dictionary to JSON string")
return json.dumps(plan_data)
else:
# For any other type, try to convert to string
logger.warning(f"Unexpected plan data type {type(plan_data).__name__}, attempting to convert to string")