""" Browser Use REST API Server Простой HTTP API для управления браузером из n8n """ import os import asyncio from typing import Optional from fastapi import FastAPI, HTTPException from pydantic import BaseModel, Field from browser_use import Agent, Browser, BrowserProfile, ChatOpenAI from browser_use.agent.views import AgentHistoryList import uvicorn from fastapi.responses import HTMLResponse # Настройки Remote Debugging REMOTE_DEBUGGING_PORT = int(os.getenv("REMOTE_DEBUGGING_PORT", "9222")) REMOTE_DEBUGGING_ENABLED = os.getenv("REMOTE_DEBUGGING_ENABLED", "false").lower() == "true" # Модель по умолчанию из переменной окружения DEFAULT_MODEL = os.getenv("LLM_MODEL_NAME", "openai/gpt-4o-mini") app = FastAPI(title="Browser Use API", version="1.0.0") # Модели запросов class TaskRequest(BaseModel): task: str max_steps: Optional[int] = 20 model: Optional[str] = Field(default_factory=lambda: os.getenv("LLM_MODEL_NAME", "openai/gpt-4o-mini")) class TaskResponse(BaseModel): task_id: str status: str result: Optional[str] = None error: Optional[str] = None # Хранилище задач tasks = {} @app.post("/api/tasks", response_model=TaskResponse) async def create_task(request: TaskRequest): """Создать новую задачу браузера""" import uuid task_id = str(uuid.uuid4())[:8] tasks[task_id] = { "status": "pending", "task": request.task, "result": None } # Запускаем задачу в фоне asyncio.create_task(run_browser_task(task_id, request.task, request.max_steps, request.model)) return TaskResponse( task_id=task_id, status="pending" ) async def run_browser_task(task_id: str, task: str, max_steps: int, model: str): """Выполнение задачи браузера""" browser = None try: tasks[task_id]["status"] = "running" # Настройка LLM через OpenRouter api_key = os.getenv("OPENROUTER_API_KEY", "") if not api_key: raise ValueError("OPENROUTER_API_KEY not set") # Используем ChatOpenAI с base_url для OpenRouter llm = ChatOpenAI( model=model, base_url="https://openrouter.ai/api/v1", api_key=api_key ) # Настройки браузера # Если включен remote debugging - запускаем Chrome вручную и подключаемся через CDP if REMOTE_DEBUGGING_ENABLED: import subprocess import glob import urllib.request # Точный путь из контейнера CHROME_PATH = "/root/.cache/ms-playwright/chromium-1208/chrome-linux64/chrome" # Ищем chrome chrome_exe = None if os.path.exists(CHROME_PATH): chrome_exe = CHROME_PATH else: # Fallback - ищем в кэше for pattern in [ "/root/.cache/ms-playwright/chromium-*/chrome-linux64/chrome", "/ms-playwright/*/chrome-linux/chrome", ]: matches = glob.glob(pattern) if matches: chrome_exe = matches[0] break if not chrome_exe: raise RuntimeError(f"Chrome not found. Path: {CHROME_PATH}") # Создаем директорию для профиля user_data_dir = "/app/browser-data" os.makedirs(user_data_dir, exist_ok=True) # Запускаем Chrome с remote debugging chrome_cmd = [ chrome_exe, f"--remote-debugging-port={REMOTE_DEBUGGING_PORT}", f"--remote-debugging-address=0.0.0.0", f"--user-data-dir={user_data_dir}", "--no-first-run", "--no-default-browser-check", "--disable-extensions", "--disable-gpu", "--no-sandbox", "--disable-dev-shm-usage", "--headless", "about:blank" ] # Запускаем процесс process = subprocess.Popen( chrome_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) # Ждем пока Chrome запустится и CDP будет готов cdp_ready = False for i in range(30): # Ждем до 30 секунд await asyncio.sleep(1) try: response = urllib.request.urlopen( f"http://localhost:{REMOTE_DEBUGGING_PORT}/json/version", timeout=2 ) if response.status == 200: cdp_ready = True break except Exception: pass if not cdp_ready: # Получаем логи Chrome stdout, stderr = process.communicate(timeout=5) raise RuntimeError( f"Chrome started but CDP not ready after 30s.\n" f"STDOUT: {stdout.decode()}\n" f"STDERR: {stderr.decode()}" ) # Подключаемся через CDP cdp_url = f"http://localhost:{REMOTE_DEBUGGING_PORT}" browser = Browser(cdp_url=cdp_url) else: # Обычный запуск browser_profile = BrowserProfile( user_data_dir="/app/browser-data", profile_directory="Default", headless=True, keep_alive=True, ) browser = Browser(browser_profile=browser_profile) # Создание агента agent = Agent( task=task, llm=llm, browser=browser, ) # Выполнение задачи history: AgentHistoryList = await agent.run(max_steps=max_steps) # Получение результата result = history.final_result() tasks[task_id]["status"] = "completed" tasks[task_id]["result"] = result except Exception as e: tasks[task_id]["status"] = "failed" tasks[task_id]["error"] = str(e) finally: # Закрытие браузера (правильный способ) if browser: try: # browser_use сам управляет закрытием pass except: pass @app.get("/api/tasks/{task_id}", response_model=TaskResponse) async def get_task(task_id: str): """Получить статус/результат задачи""" if task_id not in tasks: raise HTTPException(status_code=404, detail="Task not found") task = tasks[task_id] return TaskResponse( task_id=task_id, status=task["status"], result=task.get("result"), error=task.get("error") ) @app.get("/api/tasks") async def list_tasks(): """Получить список всех задач""" return { "tasks": [ { "task_id": task_id, "status": task["status"], "task": task.get("task", ""), "result": task.get("result"), "error": task.get("error") } for task_id, task in tasks.items() ] } @app.get("/api/health") async def health_check(): """Проверка здоровья сервера""" return { "status": "healthy", "service": "browser-use-api", "version": "1.0.0" } # Web UI - Главная страница WEB_UI_HTML = """
REST API для автоматизации браузера с помощью AI
POST /api/tasks - Создать задачуGET /api/tasks/{task_id} - Получить результатGET /api/health - Проверка здоровьяcurl -X POST http://localhost:8383/api/tasks \\
-H "Content-Type: application/json" \\
-d '{"task": "Перейди на example.com", "max_steps": 20}'