adk-finops
Universal FinOps cost, token usage, and grounding fee tracking for the Google Agent Development Kit (ADK) and Python AI agents.
Table of Contents
- Why adk-finops?
- Key Features
- Installation
- Quickstart with Google ADK
- Dual-Scope Telemetry: Turn vs. Session
- Budget Guards & Circuit Breakers
- Context Caching Savings Analytics (ROI Tracker)
- Multi-Agent Cost Attribution & Delegation Tracking
- Rich Terminal Summary Box
- Decoupled Rate Cards (Custom & Enterprise Pricing)
- Smart Tool Classification (MCP vs. Grounding)
- Gemini 2.5 Thinking Tokens Billing
- Telemetry State Schema
- Standalone Usage (Without ADK)
- Configuration Reference
- Built-In Model Rate Cards
- One-Line BigQuery Exporter
- License
Why adk-finops?
Building production AI agents with Google ADK involves multi-step tool-calling loops, reasoning models, and external search tools. However, monitoring real financial costs in production presents several challenges:
- Missing Cumulative Turn Tokens: In a tool-calling loop (e.g., Call 1 decides to call a tool $\rightarrow$ Tool runs $\rightarrow$ Call 2 generates final response), default event telemetry only displays the token count of Call 2, hiding the cost of earlier reasoning calls.
- Thinking / Reasoning Tokens: Models like Gemini 2.5 Pro separate
thoughtsTokenCountfromcandidatesTokenCount. If thinking tokens are not explicitly extracted, your token usage will not match your Google Cloud bill. - MCP vs. Paid Grounding: Generic keyword matching often misclassifies free local/MCP tools (like
search_documents) as paid Vertex AI Grounding queries ($35/1k or $2.50/1k). - Hardcoded Pricing: Model prices change frequently, and enterprises negotiate custom volume discounts. Hardcoded rates in application code require constant code updates.
adk-finops solves all of these problems with a single lightweight, decoupled plugin.
Key Features
- Native Google ADK Integration: Intercepts model and tool invocations via the ADK
BasePluginlifecycle (before_run,after_model,on_event,after_tool,after_run). - Dual-Scope Accounting: Simultaneously tracks metrics for both the active turn (all calls within a user message) and the cumulative session (entire conversation history).
- Budget Guards & Circuit Breakers: Set hard session and turn USD spending limits. Prevent runaway bills by halting execution, emitting warnings, or automatically downgrading expensive models (e.g. Gemini 2.5 Pro → Flash) when limits are breached.
- Context Caching Savings ROI: Demonstrates financial value by tracking gross cost (cost without caching) vs. actual net cost, reporting exact dollars and percentage saved (e.g. up to 90% savings via Gemini Context Caching).
- Decoupled Rate Cards: Pricing data is stored in clean JSON. Override rates via local file, remote URL, environment variable, or code without modifying the engine.
- Enterprise Volume Discounts: Configure global or provider-specific discount multipliers (e.g., 15% Google Cloud negotiated discount).
- Accurate Thinking Tokens: Automatically captures and bills
thoughts_token_countat the output rate while displaying thinking tokens separately in reports. - Smart Tool Discrimination: Automatically excludes
MCPTool,McpToolset, BigQuery, and local function tools ($0.00 fee) while accurately billing Google Search Grounding ($0.014/query) and Vertex AI Search ($0.0025/prompt). - Real-Time UI Streaming: Streams cost metrics to
event.actions.state_delta["finops_cost"]for live updates in the ADK Web UI, with formatted terminal stdout logging. - Zero Heavy Dependencies: Pure Python standard library for the core tracker.
Installation
From PyPI
pip install adk-finops
With Rich Terminal Summary
pip install "adk-finops[rich]"
With BigQuery Exporter
pip install "adk-finops[bigquery]"
Full Enterprise Suite (ADK + Rich + BigQuery)
pip install "adk-finops[all]"
With Google ADK
pip install "adk-finops[adk]"
From GitHub (Direct Git Dependency)
pip install git+https://github.com/dmoonat/adk-finops.git
Or in requirements.txt:
adk-finops @ git+https://github.com/dmoonat/adk-finops.git@main
Quickstart with Google ADK
Add FinOpsCostPlugin to your App in agent.py:
from google.adk.agents import Agent
from google.adk.apps import App
from adk_finops import FinOpsCostPlugin
# 1. Define your agent
root_agent = Agent(
model="gemini-2.5-pro",
name="my_agent",
instruction="You are a helpful assistant.",
tools=[...]
)
# 2. Instantiate the FinOps plugin
finops_plugin = FinOpsCostPlugin(
name="finops_cost_tracker",
default_model="gemini-2.5-pro",
)
# 3. Attach plugin to your App
app = App(
name="my_agent",
root_agent=root_agent,
plugins=[finops_plugin],
)
Run your agent with adk web or adk run. Telemetry will log directly to the terminal and appear in the Web UI session state under finops_cost.
Dual-Scope Telemetry: Turn vs. Session
adk-finops resolves the mismatch between single-event inspection and session-level totals by reporting both scopes concurrently:
| Scope | Description | Matches in ADK Web UI |
|---|---|---|
turn |
Cumulative metrics for the current user interaction (Call 1 + Tool Execution + Call 2) | Full cost of the current turn |
session |
Cumulative metrics across all turns in the chat session (Turn 1 + Turn 2 + ...) | ADK "Usage Summary for Session" |
State Delta Example
{
"total_tokens": 18671,
"prompt_tokens": 16617,
"completion_tokens": 1110,
"thoughts_tokens": 944,
"total_cost_usd": 0.0135785,
"currency": "USD",
"turn": {
"total_calls": 2,
"total_tool_calls": 0,
"prompt_tokens": 14222,
"completion_tokens": 1079,
"thoughts_tokens": 908,
"total_tokens": 16209,
"llm_cost_usd": 0.0092341,
"tool_cost_usd": 0.0,
"total_cost_usd": 0.0092341,
"breakdown_by_model": {
"gemini-2.5-pro": {
"calls": 2,
"prompt_tokens": 14222,
"completion_tokens": 1079,
"thoughts_tokens": 908,
"total_cost_usd": 0.0092341
}
}
},
"session": {
"total_calls": 3,
"total_tool_calls": 0,
"prompt_tokens": 16617,
"completion_tokens": 1110,
"thoughts_tokens": 944,
"total_tokens": 18671,
"llm_cost_usd": 0.0135785,
"tool_cost_usd": 0.0,
"total_cost_usd": 0.0135785
}
}
Budget Guards & Circuit Breakers
Prevent runaway agent loops and surprise bills with proactive budget enforcement. Set hard USD spending limits per session or per turn:
from adk_finops import FinOpsCostPlugin
finops_plugin = FinOpsCostPlugin(
default_model="gemini-2.5-pro",
budget_limit_usd=1.00, # Hard stop at $1.00 per chat session
turn_budget_limit_usd=0.25, # Maximum $0.25 on any single turn
on_budget_exceeded="halt", # Action: "halt", "warn", or "downgrade"
fallback_model="gemini-2.5-flash", # Target model when using "downgrade"
)
Enforcement Modes
| Mode | Behavior | Best Used For |
|---|---|---|
"halt" (default) |
Halts execution immediately, raises BudgetExceededError or returns a safe warning message in ADK to block further model calls. |
Production safeguards, preventing runaway costs. |
"downgrade" |
Automatically downgrades the agent's model to fallback_model (e.g. Gemini 2.5 Pro → Flash) once the budget threshold is reached. |
Graceful service degradation with zero user downtime. |
"warn" |
Logs a warning and marks exceeded: True in session state (finops_cost.budget) without interrupting the user. |
Soft monitoring and alerting. |
Context Caching Savings Analytics (ROI Tracker)
Context caching can reduce prompt token costs by up to 90%. adk-finops automatically calculates Gross Cost (what you would have paid without caching), Actual Net Cost, and Total Savings:
{
"total_cost_usd": 0.00334,
"gross_cost_usd": 0.00550,
"savings_usd": 0.00216,
"savings_pct": 39.3,
"cached_tokens": 8000
}
Real-time stdout log highlight:
[FinOps LLM] turn=turn_1 model=gemini-2.5-flash tokens=11000 cost=$0.003340 | 💰 Saved $0.002160 (39.3%) via Context Caching
[FinOps Summary] Turn cost=$0.003340 | Session cost=$0.003340 | 💰 Total Saved: $0.002160 (39.3%) via Caching
Multi-Agent Cost Attribution & Delegation Tracking
In hierarchical multi-agent architectures (e.g., a supervisor delegating subtasks to a researcher and a coder), each agent makes distinct model calls, executes different tools, and consumes different context windows. Without granular attribution, teams cannot identify which sub-agent is driving 80% of costs or entering a costly reasoning loop.
adk-finops automatically attributes every LLM invocation, thinking token, context caching savings, and tool fee to the specific agent executing the task, with optional per-agent budget limits:
from adk_finops import FinOpsCostPlugin
finops_plugin = FinOpsCostPlugin(
default_model="gemini-2.5-pro",
budget_limit_usd=2.00, # Total session cap: $2.00
agent_budgets={
"researcher": 0.50, # Cap researcher at $0.50
"coder": 1.00, # Cap coder at $1.00
},
on_budget_exceeded="halt", # Halt if any agent breaches its limit
)
Agent Breakdown in Session Telemetry
Every turn and session summary includes a granular breakdown_by_agent dictionary:
{
"breakdown_by_agent": {
"supervisor": {
"calls": 1,
"prompt_tokens": 1000,
"completion_tokens": 200,
"thoughts_tokens": 0,
"cached_tokens": 0,
"total_tokens": 1200,
"llm_cost_usd": 0.00225,
"tool_cost_usd": 0.0,
"total_cost_usd": 0.00225,
"savings_usd": 0.0
},
"researcher": {
"calls": 2,
"prompt_tokens": 4000,
"completion_tokens": 500,
"thoughts_tokens": 0,
"cached_tokens": 2000,
"total_tokens": 4500,
"llm_cost_usd": 0.00189,
"tool_cost_usd": 0.028,
"total_cost_usd": 0.02989,
"savings_usd": 0.00054,
"savings_pct": 22.2
},
"coder": {
"calls": 1,
"prompt_tokens": 8000,
"completion_tokens": 1500,
"thoughts_tokens": 300,
"cached_tokens": 0,
"total_tokens": 9800,
"llm_cost_usd": 0.01275,
"tool_cost_usd": 0.0,
"total_cost_usd": 0.01275,
"savings_usd": 0.0
}
}
}
Real-Time Terminal Logs
[FinOps LLM] turn=turn_1 session=sess_1 agent=supervisor model=gemini-2.5-pro tokens=1200 cost=$0.002250
[FinOps Grounding] turn=turn_1 session=sess_1 agent=researcher tool=google_search fee=$0.014000
[FinOps LLM] turn=turn_1 session=sess_1 agent=researcher model=gemini-2.5-flash tokens=4500 cost=$0.001890 | 💰 Saved $0.000540 (22.2%) via Context Caching
[FinOps Agents] supervisor: $0.0023 (1200 tok) | researcher: $0.0299 (4500 tok) | coder: $0.0128 (9800 tok)
Rich Terminal Summary Box
adk-finops includes an out-of-the-box, color-coded, border-styled terminal summary box. When running in a terminal, it provides instant financial visibility after every turn, displaying turn vs. session costs, context caching ROI, model breakdowns, and sub-agent attributions:
╭───────────────────────── 💸 ADK FinOps Cost Summary ─────────────────────────╮
│ │
│ Scope Calls Tokens LLM Cost Tool Fees Total Cost │
│ ────────────────────────────────────────────────────────────────── │
│ Current Turn 2 10,100 $0.0053 $0.0280 $0.0333 │
│ Session Total 2 10,100 $0.0053 $0.0280 $0.0333 │
│ │
│ 💰 Context Caching Savings: $0.0016 saved (4.6% reduction from $0.0349 gross)│
│ │
│ Model Calls Tokens (In/Out) Cost (USD) Savings │
│ ───────────────────────────────────────────────────────────────────────── │
│ gemini-2.5-pro 1 1,200 / 300 $0.0030 — │
│ gemini-2.5-flash 1 8,000 / 600 $0.0023 $0.0016 (41.5%) │
│ │
│ Agent Calls Tokens LLM Cost Tool Fees Total Cost │
│ ────────────────────────────────────────────────────────────────── │
│ 🤖 supervisor 1 1,500 $0.0030 $0.00 $0.0030 │
│ 🤖 researcher 1 8,600 $0.0023 $0.0280 $0.0303 │
│ │
│ 🛡️ Budget Guard: $0.0333 / $1.0000 (3.3% utilized) │
╰───────────────── adk-finops • Universal Token & Cost Engine ─────────────────╯
Features
- Rich 24-Bit Color Styling: When
richis installed (pip install "adk-finops[rich]"), it renders full color highlights, styled headers, and rounded boxes. - Pure-Python Unicode Fallback: If
richis not installed, it falls back seamlessly to an aligned pure-Python Unicode box drawing (╭─╮│╰─╯) with zero external dependencies. - Responsive 80-Column Layout: Designed to fit standard terminal windows without clipping or wrapping.
Automatic & On-Demand Integration
adk-finops supports both hands-off automated terminal reporting in Google ADK and explicit on-demand rendering for any Python workflow:
1. Automatic Integration (Google ADK)
When using FinOpsCostPlugin, the summary box is rendered automatically to stdout at the conclusion of every turn (after_run_callback):
from google.adk.agents import Agent
from google.adk.apps import App
from adk_finops import FinOpsCostPlugin
# Automatically renders the Rich box at the end of each turn
finops_plugin = FinOpsCostPlugin(
default_model="gemini-2.5-pro",
render_terminal_box=True, # Enabled by default
)
app = App(
name="support_agent",
root_agent=Agent(name="support_agent", model="gemini-2.5-pro"),
plugins=[finops_plugin],
)
[!TIP] To disable the box and retain only standard single-line log output (e.g. in headless CI/CD environments), pass
render_terminal_box=False.
2. On-Demand Integration (Standalone & Custom Workflows)
You can trigger terminal rendering programmatically at any time across your batch scripts, agent loops, or background tasks:
from adk_finops import CostTracker, format_summary_box, print_summary
# 1. Print current turn/session summary box directly to terminal stdout
print_summary()
# Or specify an explicit turn and session ID:
print_summary(run_id="turn_123", session_id="session_abc")
# 2. Or call directly from CostTracker
CostTracker.print_summary()
# 3. Export formatted box string (ANSI or plain text) for custom loggers or webhooks (Slack/Discord)
summary_data = CostTracker.get_summary()
box_text = format_summary_box(summary_data)
logger.info("\n" + box_text)
Decoupled Rate Cards (Custom & Enterprise Pricing)
Pricing is completely decoupled from the tracking engine. You can configure rates via:
1. Custom JSON Rate Card
Create a my_rates.json file:
{
"models": {
"custom-fine-tuned-model": {
"provider": "google",
"input_per_1m": 0.50,
"output_per_1m": 2.00,
"cached_input_per_1m": 0.05
}
},
"tools": {
"paid_external_api": 0.01
}
}
Pass the file path when initializing the plugin:
finops_plugin = FinOpsCostPlugin(rate_card_path="my_rates.json")
2. Environment Variable
Set the rate card path globally without touching any code:
export ADK_FINOPS_RATE_CARD_PATH="/etc/finops/rates.json"
3. Remote URL / Enterprise Pricing Endpoint
Fetch central rate cards from an internal endpoint or cloud storage bucket (GCS/S3):
from adk_finops import CostTracker
CostTracker.load_rate_card_url("https://internal.corp.com/finops/rates.json")
4. Enterprise Negotiated Discounts
Apply your organization's contracted cloud discounts:
# Apply a 15% discount across all models and tools
finops_plugin = FinOpsCostPlugin(discount_percent=15.0)
# Or configure dynamically via CostTracker:
CostTracker.set_discount(15.0) # Global 15% discount
CostTracker.set_discount(20.0, provider="google") # 20% discount on Google Cloud models
Or set via environment variable:
export ADK_FINOPS_DISCOUNT_PERCENT="15.0"
5. Programmatic Model & Tool Registration
from adk_finops import CostTracker
CostTracker.register_rate_card("my-internal-model", {
"provider": "internal",
"input_per_1m": 0.40,
"output_per_1m": 1.60,
})
CostTracker.register_tool_rate("internal_vector_db", 0.0005)
Smart Tool Classification (MCP vs. Grounding)
adk-finops distinguishes between paid cloud grounding services and free tools:
- MCP Tools (
McpToolset,MCPTool): Automatically identified and assigned $0.00 fee (e.g.search_documents). - Database & Custom Tools: BigQuery toolsets and custom Python functions incur $0.00 tool fee.
- Google Search Grounding & Web Grounding: Tools named
google_searchorGoogleSearchToolincur $0.014 / query ($14.00 per 1,000 queries; first 5,000 queries/month free). Charged for each individual Grounding Query performed by Gemini. Input tokens returned by search grounding are not charged. - Google Maps Grounding: Incurs $0.014 / query ($14.00 per 1,000 queries; first 5,000 queries/month free). Input tokens are not charged.
- Grounding with your data (Vertex AI Search / Datastores): Tools matching
vertex_search,vertex_ai_search, orGroundingToolincur $0.0025 / prompt ($2.50 per 1,000 prompts).
Gemini 2.5 Thinking Tokens Billing
Gemini 2.5 Pro and Flash separate reasoning/thinking tokens into thoughtsTokenCount.
Per Google Cloud pricing rules:
Total Billable Output Tokens =
candidates_token_count+thoughts_token_count
adk-finops automatically incorporates thinking tokens into the output token rate while reporting them as a separate field in breakdown_by_model so you can monitor your reasoning overhead.
Standalone Usage (Without ADK)
adk-finops is designed to be used in any Python application — FastAPI/Flask backends, Celery/Ray background pipelines, LangChain/LlamaIndex workflows, or raw Google GenAI SDK scripts — without requiring Google ADK.
📁 Ready-to-run examples: See the
examples/standalone/folder or thestandalone_finops_demo/workspace.
from adk_finops import CostTracker, BigQueryExporter
# Wrap any execution block with automatic lifecycle cleanup
with CostTracker.track_run("request_123"):
# Record Gemini 2.5 call with thoughts/reasoning tokens
CostTracker.record_usage(
run_id="request_123",
model_name="gemini-2.5-flash",
prompt_tokens=1500,
completion_tokens=250,
thoughts_tokens=100,
cached_tokens=0,
)
# Record grounding or tool fee ($0.014 / query)
CostTracker.record_tool_call(
run_id="request_123",
tool_name="google_search",
)
# Print color-coded terminal summary box
CostTracker.print_summary("request_123")
# Get structured metrics dictionary
summary = CostTracker.get_summary("request_123")
print(f"Total Cost: ${summary['total_cost_usd']:.6f}")
print(f"Total Tokens: {summary['total_tokens']}")
# (Optional) Export to BigQuery in 1 line
# exporter = BigQueryExporter("my-project.finops.agent_costs")
# exporter.export_summary(summary)
Configuration Reference
Environment Variables
| Variable | Type | Description |
|---|---|---|
ADK_FINOPS_RATE_CARD_PATH |
str |
Absolute or relative path to a custom JSON rate card file. |
ADK_FINOPS_DISCOUNT_PERCENT |
float |
Global enterprise discount percentage (e.g., 15.0 for 15%). |
Plugin Initialization Parameters
FinOpsCostPlugin(
name: str = "finops_cost_tracker",
default_model: str = "gemini-2.5-flash",
rate_card_path: str | Path | None = None,
rate_card: dict[str, Any] | None = None,
discount_percent: float | None = None,
budget_limit_usd: float | None = None,
turn_budget_limit_usd: float | None = None,
agent_budgets: dict[str, float] | None = None,
on_budget_exceeded: str = "halt", # "halt", "warn", or "downgrade"
fallback_model: str = "gemini-2.5-flash",
)
| Parameter | Type | Default | Description |
|---|---|---|---|
default_model |
str |
"gemini-2.5-flash" |
Fallback model name if not reported by the LLM response. |
rate_card_path |
str | Path |
None |
Path to custom rate card JSON file. |
discount_percent |
float |
None |
Enterprise volume discount percentage (e.g. 15.0 for 15%). |
budget_limit_usd |
float |
None |
Maximum cumulative spending limit in USD for the entire chat session. |
turn_budget_limit_usd |
float |
None |
Maximum spending limit in USD for any single user turn. |
agent_budgets |
dict[str, float] |
None |
Per-agent spending caps in USD (e.g. {"researcher": 0.50, "coder": 1.00}). |
on_budget_exceeded |
str |
"halt" |
Action on budget breach: "halt" (raise/block), "warn", or "downgrade". |
fallback_model |
str |
"gemini-2.5-flash" |
Target model when using "downgrade" mode. |
render_terminal_box |
bool |
True |
Renders a beautiful color-coded summary box to stdout at the end of each turn. |
Built-In Model Rate Cards
The bundled default_rates.json contains standard public pricing (USD per 1M tokens):
| Model | Provider | Input / 1M | Output / 1M | Cached / 1M | Context >128k Tier |
|---|---|---|---|---|---|
gemini-3.8-flash |
$0.75 | $3.75 | $0.075 | Standard 2027: In $1.50, Out $7.50 | |
gemini-3.7-flash |
$0.75 | $3.75 | $0.075 | Standard 2027: In $1.50, Out $7.50 | |
gemini-3.6-flash |
$0.75 | $3.75 | $0.075 | Standard 2027: In $1.50, Out $7.50 | |
codemender |
$0.75 | $3.75 | $0.075 | Standard 2027: In $1.50, Out $7.50 | |
gemini-3.5-flash |
$1.50 | $9.00 | $0.15 | — | |
gemini-2.5-flash |
$0.30 | $2.50 | $0.03 | — | |
gemini-2.5-pro |
$1.25 | $5.00 | $0.3125 | In: $2.50, Out: $10.00 | |
gpt-4o |
OpenAI | $2.50 | $10.00 | $1.25 | — |
gpt-4o-mini |
OpenAI | $0.15 | $0.60 | $0.075 | — |
o1 |
OpenAI | $15.00 | $60.00 | $7.50 | — |
o3-mini |
OpenAI | $1.10 | $4.40 | $0.55 | — |
claude-3-7-sonnet |
Anthropic | $3.00 | $15.00 | $0.30 | — |
claude-3-5-sonnet |
Anthropic | $3.00 | $15.00 | $0.30 | — |
claude-3-5-haiku |
Anthropic | $0.80 | $4.00 | $0.08 | — |
deepseek-v3 |
DeepSeek | $0.14 | $0.28 | $0.014 | — |
deepseek-r1 |
DeepSeek | $0.55 | $2.19 | $0.14 | — |
One-Line BigQuery Exporter
Stream turn, session, model, and sub-agent FinOps telemetry directly into a Google BigQuery dataset with a single configuration parameter:
from adk_finops import FinOpsCostPlugin
finops_plugin = FinOpsCostPlugin(
default_model="gemini-2.5-flash",
bigquery_table="my-gcp-project.finops.agent_costs",
bigquery_export_scope="both", # "session" (default), "turn", or "both"
bigquery_tags={"env": "production", "service": "support-agent"},
)
[!TIP] Environment Variable Auto-Discovery: You can also set
ADK_FINOPS_BIGQUERY_TABLE="my-gcp-project.finops.agent_costs"in your environment or.envfile to enable BigQuery streaming without changing a single line of application code!
Zero-Boilerplate Schema Management
When bigquery_table is specified, adk-finops automatically inspects and provisions the dataset and table with enterprise best practices:
- Partitioning: Day-partitioned on
timestampto optimize query performance and reduce scan costs. - Clustering: Clustered by
[session_id, agent_name, model_name]for sub-second filtering in Looker Studio and BI tools. - Non-Blocking Background Streaming: Ingestion runs asynchronously in a background thread pool, adding zero latency to agent responses.
Table Schema Reference
| Field Name | Type | Description |
|---|---|---|
timestamp |
TIMESTAMP |
Event timestamp in UTC (Partition Key) |
session_id |
STRING |
ADK Session ID (Clustering Key #1) |
turn_id |
STRING |
ADK Turn / Invocation ID |
scope |
STRING |
Record scope: "turn" or "session" |
agent_name |
STRING |
Attributed Agent name (Clustering Key #2) |
model_name |
STRING |
Model name / version (Clustering Key #3) |
prompt_tokens |
INTEGER |
Input prompt token count |
completion_tokens |
INTEGER |
Output candidate token count |
thoughts_tokens |
INTEGER |
Gemini 2.5 thinking token count |
cached_tokens |
INTEGER |
Context cached token count |
total_tokens |
INTEGER |
Total billable tokens |
llm_cost_usd |
FLOAT |
Net LLM API cost in USD |
tool_cost_usd |
FLOAT |
Search & Grounding fees in USD |
total_cost_usd |
FLOAT |
Total net cost in USD |
gross_cost_usd |
FLOAT |
Gross cost before caching discount |
savings_usd |
FLOAT |
Dollars saved via context caching |
savings_pct |
FLOAT |
Percentage saved via context caching |
tool_calls_count |
INTEGER |
Number of billable grounding/tool calls |
budget_limit_usd |
FLOAT |
Configured budget threshold |
budget_utilization_pct |
FLOAT |
Budget utilization percentage |
budget_exceeded |
BOOLEAN |
Whether budget guard was tripped |
breakdown_by_agent |
JSON |
Multi-agent attribution snapshot |
breakdown_by_model |
JSON |
Model distribution snapshot |
tags |
JSON |
User-provided tags (e.g. env, tenant_id) |
Understanding Streamed Rows & Dimensions (Rollup vs. Attributed Rows)
When streaming telemetry to BigQuery, adk-finops records both overall aggregates (for high-level session/turn reporting) and attributed breakdown rows (for granular drill-downs by agent or model).
Why are agent_name or model_name NULL in some rows?
In data warehousing and BI rollup patterns, NULL is assigned to dimension columns in summary/rollup rows to distinguish between overall totals and specific entity breakdowns:
- Overall Aggregate Rows (
agent_name IS NULL):- Emitted once per turn or session representing the cumulative total across all agents and models.
agent_nameisNULL(can be displayed as'OVERALL'or'TOTAL'viaCOALESCE(agent_name, 'OVERALL')).- If multiple models were used in the run,
model_nameisNULL(the complete distribution is preserved in thebreakdown_by_modelJSON column). If only a single model was used,model_nameis set to that model.
- Attributed Agent Rows (
agent_name IS NOT NULL):- Emitted for each sub-agent participating in the turn/session (
agent_name = 'research_agent', etc.). model_namecontains the primary model invoked by that agent (e.g.gemini-2.5-pro,gemini-2.5-flash).
- Emitted for each sub-agent participating in the turn/session (
Querying Best Practices (Avoiding Double-Counting)
Because both aggregate rows and attributed breakdown rows coexist in the same table, write your SQL queries according to the level of granularity you need:
-
To query overall totals (e.g. total spend per session):
SELECT session_id, total_cost_usd, total_tokens FROM `my-gcp-project.finops.agent_costs` WHERE scope = 'session' AND agent_name IS NULL;
-
To query per-agent breakdown (without double-counting with the aggregate):
SELECT agent_name, SUM(total_cost_usd) AS agent_spend FROM `my-gcp-project.finops.agent_costs` WHERE scope = 'session' AND agent_name IS NOT NULL GROUP BY 1;
-
To query by model:
SELECT model_name, SUM(total_cost_usd) AS model_spend FROM `my-gcp-project.finops.agent_costs` WHERE scope = 'session' AND model_name IS NOT NULL GROUP BY 1;
Standalone Exporter Usage
You can also use BigQueryExporter directly in any Python script, background pipeline, or custom framework:
from adk_finops import CostTracker
from adk_finops.exporters import BigQueryExporter
exporter = BigQueryExporter(
table_id="my-gcp-project.finops.agent_costs",
auto_create_table=True,
)
with CostTracker.track_run("batch_job_42"):
CostTracker.record_usage(
run_id="batch_job_42",
model_name="gemini-2.5-pro",
prompt_tokens=15000,
completion_tokens=800,
cached_tokens=12000,
agent_name="data_extractor",
)
summary = CostTracker.get_summary("batch_job_42")
exporter.export_summary(summary, scope="session", tags={"env": "prod", "pipeline": "etl"})
Sample SQL Queries for Looker Studio
Once data streams into BigQuery, power executive dashboards and chargeback reports with standard SQL:
1. Top 5 Most Expensive Agents by LLM & Grounding Spend
SELECT
agent_name,
COUNT(DISTINCT session_id) AS total_sessions,
SUM(total_tokens) AS total_tokens,
ROUND(SUM(llm_cost_usd), 4) AS llm_cost,
ROUND(SUM(tool_cost_usd), 4) AS grounding_fees,
ROUND(SUM(total_cost_usd), 4) AS total_spend,
ROUND(SUM(savings_usd), 4) AS caching_dollars_saved
FROM `my-gcp-project.finops.agent_costs`
WHERE timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
AND agent_name IS NOT NULL
GROUP BY 1
ORDER BY total_spend DESC
LIMIT 5;
2. Context Caching Savings & ROI by Model
SELECT
model_name,
SUM(cached_tokens) AS total_cached_tokens,
ROUND(SUM(gross_cost_usd), 4) AS gross_spend_without_cache,
ROUND(SUM(total_cost_usd), 4) AS actual_net_spend,
ROUND(SUM(savings_usd), 4) AS net_dollars_saved,
ROUND(SAFE_DIVIDE(SUM(savings_usd), SUM(gross_cost_usd)) * 100, 1) AS overall_savings_pct
FROM `my-gcp-project.finops.agent_costs`
WHERE timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY)
AND scope = 'session'
AND agent_name IS NULL
GROUP BY 1
ORDER BY net_dollars_saved DESC;
License
Distributed under the Apache License 2.0. See LICENSE for details.
Release files for adk-finops 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| adk_finops-0.2.0.tar.gz | 43.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| adk_finops-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 85.4 kB
Release files / adk_finops-0.2.0.tar.gz
| Download URL | adk_finops-0.2.0.tar.gz |
|---|---|
| Size | 43.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dd212e6b324ea00de972607e7bc57544bee04ce6c6fd4679e7eb7ee52ae0db5d
|
|
BLAKE2b-256 checksum How to use checksums |
59aaa52adabebb2d92aa935ef4fa8c933cdf7cfcf763795d2e6d582153561458
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.4
|
Release files / adk_finops-0.2.0-py3-none-any.whl
| Download URL | adk_finops-0.2.0-py3-none-any.whl |
|---|---|
| Size | 41.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cc1d8f0842a8c1bf95b2beb7dfb7555cbe9f1ae881c9b99a5e8e5a092cec5124
|
|
BLAKE2b-256 checksum How to use checksums |
baec3fcf7d3dad93f577015840daeb0a51f2e28809e9937aa8a83e09999fca08
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.4
|