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 pathNext lesson

A model you can run for free

ADK reaches a model through exactly one method. Anything that provides that method is a model as far as ADK is concerned, so you are about to write one in about twenty lines.

A real model turns your question into an HTTP call. Ours will answer from a list you write, which is all a lesson needs and costs nothing.

What a model has to say

Two kinds of thing, and both are ordinary content objects. Words, which end the turn:

python
from google.genai import types


def say(text):
    return types.Content(role="model", parts=[types.Part(text=text)])

A role and some text. That is what a reply looks like on the way back from any model, real or not.

The other kind is a request to run one of your functions:

python
def call(tool, **args):
    part = types.Part(function_call=types.FunctionCall(name=tool, args=args))
    return types.Content(role="model", parts=[part])

Same shape, different part. Instead of text it carries the name of a tool and the arguments the model chose. Seeing these two side by side is worth more than any diagram: everything an agent does comes from one or the other.

The model itself

One class, one method. The method receives the request ADK built and yields responses.

python
from google.adk.models.base_llm import BaseLlm
from google.adk.models.llm_response import LlmResponse


class PretendModel(BaseLlm):
    model: str = "pretend-1"
    replies: list = []

Two fields. A name, because ADK expects models to have one, and the list of replies you want it to give.

python
    async def generate_content_async(self, llm_request, stream=False):
        turn = getattr(self, "_turn", 0)
        self._turn = turn + 1
        yield LlmResponse(content=self.replies[min(turn, len(self.replies) - 1)])

It counts its turns and hands back the next reply on the list. When the list runs out it repeats the last one, which is what makes a runaway loop easy to demonstrate later.

Using it

Example
model = PretendModel(replies=[say("Hello."), say("Still here.")])

print(model.model)
print(model.replies[0].parts[0].text)

Nothing has run an agent yet. This is just an object holding two prepared replies, ready for the next lesson to drive.

The file to save

The version this course imports has one extra piece: when you give it no replies at all, it falls back to a single rule, so the early lessons can show a model genuinely choosing rather than replaying. Save it as pretend_adk.py beside your lessons.

python
"""A stand-in model, so this course runs with no API key and no cloud project.

ADK reaches a model through one method: generate_content_async. Anything that
provides it is a model as far as ADK is concerned, so twenty lines of Python is
enough to drive the whole agent loop.
"""
from google.adk.models.base_llm import BaseLlm
from google.adk.models.llm_response import LlmResponse
from google.genai import types


def say(text):
    """A reply in words, which ends the agent's turn."""
    return types.Content(role="model", parts=[types.Part(text=text)])


def call(tool, **args):
    """A request to run a tool, which keeps the loop going.

    The parameter is `tool` rather than `name`, so a tool that takes an
    argument called name does not collide with it.
    """
    part = types.Part(function_call=types.FunctionCall(name=tool, args=args))
    return types.Content(role="model", parts=[part])


class PretendModel(BaseLlm):
    """Returns scripted replies in order, or decides by a simple rule."""

    model: str = "pretend-1"
    replies: list = []

    async def generate_content_async(self, llm_request, stream=False):
        turn = getattr(self, "_turn", 0)
        self._turn = turn + 1
        if self.replies:
            reply = self.replies[min(turn, len(self.replies) - 1)]
        else:
            reply = self._decide(llm_request)
        yield LlmResponse(content=reply)

    def _decide(self, llm_request):
        """One rule: if a tool's name appears in the question, ask for it."""
        asked = ""
        for content in reversed(llm_request.contents or []):
            if content.role == "user":
                asked = " ".join(p.text or "" for p in content.parts).lower()
                break
        for tool in (llm_request.tools_dict or {}):
            if tool.split("_")[0] in asked:
                return call(tool, **{})
        return say("I do not know how to do that yet.")
Why bother
Writing the fake model is the fastest way to learn what a real one returns. You now know what a tool call looks like on the wire, which most agent tutorials never show you.
Try it yourself
  • Add a third reply and print it.
  • Give the model no replies at all and read the fallback rule in the file above.

This is what real progress feels like.