Skip to content

Commit ebf9a06

Browse files
authored
Merge branch 'browser-use:main' into main
2 parents 2953098 + f4f36b4 commit ebf9a06

10 files changed

+254
-236
lines changed

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
browser-use==0.1.40
22
pyperclip==1.9.0
3-
gradio==5.10.0
3+
gradio==5.23.1
44
json-repair
55
langchain-mistralai==0.2.4
66
langchain-google-genai==2.0.8

src/agent/custom_agent.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,8 @@ def update_step_info(
208208
@time_execution_async("--get_next_action")
209209
async def get_next_action(self, input_messages: list[BaseMessage]) -> AgentOutput:
210210
"""Get next action from LLM based on current state"""
211-
212-
ai_message = self.llm.invoke(input_messages)
211+
fixed_input_messages = self._convert_input_messages(input_messages)
212+
ai_message = self.llm.invoke(fixed_input_messages)
213213
self.message_manager._add_message_with_tokens(ai_message)
214214

215215
if hasattr(ai_message, "reasoning_content"):
@@ -222,10 +222,16 @@ async def get_next_action(self, input_messages: list[BaseMessage]) -> AgentOutpu
222222
else:
223223
ai_content = ai_message.content
224224

225-
ai_content = ai_content.replace("```json", "").replace("```", "")
226-
ai_content = repair_json(ai_content)
227-
parsed_json = json.loads(ai_content)
228-
parsed: AgentOutput = self.AgentOutput(**parsed_json)
225+
try:
226+
ai_content = ai_content.replace("```json", "").replace("```", "")
227+
ai_content = repair_json(ai_content)
228+
parsed_json = json.loads(ai_content)
229+
parsed: AgentOutput = self.AgentOutput(**parsed_json)
230+
except Exception as e:
231+
import traceback
232+
traceback.print_exc()
233+
logger.debug(ai_message.content)
234+
raise ValueError('Could not parse response.')
229235

230236
if parsed is None:
231237
logger.debug(ai_message.content)

src/agent/custom_message_manager.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import logging
4+
import pdb
45
from typing import List, Optional, Type, Dict
56

67
from browser_use.agent.message_manager.service import MessageManager
@@ -96,7 +97,7 @@ def add_state_message(
9697
self._add_message_with_tokens(state_message)
9798

9899
def _remove_state_message_by_index(self, remove_ind=-1) -> None:
99-
"""Remove last state message from history"""
100+
"""Remove state message by index from history"""
100101
i = len(self.state.history.messages) - 1
101102
remove_cnt = 0
102103
while i >= 0:

src/agent/custom_system_prompt.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,17 @@ Example:
1818

1919
# Response Rules
2020
1. RESPONSE FORMAT: You must ALWAYS respond with valid JSON in this exact format:
21-
{{"current_state": {{"evaluation_previous_goal": "Success|Failed|Unknown - Analyze the current elements and the image to check if the previous goals/actions are successful like intended by the task. Mention if something unexpected happened. Shortly state why/why not.",
22-
"important_contents": "Output important contents closely related to user's instruction on the current page. If there is, please output the contents. If not, please output ''.",
23-
"thought": "Think about the requirements that have been completed in previous operations and the requirements that need to be completed in the next one operation. If your output of evaluation_previous_goal is 'Failed', please reflect and output your reflection here.",
24-
"next_goal": "Please generate a brief natural language description for the goal of your next actions based on your thought."}},
25-
"action":[{{"one_action_name": {{// action-specific parameter}}}}, // ... more actions in sequence]}}
21+
{{
22+
"current_state": {{
23+
"evaluation_previous_goal": "Success|Failed|Unknown - Analyze the current elements and the image to check if the previous goals/actions are successful like intended by the task. Mention if something unexpected happened. Shortly state why/why not.",
24+
"important_contents": "Output important contents closely related to user\'s instruction on the current page. If there is, please output the contents. If not, please output empty string ''.",
25+
"thought": "Think about the requirements that have been completed in previous operations and the requirements that need to be completed in the next one operation. If your output of evaluation_previous_goal is 'Failed', please reflect and output your reflection here.",
26+
"next_goal": "Please generate a brief natural language description for the goal of your next actions based on your thought."
27+
}},
28+
"action": [
29+
{{"one_action_name": {{// action-specific parameter}}}}, // ... more actions in sequence
30+
]
31+
}}
2632

2733
2. ACTIONS: You can specify multiple actions in the list to be executed in sequence. But always specify only one action name per item. Use maximum {{max_actions}} actions per sequence.
2834
Common action sequences:

src/utils/agent_state.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22

3+
34
class AgentState:
45
_instance = None
56

@@ -27,4 +28,4 @@ def set_last_valid_state(self, state):
2728
self.last_valid_state = state
2829

2930
def get_last_valid_state(self):
30-
return self.last_valid_state
31+
return self.last_valid_state

src/utils/deep_research.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@
1919
from browser_use.browser.context import BrowserContext
2020
from browser_use.controller.service import Controller, DoneAction
2121
from main_content_extractor import MainContentExtractor
22-
from langchain.schema import SystemMessage, HumanMessage
22+
from langchain_core.messages import (
23+
AIMessage,
24+
BaseMessage,
25+
HumanMessage,
26+
ToolMessage,
27+
SystemMessage
28+
)
2329
from json_repair import repair_json
2430
from src.agent.custom_prompts import CustomSystemPrompt, CustomAgentMessagePrompt
2531
from src.controller.custom_controller import CustomController

src/utils/default_config_settings.py

Lines changed: 0 additions & 125 deletions
This file was deleted.

src/utils/utils.py

Lines changed: 80 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
from pathlib import Path
55
from typing import Dict, Optional
66
import requests
7+
import json
8+
import gradio as gr
9+
import uuid
710

811
from langchain_anthropic import ChatAnthropic
912
from langchain_mistralai import ChatMistralAI
1013
from langchain_google_genai import ChatGoogleGenerativeAI
1114
from langchain_ollama import ChatOllama
1215
from langchain_openai import AzureChatOpenAI, ChatOpenAI
13-
import gradio as gr
1416

1517
from .llm import DeepSeekR1ChatOpenAI, DeepSeekR1ChatOllama, UnboundChatOpenAI
1618

@@ -37,7 +39,7 @@ def get_llm_model(provider: str, **kwargs):
3739
env_var = f"{provider.upper()}_API_KEY"
3840
api_key = kwargs.get("api_key", "") or os.getenv(env_var, "")
3941
if not api_key:
40-
handle_api_key_error(provider, env_var)
42+
raise MissingAPIKeyError(provider, env_var)
4143
kwargs["api_key"] = api_key
4244

4345
if provider == "anthropic":
@@ -185,7 +187,7 @@ def get_llm_model(provider: str, **kwargs):
185187
"ollama": ["qwen2.5:7b", "qwen2.5:14b", "qwen2.5:32b", "qwen2.5-coder:14b", "qwen2.5-coder:32b", "llama2:7b",
186188
"deepseek-r1:14b", "deepseek-r1:32b"],
187189
"azure_openai": ["gpt-4o", "gpt-4", "gpt-3.5-turbo"],
188-
"mistral": ["mixtral-large-latest", "mistral-large-latest", "mistral-small-latest", "ministral-8b-latest"],
190+
"mistral": ["pixtral-large-latest", "mistral-large-latest", "mistral-small-latest", "ministral-8b-latest"],
189191
"alibaba": ["qwen-plus", "qwen-max", "qwen-turbo", "qwen-long"],
190192
"moonshot": ["moonshot-v1-32k-vision-preview", "moonshot-v1-8k-vision-preview"],
191193
"unbound": ["gemini-2.0-flash","gpt-4o-mini", "gpt-4o", "gpt-4.5-preview"]
@@ -197,6 +199,7 @@ def update_model_dropdown(llm_provider, api_key=None, base_url=None):
197199
"""
198200
Update the model name dropdown with predefined models for the selected provider.
199201
"""
202+
import gradio as gr
200203
# Use API keys from .env if not provided
201204
if not api_key:
202205
api_key = os.getenv(f"{llm_provider.upper()}_API_KEY", "")
@@ -210,15 +213,13 @@ def update_model_dropdown(llm_provider, api_key=None, base_url=None):
210213
return gr.Dropdown(choices=[], value="", interactive=True, allow_custom_value=True)
211214

212215

213-
def handle_api_key_error(provider: str, env_var: str):
214-
"""
215-
Handles the missing API key error by raising a gr.Error with a clear message.
216-
"""
217-
provider_display = PROVIDER_DISPLAY_NAMES.get(provider, provider.upper())
218-
raise gr.Error(
219-
f"💥 {provider_display} API key not found! 🔑 Please set the "
220-
f"`{env_var}` environment variable or provide it in the UI."
221-
)
216+
class MissingAPIKeyError(Exception):
217+
"""Custom exception for missing API key."""
218+
219+
def __init__(self, provider: str, env_var: str):
220+
provider_display = PROVIDER_DISPLAY_NAMES.get(provider, provider.upper())
221+
super().__init__(f"💥 {provider_display} API key not found! 🔑 Please set the "
222+
f"`{env_var}` environment variable or provide it in the UI.")
222223

223224

224225
def encode_image(img_path):
@@ -287,3 +288,70 @@ async def capture_screenshot(browser_context):
287288
return encoded
288289
except Exception as e:
289290
return None
291+
292+
293+
class ConfigManager:
294+
def __init__(self):
295+
self.components = {}
296+
self.component_order = []
297+
298+
def register_component(self, name: str, component):
299+
"""Register a gradio component for config management."""
300+
self.components[name] = component
301+
if name not in self.component_order:
302+
self.component_order.append(name)
303+
return component
304+
305+
def save_current_config(self):
306+
"""Save the current configuration of all registered components."""
307+
current_config = {}
308+
for name in self.component_order:
309+
component = self.components[name]
310+
# Get the current value from the component
311+
current_config[name] = getattr(component, "value", None)
312+
313+
return save_config_to_file(current_config)
314+
315+
def update_ui_from_config(self, config_file):
316+
"""Update UI components from a loaded configuration file."""
317+
if config_file is None:
318+
return [gr.update() for _ in self.component_order] + ["No file selected."]
319+
320+
loaded_config = load_config_from_file(config_file.name)
321+
322+
if not isinstance(loaded_config, dict):
323+
return [gr.update() for _ in self.component_order] + ["Error: Invalid configuration file."]
324+
325+
# Prepare updates for all components
326+
updates = []
327+
for name in self.component_order:
328+
if name in loaded_config:
329+
updates.append(gr.update(value=loaded_config[name]))
330+
else:
331+
updates.append(gr.update())
332+
333+
updates.append("Configuration loaded successfully.")
334+
return updates
335+
336+
def get_all_components(self):
337+
"""Return all registered components in the order they were registered."""
338+
return [self.components[name] for name in self.component_order]
339+
340+
341+
def load_config_from_file(config_file):
342+
"""Load settings from a config file (JSON format)."""
343+
try:
344+
with open(config_file, 'r') as f:
345+
settings = json.load(f)
346+
return settings
347+
except Exception as e:
348+
return f"Error loading configuration: {str(e)}"
349+
350+
351+
def save_config_to_file(settings, save_dir="./tmp/webui_settings"):
352+
"""Save the current settings to a UUID.json file with a UUID name."""
353+
os.makedirs(save_dir, exist_ok=True)
354+
config_file = os.path.join(save_dir, f"{uuid.uuid4()}.json")
355+
with open(config_file, 'w') as f:
356+
json.dump(settings, f, indent=2)
357+
return f"Configuration saved to {config_file}"

tests/test_browser_use.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,11 @@ async def test_browser_use_custom():
133133
api_key=os.getenv("GOOGLE_API_KEY", "")
134134
)
135135

136-
# llm = utils.get_llm_model(
137-
# provider="deepseek",
138-
# model_name="deepseek-reasoner",
139-
# temperature=0.8
140-
# )
136+
llm = utils.get_llm_model(
137+
provider="deepseek",
138+
model_name="deepseek-reasoner",
139+
temperature=0.8
140+
)
141141

142142
# llm = utils.get_llm_model(
143143
# provider="deepseek",

0 commit comments

Comments
 (0)