@@ -660,6 +660,43 @@ def _load_recipe(name: str, offline: bool = False) -> Optional[RecipeConfig]:
660660 return None
661661
662662
663+ def _check_package_installed (package_name : str ) -> bool :
664+ """
665+ Check if a Python package is installed using importlib.metadata.
666+
667+ This is more reliable than trying to import the package because:
668+ 1. Package names often differ from import names (e.g., tavily-python -> tavily)
669+ 2. importlib.metadata checks the actual installed packages
670+ 3. No side effects from importing modules
671+
672+ Args:
673+ package_name: The pip package name (e.g., 'tavily-python', 'pillow')
674+
675+ Returns:
676+ True if package is installed, False otherwise
677+ """
678+ try :
679+ from importlib .metadata import distributions
680+
681+ # Normalize package name for comparison (pip normalizes - and _ and case)
682+ normalized = package_name .lower ().replace ("-" , "_" ).replace ("." , "_" )
683+
684+ for dist in distributions ():
685+ dist_name = dist .metadata .get ("Name" , "" ).lower ().replace ("-" , "_" ).replace ("." , "_" )
686+ if dist_name == normalized :
687+ return True
688+
689+ return False
690+ except Exception :
691+ # Fallback: try to import with common name transformations
692+ try :
693+ import_name = package_name .replace ("-" , "_" )
694+ __import__ (import_name )
695+ return True
696+ except ImportError :
697+ return False
698+
699+
663700def _check_dependencies (recipe_config : RecipeConfig ) -> Dict [str , Any ]:
664701 """Check if recipe dependencies are satisfied."""
665702 result = {
@@ -670,28 +707,11 @@ def _check_dependencies(recipe_config: RecipeConfig) -> Dict[str, Any]:
670707 "external" : [],
671708 }
672709
673- # Common package name to import name mappings
674- PACKAGE_IMPORT_MAP = {
675- "tavily-python" : "tavily" ,
676- "google-generativeai" : "google.generativeai" ,
677- "openai-whisper" : "whisper" ,
678- "python-dotenv" : "dotenv" ,
679- "pillow" : "PIL" ,
680- "opencv-python" : "cv2" ,
681- "scikit-learn" : "sklearn" ,
682- "beautifulsoup4" : "bs4" ,
683- "pyyaml" : "yaml" ,
684- }
685-
686710 # Check Python packages
687711 for pkg in recipe_config .get_required_packages ():
688- # Get the correct import name
689- import_name = PACKAGE_IMPORT_MAP .get (pkg , pkg .replace ("-" , "_" ))
690- try :
691- __import__ (import_name )
692- result ["packages" ].append ({"name" : pkg , "available" : True })
693- except ImportError :
694- result ["packages" ].append ({"name" : pkg , "available" : False })
712+ available = _check_package_installed (pkg )
713+ result ["packages" ].append ({"name" : pkg , "available" : available })
714+ if not available :
695715 result ["all_satisfied" ] = False
696716
697717 # Check environment variables
@@ -808,6 +828,9 @@ def _execute_recipe(
808828 workflow_config , merged_config , tool_registry , options
809829 )
810830 elif "steps" in workflow_config :
831+ # Pass the workflow file path for proper execution
832+ workflow_file_path = template_path / workflow_file if template_path else None
833+ merged_config ["_workflow_file" ] = str (workflow_file_path ) if workflow_file_path else None
811834 return _execute_steps_workflow (
812835 workflow_config , merged_config , tool_registry , options
813836 )
@@ -900,9 +923,35 @@ def _execute_steps_workflow(
900923 tool_registry : Any ,
901924 options : Dict [str , Any ],
902925) -> Any :
903- """Execute a steps-based workflow."""
904- # For now, convert to simple execution
905- return {"message" : "Steps workflow executed" , "config" : config }
926+ """
927+ Execute a steps-based workflow using praisonaiagents Workflow.
928+
929+ This properly executes the workflow with all features:
930+ - include steps (modular recipes)
931+ - loop steps (parallel/sequential)
932+ - output_variable
933+ - agent execution
934+ """
935+ from praisonaiagents .workflows import YAMLWorkflowParser
936+
937+ # Get the workflow file path from config if available
938+ workflow_file = config .get ("_workflow_file" )
939+
940+ if workflow_file :
941+ # Use YAMLWorkflowParser to properly parse and execute the workflow
942+ # This handles include steps, loops, variables, tools, etc.
943+ parser = YAMLWorkflowParser (tool_registry = tool_registry )
944+ workflow = parser .parse_file (workflow_file )
945+ return workflow .start ()
946+
947+ # Fallback: Create workflow from config dict using parser
948+ from praisonaiagents import Workflow
949+ workflow = Workflow (
950+ name = workflow_config .get ("name" , "RecipeWorkflow" ),
951+ steps = workflow_config .get ("steps" , []),
952+ variables = workflow_config .get ("variables" , {}),
953+ )
954+ return workflow .start ()
906955
907956
908957def _execute_simple_agent (
0 commit comments