Skip to content
21 changes: 11 additions & 10 deletions stagehand/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
"""Stagehand - The AI Browser Automation Framework"""

from .agent import Agent
from .config import StagehandConfig, default_config
from .handlers.observe_handler import ObserveHandler
from .llm import LLMClient
from .logging import LogConfig, configure_logging
from .main import Stagehand
from .metrics import StagehandFunctionName, StagehandMetrics
from .page import StagehandPage
Expand All @@ -11,36 +15,33 @@
AgentExecuteOptions,
AgentExecuteResult,
AgentProvider,
AvailableModel,
ExtractOptions,
ExtractResult,
ObserveOptions,
ObserveResult,
)
from .utils import configure_logging

__version__ = "0.0.1"

__all__ = [
"Stagehand",
"StagehandConfig",
"default_config",
"StagehandPage",
"Agent",
"configure_logging",
"AgentConfig",
"AgentExecuteOptions",
"AgentExecuteResult",
"AgentProvider",
"ActOptions",
"ActResult",
"AvailableModel",
"ExtractOptions",
"ExtractResult",
"ObserveOptions",
"ObserveResult",
"AgentConfig",
"AgentExecuteOptions",
"AgentExecuteResult",
"AgentProvider",
"ObserveHandler",
"observe",
"LLMClient",
"configure_logging",
"StagehandFunctionName",
"StagehandMetrics",
"LogConfig",
]
16 changes: 8 additions & 8 deletions stagehand/a11y/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
if TYPE_CHECKING:
from stagehand.page import StagehandPage

from ..logging import StagehandLogger
from ..types.a11y import (
AccessibilityNode,
AXNode,
CDPSession,
TreeResult,
)
from ..utils import StagehandLogger, format_simplified_tree
from ..utils import format_simplified_tree


async def _clean_structural_nodes(
Expand Down Expand Up @@ -80,16 +81,16 @@ async def _clean_structural_nodes(
node["role"] = result_value
node_role = result_value
except Exception as tag_name_error:
# Use logger.warning (level 2)
logger.warning(
# Use logger.debug (level 2)
logger.debug(
message=f"Could not fetch tagName for node {backend_node_id}",
auxiliary={
"error": {"value": str(tag_name_error), "type": "string"}
},
)
except Exception as resolve_error:
# Use logger.warning (level 2)
logger.warning(
# Use logger.debug (level 2)
logger.debug(
message=f"Could not resolve DOM node ID {backend_node_id}",
auxiliary={"error": {"value": str(resolve_error), "type": "string"}},
)
Expand Down Expand Up @@ -277,9 +278,8 @@ async def get_accessibility_tree(
try:
await page.disable_cdp_domain("Accessibility")
except Exception:
# Log if disabling fails, but don't raise further
if logger:
logger.warning("Failed to disable Accessibility domain on cleanup.")
# Use logger.debug (level 2)
logger.debug("Failed to disable Accessibility domain on cleanup.")


# JavaScript function to get XPath (remains JavaScript)
Expand Down
10 changes: 9 additions & 1 deletion stagehand/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,20 @@ def __init__(self, stagehand_client, agent_config: AgentConfig):
self._stagehand = stagehand_client
self._config = agent_config # Store the required config

if not self._stagehand._initialized:
self._stagehand.logger.error(
"Stagehand must be initialized before creating an agent. Call await stagehand.init() first."
)
raise RuntimeError(
"Stagehand must be initialized before creating an agent. Call await stagehand.init() first."
)

# Perform provider inference and validation
if self._config.model and not self._config.provider:
if self._config.model in MODEL_TO_PROVIDER_MAP:
self._config.provider = MODEL_TO_PROVIDER_MAP[self._config.model]
else:
self._stagehand.logger.warning(
self._stagehand.logger.error(
f"Could not infer provider for model: {self._config.model}"
)

Expand Down
14 changes: 7 additions & 7 deletions stagehand/agent/anthropic_cua.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ async def run_task(
break

if not agent_action and not task_completed:
self.logger.warning(
self.logger.info(
"Model did not request an action and task not marked complete. Ending task to prevent loop.",
category=StagehandFunctionName.AGENT,
)
Expand Down Expand Up @@ -290,7 +290,7 @@ def _process_provider_response(
block.model_dump() for block in response.content
]
except Exception as e:
self.logger.warning(
self.logger.error(
f"Could not model_dump response.content blocks: {e}",
category=StagehandFunctionName.AGENT,
)
Expand Down Expand Up @@ -337,7 +337,7 @@ def _convert_tool_use_to_agent_action(
and tool_name != "goto"
and tool_name != "navigate_back"
):
self.logger.warning(
self.logger.error(
f"Unsupported tool name from Anthropic: {tool_name}",
category=StagehandFunctionName.AGENT,
)
Expand Down Expand Up @@ -501,7 +501,7 @@ def _convert_tool_use_to_agent_action(
)
action_type_str = "drag" # Normalize
else:
self.logger.warning(
self.logger.error(
"Drag action missing valid start or end coordinates.",
category=StagehandFunctionName.AGENT,
)
Expand Down Expand Up @@ -559,7 +559,7 @@ def _convert_tool_use_to_agent_action(
)
action_type_str = "function"
else:
self.logger.warning(
self.logger.error(
"Goto action from Anthropic missing URL",
category=StagehandFunctionName.AGENT,
)
Expand All @@ -572,7 +572,7 @@ def _convert_tool_use_to_agent_action(
)
action_type_str = "function"
else:
self.logger.warning(
self.logger.error(
f"Unsupported action type '{action_type_str}' from Anthropic computer tool.",
category=StagehandFunctionName.AGENT,
)
Expand Down Expand Up @@ -613,7 +613,7 @@ def _format_action_feedback(
self.format_screenshot(new_screenshot_base64)
)
else:
self.logger.warning(
self.logger.error(
"Missing screenshot for computer tool feedback (empty string passed).",
category=StagehandFunctionName.AGENT,
)
Expand Down
10 changes: 5 additions & 5 deletions stagehand/agent/google_cua.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ async def _process_provider_response(
and candidate.safety_ratings
):
error_message += f" - Safety Ratings: {candidate.safety_ratings}"
self.logger.warning(error_message, category="agent")
self.logger.info(error_message, category="agent")
return None, reasoning_text, True, error_message, None

agent_action: Optional[AgentAction] = None
Expand Down Expand Up @@ -218,7 +218,7 @@ async def _process_provider_response(
"keys": [self.key_to_playwright("PageDown")],
}
else:
self.logger.warning(
self.logger.error(
f"Unsupported scroll direction: {direction}", category="agent"
)
return (
Expand Down Expand Up @@ -264,7 +264,7 @@ async def _process_provider_response(
"arguments": {"url": "https://www.google.com"},
}
else:
self.logger.warning(
self.logger.error(
f"Unsupported Google CUA function: {action_name}", category="agent"
)
return (
Expand Down Expand Up @@ -524,7 +524,7 @@ async def run_task(
)

if not agent_action and not task_completed:
self.logger.warning(
self.logger.error(
"Model did not request an action and task not marked complete. Ending task.",
category="agent",
)
Expand All @@ -540,7 +540,7 @@ async def run_task(
usage=usage_obj,
)

self.logger.warning("Max steps reached for Google CUA task.", category="agent")
self.logger.error("Max steps reached for Google CUA task.", category="agent")
usage_obj = {
"input_tokens": total_input_tokens,
"output_tokens": total_output_tokens,
Expand Down
6 changes: 3 additions & 3 deletions stagehand/agent/openai_cua.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ def _process_provider_response(
)
# Ensure arguments is a dict, even if empty
if not isinstance(arguments, dict):
self.logger.warning(
self.logger.debug(
f"Function call arguments are not a dict: {arguments}. Using empty dict.",
category="agent",
)
Expand Down Expand Up @@ -464,7 +464,7 @@ async def run_task(
)

if not agent_action and not task_completed:
self.logger.warning(
self.logger.info(
"Model did not request an action and task not marked complete. Ending task to prevent loop.",
category="agent",
)
Expand All @@ -480,7 +480,7 @@ async def run_task(
usage=usage_obj,
)

self.logger.warning("Max steps reached for OpenAI CUA task.", category="agent")
self.logger.info("Max steps reached for OpenAI CUA task.", category="agent")
usage_obj = {
"input_tokens": total_input_tokens,
"output_tokens": total_output_tokens,
Expand Down
2 changes: 1 addition & 1 deletion stagehand/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
)

from .context import StagehandContext
from .logging import StagehandLogger
from .page import StagehandPage
from .utils import StagehandLogger


async def connect_browserbase_browser(
Expand Down
6 changes: 3 additions & 3 deletions stagehand/handlers/act_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ async def _act_from_observe_result(
)

if observe_result.method == "not-supported":
self.logger.warning(
self.logger.error(
message="Cannot execute ObserveResult with unsupported method",
category="act",
auxiliary={
Expand Down Expand Up @@ -229,7 +229,7 @@ async def _act_from_observe_result(
if (
not act_command
): # If both method and description were empty or resulted in an empty command
self.logger.warning(
self.logger.error(
"Self-heal attempt aborted: could not construct a valid command from ObserveResult.",
category="act",
auxiliary={
Expand Down Expand Up @@ -307,7 +307,7 @@ async def _perform_playwright_method(
elif hasattr(locator, method) and callable(getattr(locator, method)):
await fallback_locator_method(context)
else:
self.logger.warning(
self.logger.error(
message="chosen method is invalid",
category="act",
auxiliary={"method": {"value": method, "type": "string"}},
Expand Down
2 changes: 1 addition & 1 deletion stagehand/handlers/act_handler_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ async def handle_possible_page_navigation(
try:
await stagehand_page._wait_for_settled_dom(dom_settle_timeout_ms)
except Exception as e:
logger.warning(
logger.debug(
message="wait for settled DOM timeout hit",
category="action",
auxiliary={
Expand Down
8 changes: 4 additions & 4 deletions stagehand/handlers/cua_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ async def perform_action(self, action: AgentAction) -> ActionExecutionResult:
await self.page.go_back()
return {"success": True}
# Add other function calls like back, forward, reload if needed, similar to TS version
self.logger.warning(
self.logger.error(
f"Unsupported function call: {name}",
category=StagehandFunctionName.AGENT,
)
Expand Down Expand Up @@ -195,7 +195,7 @@ async def perform_action(self, action: AgentAction) -> ActionExecutionResult:
return {"success": True}

else:
self.logger.warning(
self.logger.error(
f"Unsupported action type: {action_type}",
category=StagehandFunctionName.AGENT,
)
Expand Down Expand Up @@ -236,7 +236,7 @@ async def _update_cursor_position(self, x: int, y: int) -> None:
f"window.__stagehandUpdateCursorPosition({x}, {y})"
)
except Exception as e:
self.logger.warning(
self.logger.debug(
f"Failed to call window.__stagehandUpdateCursorPosition: {e}",
category=StagehandFunctionName.AGENT,
)
Expand All @@ -246,7 +246,7 @@ async def _animate_click(self, x: int, y: int) -> None:
try:
await self.page.evaluate(f"window.__stagehandAnimateClick({x}, {y})")
except Exception as e:
self.logger.warning(
self.logger.debug(
f"Failed to call window.__stagehandAnimateClick: {e}",
category=StagehandFunctionName.AGENT,
)
Expand Down
Loading