Skip to content

Commit e8ef234

Browse files
authored
Merge pull request #413 from vvincent1234/feat/update_bu
Feat/update bu
2 parents 33ca5e0 + 1f878fd commit e8ef234

15 files changed

+682
-795
lines changed

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ MOONSHOT_ENDPOINT=https://api.moonshot.cn/v1
2525
MOONSHOT_API_KEY=
2626

2727
# Set to false to disable anonymized telemetry
28-
ANONYMIZED_TELEMETRY=true
28+
ANONYMIZED_TELEMETRY=false
2929

3030
# LogLevel: Set to debug to enable verbose logging, set to result to get results only. Available: result | debug | info
3131
BROWSER_USE_LOGGING_LEVEL=info

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
browser-use==0.1.37
1+
browser-use==0.1.40
22
pyperclip==1.9.0
33
gradio==5.10.0
44
json-repair

src/agent/custom_agent.py

Lines changed: 175 additions & 288 deletions
Large diffs are not rendered by default.

src/agent/custom_message_manager.py

Lines changed: 48 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@
88
from browser_use.agent.prompts import SystemPrompt, AgentMessagePrompt
99
from browser_use.agent.views import ActionResult, AgentStepInfo, ActionModel
1010
from browser_use.browser.views import BrowserState
11+
from browser_use.agent.message_manager.service import MessageManagerSettings
12+
from browser_use.agent.views import ActionResult, AgentOutput, AgentStepInfo, MessageManagerState
1113
from langchain_core.language_models import BaseChatModel
1214
from langchain_anthropic import ChatAnthropic
1315
from langchain_core.language_models import BaseChatModel
1416
from langchain_core.messages import (
15-
AIMessage,
16-
BaseMessage,
17-
HumanMessage,
18-
ToolMessage
17+
AIMessage,
18+
BaseMessage,
19+
HumanMessage,
20+
ToolMessage,
21+
SystemMessage
1922
)
2023
from langchain_openai import ChatOpenAI
2124
from ..utils.llm import DeepSeekR1ChatOpenAI
@@ -24,55 +27,55 @@
2427
logger = logging.getLogger(__name__)
2528

2629

30+
class CustomMessageManagerSettings(MessageManagerSettings):
31+
agent_prompt_class: Type[AgentMessagePrompt] = AgentMessagePrompt
32+
33+
2734
class CustomMessageManager(MessageManager):
2835
def __init__(
2936
self,
30-
llm: BaseChatModel,
3137
task: str,
32-
action_descriptions: str,
33-
system_prompt_class: Type[SystemPrompt],
34-
agent_prompt_class: Type[AgentMessagePrompt],
35-
max_input_tokens: int = 128000,
36-
estimated_characters_per_token: int = 3,
37-
image_tokens: int = 800,
38-
include_attributes: list[str] = [],
39-
max_error_length: int = 400,
40-
max_actions_per_step: int = 10,
41-
message_context: Optional[str] = None,
42-
sensitive_data: Optional[Dict[str, str]] = None,
38+
system_message: SystemMessage,
39+
settings: MessageManagerSettings = MessageManagerSettings(),
40+
state: MessageManagerState = MessageManagerState(),
4341
):
4442
super().__init__(
45-
llm=llm,
4643
task=task,
47-
action_descriptions=action_descriptions,
48-
system_prompt_class=system_prompt_class,
49-
max_input_tokens=max_input_tokens,
50-
estimated_characters_per_token=estimated_characters_per_token,
51-
image_tokens=image_tokens,
52-
include_attributes=include_attributes,
53-
max_error_length=max_error_length,
54-
max_actions_per_step=max_actions_per_step,
55-
message_context=message_context,
56-
sensitive_data=sensitive_data
44+
system_message=system_message,
45+
settings=settings,
46+
state=state
5747
)
58-
self.agent_prompt_class = agent_prompt_class
59-
# Custom: Move Task info to state_message
60-
self.history = MessageHistory()
48+
49+
def _init_messages(self) -> None:
50+
"""Initialize the message history with system message, context, task, and other initial messages"""
6151
self._add_message_with_tokens(self.system_prompt)
62-
63-
if self.message_context:
64-
context_message = HumanMessage(content=self.message_context)
52+
self.context_content = ""
53+
54+
if self.settings.message_context:
55+
self.context_content += 'Context for the task' + self.settings.message_context
56+
57+
if self.settings.sensitive_data:
58+
info = f'Here are placeholders for sensitive data: {list(self.settings.sensitive_data.keys())}'
59+
info += 'To use them, write <secret>the placeholder name</secret>'
60+
self.context_content += info
61+
62+
if self.settings.available_file_paths:
63+
filepaths_msg = f'Here are file paths you can use: {self.settings.available_file_paths}'
64+
self.context_content += filepaths_msg
65+
66+
if self.context_content:
67+
context_message = HumanMessage(content=self.context_content)
6568
self._add_message_with_tokens(context_message)
6669

