Why one AI question shows up on two different bills

Illustration: Two separate paper invoices lying side by side, each threaded onto the same single pipe that runs through both of them and off the edge of the frame

Someone asks your AI assistant one question.

It thinks for a moment, checks a few documents, writes a database query, runs it, grades its own answer, and tries again because the first attempt looked wrong.

That is one question.

At the end of the month it shows up as two separate charges on two separate bills, and nobody in the room can tell you which half was the expensive one.

This post is about why that happens and how to measure it.

By the end you will know which two tables to join in Snowflake, Databricks or BigQuery, what each platform leaves out of the numbers, and the one piece of plumbing that makes the whole thing possible. It is a day of work rather than a quarter.

Two meters, one pipe

There is a long-running argument in cost circles about which line item is the real villain: the AI model calls, or the data platform underneath them. Survey writers love it because it produces a ranking.

The ranking is the wrong thing to look at. The cost vendor Mavvrik explains why in its own report, probably by accident: "A single task can trigger multiple model calls, retrieval steps, data platform queries, tool actions, retries, and orchestration overhead."

Read that again as a sequence. One question becomes a planning call, a few searches through your documents, a written query, the running of that query, a pass where the system marks its own work, and as many retries as it feels like.

Model inference and warehouse compute are two meters on one pipe rather than two separate problems. Watch either one and it moves for reasons that live in the other.

Which is why the ranking keeps sliding around. Mavvrik asked 396 organisations what had surprised them on the bill. In its 2026 edition, 47% named data platform overages and 43% named token costs. A year earlier the same question split 56% to 37%.

Nearly a twenty point gap narrowed to four in twelve months. If it keeps moving at that rate the ordering flips next year, and every article written about this year's ordering ages badly.

Column chart comparing two survey editions: data platform usage fell from 56% in 2025 (N=372) to 47% in 2026 (n=396), while LLM token and API costs rose from 37% to 43%. Multi-select question. Source: Mavvrik, State of AI Cost Governance.
Two editions of the same multi-select question, two samples. The ranking survived; the margin mostly did not. If it keeps moving at this rate the 2027 edition flips.

One part of that survey I do find useful. Almost every organisation in it runs what the industry calls agentic workloads, meaning AI that takes several steps on its own instead of answering in one shot. Only 36% include that work in cost reporting at all.

And the forecasts are getting worse, not better. Just 11% land their AI spend forecast within 10% of the real figure, down from 15% a year earlier.

That last number is the one I would put on a wall. It measures how close a company's prediction of its AI bill comes to the invoice that actually turns up. Nine out of ten are not close.

Meanwhile tracking tools are nearly universal, which tells you that everyone is looking, just at halves.

Donut chart of 2026 AI spend forecast accuracy across 396 organisations: within 10% is 11%, off by 11-25% is 39%, off by 26-50% is 35%, off by more than 50% is 13%, no forecast 2%. Source: Mavvrik and Benchmarkit, State of AI Cost Governance 2026.
Nearly half the sample misses by more than a quarter. Tracking is almost universal; the forecast built on it is not.

One honest caveat before the practical half. "Data platform" is a slippery phrase. The one place either report spells it out, the example given is Datadog, which is monitoring software rather than a data warehouse.

So read those percentages as evidence that something off the model bill keeps surprising people. The rest of this post is what you can go and check in your own account, and that part needs nobody's survey.

Snowflake: the first table you reach for is missing half the cost

Say you want to know what last week's AI assistant actually cost you in Snowflake. The natural move is QUERY_ATTRIBUTION_HISTORY, a view that tells you the credits each query consumed, with idle warehouse time stripped out. Credits are Snowflake's internal unit of billing, a bit like minutes on an old phone plan.

It is a good table. It will also hand you a number that is confidently wrong. Snowflake's documentation lists what that credit figure leaves out, and the last item on the list is "Costs for tokens processed by AI services". Your AI spend is simply not in there.

Two smaller gaps matter more than they sound. Queries that finish in under about a tenth of a second never show up in the view at all, and agents fire a lot of tiny queries. Adaptive Warehouses are missing too.

The AI half lives next door, in CORTEX_AI_FUNCTIONS_USAGE_HISTORY, whose credits column says plainly that it "does not include warehouse usage credits". Two views, two halves, one shared QUERY_ID to stitch them together. This query does that for the last seven days and ranks the result by total spend:

select
    coalesce(q.query_tag, c.query_tag)          as task_tag,
    sum(q.credits_attributed_compute)           as warehouse_credits,
    sum(c.credits)                              as ai_credits,
    count(distinct coalesce(q.query_id, c.query_id)) as queries
from snowflake.account_usage.query_attribution_history q
full outer join snowflake.account_usage.cortex_ai_functions_usage_history c
    on q.query_id = c.query_id
where coalesce(q.start_time, c.start_time) >= dateadd(day, -7, current_timestamp())
group by 1
order by warehouse_credits + ai_credits desc;

Two things to know before you trust the output.

The history has a start date. The columns you are joining on only exist in the Cortex view for data collected after 16 February 2026, and the view itself only goes back to 5 January 2026. If your AI work predates that, the join will look empty and the emptiness means nothing.

The two halves arrive on different clocks. The AI side updates within about five minutes. The warehouse side can take up to eight hours. Reconciling a single task on the same day it ran is not slow, it is impossible. Keep that in mind before you promise someone a live dashboard.

