Spaces:
Sleeping
Sleeping
import subprocess | |
import asyncio | |
from contextlib import contextmanager | |
from typing import List, Optional | |
from threading import Lock | |
from smolagents.tools import Tool | |
from langchain_community.tools.playwright.utils import ( | |
create_async_playwright_browser, | |
) | |
from langchain_community.agent_toolkits import PlayWrightBrowserToolkit | |
import logging | |
from playwright.async_api import async_playwright | |
logging.basicConfig(level=logging.DEBUG) | |
logger = logging.getLogger(__name__) | |
class BrowserManager: | |
_instance = None | |
_lock = Lock() | |
_browser_tools: Optional[List[Tool]] = None | |
_loop: Optional[asyncio.AbstractEventLoop] = None | |
def __new__(cls): | |
if cls._instance is None: | |
with cls._lock: | |
if cls._instance is None: | |
cls._instance = super(BrowserManager, cls).__new__(cls) | |
return cls._instance | |
def __init__(self): | |
if not hasattr(self, 'initialized'): | |
# Run setup script | |
subprocess.run(["bash", "scripts.sh"]) | |
self.initialized = True | |
def _create_browser_tools(self): | |
"""Create browser tools in a new event loop""" | |
# Create a new event loop for browser tools | |
loop = asyncio.new_event_loop() | |
asyncio.set_event_loop(loop) | |
try: | |
logger.debug("Creating async browser...") | |
# Create browsers in the new loop | |
async def create_browser(): | |
playwright = await async_playwright().start() | |
return await playwright.chromium.launch(headless=True) | |
async_browser = loop.run_until_complete(create_browser()) | |
logger.debug("Async browser created successfully") | |
# Create toolkit and tools | |
logger.debug("Creating browser toolkit...") | |
browser_toolkit = PlayWrightBrowserToolkit.from_browser( | |
async_browser=async_browser, | |
sync_browser=None # Don't use sync browser | |
) | |
logger.debug("Browser toolkit created successfully") | |
logger.debug("Converting tools...") | |
tools = [] | |
for tool in browser_toolkit.get_tools(): | |
logger.debug(f"Converting tool: {tool.name}") | |
try: | |
converted_tool = Tool.from_langchain(tool) | |
converted_tool._browser = async_browser | |
converted_tool._loop = loop | |
tools.append(converted_tool) | |
logger.debug(f"Successfully converted tool: {tool.name}") | |
except Exception as e: | |
logger.error(f"Error converting tool {tool.name}: {e}") | |
raise | |
logger.debug(f"Successfully created {len(tools)} browser tools") | |
return tools | |
except Exception as e: | |
logger.error(f"Error creating browser tools: {e}") | |
if loop.is_running(): | |
loop.stop() | |
loop.close() | |
raise | |
def get_browser_tools(self): | |
"""Get browser tools in a context that ensures proper cleanup""" | |
try: | |
if self._browser_tools is None: | |
with self._lock: | |
if self._browser_tools is None: | |
logger.debug("Creating new browser tools...") | |
self._browser_tools = self._create_browser_tools() | |
logger.debug("Browser tools created successfully") | |
yield self._browser_tools | |
except Exception as e: | |
logger.error(f"Error in browser context: {e}") | |
# Reset tools on error | |
self._browser_tools = None | |
raise | |
finally: | |
# Cleanup if needed | |
if self._browser_tools: | |
for tool in self._browser_tools: | |
if hasattr(tool, '_browser') and hasattr(tool, '_loop'): | |
try: | |
loop = tool._loop | |
if loop and not loop.is_closed(): | |
logger.debug("Cleaning up browser...") | |
loop.run_until_complete(tool._browser.close()) | |
loop.close() | |
logger.debug("Browser cleanup completed") | |
except Exception as e: | |
logger.error(f"Error during browser cleanup: {e}") | |
self._browser_tools = None | |
# Create a singleton instance | |
browser_manager = BrowserManager() | |
# For backward compatibility, but prefer using browser_manager.get_browser_tools() | |
def get_browser_tools(): | |
"""Get browser tools (use with context manager)""" | |
return browser_manager.get_browser_tools() | |