refactor(docker): simplify Dockerfile and remove custom browser component
- Simplified Dockerfile by removing system dependencies, Playwright installation, and unnecessary Python packages - Removed custom browser_use_component.py (now using custom_components directory) - Updated docker-compose to build from Dockerfile and mount custom_components - Retained only essential dependencies: browser-use and langchain-openai
This commit is contained in:
1 parent
1f7d15a151
commit
0b91ee5481
5 files changed
+87
-304
No files matched your search
+3
-44
@@ -1,48 +1,7 @@
|
|||||||
# Используем официальный образ Langflow как базу
|
|
||||||
FROM langflowai/langflow:1.7.3
|
FROM langflowai/langflow:1.7.3
|
||||||
|
|
||||||
# Работаем от root
|
RUN pip install --no-cache-dir browser-use langchain-openai
|
||||||
USER root
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Устанавливаем системные зависимости для Playwright и browser-use
|
EXPOSE 7860
|
||||||
RUN apt-get update && apt-get install -y \
|
|
||||||
wget \
|
|
||||||
gnupg \
|
|
||||||
libglib2.0-0 \
|
|
||||||
libnss3 \
|
|
||||||
libnspr4 \
|
|
||||||
libdbus-1-3 \
|
|
||||||
libatk1.0-0 \
|
|
||||||
libatk-bridge2.0-0 \
|
|
||||||
libcups2 \
|
|
||||||
libdrm2 \
|
|
||||||
libxkbcommon0 \
|
|
||||||
libxcomposite1 \
|
|
||||||
libxdamage1 \
|
|
||||||
libxfixes3 \
|
|
||||||
libxrandr2 \
|
|
||||||
libgbm1 \
|
|
||||||
libasound2 \
|
|
||||||
libpango-1.0-0 \
|
|
||||||
libcairo2 \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Устанавливаем Python зависимости
|
CMD ["python", "-m", "langflow", "run", "--host", "0.0.0.0", "--port", "7860"]
|
||||||
RUN pip install --no-cache-dir \
|
|
||||||
browser-use \
|
|
||||||
langchain-community \
|
|
||||||
playwright \
|
|
||||||
httpx \
|
|
||||||
langchain-openai \
|
|
||||||
openai \
|
|
||||||
python-dotenv
|
|
||||||
|
|
||||||
# Устанавливаем Playwright браузеры
|
|
||||||
RUN playwright install chromium --with-deps
|
|
||||||
|
|
||||||
# Переменные окружения для browserless
|
|
||||||
ENV BROWSERLESS_API_URL=http://browserless.home
|
|
||||||
|
|
||||||
# По умолчанию запускаем Langflow
|
|
||||||
CMD ["langflow", "run"]
|
|
||||||
@@ -1,258 +0,0 @@
|
|||||||
"""
|
|
||||||
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}"
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from .browser_use_component import BrowserUseComponent
|
||||||
|
|
||||||
|
__all__ = ["BrowserUseComponent"]
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
"""
|
||||||
|
Browser Use Custom Component для Langflow
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from typing import Dict, Any
|
||||||
|
|
||||||
|
from lfx.custom import Component
|
||||||
|
from lfx.io import SecretStrInput, StrInput
|
||||||
|
from lfx.template import Output
|
||||||
|
|
||||||
|
|
||||||
|
class BrowserUseComponent(Component):
|
||||||
|
"""Browser Use компонент с browserless + OpenRouter"""
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
StrInput(
|
||||||
|
name="task",
|
||||||
|
display_name="Задача",
|
||||||
|
required=True,
|
||||||
|
placeholder="Например: найди информацию о...",
|
||||||
|
),
|
||||||
|
StrInput(
|
||||||
|
name="model",
|
||||||
|
display_name="OpenRouter Модель",
|
||||||
|
value="arcee-ai/trinity-large-preview:free",
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
outputs = [
|
||||||
|
Output(name="result", display_name="Результат", method="run_browser"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def run_browser(self) -> str:
|
||||||
|
"""Запускает browser-use агент"""
|
||||||
|
try:
|
||||||
|
from browser_use import Agent, Browser
|
||||||
|
from langchain_openai import ChatOpenAI
|
||||||
|
|
||||||
|
browserless_url = self.browserless_url or "http://browserless.home"
|
||||||
|
cdp_url = browserless_url.replace("http://", "ws://").replace("https://", "wss://")
|
||||||
|
cdp_url = f"{cdp_url}/playwright"
|
||||||
|
|
||||||
|
browser = Browser(cdp_url=cdp_url, headless=True)
|
||||||
|
|
||||||
|
api_key = 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}"
|
||||||
+6
-2
@@ -2,21 +2,25 @@ version: '3.8'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
langflow:
|
langflow:
|
||||||
image: langflowai/langflow:1.7.3
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "7860:7860"
|
- "7860:7860"
|
||||||
networks:
|
networks:
|
||||||
- dokploy-network
|
- dokploy-network
|
||||||
|
volumes:
|
||||||
|
- ./custom_components:/app/custom_components
|
||||||
environment:
|
environment:
|
||||||
LANGFLOW_SECRET_KEY: ${LANGFLOW_SECRET_KEY:-changeme}
|
LANGFLOW_SECRET_KEY: ${LANGFLOW_SECRET_KEY:-changeme}
|
||||||
LANGFLOW_HOST: 0.0.0.0
|
LANGFLOW_HOST: 0.0.0.0
|
||||||
LANGFLOW_PORT: 7860
|
LANGFLOW_PORT: 7860
|
||||||
|
LANGFLOW_COMPONENTS_PATH: /app/custom_components
|
||||||
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY}
|
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY}
|
||||||
OPENROUTER_BASE_URL: https://openrouter.ai/api/v1
|
OPENROUTER_BASE_URL: https://openrouter.ai/api/v1
|
||||||
BROWSERLESS_API_URL: http://browserless.home
|
BROWSERLESS_API_URL: http://browserless.home
|
||||||
BROWSER_HEADLESS: "true"
|
BROWSER_HEADLESS: "true"
|
||||||
command: ["python", "-m", "langflow", "run", "--host", "0.0.0.0", "--port", "7860"]
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:7860/"]
|
test: ["CMD", "curl", "-f", "http://localhost:7860/"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
|
|||||||
Reference in new issue
Block a user