Skip to content

Commit 5bc4978

Browse files
authored
Merge pull request #151 from vvincent1234/feat/deepseek-r1
Feat/deepseek r1
2 parents 6e55321 + 96d02b5 commit 5bc4978

8 files changed

+289
-100
lines changed

src/agent/custom_agent.py

+62-19
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ def __init__(
9595
max_actions_per_step=max_actions_per_step,
9696
tool_call_in_content=tool_call_in_content,
9797
)
98+
if self.llm.model_name in ["deepseek-reasoner"]:
99+
self.use_function_calling = False
100+
# TODO: deepseek-reasoner only support 64000 context
101+
self.max_input_tokens = 64000
102+
else:
103+
self.use_function_calling = True
98104
self.add_infos = add_infos
99105
self.agent_state = agent_state
100106
self.message_manager = CustomMassageManager(
@@ -107,6 +113,7 @@ def __init__(
107113
max_error_length=self.max_error_length,
108114
max_actions_per_step=self.max_actions_per_step,
109115
tool_call_in_content=tool_call_in_content,
116+
use_function_calling=self.use_function_calling
110117
)
111118

112119
def _setup_action_models(self) -> None:
@@ -127,7 +134,8 @@ def _log_response(self, response: CustomAgentOutput) -> None:
127134

128135
logger.info(f"{emoji} Eval: {response.current_state.prev_action_evaluation}")
129136
logger.info(f"🧠 New Memory: {response.current_state.important_contents}")
130-
logger.info(f"⏳ Task Progress: {response.current_state.completed_contents}")
137+
logger.info(f"⏳ Task Progress: \n{response.current_state.task_progress}")
138+
logger.info(f"📋 Future Plans: \n{response.current_state.future_plans}")
131139
logger.info(f"🤔 Thought: {response.current_state.thought}")
132140
logger.info(f"🎯 Summary: {response.current_state.summary}")
133141
for i, action in enumerate(response.action):
@@ -153,28 +161,54 @@ def update_step_info(
153161
):
154162
step_info.memory += important_contents + "\n"
155163

156-
completed_contents = model_output.current_state.completed_contents
157-
if completed_contents and "None" not in completed_contents:
158-
step_info.task_progress = completed_contents
164+
task_progress = model_output.current_state.task_progress
165+
if task_progress and "None" not in task_progress:
166+
step_info.task_progress = task_progress
167+
168+
future_plans = model_output.current_state.future_plans
169+
if future_plans and "None" not in future_plans:
170+
step_info.future_plans = future_plans
159171

160172
@time_execution_async("--get_next_action")
161173
async def get_next_action(self, input_messages: list[BaseMessage]) -> AgentOutput:
162174
"""Get next action from LLM based on current state"""
163-
try:
164-
structured_llm = self.llm.with_structured_output(self.AgentOutput, include_raw=True)
165-
response: dict[str, Any] = await structured_llm.ainvoke(input_messages) # type: ignore
175+
if self.use_function_calling:
176+
try:
177+
structured_llm = self.llm.with_structured_output(self.AgentOutput, include_raw=True)
178+
response: dict[str, Any] = await structured_llm.ainvoke(input_messages) # type: ignore
166179

167-
parsed: AgentOutput = response['parsed']
168-
# cut the number of actions to max_actions_per_step
169-
parsed.action = parsed.action[: self.max_actions_per_step]
170-
self._log_response(parsed)
171-
self.n_steps += 1
180+
parsed: AgentOutput = response['parsed']
181+
# cut the number of actions to max_actions_per_step
182+
parsed.action = parsed.action[: self.max_actions_per_step]
183+
self._log_response(parsed)
184+
self.n_steps += 1
172185

173-
return parsed
174-
except Exception as e:
175-
# If something goes wrong, try to invoke the LLM again without structured output,
176-
# and Manually parse the response. Temporarily solution for DeepSeek
186+
return parsed
187+
except Exception as e:
188+
# If something goes wrong, try to invoke the LLM again without structured output,
189+
# and Manually parse the response. Temporarily solution for DeepSeek
190+
ret = self.llm.invoke(input_messages)
191+
if isinstance(ret.content, list):
192+
parsed_json = json.loads(ret.content[0].replace("```json", "").replace("```", ""))
193+
else:
194+
parsed_json = json.loads(ret.content.replace("```json", "").replace("```", ""))
195+
parsed: AgentOutput = self.AgentOutput(**parsed_json)
196+
if parsed is None:
197+
raise ValueError(f'Could not parse response.')
198+
199+
# cut the number of actions to max_actions_per_step
200+
parsed.action = parsed.action[: self.max_actions_per_step]
201+
self._log_response(parsed)
202+
self.n_steps += 1
203+
204+
return parsed
205+
else:
177206
ret = self.llm.invoke(input_messages)
207+
if not self.use_function_calling:
208+
self.message_manager._add_message_with_tokens(ret)
209+
logger.info(f"🤯 Start Deep Thinking: ")
210+
logger.info(ret.reasoning_content)
211+
logger.info(f"🤯 End Deep Thinking")
178212
if isinstance(ret.content, list):
179213
parsed_json = json.loads(ret.content[0].replace("```json", "").replace("```", ""))
180214
else:
@@ -204,14 +238,22 @@ async def step(self, step_info: Optional[CustomAgentStepInfo] = None) -> None:
204238
input_messages = self.message_manager.get_messages()
205239
model_output = await self.get_next_action(input_messages)
206240
self.update_step_info(model_output, step_info)
207-
logger.info(f"🧠 All Memory: {step_info.memory}")
241+
logger.info(f"🧠 All Memory: \n{step_info.memory}")
208242
self._save_conversation(input_messages, model_output)
209-
self.message_manager._remove_last_state_message() # we dont want the whole state in the chat history
210-
self.message_manager.add_model_output(model_output)
243+
if self.use_function_calling:
244+
self.message_manager._remove_last_state_message() # we dont want the whole state in the chat history
245+
self.message_manager.add_model_output(model_output)
211246

212247
result: list[ActionResult] = await self.controller.multi_act(
213248
model_output.action, self.browser_context
214249
)
250+
if len(result) != len(model_output.action):
251+
for ri in range(len(result), len(model_output.action)):
252+
result.append(ActionResult(extracted_content=None,
253+
include_in_memory=True,
254+
error=f"{model_output.action[ri].model_dump_json(exclude_unset=True)} is Failed to execute. \
255+
Something new appeared after action {model_output.action[len(result) - 1].model_dump_json(exclude_unset=True)}",
256+
is_done=False))
215257
self._last_result = result
216258

217259
if len(result) > 0 and result[-1].is_done:
@@ -369,6 +411,7 @@ async def run(self, max_steps: int = 100) -> AgentHistoryList:
369411
max_steps=max_steps,
370412
memory="",
371413
task_progress="",
414+
future_plans=""
372415
)
373416

