Spaces:
Sleeping
Sleeping
| import asyncio | |
| import hmac | |
| import logging | |
| import os | |
| import httpx | |
| from contextlib import asynccontextmanager | |
| from fastapi import BackgroundTasks, FastAPI, HTTPException | |
| from pydantic import BaseModel | |
| import jobs | |
| from browser_agent import run_task | |
| logger = logging.getLogger(__name__) | |
| MAX_CONCURRENT = 2 | |
| API_SECRET = os.getenv("API_SECRET", "") | |
| KENSHI_CALLBACK_URL = os.getenv("KENSHI_CALLBACK_URL", "") | |
| KENSHI_CALLBACK_SECRET = os.getenv("KENSHI_CALLBACK_SECRET", "") | |
| async def lifespan(app: FastAPI): | |
| jobs.init_db() | |
| yield | |
| app = FastAPI(lifespan=lifespan) | |
| class TaskRequest(BaseModel): | |
| task: str | |
| chat_id: str = "" | |
| callback_url: str = "" | |
| secret: str = "" | |
| def _verify(secret: str): | |
| if API_SECRET and not hmac.compare_digest(secret, API_SECRET): | |
| raise HTTPException(status_code=401, detail="Invalid secret") | |
| async def root(): | |
| return {"name": "KAZE", "status": "ok", "docs": "/docs", "health": "/health"} | |
| async def health(): | |
| return { | |
| "status": "ok", | |
| "model": "google/gemini-2.5-flash-lite", | |
| "jobs_running": jobs.count_running(), | |
| } | |
| async def test_structured_output(): | |
| """Diagnostic: test LLM with_structured_output — what browser_use 0.2.x calls internally.""" | |
| import traceback as tb | |
| from pydantic import BaseModel | |
| from browser_agent import _make_llm, PRIMARY_MODEL | |
| class SimpleAnswer(BaseModel): | |
| answer: str | |
| try: | |
| llm = _make_llm(PRIMARY_MODEL) | |
| structured = llm.with_structured_output(SimpleAnswer) | |
| result = await structured.ainvoke("What is 2+2? Return a JSON with field 'answer'.") | |
| return {"status": "ok", "model": PRIMARY_MODEL, "result": result.model_dump()} | |
| except Exception as e: | |
| return { | |
| "status": "error", | |
| "model": PRIMARY_MODEL, | |
| "error": str(e), | |
| "type": type(e).__name__, | |
| "traceback": tb.format_exc(), | |
| } | |
| async def test_llm(): | |
| """Diagnostic: test LLM connection directly without browser_use.""" | |
| import traceback as tb | |
| import importlib | |
| from browser_agent import _make_llm, PRIMARY_MODEL | |
| try: | |
| llm = _make_llm(PRIMARY_MODEL) | |
| response = await llm.ainvoke("Say 'KAZE online' in exactly those two words.") | |
| return {"status": "ok", "model": PRIMARY_MODEL, "response": str(response.content)} | |
| except Exception as e: | |
| logger.exception("[KAZE] /test-llm failed") | |
| return {"status": "error", "model": PRIMARY_MODEL, "error": str(e), "type": type(e).__name__} | |
| async def test_browser(): | |
| """Diagnostic: launch bare Playwright browser and navigate to example.com.""" | |
| import traceback as tb | |
| try: | |
| from playwright.async_api import async_playwright | |
| async with async_playwright() as p: | |
| browser = await p.chromium.launch(headless=True, args=["--no-sandbox", "--disable-dev-shm-usage"]) | |
| page = await browser.new_page() | |
| await page.goto("https://example.com", timeout=15000) | |
| title = await page.title() | |
| await browser.close() | |
| return {"status": "ok", "title": title} | |
| except Exception as e: | |
| return {"status": "error", "error": str(e), "traceback": tb.format_exc()} | |
| async def test_agent(): | |
| """Diagnostic: show browser_use version + run 1-step agent.""" | |
| import traceback as tb, importlib.metadata as im | |
| from browser_agent import _make_llm, PRIMARY_MODEL | |
| info = {} | |
| try: | |
| info["browser_use_version"] = im.version("browser-use") | |
| except Exception: | |
| info["browser_use_version"] = "unknown" | |
| try: | |
| import browser_use as bu | |
| info["browser_use_api"] = dir(bu) | |
| except Exception as e: | |
| info["browser_use_api_error"] = str(e) | |
| try: | |
| from browser_use import Agent | |
| llm = _make_llm(PRIMARY_MODEL) | |
| # Try new 0.2.x API with explicit Browser | |
| try: | |
| from browser_use.browser.browser import Browser, BrowserConfig | |
| browser = Browser(config=BrowserConfig( | |
| headless=True, | |
| extra_chromium_args=["--no-sandbox", "--disable-dev-shm-usage"], | |
| )) | |
| agent = Agent(task="Navigate to example.com and return the page title.", llm=llm, browser=browser) | |
| info["api"] = "0.2.x (explicit Browser)" | |
| except ImportError: | |
| agent = Agent(task="Navigate to example.com and return the page title.", llm=llm) | |
| info["api"] = "0.1.x (auto Browser)" | |
| result = await agent.run(max_steps=3) | |
| return {"status": "ok", "result": str(result), **info} | |
| except Exception as e: | |
| return {"status": "error", "error": str(e), "traceback": tb.format_exc(), **info} | |
| async def run_sync(req: TaskRequest): | |
| _verify(req.secret) | |
| if jobs.count_running() >= MAX_CONCURRENT: | |
| raise HTTPException(status_code=503, detail="Max concurrent jobs reached. Try /run/async.") | |
| job_id = jobs.create_job(req.task, req.chat_id) | |
| jobs.update_job(job_id, "running") | |
| try: | |
| output = await run_task(req.task) | |
| jobs.update_job(job_id, "done", output["result"]) | |
| return {"status": "done", "result": output["result"], "screenshots": [], "job_id": job_id} | |
| except asyncio.TimeoutError: | |
| jobs.update_job(job_id, "timeout", "Task exceeded 120s time limit") | |
| return {"status": "timeout", "result": "Task exceeded 120s time limit", "screenshots": [], "job_id": job_id} | |
| except Exception as e: | |
| jobs.update_job(job_id, "failed", str(e)) | |
| return {"status": "failed", "result": str(e), "screenshots": [], "job_id": job_id} | |
| async def _run_async_job(job_id: str, task: str, chat_id: str, callback_url: str): | |
| jobs.update_job(job_id, "running") | |
| try: | |
| output = await run_task(task) | |
| jobs.update_job(job_id, "done", output["result"]) | |
| status, result = "done", output["result"] | |
| except asyncio.TimeoutError: | |
| jobs.update_job(job_id, "timeout", "Task exceeded 120s time limit") | |
| status, result = "timeout", "Task exceeded 120s time limit" | |
| except Exception as e: | |
| jobs.update_job(job_id, "failed", str(e)) | |
| status, result = "failed", str(e) | |
| url = callback_url or KENSHI_CALLBACK_URL | |
| if url: | |
| payload = { | |
| "agentName": "KAZE", | |
| "taskId": job_id, | |
| "chatId": chat_id, | |
| "result": result, | |
| "status": status, | |
| } | |
| headers = {"X-Agent-Secret": KENSHI_CALLBACK_SECRET} if KENSHI_CALLBACK_SECRET else {} | |
| async with httpx.AsyncClient() as client: | |
| try: | |
| await client.post(url, json=payload, headers=headers, timeout=10) | |
| except Exception as e: | |
| logger.error("[KAZE] Webhook failed for job %s: %s", job_id, e) | |
| async def run_async_endpoint(req: TaskRequest, background_tasks: BackgroundTasks): | |
| _verify(req.secret) | |
| if jobs.count_running() >= MAX_CONCURRENT: | |
| raise HTTPException(status_code=503, detail="Max concurrent jobs reached.") | |
| job_id = jobs.create_job(req.task, req.chat_id) | |
| background_tasks.add_task(_run_async_job, job_id, req.task, req.chat_id, req.callback_url) | |
| return {"job_id": job_id, "status": "queued"} | |
| async def get_job(job_id: str): | |
| job = jobs.get_job(job_id) | |
| if not job: | |
| raise HTTPException(status_code=404, detail="Job not found") | |
| return job | |