When you build multi-agent systems in CrewAI, the crewai agent role goal backstory tools tuple defines everything about how an agent behaves. This tutorial walks through constructing a working crew from scratch, showing how each parameter changes the generated prompts and the final output.
Prerequisites
- Python 3.10 or newer
crewaiandcrewai-toolsinstalled (pip install crewai crewai-tools)- An OpenAI API key (or any OpenAI-compatible endpoint) exported as
OPENAI_API_KEY - Familiarity with basic Python and function decorators
If you plan to run the code, set the key first:
export OPENAI_API_KEY="sk-your-key-here"
Step 1: Scaffold the imports
Create a file crew_demo.py and start with the minimal imports. CrewAI wraps LangChain agents but exposes a cleaner API.
from crewai import Agent, Task, Crew, Process
from crewai.tools import tool
Step 2: Define a tool
Tools are callables the agent can invoke. The @tool decorator requires a docstring—that docstring is sent to the LLM as the tool description. Keep it precise.
We’ll use a fake search to avoid external dependencies:
@tool("Fake Web Search")
def fake_search(query: str) -> str:
"""Return simulated search results for a query string."""
return f"Result for '{query}': CrewAI is a framework for orchestrating role-playing agents."
The crewai agent role goal backstory tools design expects tools to be explicit about input and output. The type hint and docstring are not optional. CrewAI uses the function signature to build the JSON schema the LLM sees. If you need two arguments, define them explicitly:
@tool("Sectioned Search")
def sectioned_search(query: str, section: str) -> str:
"""Search a specific section of docs. section must be 'api' or 'guide'."""
return f"[{section}] {query}: dummy result"
The LLM will be prompted to fill both. Missing docstring details cause the model to guess which parameters matter.
Step 3: Create agents with role, goal, backstory, tools
An Agent needs at least role, goal, and backstory. tools is a list; omit it if the agent should reason only.
researcher = Agent(
role="Senior Research Analyst",
goal="Uncover actionable insights about {topic}",
backstory=(
"You have 15 years of experience distilling complex technical "
"subjects into bullet-point briefs for executives."
),
tools=[fake_search],
verbose=True,
)
writer = Agent(
role="Tech Content Writer",
goal="Turn research findings into a tight 100-word summary",
backstory="You are a former newspaper editor who hates jargon.",
tools=[],
verbose=True,
)
The goal field accepts curly-brace templating that CrewAI fills from task inputs. The backstory is prepended to the system prompt to set persona. Neither should contradict the role. Setting verbose=True streams the agent’s thoughts and tool calls to stdout, which is indispensable when debugging prompt quality.
Step 4: Assign tasks and run the crew
Tasks bind an agent to a description and an expected_output format. The crew runs sequentially by default, but you can also use a hierarchical process where a manager agent delegates.
research_task = Task(
description="Investigate {topic} using the provided search tool.",
expected_output="Three bullet points of key findings.",
agent=researcher,
)
write_task = Task(
description="Read the research and write the summary.",
expected_output="A 100-word paragraph.",
agent=writer,
)
crew = Crew(
agents=[researcher, writer],
tasks=[research_task, write_task],
process=Process.sequential,
)
result = crew.kickoff(inputs={"topic": "multi-agent LLM frameworks"})
print(result)
Expected output (truncated for brevity):
[Research Analyst] Task output:
- CrewAI orchestrates multiple agents with defined roles.
- Role-playing improves task focus versus a single monolithic prompt.
- Tools must have clear docstrings for reliable invocation.
[Writer] Task output:
Multi-agent frameworks like CrewAI assign each agent a role, goal, and backstory...
The exact text varies by model, but the structure follows the expected_output contracts. If you omit inputs={"topic": ...} while the goal contains {topic}, CrewAI raises a missing-key error before any LLM call.
Step 5: How goal and backstory shape prompts
CrewAI compiles the agent into a system prompt roughly like:
You are a Senior Research Analyst.
Your goal is: Uncover actionable insights about multi-agent LLM frameworks.
Backstory: You have 15 years of experience...
You have access to these tools: Fake Web Search.
If you change goal to something vague like “do good work”, the agent loses direction and the task output degrades. The crewai agent role goal backstory tools pattern works because the LLM gets a constrained identity. The backstory should reinforce the role, not describe a different job. A researcher with a writer’s backstory produces confused tool usage.
Step 6: Add delegation with a third agent
For larger flows, set allow_delegation=True so an agent can hand subtasks to others.
editor = Agent(
role="Managing Editor",
goal="Ensure the writer's output is publishable",
backstory="You enforce style guides ruthlessly.",
tools=[],
allow_delegation=True,
verbose=True,
)
edit_task = Task(
description="Review the summary and fix any errors.",
expected_output="Final polished paragraph.",
agent=editor,
)
crew = Crew(
agents=[researcher, writer, editor],
tasks=[research_task, write_task, edit_task],
process=Process.sequential,
)
Delegation adds tokens and latency. Use it only when a clear hierarchy exists; otherwise a sequential crew is cheaper and more predictable.
Step 7: Point CrewAI at a different LLM endpoint
CrewAI accepts any LangChain chat model. If you want a single OpenAI-compatible endpoint that fronts 240+ models with automatic fallback when a provider is rate-limited, you can pass a ChatOpenAI instance configured for n4n.ai:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="anthropic/claude-3.5-sonnet",
base_url="https://api.n4n.ai/v1",
api_key="sk-your-n4n-key",
)
researcher = Agent(
role="Senior Research Analyst",
goal="Uncover actionable insights about {topic}",
backstory="Veteran analyst.",
tools=[fake_search],
llm=llm,
verbose=True,
)
The same crewai agent role goal backstory tools definitions work unchanged; only the underlying inference call differs. Per-token metering and provider cache-control hints are handled at the gateway layer, so your agent code stays identical.
Best practices
- Goal: Write it as a measurable outcome, not a verb phrase. “Summarize X into 3 bullets” beats “understand X”.
- Backstory: One or two sentences of relevant persona. Long backstories eat context without improving output.
- Tools: Every tool needs a docstring that states when to use it. The LLM selects tools based on that text, not the function name alone.
- Roles: Use job titles a human would recognize. “SQL Debugger” is clearer than “Agent1”.
- Verbose: Keep it on during development. The small log noise pays back in debug speed.
Common pitfalls
- Forgetting
inputs=inkickoffwhen the goal or task description has{topic}templates throws a key error. - Tool functions that mutate global state cause non-deterministic crews.
- Setting
verbose=Falseduring development hides the exact prompt sent to the LLM, making debugging slower. - Passing a tool without a docstring results in an empty description; the agent will either ignore it or call it incorrectly.
- Overusing
allow_delegationcreates loops where agents bounce tasks without producing output.
Building with the crewai agent role goal backstory tools primitives is straightforward once you treat each agent as a constrained worker with a job description. The code above runs as-is with a valid key.