Skip to main content
Welcome back. It’s Wednesday afternoon and the third email brings good news: Claire sent another message with the subject line “Access Granted.” Let’s walk through what changed and how to move from a toy demo to using the real warehouse table, analytics.customer_features, as the agent’s data source. That demo made my week. I talked to the product analytics team: you now have read access to analytics.customer_features in BigQuery. Every row is one customer — you can point your agent at the real thing. P.S. Marcus from the platform asked what exactly your agent gets to see. I said I would ask you. We’ll come back to that next week. Yesterday the agent answered a question using a dictionary with two fake rows. Today it will query a real feature table containing thousands of customers. The agent itself barely changes: same model, same runner, same session, same question. The instruction is updated so the model understands the real columns. The one real code change is inside one function: get_customer. Previously it did a dictionary lookup; now it runs a parameterized SQL query. That is the essence of a tool.
A dark-themed flowchart titled "The Real Thing" showing an "Instruction" flowing into a "Churn Agent" which calls a "get_customer" function. The get_customer node branches to "A Dictionary" and "BigQuery (analytics.customer_features)" and a sidebar lists "Same model, Same runner, Same session, Same question."
The model asks for a customer; it doesn’t care whether the answer comes from a dictionary, a REST API, or a 5,000-row feature table. Design your tool to present a consistent interface (customer_id in → dict out) and keep the tool responsible for data access.

Credentials: one for the model, one for BigQuery

This lesson is a short version and omits some deployment mechanics. You will use two credentials that serve different purposes:
A diagram titled "Two Credentials" showing two columns: on the left a "Model API Key" feeding a model (brain icon), and on the right "Default Credentials" feeding BigQuery (BigQuery icon). Each column is represented by a key icon leading to its respective service.
In short: one credential for thinking (the model), one for reading (BigQuery). The install adds one package and the login is one gcloud command. Your .env file gains exactly one new line for the Google Cloud project ID:

The schema: what the agent reads

Everything the agent decides will be drawn from these columns. Each row is one customer and carries an ID, a region, and several features. Represent these columns explicitly to the model so it can reason over them consistently. Example row from analytics.customer_features:
Note: support_tickets_90_days is a plain count (e.g., 5 tickets). Additional context (ticket severity, time to resolution) would change the agent’s reasoning — consider adding those if you need finer signals.

Implementing get_customer (parameterized SQL)

What doesn’t change is the tool contract: keep the function name get_customer, the signature (customer_id: str) and the one-line docstring explaining churn signals. The body changes to run a parameterized SELECT against analytics.customer_features and return a Python dict. Using a parameterized query (@cid) is critical: it tells BigQuery to treat the value as data (not SQL), which protects against prompt injection and SQL injection-like attacks. Here is the adjusted function:
BigQuery will treat the bound cid parameter as a scalar value, not part of the SQL text.

How to instruct the agent with real columns

Update the agent’s instruction so the model understands how to interpret these columns. The same heuristics from the toy demo apply, but now tie them to exact column names:
  • Low usage_last_30_days → disengagement.
  • Sharply negative usage_delta_30_days → steep decline in usage.
  • High support_tickets_90_days → friction and unresolved issues.
  • Low tenure_months → increased churn risk.
  • Consider plan_tier and upgrade_eligible when suggesting remediation (e.g., targeted offers).
Ask the agent to produce three outputs (same as before):
  1. A concise risk level (e.g., LOW / MEDIUM / HIGH).
  2. The signal(s) that drove the decision (list the relevant columns and values).
  3. One concrete retention action (e.g., a customer success call within 48 hours).
At-a-glance checklist of signals to surface:

Run the agent on a real customer

Now run the agent and ask about a real customer: You: “Is customer cust-0003115 at risk?” The model decides to call get_customer. The tool fires the parameterized query at BigQuery, one real row comes back, the model reads it, and returns a verdict with supporting signals. Example interaction and output:
Interpretation: this is a high-risk customer — very low usage that dropped sharply, multiple support tickets, and short tenure. The agent should explain these signals and recommend one concrete action, for example: schedule a customer success call within 48 hours to discuss friction points and propose a tailored plan.

Tool scope and limitations

Before you close the file, consider what this tool can and cannot do. get_customer answers exactly one kind of question: “tell me about this one customer.” One ID goes in, and one row comes out. This is a deliberate design:
  • Narrow tools make model-to-tool handoffs predictable.
  • Parameterized queries protect your database from injected SQL.
  • If you need to answer other question patterns (for example, “Which customers should I call urgently this week?”), write a new function and SQL that returns multiple rows or aggregates. Each distinct question pattern usually becomes a distinct tool.
Example conceptual mapping:
If you want broader queries (lists, aggregates, cohorts), add new tools with parameterized SQL that explicitly declares their input parameters.
Design tools narrowly and use parameterized queries. Narrow tools make model-tool handoffs predictable and parameterization protects your database from injected SQL.

Watch Video

Practice Lab