chore: import upstream snapshot with attribution
Test Browser Use CLI Install / uv pip install (ubuntu-latest) (push) Failing after 1s
Test Browser Use CLI Install / uvx browser-use from local wheel (push) Failing after 1s
Test Browser Use CLI Install / uvx browser-use[cli] from PyPI (push) Failing after 1s
package / pip-install-on-macos-latest-py-3.11 (push) Has been skipped
package / pip-install-on-macos-latest-py-3.13 (push) Has been skipped
package / pip-install-on-ubuntu-latest-py-3.11 (push) Has been skipped
package / pip-install-on-windows-latest-py-3.13 (push) Has been skipped
cloud_evals / trigger_cloud_eval_image_build (push) Failing after 1s
docker / build_publish_image (push) Failing after 1s
Test Browser Use CLI Install / browser-use skill sync (push) Failing after 1s
lint / code-style (push) Failing after 0s
lint / type-checker (push) Failing after 1s
package / pip-build (push) Failing after 1s
lint / syntax-errors (push) Failing after 3s
package / pip-install-on-ubuntu-latest-py-3.13 (push) Has been skipped
package / pip-install-on-windows-latest-py-3.11 (push) Has been skipped
test / ${{ matrix.test_filename }} (push) Has been skipped
test / evaluate-tasks (push) Has been skipped
test / setup-chromium (push) Failing after 2s
test / find_tests (push) Failing after 2s
Test Browser Use CLI Install / uv pip install (windows-latest) (push) Has been cancelled
Test Browser Use CLI Install / uv pip install (macos-latest) (push) Has been cancelled
Test Browser Use CLI Install / uv pip install (ubuntu-latest) (push) Failing after 1s
Test Browser Use CLI Install / uvx browser-use from local wheel (push) Failing after 1s
Test Browser Use CLI Install / uvx browser-use[cli] from PyPI (push) Failing after 1s
package / pip-install-on-macos-latest-py-3.11 (push) Has been skipped
package / pip-install-on-macos-latest-py-3.13 (push) Has been skipped
package / pip-install-on-ubuntu-latest-py-3.11 (push) Has been skipped
package / pip-install-on-windows-latest-py-3.13 (push) Has been skipped
cloud_evals / trigger_cloud_eval_image_build (push) Failing after 1s
docker / build_publish_image (push) Failing after 1s
Test Browser Use CLI Install / browser-use skill sync (push) Failing after 1s
lint / code-style (push) Failing after 0s
lint / type-checker (push) Failing after 1s
package / pip-build (push) Failing after 1s
lint / syntax-errors (push) Failing after 3s
package / pip-install-on-ubuntu-latest-py-3.13 (push) Has been skipped
package / pip-install-on-windows-latest-py-3.11 (push) Has been skipped
test / ${{ matrix.test_filename }} (push) Has been skipped
test / evaluate-tasks (push) Has been skipped
test / setup-chromium (push) Failing after 2s
test / find_tests (push) Failing after 2s
Test Browser Use CLI Install / uv pip install (windows-latest) (push) Has been cancelled
Test Browser Use CLI Install / uv pip install (macos-latest) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
from browser_use import Agent
|
||||
|
||||
secret_key = os.environ.get('OTP_SECRET_KEY')
|
||||
if not secret_key:
|
||||
# For this example copy the code from the website https://authenticationtest.com/totpChallenge/
|
||||
# For real 2fa just copy the secret key when you setup 2fa, you can get this e.g. in 1Password
|
||||
secret_key = 'JBSWY3DPEHPK3PXP'
|
||||
|
||||
|
||||
sensitive_data = {'bu_2fa_code': secret_key}
|
||||
|
||||
|
||||
task = """
|
||||
1. Go to https://authenticationtest.com/totpChallenge/ and try to log in.
|
||||
2. If prompted for 2FA code:
|
||||
Input the the secret bu_2fa_code.
|
||||
|
||||
When you input bu_2fa_code, the 6 digit code will be generated automatically.
|
||||
"""
|
||||
|
||||
|
||||
Agent(task=task, sensitive_data=sensitive_data).run_sync() # type: ignore
|
||||
@@ -0,0 +1,120 @@
|
||||
"""
|
||||
Action filters (domains) let you limit actions available to the Agent on a step-by-step/page-by-page basis.
|
||||
|
||||
@registry.action(..., domains=['*'])
|
||||
async def some_action(browser_session: BrowserSession):
|
||||
...
|
||||
|
||||
This helps prevent the LLM from deciding to use an action that is not compatible with the current page.
|
||||
It helps limit decision fatigue by scoping actions only to pages where they make sense.
|
||||
It also helps prevent mis-triggering stateful actions or actions that could break other programs or leak secrets.
|
||||
|
||||
For example:
|
||||
- only run on certain domains @registry.action(..., domains=['example.com', '*.example.com', 'example.co.*']) (supports globs, but no regex)
|
||||
- only fill in a password on a specific login page url
|
||||
- only run if this action has not run before on this page (e.g. by looking up the url in a file on disk)
|
||||
|
||||
During each step, the agent recalculates the actions available specifically for that page, and informs the LLM.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
from browser_use import ChatOpenAI
|
||||
from browser_use.agent.service import Agent, Tools
|
||||
from browser_use.browser import BrowserSession
|
||||
|
||||
# Initialize tools and registry
|
||||
tools = Tools()
|
||||
registry = tools.registry
|
||||
|
||||
|
||||
# Action will only be available to Agent on Google domains because of the domain filter
|
||||
@registry.action(description='Trigger disco mode', domains=['google.com', '*.google.com'])
|
||||
async def disco_mode(browser_session: BrowserSession):
|
||||
# Execute JavaScript using CDP
|
||||
cdp_session = await browser_session.get_or_create_cdp_session()
|
||||
await cdp_session.cdp_client.send.Runtime.evaluate(
|
||||
params={
|
||||
'expression': """(() => {
|
||||
// define the wiggle animation
|
||||
document.styleSheets[0].insertRule('@keyframes wiggle { 0% { transform: rotate(0deg); } 50% { transform: rotate(10deg); } 100% { transform: rotate(0deg); } }');
|
||||
|
||||
document.querySelectorAll("*").forEach(element => {
|
||||
element.style.animation = "wiggle 0.5s infinite";
|
||||
});
|
||||
})()"""
|
||||
},
|
||||
session_id=cdp_session.session_id,
|
||||
)
|
||||
|
||||
|
||||
# Custom filter function that checks URL
|
||||
async def is_login_page(browser_session: BrowserSession) -> bool:
|
||||
"""Check if current page is a login page."""
|
||||
try:
|
||||
# Get current URL using CDP
|
||||
cdp_session = await browser_session.get_or_create_cdp_session()
|
||||
result = await cdp_session.cdp_client.send.Runtime.evaluate(
|
||||
params={'expression': 'window.location.href', 'returnByValue': True}, session_id=cdp_session.session_id
|
||||
)
|
||||
url = result.get('result', {}).get('value', '')
|
||||
return 'login' in url.lower() or 'signin' in url.lower()
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
# Note: page_filter is not directly supported anymore, so we'll just use domains
|
||||
# and check the condition inside the function
|
||||
@registry.action(description='Use the force, luke', domains=['*'])
|
||||
async def use_the_force(browser_session: BrowserSession):
|
||||
# Check if it's a login page
|
||||
if not await is_login_page(browser_session):
|
||||
return # Skip if not a login page
|
||||
|
||||
# Execute JavaScript using CDP
|
||||
cdp_session = await browser_session.get_or_create_cdp_session()
|
||||
await cdp_session.cdp_client.send.Runtime.evaluate(
|
||||
params={
|
||||
'expression': """(() => {
|
||||
document.querySelector('body').innerHTML = 'These are not the droids you are looking for';
|
||||
})()"""
|
||||
},
|
||||
session_id=cdp_session.session_id,
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main function to run the example"""
|
||||
browser_session = BrowserSession()
|
||||
await browser_session.start()
|
||||
llm = ChatOpenAI(model='gpt-4.1-mini')
|
||||
|
||||
# Create the agent
|
||||
agent = Agent( # disco mode will not be triggered on apple.com because the LLM won't be able to see that action available, it should work on Google.com though.
|
||||
task="""
|
||||
Go to apple.com and trigger disco mode (if dont know how to do that, then just move on).
|
||||
Then go to google.com and trigger disco mode.
|
||||
After that, go to the Google login page and Use the force, luke.
|
||||
""",
|
||||
llm=llm,
|
||||
browser_session=browser_session,
|
||||
tools=tools,
|
||||
)
|
||||
|
||||
# Run the agent
|
||||
await agent.run(max_steps=10)
|
||||
|
||||
# Cleanup
|
||||
await browser_session.kill()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,38 @@
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
|
||||
from browser_use.browser.session import BrowserSession
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
from browser_use import ActionResult, Agent, ChatOpenAI, Tools
|
||||
|
||||
tools = Tools()
|
||||
|
||||
llm = ChatOpenAI(model='gpt-4.1-mini')
|
||||
|
||||
|
||||
@tools.registry.action('Click on submit button')
|
||||
async def click_submit_button(browser_session: BrowserSession):
|
||||
page = await browser_session.must_get_current_page()
|
||||
|
||||
submit_button = await page.must_get_element_by_prompt('submit button', llm)
|
||||
await submit_button.click()
|
||||
|
||||
return ActionResult(is_done=True, extracted_content='Submit button clicked!')
|
||||
|
||||
|
||||
async def main():
|
||||
task = 'go to brower-use.com and then click on the submit button'
|
||||
agent = Agent(task=task, llm=llm, tools=tools)
|
||||
|
||||
await agent.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,112 @@
|
||||
import asyncio
|
||||
import http.client
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
import logging
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from browser_use import ActionResult, Agent, ChatOpenAI, Tools
|
||||
from browser_use.browser.profile import BrowserProfile
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Person(BaseModel):
|
||||
name: str
|
||||
email: str | None = None
|
||||
|
||||
|
||||
class PersonList(BaseModel):
|
||||
people: list[Person]
|
||||
|
||||
|
||||
SERP_API_KEY = os.getenv('SERPER_API_KEY')
|
||||
if not SERP_API_KEY:
|
||||
raise ValueError('SERPER_API_KEY is not set')
|
||||
|
||||
tools = Tools(exclude_actions=['search'], output_model=PersonList)
|
||||
|
||||
|
||||
@tools.registry.action('Search the web for a specific query. Returns a short description and links of the results.')
|
||||
async def search_web(query: str):
|
||||
# do a serp search for the query
|
||||
conn = http.client.HTTPSConnection('google.serper.dev')
|
||||
payload = json.dumps({'q': query})
|
||||
headers = {'X-API-KEY': SERP_API_KEY, 'Content-Type': 'application/json'}
|
||||
conn.request('POST', '/search', payload, headers)
|
||||
res = conn.getresponse()
|
||||
data = res.read()
|
||||
serp_data = json.loads(data.decode('utf-8'))
|
||||
|
||||
# exclude searchParameters and credits
|
||||
serp_data = {k: v for k, v in serp_data.items() if k not in ['searchParameters', 'credits']}
|
||||
|
||||
# keep the value of the key "organic"
|
||||
|
||||
organic = serp_data.get('organic', [])
|
||||
# remove the key "position"
|
||||
organic = [{k: v for k, v in d.items() if k != 'position'} for d in organic]
|
||||
|
||||
# print the original data
|
||||
logger.debug(json.dumps(organic, indent=2))
|
||||
|
||||
# to string
|
||||
organic_str = json.dumps(organic)
|
||||
|
||||
return ActionResult(extracted_content=organic_str, include_in_memory=False, include_extracted_content_only_once=True)
|
||||
|
||||
|
||||
names = [
|
||||
'Ruedi Aebersold',
|
||||
'Bernd Bodenmiller',
|
||||
'Eugene Demler',
|
||||
'Erich Fischer',
|
||||
'Pietro Gambardella',
|
||||
'Matthias Huss',
|
||||
'Reto Knutti',
|
||||
'Maksym Kovalenko',
|
||||
'Antonio Lanzavecchia',
|
||||
'Maria Lukatskaya',
|
||||
'Jochen Markard',
|
||||
'Javier Pérez-Ramírez',
|
||||
'Federica Sallusto',
|
||||
'Gisbert Schneider',
|
||||
'Sonia I. Seneviratne',
|
||||
'Michael Siegrist',
|
||||
'Johan Six',
|
||||
'Tanja Stadler',
|
||||
'Shinichi Sunagawa',
|
||||
'Michael Bruce Zimmermann',
|
||||
]
|
||||
|
||||
|
||||
async def main():
|
||||
task = 'use search_web with "find email address of the following ETH professor:" for each of the following persons in a list of actions. Finally return the list with name and email if provided - do always 5 at once'
|
||||
task += '\n' + '\n'.join(names)
|
||||
model = ChatOpenAI(model='gpt-4.1-mini')
|
||||
browser_profile = BrowserProfile()
|
||||
agent = Agent(task=task, llm=model, tools=tools, browser_profile=browser_profile)
|
||||
|
||||
history = await agent.run()
|
||||
|
||||
result = history.final_result()
|
||||
if result:
|
||||
parsed: PersonList = PersonList.model_validate_json(result)
|
||||
|
||||
for person in parsed.people:
|
||||
print(f'{person.name} - {person.email}')
|
||||
else:
|
||||
print('No result')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,347 @@
|
||||
"""
|
||||
OpenAI Computer Use Assistant (CUA) Integration
|
||||
|
||||
This example demonstrates how to integrate OpenAI's Computer Use Assistant as a fallback
|
||||
action when standard browser actions are insufficient to achieve the desired goal.
|
||||
The CUA can perform complex computer interactions that might be difficult to achieve
|
||||
through regular browser-use actions.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import os
|
||||
import sys
|
||||
from io import BytesIO
|
||||
|
||||
from PIL import Image
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
from openai import AsyncOpenAI
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from browser_use import Agent, ChatOpenAI, Tools
|
||||
from browser_use.agent.views import ActionResult
|
||||
from browser_use.browser import BrowserSession
|
||||
|
||||
|
||||
class OpenAICUAAction(BaseModel):
|
||||
"""Parameters for OpenAI Computer Use Assistant action."""
|
||||
|
||||
description: str = Field(..., description='Description of your next goal')
|
||||
|
||||
|
||||
async def handle_model_action(browser_session: BrowserSession, action) -> ActionResult:
|
||||
"""
|
||||
Given a computer action (e.g., click, double_click, scroll, etc.),
|
||||
execute the corresponding operation using CDP.
|
||||
"""
|
||||
action_type = action.type
|
||||
ERROR_MSG: str = 'Could not execute the CUA action.'
|
||||
|
||||
if not browser_session.agent_focus_target_id:
|
||||
return ActionResult(error='No active browser session')
|
||||
|
||||
# Get CDP session for the focused target using the public API
|
||||
try:
|
||||
cdp_session = await browser_session.get_or_create_cdp_session(browser_session.agent_focus_target_id, focus=False)
|
||||
except Exception as e:
|
||||
return ActionResult(error=f'Failed to get CDP session: {e}')
|
||||
|
||||
try:
|
||||
match action_type:
|
||||
case 'click':
|
||||
x, y = action.x, action.y
|
||||
button = action.button
|
||||
print(f"Action: click at ({x}, {y}) with button '{button}'")
|
||||
# Not handling things like middle click, etc.
|
||||
if button != 'left' and button != 'right':
|
||||
button = 'left'
|
||||
|
||||
# Use CDP to click
|
||||
await browser_session.cdp_client.send.Input.dispatchMouseEvent(
|
||||
params={
|
||||
'type': 'mousePressed',
|
||||
'x': x,
|
||||
'y': y,
|
||||
'button': button,
|
||||
'clickCount': 1,
|
||||
},
|
||||
session_id=cdp_session.session_id,
|
||||
)
|
||||
await browser_session.cdp_client.send.Input.dispatchMouseEvent(
|
||||
params={
|
||||
'type': 'mouseReleased',
|
||||
'x': x,
|
||||
'y': y,
|
||||
'button': button,
|
||||
},
|
||||
session_id=cdp_session.session_id,
|
||||
)
|
||||
msg = f'Clicked at ({x}, {y}) with button {button}'
|
||||
return ActionResult(extracted_content=msg, include_in_memory=True, long_term_memory=msg)
|
||||
|
||||
case 'scroll':
|
||||
x, y = action.x, action.y
|
||||
scroll_x, scroll_y = action.scroll_x, action.scroll_y
|
||||
print(f'Action: scroll at ({x}, {y}) with offsets (scroll_x={scroll_x}, scroll_y={scroll_y})')
|
||||
|
||||
# Move mouse to position first
|
||||
await browser_session.cdp_client.send.Input.dispatchMouseEvent(
|
||||
params={
|
||||
'type': 'mouseMoved',
|
||||
'x': x,
|
||||
'y': y,
|
||||
},
|
||||
session_id=cdp_session.session_id,
|
||||
)
|
||||
|
||||
# Execute scroll using JavaScript
|
||||
await browser_session.cdp_client.send.Runtime.evaluate(
|
||||
params={
|
||||
'expression': f'window.scrollBy({scroll_x}, {scroll_y})',
|
||||
},
|
||||
session_id=cdp_session.session_id,
|
||||
)
|
||||
msg = f'Scrolled at ({x}, {y}) with offsets (scroll_x={scroll_x}, scroll_y={scroll_y})'
|
||||
return ActionResult(extracted_content=msg, include_in_memory=True, long_term_memory=msg)
|
||||
|
||||
case 'keypress':
|
||||
keys = action.keys
|
||||
for k in keys:
|
||||
print(f"Action: keypress '{k}'")
|
||||
# A simple mapping for common keys; expand as needed.
|
||||
key_code = k
|
||||
if k.lower() == 'enter':
|
||||
key_code = 'Enter'
|
||||
elif k.lower() == 'space':
|
||||
key_code = 'Space'
|
||||
|
||||
# Use CDP to send key
|
||||
await browser_session.cdp_client.send.Input.dispatchKeyEvent(
|
||||
params={
|
||||
'type': 'keyDown',
|
||||
'key': key_code,
|
||||
},
|
||||
session_id=cdp_session.session_id,
|
||||
)
|
||||
await browser_session.cdp_client.send.Input.dispatchKeyEvent(
|
||||
params={
|
||||
'type': 'keyUp',
|
||||
'key': key_code,
|
||||
},
|
||||
session_id=cdp_session.session_id,
|
||||
)
|
||||
msg = f'Pressed keys: {keys}'
|
||||
return ActionResult(extracted_content=msg, include_in_memory=True, long_term_memory=msg)
|
||||
|
||||
case 'type':
|
||||
text = action.text
|
||||
print(f'Action: type text: {text}')
|
||||
|
||||
# Type text character by character
|
||||
for char in text:
|
||||
await browser_session.cdp_client.send.Input.dispatchKeyEvent(
|
||||
params={
|
||||
'type': 'char',
|
||||
'text': char,
|
||||
},
|
||||
session_id=cdp_session.session_id,
|
||||
)
|
||||
msg = f'Typed text: {text}'
|
||||
return ActionResult(extracted_content=msg, include_in_memory=True, long_term_memory=msg)
|
||||
|
||||
case 'wait':
|
||||
print('Action: wait')
|
||||
await asyncio.sleep(2)
|
||||
msg = 'Waited for 2 seconds'
|
||||
return ActionResult(extracted_content=msg, include_in_memory=True, long_term_memory=msg)
|
||||
|
||||
case 'screenshot':
|
||||
# Nothing to do as screenshot is taken at each turn
|
||||
print('Action: screenshot')
|
||||
return ActionResult(error=ERROR_MSG)
|
||||
# Handle other actions here
|
||||
|
||||
case _:
|
||||
print(f'Unrecognized action: {action}')
|
||||
return ActionResult(error=ERROR_MSG)
|
||||
|
||||
except Exception as e:
|
||||
print(f'Error handling action {action}: {e}')
|
||||
return ActionResult(error=ERROR_MSG)
|
||||
|
||||
|
||||
tools = Tools()
|
||||
|
||||
|
||||
@tools.registry.action(
|
||||
'Use OpenAI Computer Use Assistant (CUA) as a fallback when standard browser actions cannot achieve the desired goal. This action sends a screenshot and description to OpenAI CUA and executes the returned computer use actions.',
|
||||
param_model=OpenAICUAAction,
|
||||
)
|
||||
async def openai_cua_fallback(params: OpenAICUAAction, browser_session: BrowserSession):
|
||||
"""
|
||||
Fallback action that uses OpenAI's Computer Use Assistant to perform complex
|
||||
computer interactions when standard browser actions are insufficient.
|
||||
"""
|
||||
print(f'🎯 CUA Action Starting - Goal: {params.description}')
|
||||
|
||||
try:
|
||||
# Get browser state summary
|
||||
state = await browser_session.get_browser_state_summary()
|
||||
page_info = state.page_info
|
||||
if not page_info:
|
||||
raise Exception('Page info not found - cannot execute CUA action')
|
||||
|
||||
print(f'📐 Viewport size: {page_info.viewport_width}x{page_info.viewport_height}')
|
||||
|
||||
screenshot_b64 = state.screenshot
|
||||
if not screenshot_b64:
|
||||
raise Exception('Screenshot not found - cannot execute CUA action')
|
||||
|
||||
print(f'📸 Screenshot captured (base64 length: {len(screenshot_b64)} chars)')
|
||||
|
||||
# Debug: Check screenshot dimensions
|
||||
image = Image.open(BytesIO(base64.b64decode(screenshot_b64)))
|
||||
print(f'📏 Screenshot actual dimensions: {image.size[0]}x{image.size[1]}')
|
||||
|
||||
# rescale the screenshot to the viewport size
|
||||
image = image.resize((page_info.viewport_width, page_info.viewport_height))
|
||||
# Save as PNG to bytes buffer
|
||||
buffer = BytesIO()
|
||||
image.save(buffer, format='PNG')
|
||||
buffer.seek(0)
|
||||
# Convert to base64
|
||||
screenshot_b64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
|
||||
print(f'📸 Rescaled screenshot to viewport size: {page_info.viewport_width}x{page_info.viewport_height}')
|
||||
|
||||
client = AsyncOpenAI(api_key=os.getenv('OPENAI_API_KEY'))
|
||||
print('🔄 Sending request to OpenAI CUA...')
|
||||
|
||||
prompt = f"""
|
||||
You will be given an action to execute and screenshot of the current screen.
|
||||
Output one computer_call object that will achieve this goal.
|
||||
Goal: {params.description}
|
||||
"""
|
||||
response = await client.responses.create(
|
||||
model='computer-use-preview',
|
||||
tools=[
|
||||
{
|
||||
'type': 'computer_use_preview',
|
||||
'display_width': page_info.viewport_width,
|
||||
'display_height': page_info.viewport_height,
|
||||
'environment': 'browser',
|
||||
}
|
||||
],
|
||||
input=[
|
||||
{
|
||||
'role': 'user',
|
||||
'content': [
|
||||
{'type': 'input_text', 'text': prompt},
|
||||
{
|
||||
'type': 'input_image',
|
||||
'detail': 'auto',
|
||||
'image_url': f'data:image/png;base64,{screenshot_b64}',
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
truncation='auto',
|
||||
temperature=0.1,
|
||||
)
|
||||
|
||||
print(f'📥 CUA response received: {response}')
|
||||
computer_calls = [item for item in response.output if item.type == 'computer_call']
|
||||
computer_call = computer_calls[0] if computer_calls else None
|
||||
if not computer_call:
|
||||
raise Exception('No computer calls found in CUA response')
|
||||
|
||||
action = computer_call.action
|
||||
print(f'🎬 Executing CUA action: {action.type} - {action}')
|
||||
|
||||
action_result = await handle_model_action(browser_session, action)
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
print('✅ CUA action completed successfully')
|
||||
return action_result
|
||||
|
||||
except Exception as e:
|
||||
msg = f'Error executing CUA action: {e}'
|
||||
print(f'❌ {msg}')
|
||||
return ActionResult(error=msg)
|
||||
|
||||
|
||||
async def main():
|
||||
# Initialize the language model
|
||||
llm = ChatOpenAI(
|
||||
model='o4-mini',
|
||||
temperature=1.0,
|
||||
)
|
||||
|
||||
# Create browser session
|
||||
browser_session = BrowserSession()
|
||||
|
||||
# Example task that might require CUA fallback
|
||||
# This could be a complex interaction that's difficult with standard actions
|
||||
task = """
|
||||
Go to https://csreis.github.io/tests/cross-site-iframe.html
|
||||
Click on "Go cross-site, complex page" using index
|
||||
Use the OpenAI CUA fallback to click on "Tree is open..." link.
|
||||
"""
|
||||
|
||||
# Create agent with our custom tools that includes CUA fallback
|
||||
agent = Agent(
|
||||
task=task,
|
||||
llm=llm,
|
||||
tools=tools,
|
||||
browser_session=browser_session,
|
||||
)
|
||||
|
||||
print('🚀 Starting agent with CUA fallback support...')
|
||||
print(f'Task: {task}')
|
||||
print('-' * 50)
|
||||
|
||||
try:
|
||||
# Run the agent
|
||||
result = await agent.run()
|
||||
print(f'\n✅ Task completed! Result: {result}')
|
||||
|
||||
except Exception as e:
|
||||
print(f'\n❌ Error running agent: {e}')
|
||||
|
||||
finally:
|
||||
# Clean up browser session
|
||||
await browser_session.kill()
|
||||
print('\n🧹 Browser session closed')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Example of different scenarios where CUA might be useful
|
||||
|
||||
print('🔧 OpenAI Computer Use Assistant (CUA) Integration Example')
|
||||
print('=' * 60)
|
||||
print()
|
||||
print("This example shows how to integrate OpenAI's CUA as a fallback action")
|
||||
print('when standard browser-use actions cannot achieve the desired goal.')
|
||||
print()
|
||||
print('CUA is particularly useful for:')
|
||||
print('• Complex mouse interactions (drag & drop, precise clicking)')
|
||||
print('• Keyboard shortcuts and key combinations')
|
||||
print('• Actions that require pixel-perfect precision')
|
||||
print("• Custom UI elements that don't respond to standard actions")
|
||||
print()
|
||||
print('Make sure you have OPENAI_API_KEY set in your environment!')
|
||||
print()
|
||||
|
||||
# Check if OpenAI API key is available
|
||||
if not os.getenv('OPENAI_API_KEY'):
|
||||
print('❌ Error: OPENAI_API_KEY environment variable not set')
|
||||
print('Please set your OpenAI API key to use CUA integration')
|
||||
sys.exit(1)
|
||||
|
||||
# Run the example
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,113 @@
|
||||
"""
|
||||
Example of implementing file upload functionality.
|
||||
|
||||
This shows how to upload files to file input elements on web pages.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
import anyio
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
from browser_use import ChatOpenAI
|
||||
from browser_use.agent.service import Agent, Tools
|
||||
from browser_use.agent.views import ActionResult
|
||||
from browser_use.browser import BrowserSession
|
||||
from browser_use.browser.events import UploadFileEvent
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Initialize tools
|
||||
tools = Tools()
|
||||
|
||||
|
||||
@tools.action('Upload file to interactive element with file path')
|
||||
async def upload_file(index: int, path: str, browser_session: BrowserSession, available_file_paths: list[str]):
|
||||
if path not in available_file_paths:
|
||||
return ActionResult(error=f'File path {path} is not available')
|
||||
|
||||
if not os.path.exists(path):
|
||||
return ActionResult(error=f'File {path} does not exist')
|
||||
|
||||
try:
|
||||
# Get the DOM element by index
|
||||
dom_element = await browser_session.get_dom_element_by_index(index)
|
||||
|
||||
if dom_element is None:
|
||||
msg = f'No element found at index {index}'
|
||||
logger.info(msg)
|
||||
return ActionResult(error=msg)
|
||||
|
||||
# Check if it's a file input element
|
||||
if dom_element.tag_name.lower() != 'input' or dom_element.attributes.get('type') != 'file':
|
||||
msg = f'Element at index {index} is not a file input element'
|
||||
logger.info(msg)
|
||||
return ActionResult(error=msg)
|
||||
|
||||
# Dispatch the upload file event
|
||||
event = browser_session.event_bus.dispatch(UploadFileEvent(node=dom_element, file_path=path))
|
||||
await event
|
||||
|
||||
msg = f'Successfully uploaded file to index {index}'
|
||||
logger.info(msg)
|
||||
return ActionResult(extracted_content=msg, include_in_memory=True)
|
||||
|
||||
except Exception as e:
|
||||
msg = f'Failed to upload file to index {index}: {str(e)}'
|
||||
logger.info(msg)
|
||||
return ActionResult(error=msg)
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main function to run the example"""
|
||||
browser_session = BrowserSession()
|
||||
await browser_session.start()
|
||||
llm = ChatOpenAI(model='gpt-4.1-mini')
|
||||
|
||||
# List of file paths the agent is allowed to upload
|
||||
# In a real scenario, you'd want to be very careful about what files
|
||||
# the agent can access and upload
|
||||
available_file_paths = [
|
||||
'/tmp/test_document.pdf',
|
||||
'/tmp/test_image.jpg',
|
||||
]
|
||||
|
||||
# Create test files if they don't exist
|
||||
for file_path in available_file_paths:
|
||||
if not os.path.exists(file_path):
|
||||
await anyio.Path(file_path).write_text('Test file content for upload example')
|
||||
|
||||
# Create the agent with file upload capability
|
||||
agent = Agent(
|
||||
task="""
|
||||
Go to https://www.w3schools.com/howto/howto_html_file_upload_button.asp and try to upload one of the available test files.
|
||||
""",
|
||||
llm=llm,
|
||||
browser_session=browser_session,
|
||||
tools=tools,
|
||||
# Pass the available file paths to the tools context
|
||||
custom_context={'available_file_paths': available_file_paths},
|
||||
)
|
||||
|
||||
# Run the agent
|
||||
await agent.run(max_steps=10)
|
||||
|
||||
# Cleanup
|
||||
await browser_session.kill()
|
||||
|
||||
# Clean up test files
|
||||
for file_path in available_file_paths:
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,43 @@
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
from browser_use import ActionResult, Agent, ChatOpenAI, Tools
|
||||
|
||||
tools = Tools()
|
||||
|
||||
|
||||
@tools.registry.action('Done with task')
|
||||
async def done(text: str):
|
||||
import yagmail # type: ignore
|
||||
|
||||
# To send emails use
|
||||
# STEP 1: go to https://support.google.com/accounts/answer/185833
|
||||
# STEP 2: Create an app password (you can't use here your normal gmail password)
|
||||
# STEP 3: Use the app password in the code below for the password
|
||||
yag = yagmail.SMTP('your_email@gmail.com', 'your_app_password')
|
||||
yag.send(
|
||||
to='recipient@example.com',
|
||||
subject='Test Email',
|
||||
contents=f'result\n: {text}',
|
||||
)
|
||||
|
||||
return ActionResult(is_done=True, extracted_content='Email sent!')
|
||||
|
||||
|
||||
async def main():
|
||||
task = 'go to brower-use.com and then done'
|
||||
model = ChatOpenAI(model='gpt-4.1-mini')
|
||||
agent = Agent(task=task, llm=model, tools=tools)
|
||||
|
||||
await agent.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,56 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
from onepassword.client import Client # type: ignore # pip install onepassword-sdk
|
||||
|
||||
from browser_use import ActionResult, Agent, ChatOpenAI, Tools
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
OP_SERVICE_ACCOUNT_TOKEN = os.getenv('OP_SERVICE_ACCOUNT_TOKEN')
|
||||
OP_ITEM_ID = os.getenv('OP_ITEM_ID') # Go to 1Password, right click on the item, click "Copy Secret Reference"
|
||||
|
||||
|
||||
tools = Tools()
|
||||
|
||||
|
||||
@tools.registry.action('Get 2FA code from 1Password for Google Account', domains=['*.google.com', 'google.com'])
|
||||
async def get_1password_2fa() -> ActionResult:
|
||||
"""
|
||||
Custom action to retrieve 2FA/MFA code from 1Password using onepassword.client SDK.
|
||||
"""
|
||||
client = await Client.authenticate(
|
||||
# setup instructions: https://github.com/1Password/onepassword-sdk-python/#-get-started
|
||||
auth=OP_SERVICE_ACCOUNT_TOKEN,
|
||||
integration_name='Browser-Use',
|
||||
integration_version='v1.0.0',
|
||||
)
|
||||
|
||||
mfa_code = await client.secrets.resolve(f'op://Private/{OP_ITEM_ID}/One-time passcode')
|
||||
|
||||
return ActionResult(extracted_content=mfa_code)
|
||||
|
||||
|
||||
async def main():
|
||||
# Example task using the 1Password 2FA action
|
||||
task = 'Go to account.google.com, enter username and password, then if prompted for 2FA code, get 2FA code from 1Password for and enter it'
|
||||
|
||||
model = ChatOpenAI(model='gpt-4.1-mini')
|
||||
agent = Agent(task=task, llm=model, tools=tools)
|
||||
|
||||
result = await agent.run()
|
||||
print(f'Task completed with result: {result}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,310 @@
|
||||
"""
|
||||
Simple parallel multi-agent example.
|
||||
|
||||
This launches multiple agents in parallel to work on different tasks simultaneously.
|
||||
No complex orchestrator - just direct parallel execution.
|
||||
|
||||
@file purpose: Demonstrates parallel multi-agent execution using asyncio
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
from browser_use import Agent
|
||||
from browser_use.llm.google import ChatGoogle
|
||||
|
||||
# ============================================================================
|
||||
# 🔧 SIMPLE CONFIGURATION - CHANGE THIS TO YOUR DESIRED TASK
|
||||
# ============================================================================
|
||||
|
||||
MAIN_TASK = 'find age of ronaldo and messi'
|
||||
|
||||
# Simple test - let's start with just one person to see what happens
|
||||
# MAIN_TASK = "find age of elon musk"
|
||||
|
||||
# ============================================================================
|
||||
|
||||
|
||||
async def create_subtasks(main_task: str, llm) -> list[str]:
|
||||
"""
|
||||
Use LLM to break down main task into logical subtasks
|
||||
|
||||
Real examples of how this works:
|
||||
|
||||
Input: "what is the revenue of nvidia, microsoft, tesla"
|
||||
Output: [
|
||||
"Find Nvidia's current revenue and financial data",
|
||||
"Find Microsoft's current revenue and financial data",
|
||||
"Find Tesla's current revenue and financial data"
|
||||
]
|
||||
|
||||
Input: "what are ages of musk, altman, bezos, gates"
|
||||
Output: [
|
||||
"Find Elon Musk's age and birth date",
|
||||
"Find Sam Altman's age and birth date",
|
||||
"Find Jeff Bezos's age and birth date",
|
||||
"Find Bill Gates's age and birth date"
|
||||
]
|
||||
|
||||
Input: "what is the population of tokyo, new york, london, paris"
|
||||
Output: [
|
||||
"Find Tokyo's current population",
|
||||
"Find New York's current population",
|
||||
"Find London's current population",
|
||||
"Find Paris's current population"
|
||||
]
|
||||
|
||||
Input: "name top 10 yc companies by revenue"
|
||||
Output: [
|
||||
"Research Y Combinator's top companies by revenue",
|
||||
"Find revenue data for top YC companies",
|
||||
"Compile list of top 10 YC companies by revenue"
|
||||
]
|
||||
"""
|
||||
|
||||
prompt = f"""
|
||||
Break down this main task into individual, separate subtasks where each subtask focuses on ONLY ONE specific person, company, or item:
|
||||
|
||||
Main task: {main_task}
|
||||
|
||||
RULES:
|
||||
- Each subtask must focus on ONLY ONE person/company/item
|
||||
- Do NOT combine multiple people/companies/items in one subtask
|
||||
- Each subtask should be completely independent
|
||||
- If the main task mentions multiple items, create one subtask per item
|
||||
|
||||
Return only the subtasks, one per line, without numbering or bullets.
|
||||
Each line should focus on exactly ONE person/company/item.
|
||||
"""
|
||||
|
||||
try:
|
||||
# Use the correct method for ChatGoogle
|
||||
response = await llm.ainvoke(prompt)
|
||||
|
||||
# Debug: Print the response type and content
|
||||
print(f'DEBUG: Response type: {type(response)}')
|
||||
print(f'DEBUG: Response content: {response}')
|
||||
|
||||
# Handle different response types - ChatGoogle returns string content
|
||||
if hasattr(response, 'content'):
|
||||
content = response.content
|
||||
elif isinstance(response, str):
|
||||
content = response
|
||||
elif hasattr(response, 'text'):
|
||||
content = response.text
|
||||
else:
|
||||
# Convert to string if it's some other type
|
||||
content = str(response)
|
||||
|
||||
# Split by newlines and clean up
|
||||
subtasks = [task.strip() for task in content.strip().split('\n') if task.strip()]
|
||||
|
||||
# Remove any numbering or bullets that the LLM might add
|
||||
cleaned_subtasks = []
|
||||
for task in subtasks:
|
||||
# Remove common prefixes like "1. ", "- ", "* ", etc.
|
||||
cleaned = task.lstrip('0123456789.-* ')
|
||||
if cleaned:
|
||||
cleaned_subtasks.append(cleaned)
|
||||
|
||||
return cleaned_subtasks if cleaned_subtasks else simple_split_task(main_task)
|
||||
except Exception as e:
|
||||
print(f'Error creating subtasks: {e}')
|
||||
# Fallback to simple split
|
||||
return simple_split_task(main_task)
|
||||
|
||||
|
||||
def simple_split_task(main_task: str) -> list[str]:
|
||||
"""Simple fallback: split task by common separators"""
|
||||
task_lower = main_task.lower()
|
||||
|
||||
# Try to split by common separators
|
||||
if ' and ' in task_lower:
|
||||
parts = main_task.split(' and ')
|
||||
return [part.strip() for part in parts if part.strip()]
|
||||
elif ', ' in main_task:
|
||||
parts = main_task.split(', ')
|
||||
return [part.strip() for part in parts if part.strip()]
|
||||
elif ',' in main_task:
|
||||
parts = main_task.split(',')
|
||||
return [part.strip() for part in parts if part.strip()]
|
||||
|
||||
# If no separators found, return the original task
|
||||
return [main_task]
|
||||
|
||||
|
||||
async def run_single_agent(task: str, llm, agent_id: int) -> tuple[int, str]:
|
||||
"""Run a single agent and return its result"""
|
||||
print(f'🚀 Agent {agent_id} starting: {task}')
|
||||
print(f' 📝 This agent will focus ONLY on: {task}')
|
||||
print(f' 🌐 Creating isolated browser instance for agent {agent_id}')
|
||||
|
||||
try:
|
||||
# Create agent with its own browser session (separate browser instance)
|
||||
import tempfile
|
||||
|
||||
from browser_use.browser import BrowserSession
|
||||
from browser_use.browser.profile import BrowserProfile
|
||||
|
||||
# Create a unique temp directory for this agent's browser data
|
||||
temp_dir = tempfile.mkdtemp(prefix=f'browser_agent_{agent_id}_')
|
||||
|
||||
# Create browser profile with custom user data directory and single tab focus
|
||||
profile = BrowserProfile()
|
||||
profile.user_data_dir = temp_dir
|
||||
profile.headless = False # Set to True if you want headless mode
|
||||
profile.keep_alive = False # Don't keep browser alive after task
|
||||
|
||||
# Add custom args to prevent new tabs and popups
|
||||
profile.args = [
|
||||
'--disable-popup-blocking',
|
||||
'--disable-extensions',
|
||||
'--disable-plugins',
|
||||
'--disable-images', # Faster loading
|
||||
'--no-first-run',
|
||||
'--disable-default-apps',
|
||||
'--disable-background-timer-throttling',
|
||||
'--disable-backgrounding-occluded-windows',
|
||||
'--disable-renderer-backgrounding',
|
||||
]
|
||||
|
||||
# Create a new browser session for each agent with the custom profile
|
||||
browser_session = BrowserSession(browser_profile=profile)
|
||||
|
||||
# Debug: Check initial tab count
|
||||
try:
|
||||
await browser_session.start()
|
||||
initial_tabs = await browser_session._cdp_get_all_pages()
|
||||
print(f' 📊 Agent {agent_id} initial tab count: {len(initial_tabs)}')
|
||||
except Exception as e:
|
||||
print(f' ⚠️ Could not check initial tabs for agent {agent_id}: {e}')
|
||||
|
||||
# Create agent with the dedicated browser session and disable auto URL detection
|
||||
agent = Agent(task=task, llm=llm, browser_session=browser_session, preload=False)
|
||||
|
||||
# Run the agent with timeout to prevent hanging
|
||||
try:
|
||||
result = await asyncio.wait_for(agent.run(), timeout=300) # 5 minute timeout
|
||||
except TimeoutError:
|
||||
print(f'⏰ Agent {agent_id} timed out after 5 minutes')
|
||||
result = 'Task timed out'
|
||||
|
||||
# Debug: Check final tab count
|
||||
try:
|
||||
final_tabs = await browser_session._cdp_get_all_pages()
|
||||
print(f' 📊 Agent {agent_id} final tab count: {len(final_tabs)}')
|
||||
for i, tab in enumerate(final_tabs):
|
||||
print(f' Tab {i + 1}: {tab.get("url", "unknown")[:50]}...')
|
||||
except Exception as e:
|
||||
print(f' ⚠️ Could not check final tabs for agent {agent_id}: {e}')
|
||||
|
||||
# Extract clean result from the agent history
|
||||
clean_result = extract_clean_result(result)
|
||||
|
||||
# Close the browser session for this agent
|
||||
try:
|
||||
await browser_session.kill()
|
||||
except Exception as e:
|
||||
print(f'⚠️ Warning: Error closing browser for agent {agent_id}: {e}')
|
||||
|
||||
print(f'✅ Agent {agent_id} completed and browser closed: {task}')
|
||||
|
||||
return agent_id, clean_result
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f'Agent {agent_id} failed: {str(e)}'
|
||||
print(f'❌ {error_msg}')
|
||||
return agent_id, error_msg
|
||||
|
||||
|
||||
def extract_clean_result(agent_result) -> str:
|
||||
"""Extract clean result from agent history"""
|
||||
try:
|
||||
# Get the last result from the agent history
|
||||
if hasattr(agent_result, 'all_results') and agent_result.all_results:
|
||||
last_result = agent_result.all_results[-1]
|
||||
if hasattr(last_result, 'extracted_content') and last_result.extracted_content:
|
||||
return last_result.extracted_content
|
||||
|
||||
# Fallback to string representation
|
||||
return str(agent_result)
|
||||
except Exception:
|
||||
return 'Result extraction failed'
|
||||
|
||||
|
||||
async def run_parallel_agents():
|
||||
"""Run multiple agents in parallel on different tasks"""
|
||||
|
||||
# Use Gemini 1.5 Flash
|
||||
llm = ChatGoogle(model='gemini-1.5-flash')
|
||||
|
||||
# Main task to break down - use the simple configuration
|
||||
main_task = MAIN_TASK
|
||||
|
||||
print(f'🎯 Main task: {main_task}')
|
||||
print('🧠 Creating subtasks using LLM...')
|
||||
|
||||
# Create subtasks using LLM
|
||||
subtasks = await create_subtasks(main_task, llm)
|
||||
|
||||
print(f'📋 Created {len(subtasks)} subtasks:')
|
||||
for i, task in enumerate(subtasks, 1):
|
||||
print(f' {i}. {task}')
|
||||
|
||||
print(f'\n🔥 Starting {len(subtasks)} agents in parallel...')
|
||||
print('🔍 Each agent will get its own browser instance with exactly ONE tab')
|
||||
print(f'📊 Expected: {len(subtasks)} browser instances, {len(subtasks)} tabs total')
|
||||
|
||||
# Create tasks for parallel execution
|
||||
agent_tasks = [run_single_agent(task, llm, i + 1) for i, task in enumerate(subtasks)]
|
||||
|
||||
# Run all agents in parallel using asyncio.gather
|
||||
results = await asyncio.gather(*agent_tasks)
|
||||
|
||||
# Print results
|
||||
print('\n' + '=' * 60)
|
||||
print('📊 PARALLEL EXECUTION RESULTS')
|
||||
print('=' * 60)
|
||||
|
||||
for agent_id, result in results:
|
||||
print(f'\n🤖 Agent {agent_id} result:')
|
||||
print(f'Task: {subtasks[agent_id - 1]}')
|
||||
print(f'Result: {result}')
|
||||
print('-' * 50)
|
||||
|
||||
print(f'\n🎉 All {len(subtasks)} parallel agents completed!')
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to run parallel agents"""
|
||||
# Check if Google API key is available
|
||||
api_key = os.getenv('GOOGLE_API_KEY')
|
||||
if not api_key:
|
||||
print('❌ Error: GOOGLE_API_KEY environment variable not set')
|
||||
print('Please set your Google API key to use parallel agents')
|
||||
print('You can set it with: export GOOGLE_API_KEY="your-key-here"')
|
||||
sys.exit(1)
|
||||
|
||||
# Check if API key looks valid (Google API keys are typically 39 characters)
|
||||
if len(api_key) < 20:
|
||||
print(f'⚠️ Warning: GOOGLE_API_KEY seems too short ({len(api_key)} characters)')
|
||||
print('Google API keys are typically 39 characters long')
|
||||
print('Continuing anyway, but this might cause authentication issues...')
|
||||
|
||||
print('🚀 Starting parallel multi-agent example...')
|
||||
print(f'📝 Task: {MAIN_TASK}')
|
||||
print('This will dynamically create agents based on task complexity')
|
||||
print('-' * 60)
|
||||
|
||||
asyncio.run(run_parallel_agents())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,50 @@
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from browser_use import ChatOpenAI
|
||||
from browser_use.agent.service import Agent
|
||||
from browser_use.tools.service import Tools
|
||||
|
||||
# Initialize tools first
|
||||
tools = Tools()
|
||||
|
||||
|
||||
class Model(BaseModel):
|
||||
title: str
|
||||
url: str
|
||||
likes: int
|
||||
license: str
|
||||
|
||||
|
||||
class Models(BaseModel):
|
||||
models: list[Model]
|
||||
|
||||
|
||||
@tools.action('Save models', param_model=Models)
|
||||
def save_models(params: Models):
|
||||
with open('models.txt', 'a') as f:
|
||||
for model in params.models:
|
||||
f.write(f'{model.title} ({model.url}): {model.likes} likes, {model.license}\n')
|
||||
|
||||
|
||||
# video: https://preview.screen.studio/share/EtOhIk0P
|
||||
async def main():
|
||||
task = 'Look up models with a license of cc-by-sa-4.0 and sort by most likes on Hugging face, save top 5 to file.'
|
||||
|
||||
model = ChatOpenAI(model='gpt-4.1-mini')
|
||||
agent = Agent(task=task, llm=model, tools=tools)
|
||||
|
||||
await agent.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user