BaseLlm: a stand-in model you write
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:
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:
def call(tool, **args):
part = types.Part(function_call=types.FunctionCall(name=tool, args=args))
return types.Content(role="model", parts=[part])This part has the same shape but carries the name of a tool and the arguments the model chose instead of text. Every action an agent takes comes from one of these two kinds of part.
The model itself
One class, one method. The method receives the request ADK built and yields responses.
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.
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.
Plugging the stand-in into an agent
model = PretendModel(replies=[say("Hello."), say("Still here.")])
print(model.model)
print(model.replies[0].parts[0].text)pretend-1 Hello.
Nothing has run an agent yet. This is 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 making a choice rather than replaying a script. Save it as pretend_adk.py beside your lessons.
"""A stand-in model, so every lesson in this course answers the same way twice.
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 typesThen the two helpers from earlier in this lesson, with the docstrings that say why each exists:
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])Then the model. This is the version with the fallback, so the replies list is optional:
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)And the rule it falls back to, which is what lets the early lessons show a model choosing a tool rather than replaying a script:
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.")- 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.