Lesson 09 CrewAI Intro
Theme:

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.

View solutions →