dapr-agents/dapr_agents/workflow/orchestrators/llm/utils.py

112 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from typing import List, Dict, Any, Optional
def update_step_statuses(plan: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
Ensures step and sub-step statuses follow logical progression:
• Parent completes if all substeps complete.
• Parent goes in_progress if any substep is in_progress.
• If substeps start completing, parent moves in_progress.
• If parent was completed but a substep reverts to in_progress, parent downgrades.
• Standalone steps (no substeps) are only updated via explicit status_updates.
"""
for step in plan:
subs = step.get("substeps", None)
# --- NO substeps: do nothing here (explicit updates only) ---
if subs is None:
continue
# If substeps is not a list or is an empty list, treat as nosubsteps too:
if not isinstance(subs, list) or len(subs) == 0:
continue
# Collect child statuses
statuses = {ss["status"] for ss in subs}
# 1. All done → parent done
if statuses == {"completed"}:
step["status"] = "completed"
# 2. Any in_progress → parent in_progress
elif "in_progress" in statuses:
step["status"] = "in_progress"
# 3. Some done, parent not yet started → bump to in_progress
elif "completed" in statuses and step["status"] == "not_started":
step["status"] = "in_progress"
# 4. If parent was completed but a child is in_progress, downgrade
elif step["status"] == "completed" and any(s != "completed" for s in statuses):
step["status"] = "in_progress"
return plan
def validate_plan_structure(plan: List[Dict[str, Any]]) -> bool:
"""
Validates if the plan structure follows the correct schema.
Args:
plan (List[Dict[str, Any]]): The execution plan.
Returns:
bool: True if the plan structure is valid, False otherwise.
"""
required_keys = {"step", "description", "status"}
for step in plan:
if not required_keys.issubset(step.keys()):
return False
if "substeps" in step:
for substep in step["substeps"]:
if not {"substep", "description", "status"}.issubset(substep.keys()):
return False
return True
def find_step_in_plan(
plan: List[Dict[str, Any]], step: int, substep: Optional[float] = None
) -> Optional[Dict[str, Any]]:
"""
Finds a specific step or substep in a plan.
Args:
plan (List[Dict[str, Any]]): The execution plan.
step (int): The step number to find.
substep (Optional[float]): The substep number (if applicable).
Returns:
Dict[str, Any] | None: The found step/substep dictionary or None if not found.
"""
for step_entry in plan:
if step_entry["step"] == step:
if substep is None:
return step_entry
for sub in step_entry.get("substeps", []):
if sub["substep"] == substep:
return sub
return None
def restructure_plan(
plan: List[Dict[str, Any]], updates: List[Dict[str, Any]]
) -> List[Dict[str, Any]]:
"""
Applies restructuring updates to the task execution plan.
Args:
plan (List[Dict[str, Any]]): The current execution plan.
updates (List[Dict[str, Any]]): A list of updates to apply.
Returns:
List[Dict[str, Any]]: The updated execution plan.
"""
for update in updates:
step_id = update["step"]
step_entry = find_step_in_plan(plan, step_id)
if step_entry:
step_entry.update(update)
return plan