Technology

Building a Data Analyst Agent with Google’s Agent Development Kit (ADK)

Author

Leandro Silva

Date Published

Building a Data Analyst Agent with Google’s Agent Development Kit (ADK)

The promise of "Chat with your data" is enticing and powerful enough that It has been guiding a considerable amount of engineering projects since ChatGPT released. However, anyone who has tried to perform data analysis using a standard LLM knows the limitations: hallucinated numbers, inability to process large datasets, and a lack of mathematical rigor. A real data analyst doesn't just guess the trend or conclude something from a few examples; they write code, execute it against the whole dataset, verify the output, and extract valuable information.

To bridge this gap, we recently embarked on a project to build a robust Data Analysis Agent for logistic analysis. The objective was to design a system that is actually able to receive datasets, analyze them through code and output reports with the results and possible actions to improve them. We built a chatbot equipped with tools to write, execute, and debug its own Python code to interrogate fine-grained data (leveraging Pandas). This capability moves beyond simple summarization; the agent produces high-fidelity visual reports that translate raw numbers into interactive plots and immediate operational actions for the stakeholders. Whether it is identifying a sudden dip in specific inventory turnover or optimizing strategies for a weekend rush, the agent turns data into a decisive roadmap for logistics management.

To build this, we utilized Google’s Agent Development Kit (ADK). As the starting point we built a template based on the data_analysis agent from the public ADK_samples repository, this template will be the primary subject of this deep dive. This framework offers easy integration with the Google Cloud ecosystem, and It shows huge potential for agentic development, although clearly still a work in progress.

In this post, we will give an overview of ADK and the data analysis system we built using agents.


Google Agent Development Kit

Google Agent Development Kit


What is Google ADK?

ADK was designed to make agent development feel more like software development, making it more familiar and easier for developers to create, deploy, and orchestrate agentic architectures which includes simple tasks and complex workflows. It is very similar to LangGraph and thus, a competitor.

While it is optimized for the Google Cloud ecosystem, with support for Gemini and Vertex AI, it is fundamentally model and deployment agnostic. 

You can easily swap out the underlying model or deploy your agent to any containerized environment without rewriting your core logic, a vision we highly praise and which tells us that ADK’s objective is to become the standard Agent development framework. Let’s now take a look at some of the core components provided by ADK.


Agents

At the heart of the framework is, of course, the Agent. Unlike simpler frameworks that treat an agent as a single loop, ADK focuses mainly on guiding developers to use multiple agents with an hierarchical structure. You start by defining a "Root Agent" which can delegate tasks to specialized Subagents For example, a "Manager Agent" might receive a user request and route it to a "Coder Agent" to write Python scripts or a "Reviewer Agent" to validate outputs. This idea allows developers to isolate (thus optimizing) prompt contexts, reuse agents for multiple tasks and design an intuitive and modular system. 

To guide these agents, ADK distinguishes between static and dynamic instructions, which essentially becomes the full prompt provided to the agent. Static instructions are the invariant "system prompt", the persona, policy, and rules that never change (e.g., "You are a Python data analyst"). The instructions are dynamic, often injected at runtime or per-turn, allowing you to steer the agent's behavior for specific tasks without rewriting its core identity. 

Here’s an example on how to create an agent:

1def build_dynamic_agent(User: user) -> Agent:
2 static_prompt="...defining persona..."
3 context_specific_details = get_session_prompt(username=user.name, date=datetime.now())
4 llm_model = get_llm_client()
5 root_agent = Agent(
6 model=llm_model,
7 static_instructions=static_prompt,
8 instructions=context_specific_details,
9 name="example_agent",
10 description="A example agent",
11 tools=[get_info],
12 subagents=[code_executor_agent]
13 )
14 return root_agent

Tools and Execution

Agents interact with the outside world strictly through Tools. When the agent decides it needs to perform a calculation or generate a plot, it has the ability to call a specific tool with the necessary parameters, similarly to a function call. For example, a code execution tool, the agent generates the code and creates a tool call which ADK intercepts, the tool would execute the code in a sandboxed environment, and returns the output (text or artifacts) back to the agent. This separation ensures that the reasoning engine (the LLM) is distinct from the execution engine.

Tools differ from subagents because tools receive concrete known a-priori parameters (e.g. tool plot_data receives a json with values such as data_csv_path), whereas subagents receive a query with a goal and the information needed to get the results (ex: “Write the code to obtain the mean of these values 1,2,3”).

Callbacks and Control Flow

One of the most powerful and helpful features for complex use cases are the Callbacks. ADK provides hooks into every stage of the lifecycle of each agent: 

  • before_agent: This hook captures execution upon receiving a user request (before agent execution begins)
  • after_agent: This hook captures execution after the agent completes its execution (last chance to capture the agent’s behaviour).
  • before_model: This hook captures execution before running the LLM.
  • after_model: This hook captures execution when the LLM finishes.
  • before_tool: This hook captures execution right before executing any tool.
  • after_tool: This hook captures execution after the tools finish.

