mirror of
https://github.com/browser-use/browser-use.git
synced 2026-09-14 19:59:47 +08:00
246 lines
9.8 KiB
Python
246 lines
9.8 KiB
Python
import json
|
|
import logging
|
|
import time
|
|
from typing import Any
|
|
from urllib.parse import urlparse
|
|
|
|
from browser_use.agent.views import ActionResult, AgentHistoryList, AgentOutput, AgentSettings, AgentState
|
|
from browser_use.browser import BrowserSession
|
|
from browser_use.browser.views import BrowserStateSummary
|
|
from browser_use.config import CONFIG
|
|
from browser_use.llm.base import BaseChatModel
|
|
from browser_use.telemetry.service import ProductTelemetry
|
|
from browser_use.telemetry.views import AgentTelemetryEvent
|
|
from browser_use.tokens.service import TokenCost
|
|
from browser_use.utils import check_latest_browser_use_version
|
|
|
|
|
|
class AgentRunLoggingMixin:
|
|
task: str
|
|
version: str
|
|
source: str
|
|
history: AgentHistoryList
|
|
llm: BaseChatModel
|
|
browser_session: BrowserSession | None
|
|
settings: AgentSettings
|
|
state: AgentState
|
|
telemetry: ProductTelemetry
|
|
token_cost_service: TokenCost
|
|
_demo_mode_enabled: bool
|
|
logger: logging.Logger
|
|
|
|
async def _log_agent_run(self) -> None:
|
|
"""Log the agent run"""
|
|
# Blue color for task
|
|
self.logger.info(f'\033[34m🎯 Task: {self.task}\033[0m')
|
|
|
|
self.logger.debug(f'🤖 Browser-Use Library Version {self.version} ({self.source})')
|
|
|
|
# Check for latest version and log upgrade message if needed
|
|
if CONFIG.BROWSER_USE_VERSION_CHECK:
|
|
latest_version = await check_latest_browser_use_version()
|
|
if latest_version and latest_version != self.version:
|
|
self.logger.info(
|
|
f'📦 Newer version available: {latest_version} (current: {self.version}). Upgrade with: uv add browser-use=={latest_version}'
|
|
)
|
|
|
|
def _log_first_step_startup(self) -> None:
|
|
"""Log startup message only on the first step"""
|
|
if len(self.history.history) == 0:
|
|
self.logger.info(
|
|
f'Starting a browser-use agent with version {self.version}, with provider={self.llm.provider} and model={self.llm.model}'
|
|
)
|
|
|
|
def _log_step_context(self, browser_state_summary: BrowserStateSummary) -> None:
|
|
"""Log step context information"""
|
|
url = browser_state_summary.url if browser_state_summary else ''
|
|
url_short = url[:50] + '...' if len(url) > 50 else url
|
|
interactive_count = len(browser_state_summary.dom_state.selector_map) if browser_state_summary else 0
|
|
self.logger.info('\n')
|
|
self.logger.info(f'📍 Step {self.state.n_steps}:')
|
|
self.logger.debug(f'Evaluating page with {interactive_count} interactive elements on: {url_short}')
|
|
|
|
def _log_next_action_summary(self, parsed: AgentOutput) -> None:
|
|
"""Log a comprehensive summary of the next action(s)"""
|
|
if not (self.logger.isEnabledFor(logging.DEBUG) and parsed.action):
|
|
return
|
|
|
|
# Collect action details
|
|
action_details = []
|
|
for _i, action in enumerate(parsed.action):
|
|
action_data = action.model_dump(exclude_unset=True)
|
|
action_name = next(iter(action_data.keys())) if action_data else 'unknown'
|
|
action_params = action_data.get(action_name, {}) if action_data else {}
|
|
|
|
# Format key parameters concisely
|
|
param_summary = []
|
|
if isinstance(action_params, dict):
|
|
for key, value in action_params.items():
|
|
if key == 'index':
|
|
param_summary.append(f'#{value}')
|
|
elif key == 'text' and isinstance(value, str):
|
|
text_preview = value[:30] + '...' if len(value) > 30 else value
|
|
param_summary.append(f'text="{text_preview}"')
|
|
elif key == 'url':
|
|
param_summary.append(f'url="{value}"')
|
|
elif key == 'success':
|
|
param_summary.append(f'success={value}')
|
|
elif isinstance(value, (str, int, bool)):
|
|
val_str = str(value)[:30] + '...' if len(str(value)) > 30 else str(value)
|
|
param_summary.append(f'{key}={val_str}')
|
|
|
|
param_str = f'({", ".join(param_summary)})' if param_summary else ''
|
|
action_details.append(f'{action_name}{param_str}')
|
|
|
|
def _prepare_demo_message(self, message: str, limit: int = 600) -> str:
|
|
# Previously truncated long entries; keep full text for better context in demo panel
|
|
return message.strip()
|
|
|
|
async def _demo_mode_log(self, message: str, level: str = 'info', metadata: dict[str, Any] | None = None) -> None:
|
|
if not self._demo_mode_enabled or not message or self.browser_session is None:
|
|
return
|
|
try:
|
|
await self.browser_session.send_demo_mode_log(
|
|
message=self._prepare_demo_message(message),
|
|
level=level,
|
|
metadata=metadata or {},
|
|
)
|
|
except Exception as exc:
|
|
self.logger.debug(f'[DemoMode] Failed to send overlay log: {exc}')
|
|
|
|
async def _broadcast_model_state(self, parsed: AgentOutput) -> None:
|
|
if not self._demo_mode_enabled:
|
|
return
|
|
|
|
state = parsed.current_state
|
|
step_meta = {'step': self.state.n_steps}
|
|
|
|
if state.thinking:
|
|
await self._demo_mode_log(state.thinking, 'thought', step_meta)
|
|
|
|
if state.evaluation_previous_goal:
|
|
eval_text = state.evaluation_previous_goal
|
|
level = 'success' if 'success' in eval_text.lower() else 'warning' if 'failure' in eval_text.lower() else 'info'
|
|
await self._demo_mode_log(eval_text, level, step_meta)
|
|
|
|
if state.memory:
|
|
await self._demo_mode_log(f'Memory: {state.memory}', 'info', step_meta)
|
|
|
|
if state.next_goal:
|
|
await self._demo_mode_log(f'Next goal: {state.next_goal}', 'info', step_meta)
|
|
|
|
def _log_step_completion_summary(self, step_start_time: float, result: list[ActionResult]) -> str | None:
|
|
"""Log step completion summary with action count, timing, and success/failure stats"""
|
|
if not result:
|
|
return None
|
|
|
|
step_duration = time.time() - step_start_time
|
|
action_count = len(result)
|
|
|
|
# Count success and failures
|
|
success_count = sum(1 for r in result if not r.error)
|
|
failure_count = action_count - success_count
|
|
|
|
# Format success/failure indicators
|
|
success_indicator = f'✅ {success_count}' if success_count > 0 else ''
|
|
failure_indicator = f'❌ {failure_count}' if failure_count > 0 else ''
|
|
status_parts = [part for part in [success_indicator, failure_indicator] if part]
|
|
status_str = ' | '.join(status_parts) if status_parts else '✅ 0'
|
|
|
|
message = (
|
|
f'📍 Step {self.state.n_steps}: Ran {action_count} action{"" if action_count == 1 else "s"} '
|
|
f'in {step_duration:.2f}s: {status_str}'
|
|
)
|
|
self.logger.debug(message)
|
|
return message
|
|
|
|
def _log_final_outcome_messages(self) -> None:
|
|
"""Log helpful messages to user based on agent run outcome"""
|
|
# Check if agent failed
|
|
is_successful = self.history.is_successful()
|
|
|
|
if is_successful is False or is_successful is None:
|
|
# Get final result to check for specific failure reasons
|
|
final_result = self.history.final_result()
|
|
final_result_str = str(final_result).lower() if final_result else ''
|
|
|
|
# Check for captcha/cloudflare related failures
|
|
captcha_keywords = ['captcha', 'cloudflare', 'recaptcha', 'challenge', 'bot detection', 'access denied']
|
|
has_captcha_issue = any(keyword in final_result_str for keyword in captcha_keywords)
|
|
|
|
if has_captcha_issue:
|
|
self.logger.warning(
|
|
'Agent was blocked by a captcha. Cloud browsers include stealth fingerprinting and proxy rotation to avoid this.\n'
|
|
' Try: Browser(use_cloud=True) | Get an API key: https://cloud.browser-use.com?utm_source=oss&utm_medium=captcha_nudge'
|
|
)
|
|
|
|
# General failure message
|
|
self.logger.info('')
|
|
self.logger.info('Did the Agent not work as expected? Let us fix this!')
|
|
self.logger.info(' Open a short issue on GitHub: https://github.com/browser-use/browser-use/issues')
|
|
|
|
def _log_agent_event(self, max_steps: int, agent_run_error: str | None = None) -> None:
|
|
"""Sent the agent event for this run to telemetry"""
|
|
|
|
token_summary = self.token_cost_service.get_usage_tokens_for_model(self.llm.model)
|
|
|
|
# Prepare action_history data correctly
|
|
action_history_data = []
|
|
for item in self.history.history:
|
|
if item.model_output and item.model_output.action:
|
|
# Convert each ActionModel in the step to its dictionary representation
|
|
step_actions = [
|
|
action.model_dump(exclude_unset=True)
|
|
for action in item.model_output.action
|
|
if action # Ensure action is not None if list allows it
|
|
]
|
|
action_history_data.append(step_actions)
|
|
else:
|
|
# Append None or [] if a step had no actions or no model output
|
|
action_history_data.append(None)
|
|
|
|
final_res = self.history.final_result()
|
|
final_result_str = json.dumps(final_res) if final_res is not None else None
|
|
|
|
# Extract judgement data if available
|
|
judgement_data = self.history.judgement()
|
|
judge_verdict = judgement_data.get('verdict') if judgement_data else None
|
|
judge_reasoning = judgement_data.get('reasoning') if judgement_data else None
|
|
judge_failure_reason = judgement_data.get('failure_reason') if judgement_data else None
|
|
judge_reached_captcha = judgement_data.get('reached_captcha') if judgement_data else None
|
|
judge_impossible_task = judgement_data.get('impossible_task') if judgement_data else None
|
|
|
|
self.telemetry.capture(
|
|
AgentTelemetryEvent(
|
|
task=self.task,
|
|
model=self.llm.model,
|
|
model_provider=self.llm.provider,
|
|
max_steps=max_steps,
|
|
max_actions_per_step=self.settings.max_actions_per_step,
|
|
use_vision=self.settings.use_vision,
|
|
version=self.version,
|
|
source=self.source,
|
|
cdp_url=urlparse(self.browser_session.cdp_url).hostname
|
|
if self.browser_session and self.browser_session.cdp_url
|
|
else None,
|
|
agent_type=None, # Regular Agent (not code-use)
|
|
action_errors=self.history.errors(),
|
|
action_history=action_history_data,
|
|
urls_visited=self.history.urls(),
|
|
steps=self.state.n_steps,
|
|
total_input_tokens=token_summary.prompt_tokens,
|
|
total_output_tokens=token_summary.completion_tokens,
|
|
prompt_cached_tokens=token_summary.prompt_cached_tokens,
|
|
total_tokens=token_summary.total_tokens,
|
|
total_duration_seconds=self.history.total_duration_seconds(),
|
|
success=self.history.is_successful(),
|
|
final_result_response=final_result_str,
|
|
error_message=agent_run_error,
|
|
judge_verdict=judge_verdict,
|
|
judge_reasoning=judge_reasoning,
|
|
judge_failure_reason=judge_failure_reason,
|
|
judge_reached_captcha=judge_reached_captcha,
|
|
judge_impossible_task=judge_impossible_task,
|
|
)
|
|
)
|