Homework — Three CrewAI Exercises
Lesson 09 · Building Your Own Agents · ~45 min
All three tasks below extend hello_crew.py from the Lab 09 walk-through —
the two-agent crew (researcher + writer) running under
Process.sequential against a single topic, with both agents pointed at
anthropic/claude-3-5-sonnet-latest via LiteLLM. Keep that file open as your
base; each task is a focused extension that exercises a different CrewAI primitive
(per-agent models, structured task outputs, shared backstory composition).
Before you start
Make sure python hello_crew.py runs end-to-end on the lab’s base
topic and prints both the researcher’s bullets and the writer’s short
brief. Confirm ANTHROPIC_API_KEY is exported and that
pip show crewai reports version 0.86.x. If anything is off,
revisit the lab guide before tackling the homework — debugging the extensions
is much easier on a working baseline.
Task 1 — Swap the writer’s model to a cheaper one
Currently both agents share the same model
(anthropic/claude-3-5-sonnet-latest). The researcher’s job involves
real judgement — deciding which facts about the topic matter and how to phrase
them — but the writer’s job is mostly transformation: take the
researcher’s bullets and rewrite them as a 2-3 sentence brief. That’s a
classic case for a smaller model.
Change the writer’s llm= to Haiku and leave the researcher on
Sonnet:
writer = Agent(
role="Technical Writer",
goal="Write a 2-3 sentence brief based on the research",
backstory="You are a concise technical writer. You never invent facts.",
llm="anthropic/claude-3-5-haiku-latest", # was sonnet
verbose=True,
)
Re-run the script against the same topic you used in the lab. The researcher’s output should be unchanged (same model, same prompt); the writer’s brief may read slightly differently but should still be coherent and faithful to the bullets. Then write a 2-3-sentence note in a comment at the top of the file explaining (a) which roles in a crew are the best candidates for cheaper models and (b) why the same logic does not apply to roles that do real judgement (planners, reviewers).
✓ Self-check: the script still completes end-to-end against the same topic; the writer’s brief is still grammatical and on-topic; your 2-3-sentence note explains the “judgement vs. transformation” distinction in your own words. If you’re tracking spend in the Anthropic console, the total cost of the run should drop noticeably compared to the all-Sonnet baseline.
Task 2 — Quiet the noise and print structured per-task outputs
The default verbose=True on both agents and the crew is great for
debugging but useless in production — it floods stdout with framework chatter
and makes it hard to extract just the artefacts you care about. Set
verbose=False everywhere:
researcher = Agent(role="...", goal="...", backstory="...", llm=MODEL, verbose=False)
writer = Agent(role="...", goal="...", backstory="...", llm=MODEL, verbose=False)
crew = Crew(
agents=[researcher, writer],
tasks=[research_task, write_task],
process=Process.sequential,
verbose=False,
)
After crew.kickoff(...) returns, print each task’s output
explicitly using the crew.tasks[N].output accessor:
result = crew.kickoff(inputs={"topic": "your topic here"})
print(f"Researcher said:\n{crew.tasks[0].output}\n")
print(f"Writer said:\n{crew.tasks[1].output}\n")
Re-run. The stdout should now be clean — just your two labelled outputs, no framework noise. This is the pattern you’d use in production to log structured artefacts to your own backend (one log entry per task, indexed by task name) instead of trying to parse CrewAI’s verbose stream.
✓ Self-check: no [DEBUG] or framework chatter in stdout; the script prints exactly two labelled blocks — the researcher’s bullets and the writer’s brief; crew.tasks[0].output matches what the researcher produced and crew.tasks[1].output matches what the writer produced.
Don’t read .output before kickoff() returns
crew.tasks[N].output is populated only after the
corresponding task has finished running — reading it before
kickoff() returns will give you None (or, worse, a stale
value from a previous run if you kicked off twice). If you need per-task hooks
during execution (e.g., to stream a partial result to a UI), use
task-level callbacks instead.
Task 3 — Inject a “house style” into both agents via shared backstory
Crews often need to follow shared rules — an editorial style guide, a
compliance restriction, a tone-of-voice contract. CrewAI 0.86 has no first-class
“crew-wide system prompt” primitive, but the supported pattern is simple:
compose the shared text into each agent’s backstory. Define a
constant and append it to both agents:
HOUSE_STYLE = (
"Write in British English. Use Oxford commas. "
"Never use the phrase 'in conclusion'."
)
researcher = Agent(
role="Senior Researcher",
goal="Find 3 short factual statements about the topic",
backstory=(
"You are a meticulous researcher. You prefer primary sources. "
+ HOUSE_STYLE
),
llm=MODEL,
verbose=True,
)
writer = Agent(
role="Technical Writer",
goal="Write a 2-3 sentence brief based on the research",
backstory=(
"You are a concise technical writer. You never invent facts. "
+ HOUSE_STYLE
),
llm=MODEL,
verbose=True,
)
Re-run with a topic that gives the writer enough rope to break the style rules (anything that tempts a model to write “in conclusion” at the end of the brief works well — current events, summaries, retrospectives). Inspect the writer’s output: spellings should be British (colour, organise), commas should follow the Oxford convention, and the forbidden phrase should be absent. The researcher’s bullets have less surface area for style to show through, but the orthography should still match.
✓ Self-check: the writer’s brief uses British spellings; commas are placed before the final “and” in any list of three or more items; the phrase “in conclusion” does not appear anywhere in the output; the HOUSE_STYLE constant is defined exactly once and referenced from both agents’ backstories (not duplicated inline).
Don’t subclass Agent to encode shared style
It’s tempting to create a StyledAgent(Agent) subclass that
auto-appends HOUSE_STYLE to backstory in
__init__. Resist — that fights the framework and breaks every
time CrewAI changes its Agent internals (which it does often at this
version). A plain make_agent(role, goal, role_specific_backstory)
helper function that does the string composition is the path of least resistance
and survives upgrades unchanged.