These can be utilized extensively for safety and observability. For instance, a before_tool callback allows you to inspect the code the agent intends to run before it hits the executor, blocking malicious commands. Conversely, after_model callbacks let you sanitize the LLM's text response before it reaches the user. This granular control is what makes ADK feel like a true engineering framework rather than a black box.

Here’s a diagram which explains how the callbacks are structured:

Agent-Callback-Structure

Agent Callback Structure

Managing Context and State

State management in LLM applications is notoriously messy, often devolving into a bloating string of conversation history. ADK addresses this by creating an object called State for each session.

In this architecture, the Context object has a Session State object which is the mutable dictionary that holds the actual data. This context object is explicitly passed around the agents as they execute. Crucially, ADK provides this context through three distinct lenses depending on the phase of the execution lifecycle: Invocation Context, Callback Context, and Tool Context.

While each of these contexts serves a different stage of the pipeline, they all hold a reference to the same underlying Session State. This means that data is distinct from the viewing angle. The Invocation Context is utilized during the incoming HTTP request to capture metadata like trace headers. The Callback Context is used inside custom callback functions to inspect the agent's progress. The Tool Context is accessed directly inside tool functions to retrieve variables without LLM generation.


Example of accessing the session state (in this case to insert input files in the code executor environment):

1callback_context.session.state["_code_executor_input_files"].append(input_file)

FastAPI and Visual Interface

Beyond the core logic, ADK provides a robust serving layer. It comes pre-packaged with a FastAPI application, meaning you immediately have production-ready endpoints for running queries, checking health, and managing sessions without writing boilerplate server code.

Fast API Endpoints

Fast API Endpoints

Additionally, ADK includes a built-in Visual UI, at endpoint /dev_ui, specifically designed for debugging and tracing. It provides a full Chat Interface supporting both token streaming and non-streaming responses, allowing you to test the conversation flow naturally. 

Generated outputs, such as plots or data files, are presented as the agent’s response when a new artifact is detected and in the artifacts tab. Meanwhile, the Tracing Tab offers a simple but essential view of the execution lifecycle. It allows developers to inspect the agent's internal process in real-time, visualizing the raw prompt sent to the model, the code generated, and the specific tool outputs. While this built-in visibility is helpful for immediate debugging, it does not substitute more complete tracing tools like LangFuse for deep production observability.

ADK UI

ADK UI

A Data analyst agentic system

The core of our project is a chatbot capable of retrieving data from a BigQuery database and transforming it into actionable visual reports. To do this we implemented a multi-agent architecture where three distinct agents handle specific stages of the data pipeline. This approach ensures that the reasoning engine is separated from the execution tools, resulting in higher reliability and security.

At the top of this hierarchy sits the Main Agent, which acts as the central orchestrator. It acts as the entry point for the user's natural language request, determining intent and routing the task to the appropriate specialist. Beneath it are two sub-agents: a SQL Agent and a Python Execution Agent.

The SQL Agent works as a dedicated data retrieval specialist. It receives a natural language request and transforms it into a valid SQL query using the schema provided via a before callback, allowing it to construct precise SQL queries. The agent has access to BigQuery tools which allow It to directly execute SQL queries against the BigQuery dataset. The result is a JSON object containing the raw data which is then returned to serve as the basis for subsequent analysis.

The Python Execution Agent then handles the heavy lifting of analysis and visualization. This agent has access to a VertexAI code executor which allows the agent to run code in a secure and isolated environment that protects the host system but restricts dependencies to a fixed set of pre-installed libraries (ex: pandas, altair, matplotlib, etc …). The agent receives the resulting data from the SQL agent execution along with a natural language question, formulated by the main agent, then it generates the necessary Python code inside a tool_code Markdown block, which is detected and executed by the code executor. The execution output is saved directly into the message history so the main agent can interpret the results and formulate the final report.

Agentic Framework

Agentic Framework

The typical workflow begins with a question from the user, such as "Plot the sales distribution of three most sold products" or “How’s the performance of product B and how to improve it?”. The main agent interprets the user’s question and delegates it to the SQL Agent, if needed. Then the SQL agent formulates and executes the SQL query directly against Google BigQuery to retrieve the results. Once the raw data is retrieved and inserted in the session’s context, the Main Agent, if needed, hands the control over to the Python Execution Agent. This agent receives the data, directly embedded in the prompt or better yet, as input files like CSVs, then it writes the Python code to perform the necessary statistical analysis or plotting and runs it in the VertexAi Sandbox. The sandbox executes the script and returns the final output data by directly inserting it in the message history, and stores artifacts (such as saved PNG charts) in the selected service, completing the cycle from raw query to visual insight reports.

An honest review of ADK

