SKIP TO CONTENT
← ALL POSTS
BuildMay 31, 20262 MIN READ

Building an Agentic Analytics Loop with Gemini Function Calling

How Brim It lets a non-technical finance manager chat with company-card data in plain English — a function-calling loop over read-only query tools that never invents a number.

AIGeminiFunction CallingAnalytics

Most "chat with your data" demos cheat. They paste a schema into the prompt, ask the model to "answer using the data," and hope it doesn't hallucinate a number. For a finance tool that triages real company-card spending, hope is not a strategy. A wrong total isn't a bad UX — it's a wrong decision.

For Brim It I wanted a non-technical finance manager to ask things like "how much did we spend on travel in Q2, by region?" and get an answer that is always grounded in an actual query — never the model's imagination.

The shape: a function-calling loop, not a prompt

The core isn't a prompt — it's a loop. Gemini is given a small set of read-only query tools and asked to call them. It can't touch raw SQL or invent values; it can only ask the data questions through a typed interface.

There are five tools, deliberately boring:

  • aggregate — sums/averages over a filtered set
  • time_series — values bucketed by day/week/month
  • top_merchants — ranked breakdown
  • list — raw rows for a filter
  • compare — two filtered sets side by side
user: "how much on travel in Q2, by region?"
model -> aggregate({ category: "travel", from: "2026-04-01", to: "2026-06-30", groupBy: "region" })
tool  -> [{ region: "TX", total: 18230.55 }, { region: "CA", total: 11890.12 }, ...]
model -> renders a bar chart + a one-line summary citing those exact totals

Every number on screen traces back to a tool result. The model's job is translation and presentation, not arithmetic.

Auto-picking the visualization

The model also returns a viz hint, and the client maps it to a component — bar for grouped aggregates, line for time series, pie for share-of-total, table for list, a single stat for one scalar. The user never picks a chart type; the shape of the question implies it.

Multi-turn follow-ups that actually reuse context

The part that makes it feel like a colleague rather than a search box: follow-ups reuse the prior filters.

"now just Texas, monthly"

That carries forward category: "travel" and the Q2 window, swaps groupBy for a monthly time_series, and adds region: "TX". The model isn't re-deriving the whole query — it's editing the last one. Keeping the previous tool call in context (not just the previous answer) is what makes this reliable.

What I'd tell my past self

  • Make the tools read-only and typed. The safety property ("never invents a number") is a consequence of the interface, not of prompting harder.
  • Return data the UI can render directly. Don't ask the model to format tables in prose; give it a viz channel.
  • Persist tool calls, not just messages. Follow-ups are diffs over the last query.

The whole thing is one loop and five small functions. Most of the "intelligence" is in constraining what the model is allowed to do.

Related project

Brim It