374417
for step in range(max_steps):

src/agent/custom_massage_manager.py

+41-44
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def __init__(
3939
max_error_length: int = 400,
4040
max_actions_per_step: int = 10,
4141
tool_call_in_content: bool = False,
42+
use_function_calling: bool = True
4243
):
4344
super().__init__(
4445
llm=llm,
@@ -53,63 +54,59 @@ def __init__(
5354
max_actions_per_step=max_actions_per_step,
5455
tool_call_in_content=tool_call_in_content,
5556
)
56-
57+
self.use_function_calling = use_function_calling
5758
# Custom: Move Task info to state_message
5859
self.history = MessageHistory()
5960
self._add_message_with_tokens(self.system_prompt)
60-
tool_calls = [
61-
{
62-
'name': 'CustomAgentOutput',
63-
'args': {
64-
'current_state': {
65-
'prev_action_evaluation': 'Unknown - No previous actions to evaluate.',
66-
'important_contents': '',
67-
'completed_contents': '',
68-
'thought': 'Now Google is open. Need to type OpenAI to search.',
69-
'summary': 'Type OpenAI to search.',
61+
62+
if self.use_function_calling:
63+
tool_calls = [
64+
{
65+
'name': 'CustomAgentOutput',
66+
'args': {
67+
'current_state': {
68+
'prev_action_evaluation': 'Unknown - No previous actions to evaluate.',
69+
'important_contents': '',
70+
'completed_contents': '',
71+
'thought': 'Now Google is open. Need to type OpenAI to search.',
72+
'summary': 'Type OpenAI to search.',
73+
},
74+
'action': [],
7075
},
71-
'action': [],
72-
},
73-
'id': '',
74-
'type': 'tool_call',
75-
}
76-
]
77-
if self.tool_call_in_content:
78-
# openai throws error if tool_calls are not responded -> move to content
79-
example_tool_call = AIMessage(
80-
content=f'{tool_calls}',
81-
tool_calls=[],
82-
)
83-
else:
84-
example_tool_call = AIMessage(
85-
content=f'',
86-
tool_calls=tool_calls,
87-
)
76+
'id': '',
77+
'type': 'tool_call',
78+
}
79+
]
80+
if self.tool_call_in_content:
81+
# openai throws error if tool_calls are not responded -> move to content
82+
example_tool_call = AIMessage(
83+
content=f'{tool_calls}',
84+
tool_calls=[],
85+
)
86+
else:
87+
example_tool_call = AIMessage(
88+
content=f'',
89+
tool_calls=tool_calls,
90+
)
8891

89-
self._add_message_with_tokens(example_tool_call)
92+
self._add_message_with_tokens(example_tool_call)
9093

94+
def cut_messages(self):
95+
"""Get current message list, potentially trimmed to max tokens"""
96+
diff = self.history.total_tokens - self.max_input_tokens
97+
i = 1 # start from 1 to keep system message in history
98+
while diff > 0 and i < len(self.history.messages):
99+
self.history.remove_message(i)
100+
diff = self.history.total_tokens - self.max_input_tokens
101+
i += 1
102+
91103
def add_state_message(
92104
self,
93105
state: BrowserState,
94106
result: Optional[List[ActionResult]] = None,
95107
step_info: Optional[AgentStepInfo] = None,
96108
) -> None:
97109
"""Add browser state as human message"""
98-
99-
# if keep in memory, add to directly to history and add state without result
100-
if result:
101-
for r in result:
102-
if r.include_in_memory:
103-
if r.extracted_content:
104-
msg = HumanMessage(content=str(r.extracted_content))
105-
self._add_message_with_tokens(msg)
106-
if r.error:
107-
msg = HumanMessage(
108-
content=str(r.error)[-self.max_error_length:]
109-
)
110-
self._add_message_with_tokens(msg)
111-
result = None # if result in history, we dont want to add it again
112-
113110
# otherwise add state message and result to next message (which will not stay in memory)
114111
state_message = CustomAgentMessagePrompt(
115112
state,

src/agent/custom_prompts.py

+29-21
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# @Author : wenshao
44
# @ProjectName: browser-use-webui
55
# @FileName: custom_prompts.py
6-
6+
import pdb
77
from typing import List, Optional
88

99
from browser_use.agent.prompts import SystemPrompt
@@ -25,8 +25,9 @@ def important_rules(self) -> str:
2525
"current_state": {
2626
"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.",
2727
"important_contents": "Output important contents closely related to user\'s instruction or task on the current page. If there is, please output the contents. If not, please output empty string ''.",
28-
"completed_contents": "Update the input Task Progress. Completed contents is a general summary of the current contents that have been completed. Just summarize the contents that have been actually completed based on the current page and the history operations. Please list each completed item individually, such as: 1. Input username. 2. Input Password. 3. Click confirm button",
29-
"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 the output of prev_action_evaluation is 'Failed', please reflect and output your reflection here. If you think you have entered the wrong page, consider to go back to the previous page in next action.",
28+
"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.",
29+
"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.",
30+
"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.",
3031
"summary": "Please generate a brief natural language description for the operation in next actions based on your Thought."
3132
},
3233
"action": [
@@ -70,6 +71,7 @@ def important_rules(self) -> str:
7071
- Don't hallucinate actions.
7172
- If the task requires specific information - make sure to include everything in the done function. This is what the user will see.
7273
- If you are running out of steps (current step), think about speeding it up, and ALWAYS use the done action as the last action.
74+
- 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.
7375
7476
6. VISUAL CONTEXT:
7577
- When an image is provided, use it to understand the page layout
@@ -100,10 +102,9 @@ def input_format(self) -> str:
100102
1. Task: The user\'s instructions you need to complete.
101103
2. Hints(Optional): Some hints to help you complete the user\'s instructions.
102104
3. Memory: Important contents are recorded during historical operations for use in subsequent operations.
103-
4. Task Progress: Up to the current page, the content you have completed can be understood as the progress of the task.
104-
5. Current URL: The webpage you're currently on
105-
6. Available Tabs: List of open browser tabs
106-
7. Interactive Elements: List in the format:
105+
4. Current URL: The webpage you're currently on
106+
5. Available Tabs: List of open browser tabs
107+
6. Interactive Elements: List in the format:
107108
index[:]<element_type>element_text</element_type>
108109
- index: Numeric identifier for interaction
109110
- element_type: HTML element type (button, input, etc.)
@@ -162,20 +163,27 @@ def __init__(
162163
self.step_info = step_info
163164

164165
def get_user_message(self) -> HumanMessage:
166+
if self.step_info:
167+
step_info_description = f'Current step: {self.step_info.step_number + 1}/{self.step_info.max_steps}'
168+
else:
169+
step_info_description = ''
170+
171+
elements_text = self.state.element_tree.clickable_elements_to_string(include_attributes=self.include_attributes)
172+
if not elements_text:
173+
elements_text = 'empty page'
165174
state_description = f"""
166-
1. Task: {self.step_info.task}
167-
2. Hints(Optional):
168-
{self.step_info.add_infos}
169-
3. Memory:
170-
{self.step_info.memory}
171-
4. Task Progress:
172-
{self.step_info.task_progress}
173-
5. Current url: {self.state.url}
174-
6. Available tabs:
175-
{self.state.tabs}
176-
7. Interactive elements:
177-
{self.state.element_tree.clickable_elements_to_string(include_attributes=self.include_attributes)}
178-
"""
175+
{step_info_description}
176+
1. Task: {self.step_info.task}
177+
2. Hints(Optional):
178+
{self.step_info.add_infos}
179+
3. Memory:
180+
{self.step_info.memory}
181+
4. Current url: {self.state.url}
182+
5. Available tabs:
183+
{self.state.tabs}
184+
6. Interactive elements:
185+
{elements_text}
186+
"""
179187

180188
if self.result:
181189
for i, result in enumerate(self.result):
@@ -202,4 +210,4 @@ def get_user_message(self) -> HumanMessage:
202210
]
203211
)
204212

205-
return HumanMessage(content=state_description)
213+
return HumanMessage(content=state_description)

src/agent/custom_views.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,16 @@ class CustomAgentStepInfo:
2020
add_infos: str
2121
memory: str
2222
task_progress: str
23+
future_plans: str
2324

2425

2526
class CustomAgentBrain(BaseModel):
2627
"""Current state of the agent"""
2728

2829
prev_action_evaluation: str
2930
important_contents: str
30-
completed_contents: str
31+
task_progress: str
32+
future_plans: str
3133
thought: str
3234
summary: str
3335

0 commit comments

Comments
 (0)