6770
def cut_messages(self):
6871
"""Get current message list, potentially trimmed to max tokens"""
69-
diff = self.history.total_tokens - self.max_input_tokens
70-
min_message_len = 2 if self.message_context is not None else 1
71-
72-
while diff > 0 and len(self.history.messages) > min_message_len:
73-
self.history.remove_message(min_message_len) # always remove the oldest message
74-
diff = self.history.total_tokens - self.max_input_tokens
75-
72+
diff = self.state.history.current_tokens - self.settings.max_input_tokens
73+
min_message_len = 2 if self.context_content is not None else 1
74+
75+
while diff > 0 and len(self.state.history.messages) > min_message_len:
76+
self.state.history.remove_message(min_message_len) # always remove the oldest message
77+
diff = self.state.history.current_tokens - self.settings.max_input_tokens
78+
7679
def add_state_message(
7780
self,
7881
state: BrowserState,
@@ -83,38 +86,23 @@ def add_state_message(
8386
) -> None:
8487
"""Add browser state as human message"""
8588
# otherwise add state message and result to next message (which will not stay in memory)
86-
state_message = self.agent_prompt_class(
89+
state_message = self.settings.agent_prompt_class(
8790
state,
8891
actions,
8992
result,
90-
include_attributes=self.include_attributes,
91-
max_error_length=self.max_error_length,
93+
include_attributes=self.settings.include_attributes,
9294
step_info=step_info,
9395
).get_user_message(use_vision)
9496
self._add_message_with_tokens(state_message)
95-
96-
def _count_text_tokens(self, text: str) -> int:
97-
if isinstance(self.llm, (ChatOpenAI, ChatAnthropic, DeepSeekR1ChatOpenAI)):
98-
try:
99-
tokens = self.llm.get_num_tokens(text)
100-
except Exception:
101-
tokens = (
102-
len(text) // self.estimated_characters_per_token
103-
) # Rough estimate if no tokenizer available
104-
else:
105-
tokens = (
106-
len(text) // self.estimated_characters_per_token
107-
) # Rough estimate if no tokenizer available
108-
return tokens
10997

11098
def _remove_state_message_by_index(self, remove_ind=-1) -> None:
11199
"""Remove last state message from history"""
112-
i = len(self.history.messages) - 1
100+
i = len(self.state.history.messages) - 1
113101
remove_cnt = 0
114102
while i >= 0:
115-
if isinstance(self.history.messages[i].message, HumanMessage):
103+
if isinstance(self.state.history.messages[i].message, HumanMessage):
116104
remove_cnt += 1
117105
if remove_cnt == abs(remove_ind):
118-
self.history.remove_message(i)
106+
self.state.history.messages.pop(i)
119107
break
120108
i -= 1

src/agent/custom_prompts.py

Lines changed: 16 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -6,113 +6,20 @@
66
from browser_use.browser.views import BrowserState
77
from langchain_core.messages import HumanMessage, SystemMessage
88
from datetime import datetime
9+
import importlib
910

1011
from .custom_views import CustomAgentStepInfo
1112

1213

1314
class CustomSystemPrompt(SystemPrompt):
14-
def important_rules(self) -> str:
15-
"""
16-
Returns the important rules for the agent.
17-
"""
18-
text = r"""
19-
1. RESPONSE FORMAT: You must ALWAYS respond with valid JSON in this exact format:
20-
{
21-
"current_state": {
22-
"prev_action_evaluation": "Success|Failed|Unknown - Analyze the current elements and the image to check if the previous goals/actions are successful like intended by the task. Ignore the action result. The website is the ground truth. Also mention if something unexpected happened like new suggestions in an input field. Shortly state why/why not. Note that the result you output must be consistent with the reasoning you output afterwards. If you consider it to be 'Failed,' you should reflect on this during your thought.",
23-
"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 ''.",
24-
"task_progress": "Task Progress is a general summary of the current contents that have been completed. Just summarize the contents that have been actually completed based on the content at current step and the history operations. Please list each completed item individually, such as: 1. Input username. 2. Input Password. 3. Click confirm button. Please return string type not a list.",
25-
"future_plans": "Based on the user's request and the current state, outline the remaining steps needed to complete the task. This should be a concise list of actions yet to be performed, such as: 1. Select a date. 2. Choose a specific time slot. 3. Confirm booking. Please return string type not a list.",
26-
"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 prev_action_evaluation is 'Failed', please reflect and output your reflection here.",
27-
"summary": "Please generate a brief natural language description for the operation in next actions based on your Thought."
28-
},
29-
"action": [
30-
* actions in sequences, please refer to **Common action sequences**. Each output action MUST be formated as: \{action_name\: action_params\}*
31-
]
32-
}
33-
34-
2. ACTIONS: You can specify multiple actions to be executed in sequence.
35-
36-
Common action sequences:
37-
- Form filling: [
38-
{"input_text": {"index": 1, "text": "username"}},
39-
{"input_text": {"index": 2, "text": "password"}},
40-
{"click_element": {"index": 3}}
41-
]
42-
- Navigation and extraction: [
43-
{"go_to_url": {"url": "https://example.com"}},
44-
{"extract_page_content": {}}
45-
]
46-
47-
48-
3. ELEMENT INTERACTION:
49-
- Only use indexes that exist in the provided element list
50-
- Each element has a unique index number (e.g., "33[:]<button>")
51-
- Elements marked with "_[:]" are non-interactive (for context only)
52-
53-
4. NAVIGATION & ERROR HANDLING:
54-
- If no suitable elements exist, use other functions to complete the task
55-
- If stuck, try alternative approaches
56-
- Handle popups/cookies by accepting or closing them
57-
- Use scroll to find elements you are looking for
58-
59-
5. TASK COMPLETION:
60-
- If you think all the requirements of user\'s instruction have been completed and no further operation is required, output the **Done** action to terminate the operation process.
61-
- Don't hallucinate actions.
62-
- If the task requires specific information - make sure to include everything in the done function. This is what the user will see.
63-
- If you are running out of steps (current step), think about speeding it up, and ALWAYS use the done action as the last action.
64-
- Note that you must verify if you've truly fulfilled the user's request by examining the actual page content, not just by looking at the actions you output but also whether the action is executed successfully. Pay particular attention when errors occur during action execution.
65-
66-
6. VISUAL CONTEXT:
67-
- When an image is provided, use it to understand the page layout
68-
- Bounding boxes with labels correspond to element indexes
69-
- Each bounding box and its label have the same color
70-
- Most often the label is inside the bounding box, on the top right
71-
- Visual context helps verify element locations and relationships
72-
- sometimes labels overlap, so use the context to verify the correct element
73-
74-
7. Form filling:
75-
- If you fill an input field and your action sequence is interrupted, most often a list with suggestions poped up under the field and you need to first select the right element from the suggestion list.
76-
77-
8. ACTION SEQUENCING:
78-
- Actions are executed in the order they appear in the list
79-
- Each action should logically follow from the previous one
80-
- If the page changes after an action, the sequence is interrupted and you get the new state.
81-
- If content only disappears the sequence continues.
82-
- Only provide the action sequence until you think the page will change.
83-
- Try to be efficient, e.g. fill forms at once, or chain actions where nothing changes on the page like saving, extracting, checkboxes...
84-
- only use multiple actions if it makes sense.
85-
86-
9. Extraction:
87-
- If your task is to find information or do research - call extract_content on the specific pages to get and store the information.
88-
89-
"""
90-
text += f" - use maximum {self.max_actions_per_step} actions per sequence"
91-
return text
92-
93-
def input_format(self) -> str:
94-
return """
95-
INPUT STRUCTURE:
96-
1. Task: The user\'s instructions you need to complete.
97-
2. Hints(Optional): Some hints to help you complete the user\'s instructions.
98-
3. Memory: Important contents are recorded during historical operations for use in subsequent operations.
99-
4. Current URL: The webpage you're currently on
100-
5. Available Tabs: List of open browser tabs
101-
6. Interactive Elements: List in the format:
102-
[index]<element_type>element_text</element_type>
103-
- index: Numeric identifier for interaction
104-
- element_type: HTML element type (button, input, etc.)
105-
- element_text: Visible text or element description
106-
107-
Example:
108-
[33]<button>Submit Form</button>
109-
[] Non-interactive text
110-
111-
112-
Notes:
113-
- Only elements with numeric indexes inside [] are interactive
114-
- [] elements provide context but cannot be interacted with
115-
"""
15+
def _load_prompt_template(self) -> None:
16+
"""Load the prompt template from the markdown file."""
17+
try:
18+
# This works both in development and when installed as a package
19+
with importlib.resources.files('src.agent').joinpath('custom_system_prompt.md').open('r') as f:
20+
self.prompt_template = f.read()
21+
except Exception as e:
22+
raise RuntimeError(f'Failed to load system prompt template: {e}')
11623

11724

11825
class CustomAgentMessagePrompt(AgentMessagePrompt):
@@ -122,13 +29,11 @@ def __init__(
12229
actions: Optional[List[ActionModel]] = None,
12330
result: Optional[List[ActionResult]] = None,
12431
include_attributes: list[str] = [],
125-
max_error_length: int = 400,
12632
step_info: Optional[CustomAgentStepInfo] = None,
12733
):
12834
super(CustomAgentMessagePrompt, self).__init__(state=state,
12935
result=result,
13036
include_attributes=include_attributes,
131-
max_error_length=max_error_length,
13237
step_info=step_info
13338
)
13439
self.actions = actions
@@ -179,19 +84,19 @@ def get_user_message(self, use_vision: bool = True) -> HumanMessage:
17984

18085
if self.actions and self.result:
18186
state_description += "\n **Previous Actions** \n"
182-
state_description += f'Previous step: {self.step_info.step_number-1}/{self.step_info.max_steps} \n'
87+
state_description += f'Previous step: {self.step_info.step_number - 1}/{self.step_info.max_steps} \n'
18388
for i, result in enumerate(self.result):
18489
action = self.actions[i]
18590
state_description += f"Previous action {i + 1}/{len(self.result)}: {action.model_dump_json(exclude_unset=True)}\n"
91+
if result.error:
92+
# only use last 300 characters of error
93+
error = result.error.split('\n')[-1]
94+
state_description += (
95+
f"Error of previous action {i + 1}/{len(self.result)}: ...{error}\n"
96+
)
18697
if result.include_in_memory:
18798
if result.extracted_content:
18899
state_description += f"Result of previous action {i + 1}/{len(self.result)}: {result.extracted_content}\n"
189-
if result.error:
190-
# only use last 300 characters of error
191-
error = result.error[-self.max_error_length:]
192-
state_description += (
193-
f"Error of previous action {i + 1}/{len(self.result)}: ...{error}\n"
194-
)
195100

196101
if self.state.screenshot and use_vision == True:
197102
# Format message for vision model

0 commit comments

Comments
 (0)