File size: 4,891 Bytes
e8f6be2
ed00413
1a04a88
 
 
e8f6be2
 
59377f3
e8f6be2
 
eda541b
 
 
 
 
e8f6be2
1a04a88
 
 
 
a1dd06e
1a04a88
 
 
 
 
 
 
 
 
 
 
 
 
 
beb3f4b
 
 
 
 
59377f3
a1dd06e
eda541b
beb3f4b
eda541b
 
 
 
 
 
beb3f4b
 
eda541b
beb3f4b
 
 
 
eda541b
beb3f4b
eda541b
 
 
 
 
 
 
 
 
 
 
 
 
beb3f4b
eda541b
beb3f4b
eda541b
beb3f4b
eda541b
a1dd06e
beb3f4b
 
 
59377f3
1a04a88
 
 
 
 
 
 
eda541b
beb3f4b
eda541b
1a04a88
 
 
 
eda541b
1a04a88
 
 
 
 
 
 
beb3f4b
1a04a88
beb3f4b
 
eda541b
beb3f4b
 
eda541b
a1dd06e
eda541b
1a04a88
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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
    
    @contextmanager
    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()