Quickstart
This guide walks you through wiring a CrewAI multi-agent workflow to Steel so your agents can research the web and produce a structured report.
Prerequisites
Make sure you have:
-
Python 3.11+
-
Steel API key (get one at app.steel.dev)
-
(Optional) any LLM provider keys CrewAI will use (e.g., OpenAI). CrewAI can run with your default env/provider setup.
Step 1: Project setup
Create and activate a virtual environment, then install dependencies:
# Create projectmkdir steel-crewai-startercd steel-crewai-starter# (Recommended) Create & activate a virtual environmentpython3 -m venv .venvsource .venv/bin/activate # On Windows: .venv\Scripts\activate# Create filestouch main.py .env# Install dependenciespip install crewai[tools] steel-sdk python-dotenv pydantic
Create a .env
file with your keys and a default task:
1STEEL_API_KEY=your-steel-api-key-here2OPENAI_API_KEY=your-openai-api-key-here3TASK=Research AI LLMs and summarize key developments
Step 2: Define a Steel-powered web tool for CrewAI
Create a minimal CrewAI BaseTool
that calls Steel’s scraping API. This tool will let agents fetch page content (e.g., as Markdown) during a task
1import os2from typing import List, Optional, Type3from pydantic import BaseModel, Field, ConfigDict, PrivateAttr45from crewai.tools import BaseTool, EnvVar6from steel import Steel78class SteelScrapeWebsiteToolSchema(BaseModel):9url: str = Field(description="Website URL to scrape")1011class SteelScrapeWebsiteTool(BaseTool):12model_config = ConfigDict(arbitrary_types_allowed=True, validate_assignment=True, frozen=False)13name: str = "Steel web scrape tool"14description: str = "Scrape webpages using Steel and return the contents"15args_schema: Type[BaseModel] = SteelScrapeWebsiteToolSchema1617api_key: Optional[str] = None18formats: Optional[List[str]] = None19proxy: Optional[bool] = None2021_steel: Optional[Steel] = PrivateAttr(None)2223# For CrewAI’s packaging & env var hints24package_dependencies: List[str] = ["steel-sdk"]25env_vars: List[EnvVar] = [26EnvVar(name="STEEL_API_KEY", description="API key for Steel services", required=True),27]2829def __init__(self, api_key: Optional[str] = None, formats: Optional[List[str]] = None,30proxy: Optional[bool] = None, **kwargs):31super().__init__(**kwargs)32self.api_key = api_key or os.getenv("STEEL_API_KEY")33if not self.api_key:34raise EnvironmentError("STEEL_API_KEY environment variable or api_key is required")3536self._steel = Steel(steel_api_key=self.api_key)37self.formats = formats or ["markdown"] # return content as Markdown by default38self.proxy = proxy3940def _run(self, url: str):41if not self._steel:42raise RuntimeError("Steel not properly initialized")43# You can set region/proxy based on your needs44return self._steel.scrape(url=url, use_proxy=self.proxy, format=self.formats, region="iad")45
Step 3: Define your Crew (agents + tasks)
Wire the tool into a researcher and a reporting_analyst agent, then compose two tasks into a sequential process.
1import warnings2from datetime import datetime3from textwrap import dedent4from typing import List5from dotenv import load_dotenv67from crewai import Agent, Process, Task8from crewai import Crew as CrewAI9from crewai.agents.agent_builder.base_agent import BaseAgent10from crewai.project import CrewBase, agent, crew, task1112warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")13load_dotenv()1415TASK = os.getenv("TASK") or "Research AI LLMs and summarize key developments"1617@CrewBase18class Crew():19"""Steel + CrewAI example crew"""20agents: List[BaseAgent]21tasks: List[Task]2223@agent24def researcher(self) -> Agent:25return Agent(26role="Instruction-Following Web Researcher",27goal="Understand and execute: {task}. Find, verify, and extract the most relevant information using the web.",28backstory=(29"You specialize in decomposing and executing complex instructions like '{task}', "30"using web research, verification, and synthesis to produce precise, actionable findings."31),32tools=[SteelScrapeWebsiteTool()],33verbose=True,34)3536@agent37def reporting_analyst(self) -> Agent:38return Agent(39role="Instruction-Following Reporting Analyst",40goal="Transform research outputs into a clear, complete report that fulfills: {task}",41backstory=(42"You convert research into exhaustive, well-structured reports that directly address "43"the original instruction '{task}', ensuring completeness and clarity."44),45tools=[SteelScrapeWebsiteTool()],46verbose=True,47)4849@task50def research_task(self) -> Task:51return Task(52description=dedent("""53Interpret and execute the following instruction: {task}54Use the web as needed. Cite and include key sources.55Consider the current year: {current_year}.56"""),57expected_output="A structured set of findings and sources that directly satisfy the instruction: {task}",58agent=self.researcher(),59)6061@task62def reporting_task(self) -> Task:63return Task(64description=dedent("""65Review the research context and produce a complete report that fulfills the instruction.66Ensure completeness, accuracy, and clear structure. Include citations.67"""),68expected_output=(69"A comprehensive markdown report that satisfies the instruction: {task}. "70"Formatted as markdown without '```'"71),72agent=self.reporting_analyst(),73)7475@crew76def crew(self) -> CrewAI:77"""Creates the sequential crew pipeline"""78return CrewAI(79agents=self.agents,80tasks=self.tasks,81process=Process.sequential,82verbose=True,83)84
Step 4: Run your crew
Add a simple main()
to validate API keys, pass inputs, and execute.
1def main():2print("🚀 Steel + CrewAI Starter")3print("=" * 60)45if not os.getenv("STEEL_API_KEY") or os.getenv("STEEL_API_KEY") == "your-steel-api-key-here":6print("⚠️ WARNING: Please set STEEL_API_KEY in your .env")7print(" Get your key at: https://app.steel.dev/settings/api-keys")8return910inputs = {11"task": TASK,12"current_year": str(datetime.now().year),13}1415try:16print("Running crew...")17Crew().crew().kickoff(inputs=inputs)18print("\n✅ Done. (If your task wrote to a file, check your project folder.)")19except Exception as e:20print(f"❌ Error while running the crew: {e}")2122if __name__ == "__main__":23main()24
Run it:
The researcher will use the Steel tool to fetch web content; the reporting_analyst will turn the context into a final report.
Full Example
Complete main.py
you can paste and run:
1import os2import warnings3from datetime import datetime4from textwrap import dedent5from typing import List, Optional, Type67from crewai import Agent, Process, Task8from crewai import Crew as CrewAI9from crewai.agents.agent_builder.base_agent import BaseAgent10from crewai.project import CrewBase, agent, crew, task11from crewai.tools import BaseTool, EnvVar12from dotenv import load_dotenv13from pydantic import BaseModel, ConfigDict, Field, PrivateAttr14from steel import Steel1516warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")17load_dotenv()1819# Replace with your own API keys20STEEL_API_KEY = os.getenv('STEEL_API_KEY') or "your-steel-api-key-here"2122# Replace with your own task23TASK = os.getenv('TASK') or 'Research AI LLMs and summarize key developments'2425class SteelScrapeWebsiteToolSchema(BaseModel):26url: str = Field(description="Website URL")2728class SteelScrapeWebsiteTool(BaseTool):29model_config = ConfigDict(arbitrary_types_allowed=True, validate_assignment=True, frozen=False)30name: str = "Steel web scrape tool"31description: str = "Scrape webpages using Steel and return the contents"32args_schema: Type[BaseModel] = SteelScrapeWebsiteToolSchema33api_key: Optional[str] = None34formats: Optional[List[str]] = None35proxy: Optional[bool] = None3637_steel: Optional[Steel] = PrivateAttr(None)38package_dependencies: List[str] = ["steel-sdk"]39env_vars: List[EnvVar] = [40EnvVar(name="STEEL_API_KEY", description="API key for Steel services", required=True),41]4243def __init__(self, api_key: Optional[str] = None, formats: Optional[List[str]] = None,44proxy: Optional[bool] = None, **kwargs):45super().__init__(**kwargs)46self.api_key = api_key or os.getenv("STEEL_API_KEY")47if not self.api_key:48raise EnvironmentError("STEEL_API_KEY environment variable or api_key is required")4950self._steel = Steel(steel_api_key=self.api_key)51self.formats = formats or ["markdown"]52self.proxy = proxy5354def _run(self, url: str):55if not self._steel:56raise RuntimeError("Steel not properly initialized")57return self._steel.scrape(url=url, use_proxy=self.proxy, format=self.formats, region="iad")5859@CrewBase60class Crew():61"""Crew crew"""62agents: List[BaseAgent]63tasks: List[Task]6465@agent66def researcher(self) -> Agent:67return Agent(68role="Instruction-Following Web Researcher",69goal="Understand and execute: {task}. Find, verify, and extract the most relevant information using the web.",70backstory=(71"You specialize in decomposing and executing complex instructions like '{task}', "72"using web research, verification, and synthesis to produce precise, actionable findings."73),74tools=[SteelScrapeWebsiteTool()],75verbose=True76)7778@agent79def reporting_analyst(self) -> Agent:80return Agent(81role="Instruction-Following Reporting Analyst",82goal="Transform research outputs into a clear, complete report that fulfills: {task}",83backstory=(84"You convert research into exhaustive, well-structured reports that directly address "85"the original instruction '{task}', ensuring completeness and clarity."86),87tools=[SteelScrapeWebsiteTool()],88verbose=True89)9091@task92def research_task(self) -> Task:93return Task(94description=dedent("""95Interpret and execute the following instruction: {task}96Use the web as needed. Cite and include key sources.97Consider the current year: {current_year}.98"""),99expected_output="A structured set of findings and sources that directly satisfy the instruction: {task}",100agent=self.researcher()101)102103@task104def reporting_task(self) -> Task:105return Task(106description=dedent("""107Review the research context and produce a complete report that fulfills the instruction.108Ensure completeness, accuracy, and clear structure. Include citations.109"""),110expected_output="A comprehensive markdown report that satisfies the instruction: {task}. Formatted as markdown without '```'",111agent=self.reporting_analyst(),112)113114@crew115def crew(self) -> CrewAI:116"""Creates the Crew crew"""117return CrewAI(118agents=self.agents,119tasks=self.tasks,120process=Process.sequential,121verbose=True,122)123124def main():125print("🚀 Steel + CrewAI Starter")126print("=" * 60)127128if STEEL_API_KEY == "your-steel-api-key-here":129print("⚠️ WARNING: Please replace 'your-steel-api-key-here' with your actual Steel API key")130print(" Get your API key at: https://app.steel.dev/settings/api-keys")131return132133inputs = {134'task': TASK,135'current_year': str(datetime.now().year)136}137138try:139print("Running crew...")140Crew().crew().kickoff(inputs=inputs)141print("\n✅ Crew finished.")142except Exception as e:143print(f"❌ An error occurred while running the crew: {e}")144145if __name__ == "__main__":146main()147
Customizing your crew’s task
Try changing the TASK
to drive different behaviors:
1TASK = "Visit https://docs.steel.dev and summarize the Sessions API lifecycle with citations."2# or3TASK = "Find the latest research trends in open-weights LLMs and produce a bullet summary with 5 sources."4# or5TASK = "Compare two AI agent frameworks and write a short pros/cons table with links."
Next steps
-
Session Lifecycles: https://docs.steel.dev/overview/sessions-api/session-lifecycle
-
Steel Sessions API: https://docs.steel.dev/overview/sessions-api/overview
-
Steel Python SDK: https://github.com/steel-dev/steel-python
-
CrewAI Docs: https://docs.crewai.com