After spending weeks deep in the ADK codebase, we’ve gathered significant insights. It is a powerful tool, but it is clearly still maturing.

The Pros

The most immediate benefit of ADK is that it is model-agnostic and deployment-agnostic which allows deploying and using LLMs from different providers, while retaining excellent Google Cloud support. 

Another great virtue is that it comes with a ready to go FastAPI application out of the box, which saves days of writing boilerplate code. It allows having immediate production-ready endpoints for running queries, checking health, and managing sessions. 

ADK allows fast and easy configuration for handling session, credential, memory and artifact storage services. It handles user sessions natively, allowing us to switch storage backends from in-memory for testing to a production database like PostGres for persistent storage, with just a simple configuration change.

The ability of easily configuring a Code Executor tool for an agent stands out, especially because ADK provides multiple types of out-of-the-box code executors like ContainerCodeExecutor for safe and isolated code execution and the VertexAICodeExecutor which is the cloud-native option. 

The built-in UI interface which includes tracing capabilities, is very complete, simple and straightforward. This makes debugging and demos a lot easier to do. The tracing feature is simple but good enough for simple inspections, lets you see exactly what the agent “sees”, what was the response, what code it wrote, and what the tool output was, what was the latency for each step. It also includes visualizing the artifacts, sessions, state, events and even creating simple evaluation runs, this transparency is quite useful for prompt engineering and debugging.

The Cons:

Our biggest hurdle was the lack of documentation regarding artifacts. While the official documentation is generally helpful, it is virtually silent on how to handle image files generated by code executors or how to provide them as input to the code executor. We had to read ADK’s source code to understand the process for inputting, storing and retrieving generated plots.

Technical stability was also a concern. We encountered a breaking issue where the Code Executors simply stopped working on versions greater than 1.20, forcing us to pin older versions to maintain functionality. There is also an open issue with the FastAPI app where the OpenAI docs don’t render correctly in Swagger.

Additionally, the execution environment of VertexAI is severely locked down. You cannot install custom libraries, so you're stuck with the basic pre-installed libraries, which are good enough for most use cases, but for more complex problems might not suffice. In order to get interactive plots, Plotly is usually the first option, but it is absent in the VertexAI code executor, the mandatory alternative is to use the Altair library which does come pre-installed.

Because ADK’s docs are incomplete, we had to fight our way through ADK’s source code in order to find how to manually inject file contexts into the code runner. Finally, we also found that the default API endpoints were insufficient or not user-friendly for an easy frontend integration for a rich user interface. The standard message history endpoint returned text but ignored generated artifacts, requiring us to build custom FastAPI endpoints to fetch message history that included specific tags for the generated images/files.

Conclusion

Building production-grade complex agentic systems requires more than just prompting an LLM, it demands a robust engineering framework to handle state, security, observability and execution. Google's ADK provides a strong skeleton for this, particularly if you are already invested in the Vertex AI/Google Cloud ecosystem. The obvious documentation gaps and problematic version instability present real hurdles often forcing a "read the source code" mindset and many breakpoints. Some of the problems are easily fixed and could be justified by the fact of ADK still being a recent tool, but I was expecting more from Google. Despite that, ADK’s potential is definitely notable; the ability to effortlessly build an hierarchical multi-agent system with access to secure, sandboxed code executors, while providing a lot of control and configurable components, is honestly quite impressive. It is great for beginner developers to rapidly build simple agentic workflows with readable code, but dragging these agents into a production-ready state still demands a substantial amount of adaptation, tuning, and engineering effort.

It is a step toward standardizing agentic workflows, provided you are willing to navigate its early-stage rough edges to unlock its full potential.


TLDR

If you are building an agentic workflow on Google Cloud, ADK is the logical starting point, but be prepared to handle some issues and adapt your vision to ADK’s capabilities.

Pros (The Good)

Cons (The Bad)

Model & Deployment Agnostic

While optimized for Vertex AI/Gemini, you aren't constrained to them. You can easily swap models or platforms.

Documentation Gaps

Critical features like handling generated files (artifacts/plots) are undocumented. You often have to read the source code to understand how to use them.

Production-Ready Scaffolding

Comes with a pre-packaged FastAPI app. You get health checks, session management, and endpoints immediately without writing boilerplate.

Version Instability

The text notes that Code Executors broke in versions >1.20, forcing developers to pin older versions to keep systems running.

Secure Code Execution

Native support for sandboxed environments (VertexAICodeExecutor). This is critical for data agents preventing them from running malicious code on your server.

Strict Sandbox Limitations

The Vertex AI environment is locked down. You cannot install custom libraries (e.g., you are forced to use Altair instead of Plotly).

Built-in Observability

Includes a generic Dev UI for debugging. You can trace exactly what the agent "saw," the code it wrote, and the tool outputs in real-time.

Frontend Integration Friction

The default API endpoints for message history return text but often ignore generated artifacts (images), requiring you to build custom endpoints.