Google ADKgoogle-adk 2.8 · Python 3.10+
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
28 small wins to finish your path

Build the support desk

Everything in this course, in one program, on the agent lesson 0 promised.

It is built in four pieces: two tools, a guard, a specialist, and the agent the customer talks to. Each one is something you wrote alone in an earlier lesson.

The two tools

python
def lookup_order(order_id: str, tool_context: ToolContext) -> dict:
    """Look up the delivery status of an existing order by its id."""
    tool_context.state["last_order"] = order_id
    return {"status": "success", "order_id": order_id, "state": "shipped", "date": "3 March"}

The lookup writes the order id into state, so nothing later has to ask the customer again, and no model has to remember it.

python
def refund(amount: int, tool_context: ToolContext) -> dict:
    """Refund an amount in rupees for the order this customer is asking about."""
    return {"status": "success", "refunded": amount,
            "order_id": tool_context.state.get("last_order")}

The refund reads that id back out of state rather than taking it as a parameter. Lesson 10's rule, applied where it matters: the model chooses the amount, never the account.

The guard

python
def needs_a_person(tool, args, tool_context):
    """Refuse anything expensive until a human has approved it."""
    if tool.name == "refund" and args.get("amount", 0) > 100:
        if not tool_context.state.get("approved_by_human"):
            return {"status": "refused", "reason": "refunds over 100 need a person"}

Four lines, from lesson 14. It returns a refusal for a big refund and nothing at all otherwise, which lets every other call through untouched.

Two agents

python
billing = LlmAgent(
    name="billing",
    description="Handles refunds and charges for a customer's order.",
    model=PretendModel(replies=[call("refund", amount=500),
                                say("That refund needs a person to approve it.")]),
    instruction="Handle billing for {customer?}. Use the refund tool.",
    tools=[refund],
    before_tool_callback=needs_a_person,
)

The specialist owns the refund tool and the guard. Nothing else in the desk can issue money.

python
support = LlmAgent(
    name="support",
    description="Answers questions about orders.",
    model=PretendModel(replies=[call("lookup_order", order_id="A17"),
                                say("Order A17 shipped on 3 March."),
                                call("transfer_to_agent", agent_name="billing")]),
    instruction="Help {customer?} with orders. Send billing questions to the billing agent.",
    tools=[lookup_order],
    sub_agents=[billing],
)

The agent the customer talks to. It answers order questions itself and hands billing over, which is lesson 16.

Running two turns

python
runner = InMemoryRunner(agent=support, app_name="desk")
session = await runner.session_service.create_session(
    app_name="desk", user_id="u1", state={"customer": "Asha"})

A session seeded with the customer's name, which both instructions read through their templates.

python
async def turn(text):
    message = types.Content(role="user", parts=[types.Part(text=text)])
    async for event in runner.run_async(user_id="u1", session_id=session.id,
                                        new_message=message,
                                        run_config=RunConfig(max_llm_calls=20)):
        for part in (event.content.parts if event.content else []):
            if part.text:
                print(f"{event.author:<8} {part.text.strip()}")
            elif part.function_response:
                print(f"{event.author:<8} tool -> {part.function_response.response}")

The session is seeded with the customer's name, which both instructions read through their templates, and every run carries the call limit from lesson 15.

Example
print("--- turn 1")
await turn("Where is order A17?")
print("--- turn 2")
await turn("I want my money back, 500 rupees")

done = await runner.session_service.get_session(
    app_name="desk", user_id="u1", session_id=session.id)
print("--- state")
print(dict(done.state))

The first turn is one agent and one tool. The second is the interesting one: support handed over, billing asked for the refund, and the guard refused it before the tool ran. The customer got an explanation rather than an error, and no money moved.

Where each piece came from

PieceLesson
Tools with docstrings the model reads5, 6, 7
ToolContext, so the refund reads the order from state8
An instruction template, {customer?}10
A before_tool_callback that can refuse12
max_llm_calls, so a stuck run stops13
A sub agent and a transfer14
Session state, seeded and written to19, 20

What to change first

  • Set approved_by_human in state and run turn two again. The refund goes through, and nothing else changes.
  • Give billing its own disallow_transfer_to_parent and see what happens when it tries to hand back.
  • Swap one model for a real one and read the events. The scripted answers become decisions.
  • Add the monthly summary as a workflow, with the counting done in a function node.

What this course left out

Left outWhat it is
DeploymentCloud Run, GKE and the managed agent runtime, plus adk deploy
Evaluationadk eval, eval sets, criteria and simulated users
Live and voice agentsThe bidirectional streaming API, audio and video
A2AThe agent to agent protocol, for agents from different vendors
ArtifactsFiles an agent produces or is given, saved through the artifact service
Plugins and callbacks in depthReusable cross-cutting behaviour across a whole app
The integrations catalogueMore than a hundred connectors, from BigQuery to Slack
Other languagesThe same ADK in TypeScript, Go, Java and Kotlin

None of those change what you learned. They are the same agents with more around them, and the documentation reads like a manual once the loop makes sense.

You are done

You can explain what happens between a question and an answer in ADK, write tools a model can choose between, shape the reply into something your code can use, split work across agents and workflows, keep what matters in state, and stop the things that should not happen without a person. That is the whole toolkit, minus the parts you will read when you need them.

Try it yourself
  • Run the capstone, then set approved_by_human and run it again.
  • Put your own project's first agent together this week, with one tool and one guard.

You understood something today that you didn't yesterday.