Leon4gr45 commited on
Commit
edefb0b
Β·
verified Β·
1 Parent(s): cceb842

Update src/agent/custom_agent.py

Browse files
Files changed (1) hide show
  1. src/agent/custom_agent.py +268 -268
src/agent/custom_agent.py CHANGED
@@ -1,268 +1,268 @@
1
- # -*- coding: utf-8 -*-
2
- # @Time : 2025/1/2
3
- # @Author : wenshao
4
- # @ProjectName: browser-use-webui
5
- # @FileName: custom_agent.py
6
-
7
- import asyncio
8
- import base64
9
- import io
10
- import json
11
- import logging
12
- import os
13
- import pdb
14
- import textwrap
15
- import time
16
- import uuid
17
- from io import BytesIO
18
- from pathlib import Path
19
- from typing import Any, Optional, Type, TypeVar
20
-
21
- from dotenv import load_dotenv
22
- from langchain_core.language_models.chat_models import BaseChatModel
23
- from langchain_core.messages import (
24
- BaseMessage,
25
- SystemMessage,
26
- )
27
- from openai import RateLimitError
28
- from PIL import Image, ImageDraw, ImageFont
29
- from pydantic import BaseModel, ValidationError
30
-
31
- from browser_use.agent.message_manager.service import MessageManager
32
- from browser_use.agent.prompts import AgentMessagePrompt, SystemPrompt
33
- from browser_use.agent.service import Agent
34
- from browser_use.agent.views import (
35
- ActionResult,
36
- AgentError,
37
- AgentHistory,
38
- AgentHistoryList,
39
- AgentOutput,
40
- AgentStepInfo,
41
- )
42
- from browser_use.browser.browser import Browser
43
- from browser_use.browser.context import BrowserContext
44
- from browser_use.browser.views import BrowserState, BrowserStateHistory
45
- from browser_use.controller.registry.views import ActionModel
46
- from browser_use.controller.service import Controller
47
- from browser_use.dom.history_tree_processor.service import (
48
- DOMHistoryElement,
49
- HistoryTreeProcessor,
50
- )
51
- from browser_use.telemetry.service import ProductTelemetry
52
- from browser_use.telemetry.views import (
53
- AgentEndTelemetryEvent,
54
- AgentRunTelemetryEvent,
55
- AgentStepErrorTelemetryEvent,
56
- )
57
- from browser_use.utils import time_execution_async
58
-
59
- from .custom_views import CustomAgentOutput, CustomAgentStepInfo
60
- from .custom_massage_manager import CustomMassageManager
61
-
62
- logger = logging.getLogger(__name__)
63
-
64
-
65
- class CustomAgent(Agent):
66
-
67
- def __init__(
68
- self,
69
- task: str,
70
- llm: BaseChatModel,
71
- add_infos: str = '',
72
- browser: Browser | None = None,
73
- browser_context: BrowserContext | None = None,
74
- controller: Controller = Controller(),
75
- use_vision: bool = True,
76
- save_conversation_path: Optional[str] = None,
77
- max_failures: int = 5,
78
- retry_delay: int = 10,
79
- system_prompt_class: Type[SystemPrompt] = SystemPrompt,
80
- max_input_tokens: int = 128000,
81
- validate_output: bool = False,
82
- include_attributes: list[str] = [
83
- 'title',
84
- 'type',
85
- 'name',
86
- 'role',
87
- 'tabindex',
88
- 'aria-label',
89
- 'placeholder',
90
- 'value',
91
- 'alt',
92
- 'aria-expanded',
93
- ],
94
- max_error_length: int = 400,
95
- max_actions_per_step: int = 10,
96
- ):
97
- super().__init__(task, llm, browser, browser_context, controller, use_vision, save_conversation_path,
98
- max_failures, retry_delay, system_prompt_class, max_input_tokens, validate_output,
99
- include_attributes, max_error_length, max_actions_per_step)
100
- self.add_infos = add_infos
101
- self.message_manager = CustomMassageManager(
102
- llm=self.llm,
103
- task=self.task,
104
- action_descriptions=self.controller.registry.get_prompt_description(),
105
- system_prompt_class=self.system_prompt_class,
106
- max_input_tokens=self.max_input_tokens,
107
- include_attributes=self.include_attributes,
108
- max_error_length=self.max_error_length,
109
- max_actions_per_step=self.max_actions_per_step,
110
- )
111
-
112
- def _setup_action_models(self) -> None:
113
- """Setup dynamic action models from controller's registry"""
114
- # Get the dynamic action model from controller's registry
115
- self.ActionModel = self.controller.registry.create_action_model()
116
- # Create output model with the dynamic actions
117
- self.AgentOutput = CustomAgentOutput.type_with_custom_actions(self.ActionModel)
118
-
119
- def _log_response(self, response: CustomAgentOutput) -> None:
120
- """Log the model's response"""
121
- if 'Success' in response.current_state.prev_action_evaluation:
122
- emoji = 'βœ…'
123
- elif 'Failed' in response.current_state.prev_action_evaluation:
124
- emoji = '❌'
125
- else:
126
- emoji = '🀷'
127
-
128
- logger.info(f'{emoji} Eval: {response.current_state.prev_action_evaluation}')
129
- logger.info(f'🧠 New Memory: {response.current_state.important_contents}')
130
- logger.info(f'⏳ Task Progress: {response.current_state.completed_contents}')
131
- logger.info(f'πŸ€” Thought: {response.current_state.thought}')
132
- logger.info(f'🎯 Summary: {response.current_state.summary}')
133
- for i, action in enumerate(response.action):
134
- logger.info(
135
- f'πŸ› οΈ Action {i + 1}/{len(response.action)}: {action.model_dump_json(exclude_unset=True)}'
136
- )
137
-
138
- def update_step_info(self, model_output: CustomAgentOutput, step_info: CustomAgentStepInfo = None):
139
- """
140
- update step info
141
- """
142
- if step_info is None:
143
- return
144
-
145
- step_info.step_number += 1
146
- important_contents = model_output.current_state.important_contents
147
- if important_contents and 'None' not in important_contents and important_contents not in step_info.memory:
148
- step_info.memory += important_contents + '\n'
149
-
150
- completed_contents = model_output.current_state.completed_contents
151
- if completed_contents and 'None' not in completed_contents:
152
- step_info.task_progress = completed_contents
153
-
154
- @time_execution_async('--get_next_action')
155
- async def get_next_action(self, input_messages: list[BaseMessage]) -> AgentOutput:
156
- """Get next action from LLM based on current state"""
157
-
158
- ret = self.llm.invoke(input_messages)
159
- parsed_json = json.loads(ret.content.replace('```json', '').replace("```", ""))
160
- parsed: AgentOutput = self.AgentOutput(**parsed_json)
161
- # cut the number of actions to max_actions_per_step
162
- parsed.action = parsed.action[: self.max_actions_per_step]
163
- self._log_response(parsed)
164
- self.n_steps += 1
165
-
166
- return parsed
167
-
168
- @time_execution_async('--step')
169
- async def step(self, step_info: Optional[CustomAgentStepInfo] = None) -> None:
170
- """Execute one step of the task"""
171
- logger.info(f'\nπŸ“ Step {self.n_steps}')
172
- state = None
173
- model_output = None
174
- result: list[ActionResult] = []
175
-
176
- try:
177
- state = await self.browser_context.get_state(use_vision=self.use_vision)
178
- self.message_manager.add_state_message(state, self._last_result, step_info)
179
- input_messages = self.message_manager.get_messages()
180
- model_output = await self.get_next_action(input_messages)
181
- self.update_step_info(model_output, step_info)
182
- logger.info(f'🧠 All Memory: {step_info.memory}')
183
- self._save_conversation(input_messages, model_output)
184
- self.message_manager._remove_last_state_message() # we dont want the whole state in the chat history
185
- self.message_manager.add_model_output(model_output)
186
-
187
- result: list[ActionResult] = await self.controller.multi_act(
188
- model_output.action, self.browser_context
189
- )
190
- self._last_result = result
191
-
192
- if len(result) > 0 and result[-1].is_done:
193
- logger.info(f'πŸ“„ Result: {result[-1].extracted_content}')
194
-
195
- self.consecutive_failures = 0
196
-
197
- except Exception as e:
198
- result = self._handle_step_error(e)
199
- self._last_result = result
200
-
201
- finally:
202
- if not result:
203
- return
204
- for r in result:
205
- if r.error:
206
- self.telemetry.capture(
207
- AgentStepErrorTelemetryEvent(
208
- agent_id=self.agent_id,
209
- error=r.error,
210
- )
211
- )
212
- if state:
213
- self._make_history_item(model_output, state, result)
214
-
215
- async def run(self, max_steps: int = 100) -> AgentHistoryList:
216
- """Execute the task with maximum number of steps"""
217
- try:
218
- logger.info(f'πŸš€ Starting task: {self.task}')
219
-
220
- self.telemetry.capture(
221
- AgentRunTelemetryEvent(
222
- agent_id=self.agent_id,
223
- task=self.task,
224
- )
225
- )
226
-
227
- step_info = CustomAgentStepInfo(task=self.task,
228
- add_infos=self.add_infos,
229
- step_number=1,
230
- max_steps=max_steps,
231
- memory='',
232
- task_progress=''
233
- )
234
-
235
- for step in range(max_steps):
236
- if self._too_many_failures():
237
- break
238
-
239
- await self.step(step_info)
240
-
241
- if self.history.is_done():
242
- if (
243
- self.validate_output and step < max_steps - 1
244
- ): # if last step, we dont need to validate
245
- if not await self._validate_output():
246
- continue
247
-
248
- logger.info('βœ… Task completed successfully')
249
- break
250
- else:
251
- logger.info('❌ Failed to complete task in maximum steps')
252
-
253
- return self.history
254
-
255
- finally:
256
- self.telemetry.capture(
257
- AgentEndTelemetryEvent(
258
- agent_id=self.agent_id,
259
- task=self.task,
260
- success=self.history.is_done(),
261
- steps=len(self.history.history),
262
- )
263
- )
264
- if not self.injected_browser_context:
265
- await self.browser_context.close()
266
-
267
- if not self.injected_browser and self.browser:
268
- await self.browser.close()
 
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2025/1/2
3
+ # @Author : wenshao
4
+ # @ProjectName: browser-use-webui
5
+ # @FileName: custom_agent.py
6
+
7
+ import asyncio
8
+ import base64
9
+ import io
10
+ import json
11
+ import logging
12
+ import os
13
+ import pdb
14
+ import textwrap
15
+ import time
16
+ import uuid
17
+ from io import BytesIO
18
+ from pathlib import Path
19
+ from typing import Any, Optional, Type, TypeVar
20
+
21
+ from dotenv import load_dotenv
22
+ from langchain_core.language_models.chat_models import BaseChatModel
23
+ from langchain_core.messages import (
24
+ BaseMessage,
25
+ SystemMessage,
26
+ )
27
+ from openai import RateLimitError
28
+ from PIL import Image, ImageDraw, ImageFont
29
+ from pydantic import BaseModel, ValidationError
30
+
31
+ from browser_use.agent.message_manager.service import MessageManager
32
+ from browser_use.agent.prompts import AgentMessagePrompt, SystemPrompt
33
+ from browser_use.agent.service import Agent
34
+ from browser_use.agent.views import (
35
+ ActionResult,
36
+ AgentError,
37
+ AgentHistory,
38
+ AgentHistoryList,
39
+ AgentOutput,
40
+ AgentStepInfo,
41
+ )
42
+ from browser_use.browser.browser import Browser
43
+ from browser_use.browser.context import BrowserContext
44
+ from browser_use.browser.views import BrowserState, BrowserStateHistory
45
+ from browser_use.controller.registry.views import ActionModel
46
+ from browser_use.controller.service import Controller
47
+ from browser_use.dom.history_tree_processor.service import (
48
+ DOMHistoryElement,
49
+ HistoryTreeProcessor,
50
+ )
51
+ from browser_use.telemetry.service import ProductTelemetry
52
+ from browser_use.telemetry.views import (
53
+ AgentEndTelemetryEvent,
54
+ AgentRunTelemetryEvent,
55
+ AgentStepTelemetryEvent # Changed from AgentStepErrorTelemetryEvent
56
+ )
57
+ from browser_use.utils import time_execution_async
58
+
59
+ from .custom_views import CustomAgentOutput, CustomAgentStepInfo
60
+ from .custom_massage_manager import CustomMassageManager
61
+
62
+ logger = logging.getLogger(__name__)
63
+
64
+
65
+ class CustomAgent(Agent):
66
+
67
+ def __init__(
68
+ self,
69
+ task: str,
70
+ llm: BaseChatModel,
71
+ add_infos: str = '',
72
+ browser: Browser | None = None,
73
+ browser_context: BrowserContext | None = None,
74
+ controller: Controller = Controller(),
75
+ use_vision: bool = True,
76
+ save_conversation_path: Optional[str] = None,
77
+ max_failures: int = 5,
78
+ retry_delay: int = 10,
79
+ system_prompt_class: Type[SystemPrompt] = SystemPrompt,
80
+ max_input_tokens: int = 128000,
81
+ validate_output: bool = False,
82
+ include_attributes: list[str] = [
83
+ 'title',
84
+ 'type',
85
+ 'name',
86
+ 'role',
87
+ 'tabindex',
88
+ 'aria-label',
89
+ 'placeholder',
90
+ 'value',
91
+ 'alt',
92
+ 'aria-expanded',
93
+ ],
94
+ max_error_length: int = 400,
95
+ max_actions_per_step: int = 10,
96
+ ):
97
+ super().__init__(task, llm, browser, browser_context, controller, use_vision, save_conversation_path,
98
+ max_failures, retry_delay, system_prompt_class, max_input_tokens, validate_output,
99
+ include_attributes, max_error_length, max_actions_per_step)
100
+ self.add_infos = add_infos
101
+ self.message_manager = CustomMassageManager(
102
+ llm=self.llm,
103
+ task=self.task,
104
+ action_descriptions=self.controller.registry.get_prompt_description(),
105
+ system_prompt_class=self.system_prompt_class,
106
+ max_input_tokens=self.max_input_tokens,
107
+ include_attributes=self.include_attributes,
108
+ max_error_length=self.max_error_length,
109
+ max_actions_per_step=self.max_actions_per_step,
110
+ )
111
+
112
+ def _setup_action_models(self) -> None:
113
+ """Setup dynamic action models from controller's registry"""
114
+ # Get the dynamic action model from controller's registry
115
+ self.ActionModel = self.controller.registry.create_action_model()
116
+ # Create output model with the dynamic actions
117
+ self.AgentOutput = CustomAgentOutput.type_with_custom_actions(self.ActionModel)
118
+
119
+ def _log_response(self, response: CustomAgentOutput) -> None:
120
+ """Log the model's response"""
121
+ if 'Success' in response.current_state.prev_action_evaluation:
122
+ emoji = 'βœ…'
123
+ elif 'Failed' in response.current_state.prev_action_evaluation:
124
+ emoji = '❌'
125
+ else:
126
+ emoji = '🀷'
127
+
128
+ logger.info(f'{emoji} Eval: {response.current_state.prev_action_evaluation}')
129
+ logger.info(f'🧠 New Memory: {response.current_state.important_contents}')
130
+ logger.info(f'⏳ Task Progress: {response.current_state.completed_contents}')
131
+ logger.info(f'πŸ€” Thought: {response.current_state.thought}')
132
+ logger.info(f'🎯 Summary: {response.current_state.summary}')
133
+ for i, action in enumerate(response.action):
134
+ logger.info(
135
+ f'πŸ› οΈ Action {i + 1}/{len(response.action)}: {action.model_dump_json(exclude_unset=True)}'
136
+ )
137
+
138
+ def update_step_info(self, model_output: CustomAgentOutput, step_info: CustomAgentStepInfo = None):
139
+ """
140
+ update step info
141
+ """
142
+ if step_info is None:
143
+ return
144
+
145
+ step_info.step_number += 1
146
+ important_contents = model_output.current_state.important_contents
147
+ if important_contents and 'None' not in important_contents and important_contents not in step_info.memory:
148
+ step_info.memory += important_contents + '\n'
149
+
150
+ completed_contents = model_output.current_state.completed_contents
151
+ if completed_contents and 'None' not in completed_contents:
152
+ step_info.task_progress = completed_contents
153
+
154
+ @time_execution_async('--get_next_action')
155
+ async def get_next_action(self, input_messages: list[BaseMessage]) -> AgentOutput:
156
+ """Get next action from LLM based on current state"""
157
+
158
+ ret = self.llm.invoke(input_messages)
159
+ parsed_json = json.loads(ret.content.replace('```json', '').replace("```", ""))
160
+ parsed: AgentOutput = self.AgentOutput(**parsed_json)
161
+ # cut the number of actions to max_actions_per_step
162
+ parsed.action = parsed.action[: self.max_actions_per_step]
163
+ self._log_response(parsed)
164
+ self.n_steps += 1
165
+
166
+ return parsed
167
+
168
+ @time_execution_async('--step')
169
+ async def step(self, step_info: Optional[CustomAgentStepInfo] = None) -> None:
170
+ """Execute one step of the task"""
171
+ logger.info(f'\nπŸ“ Step {self.n_steps}')
172
+ state = None
173
+ model_output = None
174
+ result: list[ActionResult] = []
175
+
176
+ try:
177
+ state = await self.browser_context.get_state(use_vision=self.use_vision)
178
+ self.message_manager.add_state_message(state, self._last_result, step_info)
179
+ input_messages = self.message_manager.get_messages()
180
+ model_output = await self.get_next_action(input_messages)
181
+ self.update_step_info(model_output, step_info)
182
+ logger.info(f'🧠 All Memory: {step_info.memory}')
183
+ self._save_conversation(input_messages, model_output)
184
+ self.message_manager._remove_last_state_message() # we dont want the whole state in the chat history
185
+ self.message_manager.add_model_output(model_output)
186
+
187
+ result: list[ActionResult] = await self.controller.multi_act(
188
+ model_output.action, self.browser_context
189
+ )
190
+ self._last_result = result
191
+
192
+ if len(result) > 0 and result[-1].is_done:
193
+ logger.info(f'πŸ“„ Result: {result[-1].extracted_content}')
194
+
195
+ self.consecutive_failures = 0
196
+
197
+ except Exception as e:
198
+ result = self._handle_step_error(e)
199
+ self._last_result = result
200
+
201
+ finally:
202
+ if not result:
203
+ return
204
+ for r in result:
205
+ if r.error:
206
+ self.telemetry.capture(
207
+ AgentStepTelemetryEvent( # Changed from AgentStepErrorTelemetryEvent
208
+ agent_id=self.agent_id,
209
+ error=r.error,
210
+ )
211
+ )
212
+ if state:
213
+ self._make_history_item(model_output, state, result)
214
+
215
+ async def run(self, max_steps: int = 100) -> AgentHistoryList:
216
+ """Execute the task with maximum number of steps"""
217
+ try:
218
+ logger.info(f'πŸš€ Starting task: {self.task}')
219
+
220
+ self.telemetry.capture(
221
+ AgentRunTelemetryEvent(
222
+ agent_id=self.agent_id,
223
+ task=self.task,
224
+ )
225
+ )
226
+
227
+ step_info = CustomAgentStepInfo(task=self.task,
228
+ add_infos=self.add_infos,
229
+ step_number=1,
230
+ max_steps=max_steps,
231
+ memory='',
232
+ task_progress=''
233
+ )
234
+
235
+ for step in range(max_steps):
236
+ if self._too_many_failures():
237
+ break
238
+
239
+ await self.step(step_info)
240
+
241
+ if self.history.is_done():
242
+ if (
243
+ self.validate_output and step < max_steps - 1
244
+ ): # if last step, we dont need to validate
245
+ if not await self._validate_output():
246
+ continue
247
+
248
+ logger.info('βœ… Task completed successfully')
249
+ break
250
+ else:
251
+ logger.info('❌ Failed to complete task in maximum steps')
252
+
253
+ return self.history
254
+
255
+ finally:
256
+ self.telemetry.capture(
257
+ AgentEndTelemetryEvent(
258
+ agent_id=self.agent_id,
259
+ task=self.task,
260
+ success=self.history.is_done(),
261
+ steps=len(self.history.history),
262
+ )
263
+ )
264
+ if not self.injected_browser_context:
265
+ await self.browser_context.close()
266
+
267
+ if not self.injected_browser and self.browser:
268
+ await self.browser.close()