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():