browser use upgrade

frdel committed Sep 23, 2025 at 14:55 UTC 43613d8f80c0cfa7bb839bf3b70bb04282630595
4 files changed +76 -24
models.py
+69 -20
@@ -19,7 +19,7 @@ import litellm
19 import openai
20
21 from python.helpers import dotenv
22 -from python.helpers import settings
22 +from python.helpers import settings, dirty_json
23 from python.helpers.dotenv import load_dotenv
24 from python.helpers.providers import get_provider_config
25 from python.helpers.rate_limiter import RateLimiter
@@ -545,7 +545,27 @@ class LiteLLMChatWrapper(SimpleChatModel):
545 await asyncio.sleep(retry_delay_s)
546
547
548 -class BrowserCompatibleChatWrapper(LiteLLMChatWrapper):
548 +class AsyncAIChatReplacement:
549 + class _Completions:
550 + def __init__(self, wrapper):
551 + self._wrapper = wrapper
552 +
553 + async def create(self, *args, **kwargs):
554 + # call the async _acall method on the wrapper
555 + return await self._wrapper._acall(*args, **kwargs)
556 +
557 + class _Chat:
558 + def __init__(self, wrapper):
559 + self.completions = AsyncAIChatReplacement._Completions(wrapper)
560 +
561 + def __init__(self, wrapper, *args, **kwargs):
562 + self._wrapper = wrapper
563 + self.chat = AsyncAIChatReplacement._Chat(wrapper)
564 +
565 +
566 +from browser_use.llm import ChatOllama, ChatOpenRouter, ChatGoogle, ChatAnthropic, ChatGroq, ChatOpenAI
567 +
568 +class BrowserCompatibleChatWrapper(ChatOpenRouter):
569 """
570 A wrapper for browser agent that can filter/sanitize messages
571 before sending them to the LLM.
@@ -553,32 +573,61 @@ class BrowserCompatibleChatWrapper(LiteLLMChatWrapper):
573
574 def __init__(self, *args, **kwargs):
575 turn_off_logging()
556 - super().__init__(*args, **kwargs)
576 + # Create the underlying LiteLLM wrapper
577 + self._wrapper = LiteLLMChatWrapper(*args, **kwargs)
578 # Browser-use may expect a 'model' attribute
558 - self.model = self.model_name
579 + self.model = self._wrapper.model_name
580 + self.kwargs = self._wrapper.kwargs
581
560 - def _call(
561 - self,
562 - messages: List[BaseMessage],
563 - stop: Optional[List[str]] = None,
564 - run_manager: Optional[CallbackManagerForLLMRun] = None,
565 - **kwargs: Any,
566 - ) -> str:
567 - turn_off_logging()
568 - result = super()._call(messages, stop, run_manager, **kwargs)
569 - return result
582 + @property
583 + def model_name(self) -> str:
584 + return self._wrapper.model_name
585
571 - async def _astream(
586 + @property
587 + def provider(self) -> str:
588 + return self._wrapper.provider
589 +
590 + def get_client(self, *args, **kwargs): # type: ignore
591 + return AsyncAIChatReplacement(self, *args, **kwargs)
592 +
593 + async def _acall(
594 self,
595 messages: List[BaseMessage],
596 stop: Optional[List[str]] = None,
575 - run_manager: Optional[AsyncCallbackManagerForLLMRun] = None,
597 + run_manager: Optional[CallbackManagerForLLMRun] = None,
598 **kwargs: Any,
577 - ) -> AsyncIterator[ChatGenerationChunk]:
578 - turn_off_logging()
579 - async for chunk in super()._astream(messages, stop, run_manager, **kwargs):
580 - yield chunk
599 + ):
600 + # Apply rate limiting if configured
601 + apply_rate_limiter_sync(self._wrapper.a0_model_conf, str(messages))
602
603 + # Call the model
604 + try:
605 + model = kwargs.pop("model", None)
606 + kwrgs = {**self._wrapper.kwargs, **kwargs}
607 +
608 + # hack from browser-use to fix json schema for gemini
609 + if "response_format" in kwrgs and "json_schema" in kwrgs["response_format"] and model.startswith("gemini/"):
610 + kwrgs["response_format"]["json_schema"] = ChatGoogle("")._fix_gemini_schema(self._wrapper.kwargs)
611 +
612 + resp = await acompletion(
613 + model=self._wrapper.model_name,
614 + messages=messages,
615 + stop=stop,
616 + **kwrgs,
617 + )
618 + except Exception as e:
619 + raise e
620 +
621 + # another hack for browser-use post process invalid jsons
622 + try:
623 + if "response_format" in kwrgs and "json_schema" in kwrgs["response_format"] or "json_object" in kwrgs["response_format"]:
624 + if resp.choices[0].message.content is not None and not resp.choices[0].message.content.startswith("{"): # type: ignore
625 + js = dirty_json.parse(resp.choices[0].message.content) # type: ignore
626 + resp.choices[0].message.content = dirty_json.stringify(js) # type: ignore
627 + except Exception as e:
628 + pass
629 +
630 + return resp
631
632 class LiteLLMEmbeddingWrapper(Embeddings):
633 model_name: str
python/tools/browser_agent.py
+2 -1
@@ -148,6 +148,7 @@ class State:
148 ),
149 controller=controller,
150 enable_memory=False, # Disable memory to avoid state conflicts
151 + llm_timeout=3000, # TODO rem
152 sensitive_data=cast(dict[str, str | dict[str, str]] | None, secrets_dict or {}), # Pass secrets
153 )
154 except Exception as e:
@@ -387,7 +388,7 @@ class BrowserAgent(Tool):
388 def get_use_agent_log(use_agent: browser_use.Agent | None):
389 result = ["🚦 Starting task"]
390 if use_agent:
390 - action_results = use_agent.state.history.action_results() or []
391 + action_results = use_agent.history.action_results() or []
392 short_log = []
393 for item in action_results:
394 # final results
requirements.txt
+2 -2
@@ -1,6 +1,6 @@
1 a2wsgi==1.10.8
2 ansio==0.0.1
3 -browser-use==0.2.5
3 +browser-use==0.5.11
4 docker==7.1.0
5 duckduckgo-search==6.1.12
6 faiss-cpu==1.11.0
@@ -19,7 +19,7 @@ langchain-unstructured[all-docs]==0.1.6
19 openai-whisper==20240930
20 lxml_html_clean==0.3.1
21 markdown==3.7
22 -mcp==1.12.4
22 +mcp==1.13.1
23 newspaper3k==0.2.8
24 paramiko==3.5.0
25 playwright==1.52.0
run_ui.py
+3 -1
@@ -1,3 +1,4 @@
1 +import asyncio
2 from datetime import timedelta
3 import os
4 import secrets
@@ -8,6 +9,7 @@ import struct
9 from functools import wraps
10 import threading
11 from flask import Flask, request, Response, session, redirect, url_for, render_template_string
12 +from werkzeug.wrappers.response import Response as BaseResponse
13 import initialize
14 from python.helpers import files, git, mcp_server, fasta2a_server
15 from python.helpers.files import get_abs_path
@@ -217,7 +219,7 @@ def run():
219 name = handler.__module__.split(".")[-1]
220 instance = handler(app, lock)
221
220 - async def handler_wrap():
222 + async def handler_wrap() -> BaseResponse:
223 return await instance.handle_request(request=request)
224
225 if handler.requires_loopback():