Want one number for a whole multi-step procedure rather than one per step? Group on ROOT_QUERY_ID. That is as close as Snowflake gets to answering "what did one task cost".

Cortex Analyst, the tool that turns plain-English questions into SQL, states the split on its own cost page: "Additional warehouse costs apply when you execute the SQL generated by Cortex Analyst." Writing the query and running it are separate charges.

Databricks: both meters in one table

Databricks makes the two-meters point better than any survey does, by putting them side by side. In system.billing.usage, one column called usage_type holds the values COMPUTE_TIME, STORAGE_SPACE, NETWORK_BYTE, NETWORK_HOUR, API_OPERATION, TOKEN, GPU_TIME, ANSWER. Compute and tokens, one list, no join required to see them together.

Attribution comes from three columns. custom_tags holds whatever labels you attach yourself, and the other two are identity_metadata and usage_metadata. That last one carries agent_bricks_id, which is how you pick out Knowledge Assistant and Supervisor Agent traffic specifically.

Turning usage into dollars needs the price list, and there is a subtlety here. system.billing.list_prices only writes a row when a price changes. It is a history, not a lookup of today's prices. So join it by asking "what was the price at the moment this usage happened":

select
    u.usage_metadata.agent_bricks_id as agent,
    u.usage_type,
    sum(u.usage_quantity * p.pricing.effective_list.default) as usd
from system.billing.usage u
join system.billing.list_prices p
    on u.sku_name = p.sku_name
   and u.usage_end_time >= p.price_start_time
   and (p.price_end_time is null or u.usage_end_time < p.price_end_time)
where u.record_type = 'ORIGINAL'
  and u.usage_date >= current_date() - 7
group by 1, 2;

That record_type = 'ORIGINAL' filter is not optional. Databricks restates usage records, and without the filter you count the same spend twice. There are two more quirks. job_id stays empty for jobs running on all-purpose compute, and budget_policy_id has been replaced by usage_policy_id.

BigQuery: put the label on the job, not the table

This is the one that bites people, so get it right before writing any SQL.

In BigQuery you attribute cost using labels, which are just key-value stickers you attach to things. Where you stick them decides whether they reach your bill at all, and Google documents three different outcomes on one page.

A label on a job "is included in your billing data". A label on a dataset "is included in your storage billing data, but not in your job-related billing data". It also says "Table and view labels are not included in billing data."

This is the trap. Google's other labels page cheerfully says labels go on datasets, tables, reservations and views, and get passed through to billing. It mentions none of those exclusions. Read that page, do the sensible-looking thing, label your tables, and you get precisely nothing back.

So label the job. Set it with SET @@query_label = "task:abc123"; or the labels property when you submit the job, then read your attribution out of INFORMATION_SCHEMA.JOBS. If you are on capacity pricing, where you buy a pool of compute rather than paying per query, an individual job has no price of its own, so divide the pool by each job's share of total_slot_ms.

Before you blame the agent for the bill, read Google's cost guidance on how that pool grows. BigQuery "autoscales in multiples of 50 slots". And the autoscaler "has a 1 minute minimum period before scaling down by default, which translates into at least 1 minute being charged even if the query used the slots for less time, for example, for only 10 seconds out of the minute".

Think of it as a taxi with a minimum fare. Agents generate exactly the wrong shape of traffic for that, with short bursts, lots of them, in parallel. Ten seconds of work, a minute on the meter, over and over.

The fix exists and most people have not switched it on. You can "opt in to BigQuery fluid scaling at the reservation level for per-second billing with no minimum duration". That is a settings change, not a project.

Carry one ID per task, not per request

None of these joins work unless something connects the model call to the query it triggered. So you need an identifier travelling with the work.

The tempting move is a fresh unique ID for every single request. Both platforms tell you not to. Google caps labels at 64 per resource and warns against "creating large numbers of unique labels, such as for timestamps or individual values for every API call".

Snowflake is just as blunt. Its per-request view does break credits down by model, then admits it "can't be reconciled request-for-request against activity data".

So pick the task as your unit, not the request. Generate one ID where the request enters your system and carry it into all three places (the Snowflake query tag, the Databricks custom tag, the BigQuery job label). Below the level of a whole task, the platforms are not going to help you.

The vendors reached the same conclusion, and fast. Databricks shipped AI spend controls in July 2026 and Smart Routing in August. Snowflake's Cortex AI Gateway hit public preview in September.

Three single points of control in front of the models, in one quarter. When rivals build the same thing that quickly, it is usually because customers kept asking the same question.

The mechanism is the interesting bit. Databricks found that routing by task beats routing by request, since "at scale, costs are dominated by cache hit rate". Caching only pays off when you can see the whole task.

What to do this week

The FinOps Foundation prescribes the same two joins from outside the vendor fight. First get query-level attribution working, then extend it to AI activity to "connect token consumption back to credit usage and operational owners". Their target is useful insight in under a week, against an invoice that takes a month to arrive.

So, in order:

The ranking argument will keep moving, and the headlines will move with it. Cost per completed task does not care which invoice it lands on, which is the main reason I would rather measure that.

Cloudacio builds, secures and runs platforms on AWS, GCP and Azure. More about us.

← All articles