Lesson 04 A Tool-Heavy Research Assistant
Theme:
Lesson 04 · Slide 1

Lesson 04 — A Tool-Heavy Research Assistant

The Claude Agent SDK at its sweet spot: 5 tools, unbounded tool-call order, ~120 lines of Python.

Agents Module 2 Claude Agent SDK Tool-Heavy

Lesson 04 · Slide 2

Why this is the SDK's sweet spot

Hits every condition on L03 Slide 11's “Pick it when” list.

Lesson 04 · Slide 3

The assistant we're building

Goal

Given a research question, return a short report (intro + 3 bullet findings + sources) backed by web-search and document-fetch tools.

Real example question: “What changed in Linux kernel 6.14's networking stack?”

The agent decides on its own which sources to consult, in what order, and when it has enough material to write the report.

Lesson 04 · Slide 4

The five tools

ToolWhat it does (signature)
pkb.search(query)Search the personal knowledge base (mocked dict of pre-seeded docs).
pkb.fetch(doc_id)Fetch the full text of a single doc.
web_search(query)Search the web (mocked — returns a list of {title, url, snippet}).
web_fetch(url)Fetch a URL's text (mocked — returns the snippet).
write_report(intro, bullets, sources)Final answer tool: structures the report.
Lesson 04 · Slide 5

Why mocked, not real?

Notes: Keep the tool surface stable; swap the implementation later. Same lesson applies in production.
Lesson 04 · Slide 6

The system prompt

You are a research assistant. To answer a question:
1. Check `pkb.search` first; fetch any promising doc with `pkb.fetch`.
2. If you need more, use `web_search`; fetch with `web_fetch`.
3. When you have enough material, call `write_report(intro, bullets, sources)`.
Always cite sources by URL or doc_id. Be concise.

The prompt does not prescribe an exact tool order — it gives the LLM a strategy. The LLM picks the actual sequence per question.

Lesson 04 · Slide 7

The write_report final-answer tool pattern

Lesson 04 · Slide 8

The @tool definitions

@tool
def pkb_search(query: str) -> str:
    """Search the personal knowledge base by substring. Returns
    a list of '{doc_id}: {title} - {one-line snippet}' lines."""
    hits = [f"{k}: {v['title']} - {v['snippet']}"
            for k, v in PKB.items() if query.lower() in v['text'].lower()]
    return "\n".join(hits) if hits else "no matches"

@tool
def pkb_fetch(doc_id: str) -> str:
    """Fetch the full text of a PKB doc by id."""
    return PKB.get(doc_id, {}).get("text", f"unknown doc: {doc_id}")

Two of the five shown here; the others (web_search, web_fetch, write_report) follow the same pattern — one function, one decorator, one docstring.

Lesson 04 · Slide 9

The agent assembly

from anthropic import Anthropic
from anthropic.agents import Agent

agent = Agent(
    client=Anthropic(),
    model="claude-3-5-sonnet-latest",
    system=SYSTEM_PROMPT,
    tools=[pkb_search, pkb_fetch, web_search, web_fetch, write_report],
    max_turns=15,
    max_cost_usd=0.50,
    on_step=log_step,
)
report = agent.run(question)
print(report.final_text)

~12 lines of glue around the tool definitions. The agent loop, message-dance, and stop conditions are all inside Agent.run().

Lesson 04 · Slide 10

A real run trace

[step 1] tools=[pkb_search]            cost=$0.003
[step 2] tools=[pkb_fetch]             cost=$0.007
[step 3] tools=[web_search]            cost=$0.011
[step 4] tools=[web_fetch, web_fetch]  cost=$0.018
[step 5] tools=[write_report]          cost=$0.024
[step 6] tools=[]                      cost=$0.026  (end_turn)

Note: turn 4 has parallel tool calls — the SDK runs both web_fetch calls concurrently, and the LLM gets both results back together.

Lesson 04 · Slide 11

Parallel tool calls — free with the SDK

Notes: The agent doesn't always parallelise — it depends on whether the LLM decides the calls are independent. But when it does, you save real wall-clock time.
Lesson 04 · Slide 12

Cost and latency profile

For a research assistant that gets called interactively a few times an hour, this is a comfortable budget.

Lesson 04 · Slide 13

What's left to wire (Module 6 hook)

Cross-framework scorecard

Plan F (Module 6) wires this exact agent.py into agents/_eval/run_all.py as the Claude SDK row of the cross-framework scorecard.

The same shape of agent gets rebuilt in LangGraph (M3), OpenAI SDK (M4), and CrewAI (M5) so you can compare cost / latency / LoC apples-to-apples.

Today's agent.py is the canonical Claude entry.

Lesson 04 · Slide 14

Recap & what's next

Coming up: Module 3 — LangGraph. Same shape of agent, but the control flow is a drawn graph.
1 / ?