chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
MODEL_API_KEY=
|
||||
OPENAI_API_KEY=
|
||||
@@ -0,0 +1,82 @@
|
||||
# Web Browsing Multi-Agent Workflow
|
||||
|
||||
We're building a local, multi-agent browser automation system powered by CrewAI and Stagehand. It leverages autonomous agents to plan, execute, and synthesize web automation tasks using natural language queries.
|
||||
|
||||
How It Works:
|
||||
|
||||
1. **Query Submission**: User submits natural language query describing desired browser automation task.
|
||||
2. **Task Planning**: A **Planner Agent** interprets query and generates structured automation plan, including website URL and task description.
|
||||
3. **Plan Execution**: **Browser Automation Agent** executes plan using Stagehand Tool, which autonomously navigates web pages, interacts with elements, and extracts relevant content.
|
||||
4. **Response Synthesis**: **Synthesis Agent** takes raw output from execution phase and converts it into clean user-friendly response.
|
||||
5. **Final Output**: User receives a polished result containing results of web automation task, such as extracted data or completed actions.
|
||||
|
||||
We use:
|
||||
|
||||
- [Stagehand](https://docs.stagehand.dev/) for open-source AI browser automation
|
||||
- [CrewAI](https://docs.crewai.com) for multi-agent orchestration
|
||||
|
||||
## Set Up
|
||||
|
||||
Follow these steps one by one:
|
||||
|
||||
### Create .env File
|
||||
|
||||
Create a `.env` file in the root directory of your project with the following content:
|
||||
|
||||
```env
|
||||
OPENAI_API_KEY=<your_openai_api_key>
|
||||
MODEL_API_KEY=<your_openai_api_key>
|
||||
```
|
||||
|
||||
### Download Ollama
|
||||
|
||||
Download and install [Ollama](https://ollama.com/download) for your operating system. Ollama is used to run large language models locally.
|
||||
|
||||
For example, on linux, you can use the following command:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://ollama.com/install.sh | sh
|
||||
```
|
||||
|
||||
Pull the required model:
|
||||
|
||||
```bash
|
||||
ollama pull gpt-oss
|
||||
```
|
||||
|
||||
### Install Playwright
|
||||
|
||||
Install Playwright for browser automation from the official website: [Playwright](https://playwright.dev/docs/intro).
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
uv sync
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
This command will install all the required dependencies for the project. Additionally, make sure to install the necessary browser binaries by running:
|
||||
|
||||
```bash
|
||||
playwright install
|
||||
```
|
||||
|
||||
## Run CrewAI Agentic Workflow
|
||||
|
||||
To run the CrewAI flow, execute the following command:
|
||||
|
||||
```bash
|
||||
python flow.py
|
||||
```
|
||||
|
||||
Running this command will start the CrewAI agentic workflow, which will handle the multi-agent orchestration for web browsing tasks using Stagehand as AI powered browser automation.
|
||||
|
||||
## 📬 Stay Updated with Our Newsletter!
|
||||
|
||||
**Get a FREE Data Science eBook** 📖 with 150+ essential lessons in Data Science when you subscribe to our newsletter! Stay in the loop with the latest tutorials, insights, and exclusive resources. [Subscribe now!](https://join.dailydoseofds.com)
|
||||
|
||||
[](https://join.dailydoseofds.com)
|
||||
|
||||
## Contribution
|
||||
|
||||
Contributions are welcome! Feel free to fork this repository and submit pull requests with your improvements.
|
||||
@@ -0,0 +1,161 @@
|
||||
from typing import Dict, Any
|
||||
from dotenv import load_dotenv
|
||||
from pydantic import BaseModel
|
||||
from crewai import Agent, Task, Crew
|
||||
from crewai import LLM
|
||||
from crewai.tools import tool
|
||||
from crewai.flow.flow import Flow, start, listen
|
||||
from stagehand_tool import browser_automation
|
||||
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# Define our LLMs for providing to agents
|
||||
planner_llm = LLM(model="ollama/gpt-oss")
|
||||
automation_llm = LLM(model="openai/gpt-4")
|
||||
response_llm = LLM(model="ollama/gpt-oss")
|
||||
|
||||
|
||||
@tool("Stagehand Browser Tool")
|
||||
def stagehand_browser_tool(task_description: str, website_url: str) -> str:
|
||||
"""
|
||||
A tool that allows to interact with a web browser.
|
||||
The tool is used to perform browser automation tasks powered by Stagehand capabilities.
|
||||
|
||||
Args:
|
||||
task_description (str): The task description for the agent to perform.
|
||||
website_url (str): The URL of the website to interact and navigate to.
|
||||
|
||||
Returns:
|
||||
str: The result of the browser automation task.
|
||||
"""
|
||||
return browser_automation(task_description, website_url)
|
||||
|
||||
|
||||
class BrowserAutomationFlowState(BaseModel):
|
||||
query: str = ""
|
||||
result: str = ""
|
||||
|
||||
|
||||
class AutomationPlan(BaseModel):
|
||||
task_description: str
|
||||
website_url: str
|
||||
|
||||
|
||||
class BrowserAutomationFlow(Flow[BrowserAutomationFlowState]):
|
||||
"""
|
||||
A CrewAI Flow to intelligently handle browser automation tasks
|
||||
through specialized agents using Stagehand tools.
|
||||
"""
|
||||
|
||||
@start()
|
||||
def start_flow(self) -> Dict[str, Any]:
|
||||
print(f"Flow started with query: {self.state.query}")
|
||||
return {"query": self.state.query}
|
||||
|
||||
@listen(start_flow)
|
||||
def plan_task(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
|
||||
print("--- Using Automation Planner to plan the task ---")
|
||||
|
||||
planner_agent = Agent(
|
||||
role="Automation Planner Specialist",
|
||||
goal="Plan the automation task for the user's query.",
|
||||
backstory="You are a browser automation specialist that plans the automation task for the user's query.",
|
||||
llm=planner_llm,
|
||||
)
|
||||
|
||||
plan_task = Task(
|
||||
description=f"Analyze the following user query and determine the website url and the task description: '{inputs['query']}'.",
|
||||
agent=planner_agent,
|
||||
output_pydantic=AutomationPlan,
|
||||
expected_output=(
|
||||
"A JSON object with the following format:\n"
|
||||
"{\n"
|
||||
' "task_description": "<brief description of what needs to be done>",\n'
|
||||
' "website_url": "<URL of the target website>"\n'
|
||||
"}"
|
||||
),
|
||||
)
|
||||
|
||||
crew = Crew(agents=[planner_agent], tasks=[plan_task], verbose=True)
|
||||
result = crew.kickoff()
|
||||
|
||||
# Add a fallback check to ensure we always have a valid website URL
|
||||
website_url = result.pydantic.website_url
|
||||
if not website_url or website_url.lower() in ["", "none", "null", "n/a"]:
|
||||
result["website_url"] = "https://www.google.com"
|
||||
|
||||
return {
|
||||
"task_description": result["task_description"],
|
||||
"website_url": website_url,
|
||||
}
|
||||
|
||||
@listen(plan_task)
|
||||
def handle_browser_automation(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
|
||||
print("--- Delegating to Browser Automation Specialist ---")
|
||||
|
||||
automation_agent = Agent(
|
||||
role="Browser Automation Specialist",
|
||||
goal="Execute browser automation using the Stagehand tool",
|
||||
backstory="You specialize in executing user-defined automation tasks on websites using the Stagehand tool.",
|
||||
tools=[stagehand_browser_tool],
|
||||
llm=automation_llm,
|
||||
)
|
||||
|
||||
automation_task = Task(
|
||||
description=(
|
||||
f"Perform the following browser automation task:\n\n"
|
||||
f"Website: {inputs['website_url']}\n"
|
||||
f"Task: {inputs['task_description']}\n\n"
|
||||
f"Use the Stagehand tool to complete this task accurately."
|
||||
),
|
||||
agent=automation_agent,
|
||||
expected_output="A string containing the result of executing the browser automation task using Stagehand.",
|
||||
markdown=True,
|
||||
)
|
||||
|
||||
crew = Crew(agents=[automation_agent], tasks=[automation_task], verbose=True)
|
||||
result = crew.kickoff()
|
||||
return {"result": str(result)}
|
||||
|
||||
@listen(handle_browser_automation)
|
||||
def synthesize_result(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
|
||||
print("--- Synthesizing Final Response ---")
|
||||
|
||||
synthesis_agent = Agent(
|
||||
role="Response Synthesis Specialist",
|
||||
goal="Craft a clear, concise, and user-friendly response based on the tool calling output from the browser automation specialist.",
|
||||
backstory="An expert in communication and assistance.",
|
||||
llm=response_llm,
|
||||
)
|
||||
|
||||
synthesis_task = Task(
|
||||
description=(
|
||||
f"Review the following browser automation specialist result and present it as a generalized, coherent response for the end user:\n\n"
|
||||
f"{inputs['result']}"
|
||||
),
|
||||
expected_output="A concise, user-facing response of the browser automation result.",
|
||||
agent=synthesis_agent,
|
||||
)
|
||||
|
||||
crew = Crew(agents=[synthesis_agent], tasks=[synthesis_task], verbose=True)
|
||||
final_result = crew.kickoff()
|
||||
return {"result": str(final_result)}
|
||||
|
||||
|
||||
# Usage example
|
||||
async def main():
|
||||
flow = BrowserAutomationFlow()
|
||||
flow.state.query = "Extract the top contributor's username from this GitHub repository: https://github.com/browserbase/stagehand"
|
||||
result = await flow.kickoff_async()
|
||||
|
||||
print(f"\n{'='*50}")
|
||||
print(f"FINAL RESULT")
|
||||
print(f"{'='*50}")
|
||||
print(result["result"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,5 @@
|
||||
Extract the top contributor's username from this GitHub repository: https://github.com/browserbase/stagehand
|
||||
Extract names and descriptions of 5 companies from this website: https://www.aigrant.com
|
||||
Find the current stock price of Tesla
|
||||
What was the score of the last Lakers game?
|
||||
Play a game of 2048
|
||||
@@ -0,0 +1,11 @@
|
||||
[project]
|
||||
name = "web-browsing-agent"
|
||||
version = "0.1.0"
|
||||
description = "Web Browsing Multi-Agent Workflow Utilizing CrewAI and Stagehand"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"crewai>=0.141.0",
|
||||
"nest-asyncio>=1.6.0",
|
||||
"stagehand>=0.4.0",
|
||||
]
|
||||
@@ -0,0 +1,55 @@
|
||||
import os
|
||||
from stagehand import Stagehand, StagehandConfig
|
||||
|
||||
import nest_asyncio
|
||||
import asyncio
|
||||
|
||||
# Allow nested loops in async (for environments like Jupyter or already-running loops)
|
||||
nest_asyncio.apply()
|
||||
|
||||
|
||||
def browser_automation(task_description: str, website_url: str) -> str:
|
||||
"""Performs automated browser tasks using AI agent capabilities."""
|
||||
|
||||
async def _execute_automation():
|
||||
stagehand = None
|
||||
|
||||
try:
|
||||
config = StagehandConfig(
|
||||
env="LOCAL",
|
||||
model_name="gpt-4o",
|
||||
self_heal=True,
|
||||
system_prompt="You are a browser automation assistant that helps users navigate websites effectively.",
|
||||
model_client_options={"apiKey": os.getenv("MODEL_API_KEY")},
|
||||
verbose=1,
|
||||
)
|
||||
|
||||
stagehand = Stagehand(config)
|
||||
await stagehand.init()
|
||||
|
||||
agent = stagehand.agent(
|
||||
model="computer-use-preview",
|
||||
provider="openai",
|
||||
instructions="You are a helpful web navigation assistant that helps users find information. Do not ask follow-up questions.",
|
||||
options={"apiKey": os.getenv("MODEL_API_KEY")},
|
||||
)
|
||||
|
||||
await stagehand.page.goto(website_url)
|
||||
|
||||
agent_result = await agent.execute(
|
||||
instruction=task_description,
|
||||
max_steps=20,
|
||||
auto_screenshot=True,
|
||||
)
|
||||
|
||||
result_message = (
|
||||
agent_result.message or "No specific result message was provided."
|
||||
)
|
||||
return f"Browser Automation Tool result:\n{result_message}"
|
||||
|
||||
finally:
|
||||
if stagehand:
|
||||
await stagehand.close()
|
||||
|
||||
# Run async in a sync context
|
||||
return asyncio.run(_execute_automation())
|
||||
Generated
+3030
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user