Home / Articles / Beyond the Chat Box: Architecture of a WhatsApp AI Agent on Google ADK

This article is published in English.

Beyond the Chat Box: Architecture of a WhatsApp AI Agent on Google ADK

How a production WhatsApp assistant combines Google ADK specialist agents, RAG, deterministic tools, session state, human handoff and trajectory-based evaluation.

2371 words

Most AI demos are a text box wired to a model: a question goes in, a fluent answer comes out, and the audience is satisfied. A product that real customers depend on has a longer list of requirements. It has to remember the conversation, work with live company data, take actions, survive failures and hand the customer to a person when automation is not enough.

This article walks through the architecture of a production assistant that runs inside WhatsApp for an EV charging marketplace in Sri Lanka. It serves EV drivers, property owners who might host chargers, and people who simply want to learn about EV charging. By the end you will have a concrete blueprint for the pieces that surround the model, and a set of design rules you can reuse in your own agent, whatever channel it lives in.

Why the channel shapes the product

The team chose WhatsApp so that nobody had to install yet another app just to ask a question. The target users already live in WhatsApp: they know how to send a message, share a location, save a contact and reply to a specific message in a thread. Meeting users there removes the onboarding problem entirely. Instead of teaching people how to use an AI product, the assistant shows up in a tool they already understand.

That choice also imposes constraints that a web chatbot never faces:

  • Replies must be short, because long answers feel heavy on a phone.
  • Messages should arrive at a natural pace rather than as one wall of text.
  • Location requests should use WhatsApp's native location-sharing interface.
  • Contact details should arrive as a real contact card, not as pasted text.

The goal was a conversation that feels native to WhatsApp, not a desktop chatbot squeezed into a messaging app. Keep this in mind for any channel: the interface conventions of the platform are part of the specification, not decoration.

The request path from webhook to reply

The backend is written in Python using Google's Agent Development Kit (ADK) and FastAPI. ADK can generate a ready-made API server for an agent, with built-in support for running agents, managing sessions and streaming results back as events. That API server runs on FastAPI and Uvicorn, which made it straightforward to extend with a custom WhatsApp webhook and business-specific endpoints.

An ADK agent is assembled from four ingredients:

  • A model, such as Gemini.
  • Instructions that define the agent's role and boundaries.
  • Tools that let it look up data or perform actions.
  • A session that holds conversation memory.

Following a single message end to end makes the design concrete:

  1. A user sends a WhatsApp message, and Meta delivers it to the FastAPI webhook.
  2. The backend parses the message and mirrors it into Chatwoot so the support team can follow the conversation.
  3. The backend looks up the session for that user and passes the message to ADK.
  4. ADK runs the coordinator, which routes the request: technical questions to the EV agent, earnings questions to the pricing agent, station searches to the station agent.
  5. The chosen agent either searches the Vertex AI knowledge base or calls a tool, for example to query station data, estimate earnings, send a contact card or request the user's location.
  6. When the answer is ready, FastAPI formats it into short WhatsApp messages, sends them through Meta's API and copies the reply into Chatwoot.

Notice how little of this path is the model itself. Parsing, session lookup, routing, formatting, delivery and mirroring are all ordinary backend work.

One coordinator, three specialists

The assistant is organised as a coordinator that delegates to three specialist agents:

  • An EV infrastructure agent for charger types, installation and regulations.
  • A pricing and business agent for hosting economics.
  • A station search agent for finding charging locations.

The coordinator's only job is to understand intent and hand the conversation to the right specialist. A question about charger types goes to infrastructure, a property owner asking about potential income goes to pricing, and a request for stations near Galle goes to station search. These handoffs are invisible to the user, who experiences a single continuous conversation with one assistant.

The alternative was one large agent with a long system prompt and every tool attached. That works for a prototype, but as tools and conversation flows accumulated, splitting responsibilities paid off in three ways: each prompt stayed small, it was easy to see which agent could call which tool, and each flow could be tested on its own. Separating a coordinator from its specialists, one of the documented agent orchestration patterns, also made change cheaper, since the pricing flow could be revised without touching station search.

There is a cost worth naming. Every routing decision is another model call that can go wrong, so a misrouted question fails in a way a single agent never would. That is one reason the evaluation strategy described later checks which agent and tool were chosen, not just the final wording. For a deeper treatment of how routing and specialist agents fit together, see specialist agents, a keyword router and interrupt in LangGraph.

Grounding answers in a controlled knowledge base

A company assistant must not invent facts, and EV charging is full of details that are easy to get wrong: charger capacities, charging times, installation requirements, regulations, vehicle models and company-specific information. Many of these change over time, and a general model has little knowledge of the local market.

The answer is retrieval-augmented generation. The knowledge is stored as a corpus in Vertex AI's RAG Engine, and before answering a technical question the agent can query it for the most relevant passages. Two separate retrieval paths exist: one for general EV infrastructure, and one for vehicle models and charging-time questions. Splitting the corpus this way keeps results focused, because a question about how long a specific car takes to charge does not compete with regulatory documents for the top results.

The division of labour is the key idea. The model still composes the answer, but the facts come from sources the company controls and can update without retraining anything.

Turning conversation into action

The largest leap was going beyond answering questions. The assistant has tools that do real work. It can:

  • Query the marketplace backend for how many charging stations exist near a given city.
  • Send a native WhatsApp location request.
  • Send the company's contact card.
  • Share the office location as a map pin.
  • Estimate potential earnings for someone considering hosting a charger.
  • Flag a conversation for a human when the user needs one.

