BigQuery Conversational Agent on Vertex AI with Google ADK: Six Traps
Build a natural-language BigQuery agent with Google ADK and Gemini, deploy it to Vertex AI Agent Engine, and avoid the Python, dependency, billing and IAM traps.
This article is published in English.
Sources: Building a BigQuery Conversational Agent on Vertex AI with ADK and the traps nobody warns you about
The problem: simple questions stuck in a queue
Business teams constantly need numbers that already sit in the data warehouse. How many orders shipped last month? Which product sells best? Each question turns into a ticket, then a manual extraction, then a round of back-and-forth that can eat hours or days, even though the answer is a single query away in BigQuery.
The alternative is a conversational agent. A user types a question in plain English, the agent works out the SQL itself, runs it against BigQuery and returns the result. No ticket, no extraction, no SQL skills required. Making that work on a laptop is the easy part. Getting it deployed properly on Google Cloud is where the hidden problems show up, including Python version conflicts, undeclared dependencies and an IAM surprise that only appears once the agent runs in the cloud.
The stack for this build has four parts:
- Google ADK, the Agent Development Kit framework that orchestrates the agent.
- Gemini 2.5 Flash on Vertex AI as the reasoning engine.
- The BigQuery toolset that ADK ships for inspecting schemas and executing SQL.
- Vertex AI Agent Engine, the managed runtime used for deployment.
Setting up the project
A fresh Python project with ADK installed is the foundation, and on paper it takes three commands: create a virtual environment, activate it and pip-install the package.
python3 -m venv .venv
source .venv/bin/activate
pip install google-adk
Trap 1: the Python version. ADK needs Python 3.10 or newer. A Mac that still carries the system Python 3.9 appears fine until the agent starts, and then it prints a pile of FutureWarning messages followed by a hard error saying MCP requires Python 3.10 or above. On macOS, skip any fight with the system interpreter and install a clean one through Homebrew, as in the next block.
brew install python@3.12
python3.12 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install google-adk
Check the result with python --version, where you want 3.12.x, and make sure the shell prompt begins with (.venv). After that, generate the agent scaffold with the ADK command line.
adk create bq_agent_app
Trap 2: folder names. The agent's folder has to be a valid Python identifier, which means letters, digits and underscores only. A hyphenated name like bq-agent-app makes the deploy fail with an Invalid agent name error. The underscore form bq_agent_app works. It's a tiny detail that can easily burn ten minutes.
The whole agent in one file
This is where ADK pays off. Logic that would otherwise need hundreds of lines of orchestration code fits into one declarative file. The file starts with imports for Google auth, the Agent class, the BigQuery toolset and its credential and tool configuration classes.
import google.auth
from google.adk.agents import Agent
from google.adk.tools.bigquery import BigQueryToolset, BigQueryCredentialsConfig
from google.adk.tools.bigquery.config import BigQueryToolConfig
The remainder reuses Application Default Credentials, builds the credentials config, configures the toolset and defines the agent itself, including its model, instruction and tool filter.
# Reuse your Application Default Credentials
application_default_credentials, project = google.auth.default()
credentials_config = BigQueryCredentialsConfig(
credentials=application_default_credentials
)# Google's first-party BigQuery toolset
bigquery_toolset = BigQueryToolset(
credentials_config=credentials_config,
bigquery_tool_config=BigQueryToolConfig(
compute_project_id="your-project-id", # bills the query jobs
),
tool_filter=[
"list_dataset_ids",
"get_dataset_info",
"list_table_ids",
"get_table_info",
"execute_sql",
],
)root_agent = Agent(
model="gemini-2.5-flash",
name="bq_data_assistant",
description="Answers natural-language questions over BigQuery data.",
instruction=(
"You are a data assistant for business teams. "
"When asked a question, first inspect the schema of the relevant "
"tables (get_table_info) BEFORE writing any SQL, so you use real "
"column names and never invent them. "
"You always run your query jobs in the configured billing project, "
"but you CAN read data from any accessible dataset, including public "
"ones. Reading a public dataset from your billing project is normal "
"and allowed, never refuse for that reason. "
"Write standard BigQuery SQL, execute it with execute_sql, then "
"return a clear answer with the number and, in one line, the query used. "
"If a question is ambiguous, ask for clarification before running."
),
tools=[bigquery_toolset],
)
Three points in that file carry the real idea of an agent.
First, thinking and acting are split. Gemini handles the reasoning, but by itself it has no knowledge of your data and no route to BigQuery. The BigQueryToolset provides the hands, meaning tools the model may decide to call, such as checking a schema or running a query. It all runs as a loop. The model considers the question and chooses a tool, ADK runs it, the output comes back, and the model picks its next move until it can answer. That loop is the dividing line between an agent and a chatbot. A chatbot talks, while an agent acts on your systems within a controlled cycle.
Second, the instruction is the main lever. The line telling the agent to inspect the schema before writing SQL is what keeps it from making up column names. Behavior is shaped through the prompt and not through code, and that matters more than it first appears.
Third, tool_filter is the least-privilege control, exposing only the tools the agent really needs. For production on sensitive data you'd trim it to read-only so the agent can run queries but can't modify anything.
Running it locally
ADK gives two ways to run an agent. adk run bq_agent_app opens a plain terminal chat, and adk web starts a local web interface with a chat pane plus an Events panel listing each tool call. The web UI is nicer, though adk run is the better pick for troubleshooting since it shows errors openly instead of failing silently.
Trap 3: dependencies the toolset never declares
Things do break at this stage, and this one costs the most back-and-forth. The BigQuery toolset is described as built into ADK, yet it quietly relies on packages that google-adk doesn't install. The first launch fails with ModuleNotFoundError: No module named 'google.cloud'. Installing google-cloud-bigquery leads to a different failure, cannot import name 'dataplex_v1', and then a request for db-dtypes. That's three failures in a row, each caused by a package the toolset assumes but never lists.
The way out is to install all three up front and skip the loop.
pip install google-cloud-bigquery google-cloud-dataplex db-dtypes
Then start the web UI.
adk web
Open the local URL, select the agent and ask something simple to verify the plumbing.
List the tables in bigquery-public-data.thelook_ecommerce
The test data is thelook_ecommerce, a synthetic e-commerce dataset that Google publishes openly. Customers, products and orders make it a good fit for a business-question agent, and no real data is involved. If the table list comes back, the setup works. Keep an eye on the Events panel as it answers, where the agent chains get_table_info into execute_sql. That panel is the clearest view of the agent's behavior and works well in demos.
Trap 4: the billing project
With the plumbing confirmed, try a real question.
What is the most sold product?
The agent politely refuses. It reports that it lacks permission to create jobs in the bigquery-public-data project, then shows the SQL it would have run and asks to be pointed at a project where it has rights. It looks like a misconfiguration, but it isn't. This is a BigQuery concept that's obvious in hindsight and trips up nearly everyone at first.
bigquery-public-data is a read-only project owned by Google. You may read its tables, but you can't run queries inside it, since running a query creates a job and a job must be billed to a project where you hold rights. Google won't let anyone bill compute to its project. Reading data and executing a query are therefore two separate actions tied to two separate projects: one holds the data and the other pays for compute.
The fix tells the toolset to run jobs in your own project while it still reads from the public dataset. The setting is compute_project_id, and it lives in BigQueryToolConfig, which needs importing first.
from google.adk.tools.bigquery.config import BigQueryToolConfig
Then the toolset is built with that config, with a comment marking that jobs run and bill in your project.
bigquery_toolset = BigQueryToolset(
credentials_config=credentials_config,
bigquery_tool_config=BigQueryToolConfig(
compute_project_id="your-project-id", # jobs run and bill here
),
tool_filter=[...],
)
After redeploying, a second and more interesting wall appears. The agent now knows to bill your project, but it concludes it can't read the public dataset at all and refuses again. This time it's wrong, because reading a public dataset from your own billing project is normal and allowed. The agent was simply overcautious.
This is where the instruction-as-lever point returns. The problem lives in the prompt, not the code, because the agent had reasoned its way into a rule that doesn't exist. One explicit sentence in the instruction fixes it. The sentence says that query jobs always run in the configured billing project, that data from any accessible dataset, public ones included, can be read, and that reading a public dataset from the billing project is normal and should never be a reason to refuse.
After another redeploy, the agent inspects the schema, writes the join, runs it and returns the top product along with the query it used. The detour shows that an agent can have the right technical access and still behave badly because of an ambiguous instruction. Prompt wording is part of the engineering here, not a side skill.
Deploying to Agent Engine
Running on a laptop is one thing. Running on Google's infrastructure on its own is where the agent becomes real. Vertex AI Agent Engine is the managed runtime built for ADK agents: you hand over the agent, and Google runs it, scales it and keeps it alive with no server to manage.
Deployment starts by enabling a couple of APIs and creating a staging bucket where ADK places the packaged code.
gcloud services enable aiplatform.googleapis.com storage.googleapis.com
gcloud storage buckets create gs://your-project-id-adk-staging --location=us-central1
Then comes the deploy command, which takes the project, region and staging bucket.
adk deploy agent_engine \
--project=your-project-id \
--region=us-central1 \
--staging_bucket=gs://your-project-id-adk-staging \
bq_agent_app
It takes a few minutes because a container is built behind the scenes, and the next trap lives inside that container.
Trap 5: the container knows nothing about your laptop
Agent Engine's first run ends in a crash with the very same cannot import name 'dataplex_v1' error that looked solved. A deployment can't see what's installed on a developer machine. It knows only the packages named in a requirements.txt placed inside the agent folder. So the laptop held the dependencies and the container didn't. The lesson reaches every GCP target, be it Cloud Functions, Cloud Run or Agent Engine. Passing locally and passing in production are different claims, and an explicit dependency list connects the two.
The remedy is to spell out the packages.
# requirements.txt
google-adk
google-cloud-bigquery
google-cloud-dataplex
db-dtypes
Redeploy, and the container installs what it needs.
Trap 6: the agent isn't you anymore
The last trap teaches the most about agents in production. The deploy succeeds, but a question typed into the cloud Playground gets rejected by BigQuery for lacking the bigquery.jobs.create permission. That's not a bug. It's IAM working as designed.
On a laptop, the agent acted as you, using all of your permissions. After deployment it runs under a service account, essentially a robot identity, and that identity starts with no BigQuery rights. This design is what makes agents safe near sensitive data. An agent can't exceed what its identity allows, nothing is hidden, and IAM limits it just as it limits any person.
The service account needs two roles, one to run queries and one to read data. The first block grants roles/bigquery.jobUser.
gcloud projects add-iam-policy-binding your-project-id \
--member="serviceAccount:SERVICE_ACCOUNT_EMAIL" \
--role="roles/bigquery.jobUser"
The second grants roles/bigquery.dataViewer.
gcloud projects add-iam-policy-binding your-project-id \
--member="serviceAccount:SERVICE_ACCOUNT_EMAIL" \
--role="roles/bigquery.dataViewer"
Wait a minute for the change to propagate and ask again in the Playground. The agent then answers from the cloud, running fully on Google's infrastructure while the laptop sits closed.
Lessons from the build
- An agent isn't a chatbot. The difference is the loop in which the model reasons, calls a tool, sees the result and repeats until it can answer.
- The instruction does most of the real work. Identical code and access produce very different behavior depending on how the instruction is worded, and stopping invented column names and unnecessary refusals was pure prompt work.
- Built-in doesn't guarantee installed. The BigQuery toolset arrives with ADK yet depends on
google-cloud-bigquery,google-cloud-dataplexanddb-dtypes, so put them in first. - Local and production are different worlds. Locally the agent runs as your identity with your packages. Deployed, it runs as a service account with only what
requirements.txtlists, and both gaps are closed explicitly. - IAM is the safety story. A deployed agent can't exceed its service account's rights, so every access is explicit, traceable and revocable, just like a human user's. That's the reassurance a business stakeholder needs before agent access to real data.
A project that begins as a question about pulling natural-language answers from BigQuery finishes as a fully deployed Vertex AI agent that answers business questions with no SQL and no manual extraction. All of it is native GCP, so a team already on BigQuery isn't adopting a new stack, only adding Gemini to tools it already uses. Tutorials mention none of these traps: the Python version, the undeclared dependencies, the billing-project mix-up and the service account without rights. Knowing where the holes are saves hours, so start small, break things and read the errors.