add new tool: to_captcha
This commit is contained in:
parent
50589232d6
commit
f1f32d8366
14 changed files with 1008 additions and 130 deletions
|
|
@ -1,65 +1,130 @@
|
|||
import json
|
||||
import os
|
||||
import time
|
||||
from urllib import error, request
|
||||
|
||||
from tools.registry import registry
|
||||
|
||||
|
||||
def run_browser_task(task):
|
||||
def _browser_api_base_url() -> str:
|
||||
return os.getenv("BROWSER_API_URL", "http://browser-api:8088/api/browser").rstrip("/")
|
||||
|
||||
|
||||
def _http_json(url: str, method: str = "GET", payload: dict | None = None, timeout_sec: int = 30) -> dict:
|
||||
body = None
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if payload is not None:
|
||||
body = json.dumps(payload).encode("utf-8")
|
||||
req = request.Request(url, data=body, headers=headers, method=method)
|
||||
try:
|
||||
with request.urlopen(req, timeout=timeout_sec) as resp:
|
||||
raw = resp.read().decode("utf-8")
|
||||
return json.loads(raw) if raw else {}
|
||||
except error.HTTPError as http_err:
|
||||
raw = http_err.read().decode("utf-8", errors="replace")
|
||||
try:
|
||||
data = json.loads(raw) if raw else {}
|
||||
except json.JSONDecodeError:
|
||||
data = {"details": raw}
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Browser API returned HTTP {http_err.code}",
|
||||
"details": data,
|
||||
}
|
||||
except Exception as err:
|
||||
return {"success": False, "error": f"Browser API request failed: {err}"}
|
||||
|
||||
|
||||
def run_browser_task(task: str):
|
||||
if not task or not str(task).strip():
|
||||
return json.dumps({"success": False, "error": "Task is required"}, ensure_ascii=False)
|
||||
|
||||
rpc_url = os.getenv("BROWSER_USE_RPC_URL", "http://browser:8787/run")
|
||||
timeout_sec = int(os.getenv("BROWSER_USE_RPC_TIMEOUT", "900"))
|
||||
payload = json.dumps({"task": task}).encode("utf-8")
|
||||
req = request.Request(rpc_url, data=payload, headers={"Content-Type": "application/json"}, method="POST")
|
||||
poll_interval = float(os.getenv("BROWSER_API_POLL_INTERVAL", "1.5"))
|
||||
api_base = _browser_api_base_url()
|
||||
|
||||
try:
|
||||
with request.urlopen(req, timeout=timeout_sec) as resp:
|
||||
body = resp.read().decode("utf-8")
|
||||
return body
|
||||
except error.HTTPError as http_err:
|
||||
body = http_err.read().decode("utf-8", errors="replace")
|
||||
accepted = _http_json(
|
||||
f"{api_base}/tasks",
|
||||
method="POST",
|
||||
payload={"task": task, "timeout": timeout_sec, "metadata": {"source": "internet_browser"}},
|
||||
timeout_sec=30,
|
||||
)
|
||||
task_id = accepted.get("task_id")
|
||||
if not task_id:
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"error": f"browser-use RPC returned HTTP {http_err.code}",
|
||||
"details": body,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as err:
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"error": f"browser-use RPC request failed: {err}",
|
||||
"error": accepted.get("error", "Browser task was not accepted"),
|
||||
"details": accepted,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
deadline = time.time() + timeout_sec + 10
|
||||
status_url = f"{api_base}/tasks/{task_id}"
|
||||
result_url = f"{api_base}/tasks/{task_id}/result"
|
||||
|
||||
while time.time() < deadline:
|
||||
status_payload = _http_json(status_url, timeout_sec=15)
|
||||
status = status_payload.get("status")
|
||||
if not status and status_payload.get("error"):
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"status": "failed",
|
||||
"task_id": task_id,
|
||||
"error": status_payload.get("error"),
|
||||
"details": status_payload.get("details"),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
if status == "awaiting_user_captcha":
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"status": status,
|
||||
"task_id": task_id,
|
||||
"human_intervention": status_payload.get("human_intervention"),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
if status in {"succeeded", "failed"}:
|
||||
result_payload = _http_json(result_url, timeout_sec=30)
|
||||
result_payload.setdefault("task_id", task_id)
|
||||
return json.dumps(result_payload, ensure_ascii=False)
|
||||
time.sleep(poll_interval)
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"status": "failed",
|
||||
"task_id": task_id,
|
||||
"error": "Timed out while waiting for browser task result",
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
|
||||
registry.register(
|
||||
name="internet_browser",
|
||||
toolset="browse_cmd",
|
||||
toolset="browse_cmd",
|
||||
schema={
|
||||
"name": "internet_browser",
|
||||
"description": (
|
||||
"ГЛАВНЫЙ ИНСТРУМЕНТ ДЛЯ ВЕБ-СЕРФИНГА. Вызывай этот инструмент НАПРЯМУЮ (через стандартный tool call/function call). "
|
||||
"КАТЕГОРИЧЕСКИ ЗАПРЕЩЕНО использовать `execute_code` или `delegate_task` для работы с браузером. "
|
||||
"Не пиши Python-скрипты! Просто передай в этот инструмент параметр `task`. "
|
||||
"Используй для любых задач в интернете: поиск товаров (Wildberries, Ozon), чтение статей, клики, навигация."
|
||||
"Main browser automation tool for internet tasks. Call it directly via a normal tool/function call. "
|
||||
"Do not use execute_code or delegate_task for browser work. Pass the task in natural language."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"task": {
|
||||
"type": "string",
|
||||
"description": "Подробная задача на естественном языке. Например: 'Зайди на wildberries.ru, найди черную футболку и верни цену'."
|
||||
"type": "string",
|
||||
"description": "Detailed natural-language browser task."
|
||||
}
|
||||
},
|
||||
"required": ["task"]
|
||||
}
|
||||
},
|
||||
|
||||
handler=lambda args, **kw: run_browser_task(args.get("task")),
|
||||
emoji="🌐",
|
||||
)
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue