Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/certain-nice-caracara.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"stagehand": patch
---

fix: invalid_request_error when using anthropic cua client
54 changes: 47 additions & 7 deletions examples/agent_example_local.py
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want to revert this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ya my bad. done

Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,34 @@

load_dotenv()

ANTHROPIC_MODEL = "claude-sonnet-4-20250514"
REPRO_INSTRUCTION = (
"Step 1: Use the goto tool to open https://example.com/.\n"
"Step 2: After it loads, scroll down and click the 'More information...' link "
"to open the iana.org page, then report the heading text you see."
)
ERROR_SIGNATURES = [
"messages.1.content.1.tool_use.caller",
"Extra inputs are not permitted",
]

# Configure logging with the utility function
configure_logging(
level=logging.INFO, # Set to INFO for regular logs, DEBUG for detailed
quiet_dependencies=True, # Reduce noise from dependencies
)


def require_env_var(var_name: str) -> str:
"""Fetch a required env var with a helpful error for local runs."""
value = os.getenv(var_name)
if not value:
raise RuntimeError(
f"{var_name} is not set. Add it to your .env before running this example."
)
return value


async def main():
# Build a unified configuration object for Stagehand
config = StagehandConfig(
Expand All @@ -53,19 +75,37 @@ async def main():
await stagehand.page.goto("https://google.com/")
console.print("✅ [success]Navigated to Google[/]")

console.print("\n▶️ [highlight] Using Agent to perform a task[/]: playing a game of 2048")
console.print(
"\n▶️ [highlight]Using Anthropic CUA agent[/]: reproducing the tool_use caller bug"
)
anthropic_api_key = require_env_var("ANTHROPIC_API_KEY")
agent = stagehand.agent(
model="gemini-2.5-computer-use-preview-10-2025",
instructions="You are a helpful web navigation assistant that helps users find information. You are currently on the following page: google.com. Do not ask follow up questions, the user will trust your judgement.",
options={"apiKey": os.getenv("GEMINI_API_KEY")}
model=ANTHROPIC_MODEL,
instructions=(
"You are controlling a fullscreen local browser with the Anthropic computer-use tools. "
"Read the current page carefully, decide on your next action, and avoid asking follow-up questions."
),
options={"apiKey": anthropic_api_key}
)
agent_result = await agent.execute(
instruction="Play a game of 2048",
max_steps=20,
instruction=REPRO_INSTRUCTION,
max_steps=5,
auto_screenshot=True,
)

console.print(agent_result)
if agent_result.message and any(
signature in agent_result.message for signature in ERROR_SIGNATURES
):
console.print(
"🐛 [error]Reproduced the Anthropic `tool_use.caller` validation error.[/]\n"
" Check the logs above for 'Extra inputs are not permitted' to link back to the GitHub issue."
)
else:
console.print(
"⚠️ [warning]Bug signature not detected in this run. "
"Re-run the example or tweak the instructions if you need the failing payload."
)

console.print("📊 [info]Agent execution result:[/]")
console.print(f"🎯 Completed: [bold]{'Yes' if agent_result.completed else 'No'}[/]")
Expand Down Expand Up @@ -100,4 +140,4 @@ async def main():
padding=(1, 10),
),
)
asyncio.run(main())
asyncio.run(main())
10 changes: 7 additions & 3 deletions stagehand/agent/anthropic_cua.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,9 +295,13 @@ def _process_provider_response(
if hasattr(response, "content") and isinstance(response.content, list):
# Serialize Pydantic models from response.content for history
try:
raw_assistant_content_blocks = [
block.model_dump() for block in response.content
]
for block in response.content:
block_dict = block.model_dump()
if isinstance(block_dict, dict):
# Anthropic beta responses include a `caller` field on tool_use blocks
# but the API rejects that key on subsequent requests.
block_dict.pop("caller", None)
raw_assistant_content_blocks.append(block_dict)
except Exception as e:
self.logger.error(
f"Could not model_dump response.content blocks: {e}",
Expand Down
Loading