Source code for tooluniverse.package_discovery_tool

"""Dynamic package discovery and evaluation"""

import requests
import time
from typing import Dict, Any, List
from .base_tool import BaseTool
from .tool_registry import register_tool


[docs] @register_tool("DynamicPackageDiscovery") class DynamicPackageDiscovery(BaseTool): """Searches PyPI and evaluates packages dynamically based on requirements"""
[docs] def __init__(self, tool_config: Dict[str, Any]): super().__init__(tool_config) self.pypi_search_url = "https://pypi.org/pypi/{package}/json" self.pypi_search_api = "https://pypi.org/search/" self.session = requests.Session() self.session.headers.update({"User-Agent": "ToolUniverse-PackageDiscovery/1.0"}) # Initialize WebSearchTool instance from .web_search_tool import WebSearchTool self.web_search_tool = WebSearchTool({"name": "WebSearchTool"})
[docs] def _search_pypi_via_web(self, query: str) -> List[Dict[str, Any]]: """Search PyPI using web search tool""" try: # Use pre-initialized WebSearchTool instance result = self.web_search_tool.run( { "query": f"{query} site:pypi.org", "max_results": 10, "search_type": "python_packages", } ) packages = [] if result.get("status") == "success": for item in result.get("results", []): url = item.get("url", "") if "pypi.org/project/" in url: # Extract package name from URL pkg_name = url.split("/project/")[-1].rstrip("/") packages.append( { "name": pkg_name, "source": "pypi_web", "title": item.get("title", ""), "snippet": item.get("snippet", ""), "url": url, } ) return packages except Exception as e: print(f"⚠️ Web search for PyPI packages failed: {e}") return []
[docs] def _evaluate_package(self, package_name: str) -> Dict[str, Any]: """Evaluate a package's suitability by fetching PyPI metadata""" try: response = self.session.get( self.pypi_search_url.format(package=package_name), timeout=10 ) if response.status_code == 200: data = response.json() info = data.get("info", {}) urls = info.get("project_urls", {}) # Extract key metrics evaluation = { "name": package_name, "version": info.get("version"), "description": info.get("summary", ""), "author": info.get("author", ""), "license": info.get("license", ""), "home_page": info.get("home_page", ""), "download_url": info.get("download_url", ""), "requires_python": info.get("requires_python", ""), "dependencies": info.get("requires_dist", []), "classifiers": info.get("classifiers", []), # Quality indicators "has_docs": bool(urls.get("Documentation")), "has_source": bool(urls.get("Source")), "has_homepage": bool(info.get("home_page")), "has_bug_tracker": bool(urls.get("Bug Reports")), "project_urls": urls, # Popularity indicators "is_stable": "Development Status :: 5 - Production/Stable" in info.get("classifiers", []), "is_mature": "Development Status :: 6 - Mature" in info.get("classifiers", []), "has_tests": "Topic :: Software Development :: Testing" in info.get("classifiers", []), "is_typed": "Typing :: Typed" in info.get("classifiers", []), } # Calculate a basic quality score quality_score = 0 if evaluation["has_docs"]: quality_score += 20 if evaluation["has_source"]: quality_score += 15 if evaluation["is_stable"] or evaluation["is_mature"]: quality_score += 25 if evaluation["has_tests"]: quality_score += 15 if evaluation["is_typed"]: quality_score += 10 if evaluation["has_homepage"]: quality_score += 10 if evaluation["has_bug_tracker"]: quality_score += 5 evaluation["quality_score"] = min(quality_score, 100) return evaluation else: return { "name": package_name, "error": f"HTTP {response.status_code}", "quality_score": 0, } except Exception as e: return {"name": package_name, "error": str(e), "quality_score": 0}
[docs] def _rank_packages( self, packages: List[Dict[str, Any]], requirements: str, functionality: str ) -> List[Dict[str, Any]]: """Rank packages by relevance and quality""" if not packages: return [] # Filter out packages with errors valid_packages = [pkg for pkg in packages if "error" not in pkg] # Sort by quality score (descending) ranked = sorted( valid_packages, key=lambda x: x.get("quality_score", 0), reverse=True ) # Add ranking metadata for i, pkg in enumerate(ranked): pkg["rank"] = i + 1 pkg["reasoning"] = f"Quality score: {pkg.get('quality_score', 0)}/100" return ranked
[docs] def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]: """ Dynamically discover and evaluate packages Args: requirements: Description of what's needed functionality: Specific functionality required constraints: Any constraints (Python version, license, etc.) """ try: requirements = arguments.get("requirements", "") functionality = arguments.get("functionality", "") # Search for candidate packages search_query = f"{requirements} {functionality}".strip() print(f"🔍 Searching for packages: {search_query}") candidates = self._search_pypi_via_web(search_query) if not candidates: return { "status": "success", "candidates": [], "recommendation": None, "message": "No packages found", } print(f"📦 Found {len(candidates)} package candidates") # Evaluate each candidate evaluated = [] for i, pkg in enumerate(candidates): print(f" Evaluating {i+1}/{len(candidates)}: {pkg['name']}") evaluation = self._evaluate_package(pkg["name"]) # Merge web search info with PyPI evaluation evaluation.update({k: v for k, v in pkg.items() if k not in evaluation}) evaluated.append(evaluation) # Rate limiting time.sleep(0.2) # Rank by suitability ranked = self._rank_packages(evaluated, requirements, functionality) top_recommendation = ranked[0] if ranked else None if top_recommendation: score = top_recommendation.get("quality_score", 0) print( f"🏆 Top recommendation: {top_recommendation['name']} (score: {score})" ) return { "status": "success", "candidates": ranked, "recommendation": top_recommendation, "total_evaluated": len(evaluated), } except Exception as e: return { "status": "error", "error": str(e), "candidates": [], "recommendation": None, }