diff --git a/Dockerfile b/Dockerfile index 74dea6b..98a2827 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,7 +31,8 @@ RUN pip install --no-cache-dir \ playwright \ httpx \ langchain-openai \ - openai + openai \ + python-dotenv # Устанавливаем Playwright браузеры RUN playwright install chromium --with-deps diff --git a/browser_use_component.py b/browser_use_component.py new file mode 100644 index 0000000..50f0894 --- /dev/null +++ b/browser_use_component.py @@ -0,0 +1,258 @@ +""" +Browser Use Custom Component для Langflow +Интеграция с browserless + OpenRouter + +Основано на документации из Context7 +""" +import os +from typing import Optional, Dict, Any, List + +from langflow.custom import Component +from langflow.inputs import SecretStrInput, StrInput, DictInput +from langflow.template import Output + + +class BrowserUseComponent(Component): + """ + Browser Use компонент с интеграцией browserless + OpenRouter + + Подключение к browserless через CDP URL. + Использует OpenRouter API для LLM. + """ + display_name = "Browser Use" + description = "browser-use с browserless + OpenRouter" + icon = "Globe" + + inputs = [ + SecretStrInput( + name="browserless_url", + display_name="Browserless URL", + value=os.getenv("BROWSERLESS_API_URL", "http://browserless.home"), + required=True, + helper_text="CDP URL browserless сервера", + ), + StrInput( + name="task", + display_name="Задача", + required=True, + placeholder="Например: найди информацию о...", + ), + StrInput( + name="model", + display_name="OpenRouter Модель", + value="arcee-ai/trinity-large-preview:free", + advanced=True, + helper_text="Модель: anthropic/claude-3-opus, openai/gpt-4o, google/gemini-pro и др.", + ), + SecretStrInput( + name="openrouter_api_key", + display_name="OpenRouter API Key", + value=os.getenv("OPENROUTER_API_KEY", ""), + required=True, + ), + ] + + outputs = [ + Output(name="result", display_name="Результат", method="run_browser"), + ] + + def run_browser(self) -> str: + """ + Запускает browser-use агент с browserless + OpenRouter + """ + try: + from browser_use import Agent, Browser + from langchain_openai import ChatOpenAI + + # URL browserless + browserless_url = self.browserless_url + + # Формируем WebSocket URL для browserless + cdp_url = browserless_url.replace("http://", "ws://").replace("https://", "wss://") + cdp_url = f"{cdp_url}/playwright" + + # Конфигурация браузера с CDP подключением + browser = Browser( + cdp_url=cdp_url, + headless=True, + ) + + # Инициализация LLM через OpenRouter + api_key = self.openrouter_api_key or os.getenv("OPENROUTER_API_KEY") + + llm = ChatOpenAI( + model=self.model, + api_key=api_key, + base_url="https://openrouter.ai/api/v1", + default_headers={ + "HTTP-Referer": "http://langflow.home", + "X-Title": "Langflow", + }, + ) + + # Создание агента + agent = Agent( + task=self.task, + llm=llm, + browser=browser, + ) + + # Запуск агента + result = agent.run() + + return str(result) + + except ImportError as e: + return f"Ошибка импорта: {e}. Убедитесь, что browser-use установлен." + except Exception as e: + return f"Ошибка выполнения: {e}" + + +class BrowserUsePlaywrightComponent(Component): + """ + Альтернативный компонент - Playwright напрямую с browserless + """ + display_name = "Browser Use (Playwright)" + description = "Playwright напрямую с browserless" + icon = "Globe" + + inputs = [ + SecretStrInput( + name="browserless_url", + display_name="Browserless URL", + value=os.getenv("BROWSERLESS_API_URL", "http://browserless.home"), + required=True, + ), + StrInput( + name="url", + display_name="URL", + required=True, + placeholder="https://example.com", + ), + StrInput( + name="action", + display_name="Действие", + value="navigate", + advanced=True, + options=["navigate", "click", "fill", "screenshot", "get_content"], + ), + StrInput( + name="selector", + display_name="CSS Selector", + advanced=True, + ), + StrInput( + name="fill_text", + display_name="Текст для заполнения", + advanced=True, + ), + ] + + outputs = [ + Output(name="result", display_name="Результат", method="run_playwright"), + ] + + async def run_playwright(self) -> Dict[str, Any]: + """Запускает Playwright с browserless""" + from playwright.async_api import async_playwright + + browserless_url = self.browserless_url + ws_endpoint = browserless_url.replace("http://", "ws://").replace("https://", "wss://") + ws_endpoint = f"{ws_endpoint}/playwright?launch=%s" % str({"headless": True}) + + async with async_playwright() as p: + try: + browser = await p.chromium.connect_over_cdp(ws_endpoint) + page = await browser.new_page() + + try: + if self.action == "navigate": + await page.goto(self.url) + content = await page.content() + return {"status": "success", "content": content[:5000]} + + elif self.action == "click": + await page.click(self.selector or "body") + return {"status": "success", "action": "clicked"} + + elif self.action == "fill": + await page.fill(self.selector or "body", self.fill_text or "") + return {"status": "success", "action": "filled"} + + elif self.action == "screenshot": + await page.goto(self.url) + screenshot_bytes = await page.screenshot() + import base64 + return {"status": "success", "screenshot": base64.b64encode(screenshot_bytes).decode()} + + elif self.action == "get_content": + await page.goto(self.url) + content = await page.content() + return {"status": "success", "content": content} + + finally: + await page.close() + await browser.close() + + except Exception as e: + return {"status": "error", "message": str(e)} + + +class SimpleBrowserAgentComponent(Component): + """ + Простой browser-use агент с OpenRouter (локальный браузер) + """ + display_name = "Simple Browser Agent + OpenRouter" + description = "browser-use агент с OpenRouter (локальный браузер)" + icon = "Globe" + + inputs = [ + StrInput( + name="task", + display_name="Задача", + required=True, + ), + StrInput( + name="model", + display_name="Модель", + value="arcee-ai/trinity-large-preview:free", + advanced=True, + ), + SecretStrInput( + name="openrouter_api_key", + display_name="OpenRouter API Key", + value=os.getenv("OPENROUTER_API_KEY", ""), + required=True, + ), + ] + + outputs = [ + Output(name="result", display_name="Результат", method="run_agent"), + ] + + def run_agent(self) -> str: + try: + from browser_use import Agent, Browser + from langchain_openai import ChatOpenAI + + # Локальный браузер + browser = Browser(headless=True) + + api_key = self.openrouter_api_key or os.getenv("OPENROUTER_API_KEY") + + llm = ChatOpenAI( + model=self.model, + api_key=api_key, + base_url="https://openrouter.ai/api/v1", + default_headers={ + "HTTP-Referer": "http://langflow.home", + "X-Title": "Langflow", + }, + ) + + agent = Agent(task=self.task, llm=llm, browser=browser) + result = agent.run() + + return str(result) + except Exception as e: + return f"Ошибка: {e}" diff --git a/docker-compose.yml b/docker-compose.yml index f44dd90..db9b1c2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,16 +5,22 @@ services: build: context: . dockerfile: Dockerfile + image: langflow:${VERSION:-latest} + container_name: langflow restart: unless-stopped + expose: + - "7860" environment: # Langflow настройки LANGFLOW_DATABASE_URL: sqlite:///./langflow.db LANGFLOW_SECRET_KEY: ${LANGFLOW_SECRET_KEY:-your-secret-key-change-in-production} - LANGFLOW_AUTO_LOGY: "false" + LANGFLOW_AUTO_LOGIN: "false" LANGFLOW_LOG_LEVEL: INFO + LANGFLOW_URL: ${LANGFLOW_URL:-http://langflow.home} - # OpenAI API Key для browser-use агентов - OPENAI_API_KEY: ${OPENAI_API_KEY:-} + # OpenRouter API для LLM + OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-} + OPENROUTER_BASE_URL: https://openrouter.ai/api/v1 # Настройки для browser-use BROWSERLESS_API_URL: http://browserless.home @@ -24,15 +30,12 @@ services: # Прокси для доступа к browserless HTTP_PROXY: ${HTTP_PROXY:-} HTTPS_PROXY: ${HTTPS_PROXY:-} - NO_PROXY: ${NO_PROXY:-localhost,127.0.0.1} + NO_PROXY: ${NO_PROXY:-localhost,127.0.0.1,browserless.home} volumes: - #persistent volume for SQLite database and files - langflow_data:/app/langflow - langflow_cache:/root/.cache - networks: - - langflow_network healthcheck: test: ["CMD", "curl", "-f", "http://localhost:7860/health"] interval: 30s