Why the earnings calculation is plain Python

The earnings flow shows the most important design rule in the system. The assistant first gathers details about the user's property and charging requirements through conversation. It then computes monthly energy use, expected revenue, electricity cost, monthly profit and yearly profit.

That computation is deterministic Python, not something the language model works out. Models are unreliable at arithmetic, and financial figures shown to a prospective customer must be reproducible and auditable. The model manages the dialogue and extracts the inputs; the code produces the numbers. The result is delivered through a structured WhatsApp template, and the lead is recorded in Google Sheets.

Use the model for language and decisions, and use ordinary software for anything that must be exact.

This rule generalises well beyond pricing: date handling, unit conversion, eligibility checks and anything with legal or financial weight belong in code that the model calls, not in the model's output.

Session state is product logic

A good conversation depends on what came before it. The assistant keeps a separate session for each WhatsApp number, and that session holds the user's name, phone number, the menu option they selected, recent messages, location state and the ID of the last message.

Storing the last message ID is what lets the system make sense of replies to a specific menu message, which WhatsApp users do constantly. Session state also lets multi-step flows continue naturally. A location handoff, for instance, runs like this:

  1. Ask whether the user would like to share their location.
  2. Wait for WhatsApp's location message to arrive.
  3. Save the coordinates to the session.
  4. Resume the setup conversation where it left off.

Without that state, every message would be treated as the start of a new conversation. Memory in an agent is not an optional add-on; it defines how the product behaves, and it deserves the same design attention as any other feature.

Formatting for a small screen

A surprising lesson was that a technically correct answer could still feel wrong purely because of its shape. Models tend to produce long paragraphs, Markdown lists and several ideas packed into one block. That reads fine on a desktop and poorly in a chat bubble.

A dedicated formatting layer handles this. It:

  • Converts Markdown into WhatsApp's own formatting syntax.
  • Detects lists hidden inside running prose.
  • Rebuilds them as readable bullet points.
  • Splits long answers into several shorter messages.

Short delays between the chunks give the reader time to absorb each idea. The point is not to pretend a human is typing; it is pacing. Typing indicators, sent through the Meta API, show that a reply is being prepared.

The assistant also reacts to some messages with an emoji, and does so selectively. A lightweight model such as Flash Lite decides whether a message deserves a reaction, and if it does, the reaction is sent through the Meta API. Emotional, exciting, funny or meaningful messages may get one; routine messages such as "okay", "thanks" or plain instructions usually do not. Using a small, cheap model for this side decision keeps latency and cost low while the main agents handle the substance. Details like these are minor individually, but together they decide whether the product feels natural.

Keeping a path to a human

Automation should never become a wall between customers and the company. Every incoming conversation is synchronised with Chatwoot, and the assistant's replies are added as well, so the support team always sees exactly what was said.

When a user asks for a real person or shows signs of frustration, the assistant triggers a human-help flow that records the request, along with the user's question, for the team to pick up. Because the whole conversation is already mirrored, the person taking over does not need to ask the customer to repeat themselves.

Automation should remove repetitive work, not remove access to a person.

Reliability work outside the conversation

The system also sends outbound WhatsApp template campaigns, which raises a subtle problem: a promotional broadcast should never interrupt someone who is in the middle of a support conversation. Before sending, the system looks at when each recipient last interacted with the assistant. Anyone who was chatting a short while ago is left alone for now: their message is held back, written to SQLite, and made available to a retry endpoint that can send it later.

Around this sit the unglamorous features every service needs:

  • Deduplication of incoming messages, since webhooks can deliver the same event more than once.
  • Health monitoring.
  • Handling for access-token refresh.
  • CORS controls on the HTTP endpoints.
  • Docker-based deployment.
  • Error tracking with Sentry.

None of these would impress anyone in a demo. All of them become essential the moment the demo turns into a service that customers rely on.

Testing behaviour, not just text

Agents are harder to test than ordinary functions because the same input can yield slightly different output. Worse, the failure modes are not obvious from the final text. A reply can read well while having called the wrong tool, or the correct tool can be called and the result communicated badly.

The evaluation suite therefore uses simulated conversations covering infrastructure questions, pricing flows, and conversations that move between specialists. The checks examine the tool trajectory, meaning which agents and tools were invoked in what order, not only the wording of the final response.

Prompt changes are handled the same way. Rather than editing the system prompt by hand, the team experimented with an optimisation loop in which candidate instructions are scored across a fixed set of conversations, including GEPA-based prompt optimisation. Candidates run against training questions, and a separate Gemini-powered rater scores each response for accuracy and personality. This turns prompt work into something closer to engineering: instead of keeping a change because it feels better, you can compare behaviour across a repeatable set of conversations. One caveat applies to any model-as-rater setup: the rater has its own biases, so it is worth spot-checking its scores against human judgement before trusting it for large decisions.

Key takeaways

  • The model is one component; most of the engineering lives in routing, retrieval, state, tools, formatting, failure handling and escalation.
  • Split a growing agent into a coordinator and focused specialists once prompts and tool lists become hard to reason about, and test the routing itself.
  • Ground factual answers in a knowledge base you control, and keep exact work such as calculations in deterministic code.
  • Treat session state and channel-specific formatting as core product logic.
  • Always preserve a visible, low-friction path to a human.
  • Evaluate tool trajectories over a fixed conversation set so prompt changes are measured rather than guessed.

Wrapping a prompt around an API call gives you a demo. A dependable agent is a whole system in which language models, data, tools, product design and conventional backend engineering each do the part they are best at.