PromptisePromptise
Docs
GitHub
Promptise - AI Framework LogoPromptise

The foundation layer for agentic intelligence. Build, secure and operate autonomous AI systems with Promptise Foundry.

pip install promptise

[01] Foundry

  • MCPcast
  • The Promptise Agent
  • Reasoning Engine
  • MCP
  • Agent Runtime
  • Prompt Engineering
  • Execution Engine
  • Agent Identity

[02] Resources

  • Documentation
  • GitHub
  • Guides
  • Learning Paths
  • Questions

[03] Company

  • About
  • Terms of Service
  • Privacy Policy
  • Cookie Policy
  • Subprocessors

© 2026 Promptise by Manser Ventures. All rights reserved.

Open source · Python

← Learning paths> Agentic Engineering with Promptise Foundry
Lesson 61 / 71
86%

AI Agent Cost Tracking: Tokens and Cost per Run and User

Track AI agent token usage and cost per model call, run, user and agent: price tables, cost ledgers, alerts and limits. Runnable code with real output.

Intermediate16 minCost ControlObservability
All 71 lessons↓
  1. ✓What Is an AI Agent? Definition, How It Works, Examples
  2. ✓How to Build an AI Agent in Python (2026 Guide)
  3. ✓What Is an Agent Harness? Parts and How to Build One
  4. ✓System Prompt Best Practices for AI Agents in Python
  5. ✓Best LLM for AI Agents: Test and Switch Models in Python
  6. ✓AI Agent YAML: Define and Run an Agent From a Config File
  7. ✓Streaming LLM Responses with FastAPI: Tokens and Tool Calls
  8. ✓Claude AI Agent with MCP: Use Anthropic Models in Promptise
  9. ✓Gemini AI Agent with MCP: Use Google Models in Promptise
  10. ✓Azure OpenAI Agent with MCP: Use Deployments in Promptise
  11. ✓OpenRouter AI Agent with MCP: Many Models in Promptise
  12. ✓Ollama AI Agent with MCP: Run Local Models in Promptise
  13. ✓LLM Semantic Cache and Model Fallback in Python
  14. ✓What Is MCP (Model Context Protocol)? A Developer's Guide
  15. ✓What Is Tool Calling? LLM Function Calling Explained
  16. ✓MCP Tools vs Resources vs Prompts (With Python Examples)
  17. ✓How to Connect MCP Servers to Your AI Agent in Python
  18. ✓Build a Production MCP Server in Python (Step by Step)
  19. ✓MCP Transports: stdio vs Streamable HTTP vs SSE (Python)
  20. ✓FastAPI MCP Server: Turn Your FastAPI App into MCP Tools
  21. ✓OpenAPI to MCP: Turn Any REST API into an MCP Server
  22. ✓MCP Authentication in Python: API Keys, JWT and OAuth
  23. ✓Build an Authenticated MCP Server for Your AI Agent (Python)
  24. ✓MCP Security Best Practices: A Checklist for MCP Servers
  25. ✓Testing MCP Servers in Python: pytest Without a Live Server
  26. ✓Too Many MCP Tools? LLM Tool Selection, Measured
  27. ✓MCP Server Caching, Timeouts and Circuit Breakers in Python
  28. ✓MCP vs CLI for AI Agents: Tokens, Security, When to Use Each
  29. ✓AI Agent Audit Logging: Tamper-Evident Logs in Python
  30. ✓AI Agent Human in the Loop: Approve Tool Calls in Python
  31. ✓AI Agent Prompt Injection: How to Defend Against It
  32. ✓Build a Multi-Agent System in Python (and When Not To)
  33. ✓Context Engineering for AI Agents: Context Window Management
  34. ✓How to Deploy an AI Agent with Docker (2026 Guide)
  35. ✓LLM Tool Rate Limiting: Per-Client Limits for MCP Servers
  36. ✓MCP Long-Running Tasks: Background Jobs in Python
  37. ✓MCP Server Monitoring: Prometheus, OpenTelemetry and Logs
  38. ✓Python AI Agent Frameworks Compared: Which to Choose (2026)
  39. ✓Self-Improving AI Agent: Learn From Tool Failures in Python
  40. ✓AI Agent Identity: Authenticate Agents to MCP Servers
  41. ✓Code-Action Agents in Python: One Program vs 37 Tool Calls
  42. ✓Multi-Tenant AI Agents: Keep Each Customer's Data Apart
  43. ✓What Is Agentic RAG? How It Differs from Classic RAG
  44. ✓AI Agent Memory in Python: Short-Term and Long-Term
  45. ✓Agentic RAG in Python: Give Your AI Agent a Search Tool
  46. ✓AI Agent Reasoning Patterns in Python: 9 Patterns Tested
  47. ✓Plan-and-Execute Agents in Python: Beyond the ReAct Loop
  48. ✓Self-Modifying AI Agent in Python: Open Mode & Limits
  49. ✓CrewAI Alternatives: 7 Options and How to Migrate (2026)
  50. ✓Migrate from AutoGen: Agent Framework, Promptise and More
  51. ✓What Is an LLM Guardrail? Types, Examples and Limits
  52. ✓AI Agent Guardrails in Python: Block Injection, Redact PII
  53. ✓AI Agent Code Execution Sandbox: Run LLM Code Safely
  54. ✓AI Agent Production Checklist: What to Check Before Launch
  55. ✓Approval Fatigue: Auto-Approve Safe AI Agent Actions
  56. ✓What Is Human in the Loop AI? Approvals for AI Agents
  57. ✓AI Agent Notifications: Webhook and Slack Alerts in Python
  58. ✓AI Agent Observability: Trace Tool Calls, Tokens and Cost
  59. ✓AI Agent Stuck in a Loop? How to Detect and Stop It
  60. ✓How to Run, Debug and Deploy an AI Agent in Python
  61. 61AI Agent Cost Tracking: Tokens and Cost per Run and User
  62. 62How to Evaluate AI Agents: Evals, LLM Judges and pass^k
  63. 63AI Agent Cost Control: Budgets That Stop Runaway Spend
  64. 64How to Test LLM Prompts and AI Agents in Python
  65. 65AI Agent Evals in CI: Run Them on Every Pull Request
  66. 66Autonomous AI Agent Runtime: Budgets, Crash Recovery, API
  67. 67Event-Driven AI Agents: Webhook, Cron and File Triggers
  68. 68Durable AI Agents: Crash Recovery and Safe Resume
  69. 69Deploy AI Agents in Production: Long-Running Python Agents
  70. 70What Is an AI Agent Framework? When You Need One
  71. 71LangGraph Alternatives: 7 Options and How to Migrate (2026)

Lessons in this path

  1. ✓What Is an AI Agent? Definition, How It Works, Examples
  2. ✓How to Build an AI Agent in Python (2026 Guide)
  3. ✓What Is an Agent Harness? Parts and How to Build One
  4. ✓System Prompt Best Practices for AI Agents in Python
  5. ✓Best LLM for AI Agents: Test and Switch Models in Python
  6. ✓AI Agent YAML: Define and Run an Agent From a Config File
  7. ✓Streaming LLM Responses with FastAPI: Tokens and Tool Calls
  8. ✓Claude AI Agent with MCP: Use Anthropic Models in Promptise
  9. ✓Gemini AI Agent with MCP: Use Google Models in Promptise
  10. ✓Azure OpenAI Agent with MCP: Use Deployments in Promptise
  11. ✓OpenRouter AI Agent with MCP: Many Models in Promptise
  12. ✓Ollama AI Agent with MCP: Run Local Models in Promptise
  13. ✓LLM Semantic Cache and Model Fallback in Python
  14. ✓What Is MCP (Model Context Protocol)? A Developer's Guide
  15. ✓What Is Tool Calling? LLM Function Calling Explained
  16. ✓MCP Tools vs Resources vs Prompts (With Python Examples)
  17. ✓How to Connect MCP Servers to Your AI Agent in Python
  18. ✓Build a Production MCP Server in Python (Step by Step)
  19. ✓MCP Transports: stdio vs Streamable HTTP vs SSE (Python)
  20. ✓FastAPI MCP Server: Turn Your FastAPI App into MCP Tools
  21. ✓OpenAPI to MCP: Turn Any REST API into an MCP Server
  22. ✓MCP Authentication in Python: API Keys, JWT and OAuth
  23. ✓Build an Authenticated MCP Server for Your AI Agent (Python)
  24. ✓MCP Security Best Practices: A Checklist for MCP Servers
  25. ✓Testing MCP Servers in Python: pytest Without a Live Server
  26. ✓Too Many MCP Tools? LLM Tool Selection, Measured
  27. ✓MCP Server Caching, Timeouts and Circuit Breakers in Python
  28. ✓MCP vs CLI for AI Agents: Tokens, Security, When to Use Each
  29. ✓AI Agent Audit Logging: Tamper-Evident Logs in Python
  30. ✓AI Agent Human in the Loop: Approve Tool Calls in Python
  31. ✓AI Agent Prompt Injection: How to Defend Against It
  32. ✓Build a Multi-Agent System in Python (and When Not To)
  33. ✓Context Engineering for AI Agents: Context Window Management
  34. ✓How to Deploy an AI Agent with Docker (2026 Guide)
  35. ✓LLM Tool Rate Limiting: Per-Client Limits for MCP Servers
  36. ✓MCP Long-Running Tasks: Background Jobs in Python
  37. ✓MCP Server Monitoring: Prometheus, OpenTelemetry and Logs
  38. ✓Python AI Agent Frameworks Compared: Which to Choose (2026)
  39. ✓Self-Improving AI Agent: Learn From Tool Failures in Python
  40. ✓AI Agent Identity: Authenticate Agents to MCP Servers
  41. ✓Code-Action Agents in Python: One Program vs 37 Tool Calls
  42. ✓Multi-Tenant AI Agents: Keep Each Customer's Data Apart
  43. ✓What Is Agentic RAG? How It Differs from Classic RAG
  44. ✓AI Agent Memory in Python: Short-Term and Long-Term
  45. ✓Agentic RAG in Python: Give Your AI Agent a Search Tool
  46. ✓AI Agent Reasoning Patterns in Python: 9 Patterns Tested
  47. ✓Plan-and-Execute Agents in Python: Beyond the ReAct Loop
  48. ✓Self-Modifying AI Agent in Python: Open Mode & Limits
  49. ✓CrewAI Alternatives: 7 Options and How to Migrate (2026)
  50. ✓Migrate from AutoGen: Agent Framework, Promptise and More
  51. ✓What Is an LLM Guardrail? Types, Examples and Limits
  52. ✓AI Agent Guardrails in Python: Block Injection, Redact PII
  53. ✓AI Agent Code Execution Sandbox: Run LLM Code Safely
  54. ✓AI Agent Production Checklist: What to Check Before Launch
  55. ✓Approval Fatigue: Auto-Approve Safe AI Agent Actions
  56. ✓What Is Human in the Loop AI? Approvals for AI Agents
  57. ✓AI Agent Notifications: Webhook and Slack Alerts in Python
  58. ✓AI Agent Observability: Trace Tool Calls, Tokens and Cost
  59. ✓AI Agent Stuck in a Loop? How to Detect and Stop It
  60. ✓How to Run, Debug and Deploy an AI Agent in Python
  61. 61AI Agent Cost Tracking: Tokens and Cost per Run and User
  62. 62How to Evaluate AI Agents: Evals, LLM Judges and pass^k
  63. 63AI Agent Cost Control: Budgets That Stop Runaway Spend
  64. 64How to Test LLM Prompts and AI Agents in Python
  65. 65AI Agent Evals in CI: Run Them on Every Pull Request
  66. 66Autonomous AI Agent Runtime: Budgets, Crash Recovery, API
  67. 67Event-Driven AI Agents: Webhook, Cron and File Triggers
  68. 68Durable AI Agents: Crash Recovery and Safe Resume
  69. 69Deploy AI Agents in Production: Long-Running Python Agents
  70. 70What Is an AI Agent Framework? When You Need One
  71. 71LangGraph Alternatives: 7 Options and How to Migrate (2026)
← Path overview

AI agent cost tracking answers a question your provider's invoice can't: which run, user, agent or feature spent the money. This guide is for developers running agents for real users who need that answer before the bill arrives. You will learn how agent costs add up, what to record for every model call, how to roll it up per run, per user and per agent, and how to add alerts and limits. The implementation uses Promptise Foundry, and every output below comes from running the code.

[01]

How do you track the cost of an AI agent?

Record the input and output tokens of every model call, together with the model name, the run it belongs to and the user who started it. Multiply the tokens by your price table to get a cost per call. Then add the calls up per run, per user, per agent and per day. Store those totals where you can query them, compare them with a limit before each new run, and alert when a total crosses a threshold.

  • Count per model call, not per request. One agent request makes several model calls, and each one resends the growing conversation.

  • Attribute at the source. Tag every call with a run id and a user id when it happens. You can't split an invoice afterwards.

  • Keep your own price table. Providers report dated model names and change prices, so match by pattern and review the table when prices change.

  • Separate alerts from limits. An alert tells you a user is expensive. A limit refuses the next run. You usually want both.

  • Reconcile with the invoice. Token-based estimates miss cache discounts, batch pricing and tool costs. Use them for attribution and limits, and the invoice for accounting.


[02]

How AI agent costs add up

A chat completion has one cost: the prompt tokens times the input price, plus the response tokens times the output price. An agent multiplies that. Each request runs a loop, and every turn of the loop is a full model call that includes the system prompt, the tool schemas, the conversation so far and every tool result:

Text
1
2
3
4
5
6
7
8
9
request "Is TENT-2P in stock, and what about order A1001?"
  │
  ├─ model call 1   input: system + tools + question               211 tokens
  │                 output: two tool calls                         124 tokens
  ├─ tools run      check_stock, get_order   (no model tokens, may have their own cost)
  └─ model call 2   input: everything above + both tool results    331 tokens
                    output: the answer                             177 tokens
                                                                   ─────────
  cost of the run = Σ (input tokens × input price + output tokens × output price)

Four things make agent cost hard to predict:

  • Input grows with every turn. The second call above carries everything the first one did, plus the tool results. A run with ten tool calls can resend the same system prompt ten times.

  • Output usually costs more per token. For the models in the table below, the output price is five to eight times the input price. Reasoning models also bill their hidden reasoning as output tokens, so a short answer can carry hundreds of output tokens.

  • Some input is discounted. Providers charge less for cached prompt prefixes and batch requests. An estimate that prices every input token at the full rate is an upper bound.

  • Tools have their own costs. A search API, a geocoder or a payment fee is billed by someone else. Model tokens don't include it.


[03]

What should you track?

Level

What it answers

Where the number comes from

---|---|---

Per model call

Which step is expensive? Which model?

The usage the provider returns with each call

Per run (one request)

What does one answer cost? Which requests loop?

The sum of its calls

Per user or tenant

Who drives the bill? Who should be limited?

The runs attributed to that caller

Per agent or feature

Is the triage agent cheaper than the support agent?

The runs of each agent

Per day or month

Are we on budget?

All runs in the period, from your store

Record the call level, and compute everything else from it. If you only keep totals, you can't answer the next question someone asks.


[04]

What do LLM tokens cost?

Prices are set per million tokens and change several times a year. These are list prices for standard text tokens on 2026-10-18, from the OpenAI pricing page and the Anthropic pricing page. Check the pages before you copy a number:

Model

Input (USD / 1M)

Cached input

Output (USD / 1M)

---|---|---|---

gpt-5-nano

0.05

0.005

0.40

gpt-5-mini

0.25

0.025

2.00

gpt-5.4-mini

0.75

0.075

4.50

gpt-5.5

5.00

0.50

30.00

Claude Haiku 5.5 (prompts up to 100k tokens)

0.10

0.01

0.50

Claude Sonnet 5.5

2.00

0.10

10.00

Claude Opus 5.5

4.00

0.20

20.00

Two details matter for tracking. Providers report dated model names (gpt-5-mini-2025-08-07), so match your table by prefix or glob. And some models change price above a prompt length, like Claude Haiku 5.5 above 100,000 tokens, so a single rate per model is an estimate for long prompts.


[05]

When to build your own cost tracking (and when not to)

The provider's usage dashboard shows spend per API key and per model, which is enough for a single internal tool. An LLM gateway or proxy sees every call from every service and can meter them centrally. Build tracking into the agent when you need what only the application knows: which end user, tenant, conversation or feature a call belongs to, and the ability to refuse a run before it spends anything. Most production agents end up with the dashboard for accounting and in-app tracking for attribution and limits.


[06]

What you need

  • Python 3.10 or later and Promptise Foundry 1.4.1 or later: pip install "promptise>=1.4.1".

  • An OpenAI API key in OPENAI_API_KEY. The example uses gpt-5-mini, and the whole guide costs well under a cent to run.

  • A small shop MCP server, server.py, with two read-only tools (get_order, check_stock). It is in the example files, and any tools work the same way.


[07]

Step 1: Write the price table and the cost of one call

Start with the part every tracking system needs, whatever the framework: prices per million tokens and a function that prices one call.

Pythoncosts.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
"""Token cost tracking: price table, per-call cost, and a ledger per run, user and agent."""
import logging
from collections import defaultdict
from fnmatch import fnmatchcase

log = logging.getLogger("costs")

# USD per 1 million tokens: (input, output). Check your provider's pricing page:
# these change, and providers report dated model names (gpt-5-mini-2025-08-07).
PRICES = {
    "gpt-5-mini*": (0.25, 2.00),
    "gpt-5-nano*": (0.05, 0.40),
}


def call_cost(model: str, prompt_tokens: int, completion_tokens: int) -> float | None:
    """Estimated USD cost of one model call, or None when the model has no price."""
    for pattern, (input_price, output_price) in PRICES.items():
        if fnmatchcase(model, pattern):
            return (prompt_tokens * input_price + completion_tokens * output_price) / 1_000_000
    return None


class CostLedger:
    """Folds observability events into cost totals per run, per user and per agent."""

    def __init__(self, user_budget_usd: float) -> None:
        self.user_budget_usd = user_budget_usd
        self.by_run: dict[str, float] = defaultdict(float)
        self.by_user: dict[str, float] = defaultdict(float)
        self.by_agent: dict[str, float] = defaultdict(float)
        self.unpriced: set[str] = set()
        self._alerted: set[str] = set()

    def on_event(self, entry) -> None:
        """Called for every timeline event; only llm.end carries token usage."""
        if entry.event_type.value != "llm.end":
            return
        meta = entry.metadata
        model = meta.get("model", "unknown")
        cost = call_cost(model, meta.get("prompt_tokens", 0), meta.get("completion_tokens", 0))
        if cost is None:
            self.unpriced.add(model)
            return
        user = entry.user_id or "anonymous"
        self.by_run[entry.parent_id or "no-run"] += cost
        self.by_user[user] += cost
        self.by_agent[entry.agent_id or "agent"] += cost
        if self.by_user[user] > self.user_budget_usd and user not in self._alerted:
            self._alerted.add(user)
            log.warning("user %s is over budget: $%.6f > $%.6f", user, self.by_user[user], self.user_budget_usd)

call_cost returns None for a model without a price instead of guessing, and the ledger records such models in unpriced, so a new model shows up as a gap rather than as zero spend. The CostLedger adds each priced call to three totals: the run (parent_id), the user and the agent. It also logs one warning the first time a user crosses user_budget_usd.


[08]

Step 2: Feed every model call into the ledger

Promptise records an llm.end event for every model call when observability is on. Its metadata carries model, prompt_tokens and completion_tokens, and the event itself carries agent_id, user_id and parent_id (the run it belongs to). A callback transporter hands each event to the ledger as it happens:

Pythonagent.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"""The support agent, built by a factory so main.py and promptise run share it."""
import sys

from promptise import PromptiseAgent, build_agent
from promptise.config import StdioServerSpec
from promptise.observability_config import ObservabilityConfig, ObserveLevel, TransporterType

from costs import CostLedger

LEDGER = CostLedger(user_budget_usd=0.001)


async def create_agent(ctx=None) -> PromptiseAgent:
    return await build_agent(
        model="openai:gpt-5-mini",
        instructions="You answer shop customers. Use the tools for facts. Keep answers short.",
        servers={"shop": StdioServerSpec(command=sys.executable, args=["server.py"])},
        observer_agent_id="support",
        observe=ObservabilityConfig(
            level=ObserveLevel.STANDARD,           # one llm.end event per model call
            transporters=[TransporterType.CALLBACK],
            on_event=LEDGER.on_event,
        ),
    )

ObserveLevel.STANDARD is the level that records one event per model call. BASIC only records the run totals, which is enough for cost per run but not for cost per model or per step. observer_agent_id names the agent in every event, which is what the per-agent totals group by. The factory pattern (create_agent) lets the same agent run from main.py, from tests and from promptise run.


[09]

Step 3: Attribute each run to a user

Pass the caller with each request. Promptise stores it in a context variable for the duration of the run, and every event recorded during the run carries its user_id:

Pythonmain.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
"""Answer a few customer questions and report what each run, user and agent cost."""
import asyncio
import logging

from promptise import CallerContext

from agent import LEDGER, create_agent
from costs import call_cost

logging.basicConfig(level=logging.WARNING, format="%(levelname)s %(name)s: %(message)s")

REQUESTS = [
    ("alice", "Where is my order A1002?"),
    ("bob", "Do you have STOVE-MINI in stock?"),
    ("alice", "Is TENT-2P in stock, and what about my order A1001?"),
    ("alice", "And is STOVE-MINI back in stock?"),
]
DAILY_LIMIT_USD = 0.0012   # per user; tiny on purpose, so the example hits it


async def main() -> None:
    agent = await create_agent()
    try:
        for user, question in REQUESTS:
            if LEDGER.by_user[user] >= DAILY_LIMIT_USD:      # check before you spend
                print(f"refused {user}: ${LEDGER.by_user[user]:.6f} spent today")
                continue
            await agent.ainvoke(
                {"messages": [{"role": "user", "content": question}]},
                caller=CallerContext(user_id=user),   # attributes every event of the run
            )

        print("per call:")
        for e in agent.collector.query():
            if e.event_type.value == "llm.end":
                m = e.metadata
                cost = call_cost(m["model"], m["prompt_tokens"], m["completion_tokens"])
                print(f"  {e.user_id:<6} {m['model']}  in={m['prompt_tokens']:>4} "
                      f"out={m['completion_tokens']:>4}  ${cost:.6f}")
        print("per run:  ", {run[:8]: round(c, 6) for run, c in LEDGER.by_run.items()})
        print("per user: ", {u: round(c, 6) for u, c in LEDGER.by_user.items()})
        print("per agent:", {a: round(c, 6) for a, c in LEDGER.by_agent.items()})

        stats = agent.get_stats()
        print("get_stats:", {k: stats[k] for k in
              ("llm_call_count", "total_prompt_tokens", "total_completion_tokens", "total_tokens")})
    finally:
        await agent.shutdown()


if __name__ == "__main__":
    asyncio.run(main())

The loop also shows the simplest hard limit: check the user's spend before the run, and refuse when it is over. Checking first is the point. A limit checked after the run lets the expensive run happen.


[10]

Step 4: Run it and read the numbers

>_Terminal
python main.py
Output
WARNING costs: user alice is over budget: $0.001076 > $0.001000
refused alice: $0.001512 spent today
per call:
  alice  gpt-5-mini-2025-08-07  in= 201 out=  90  $0.000230
  alice  gpt-5-mini-2025-08-07  in= 267 out= 239  $0.000545
  bob    gpt-5-mini-2025-08-07  in= 203 out=  26  $0.000103
  bob    gpt-5-mini-2025-08-07  in= 249 out= 369  $0.000800
  alice  gpt-5-mini-2025-08-07  in= 211 out= 124  $0.000301
  alice  gpt-5-mini-2025-08-07  in= 331 out= 177  $0.000437
per run:   {'fc8409b6': 0.000775, '560a0960': 0.000903, '7a897213': 0.000738}
per user:  {'alice': 0.001512, 'bob': 0.000903}
per agent: {'support': 0.002415}
get_stats: {'llm_call_count': 6, 'total_prompt_tokens': 1462, 'total_completion_tokens': 1025, 'total_tokens': 2487}

How to read it:

  • Per call: every run made two model calls, and the second always had more input (267 vs 201 tokens, 331 vs 211) because it carried the tool results. Output was the larger part of the cost on every call, often with fewer output tokens than input tokens, because gpt-5-mini charges eight times more per output token.

  • The alert and the limit: alice crossed the $0.001 alert threshold during her second run, so the ledger logged a warning. Her third request was refused before it started, because her total ($0.001512) was over the $0.0012 limit. Bob was never limited.

  • Per user and per agent: the per-user totals add up to the agent total ($0.001512 + $0.000903 = $0.002415).

  • Cross-check: get_stats() counts 1,462 input and 1,025 output tokens. At $0.25 and $2.00 per million, that is $0.000366 + $0.00205 = $0.002416, the same as the ledger apart from rounding. When the two disagree, a model is missing from your price table.


[11]

Step 5: See cost while you develop

You don't need the ledger to see what a single run costs. Put your prices in the project's promptise.toml, and the dev tools show tokens and cost for every run:

TOMLpromptise.toml
1
2
3
4
5
6
7
8
9
name = "support-bot"
main = "main.py"
agent = "agent.py:create_agent"

[env]
required = ["OPENAI_API_KEY"]

[pricing]
"gpt-5-mini*" = { input = 0.25, output = 2.00 }
>_Terminal
promptise run --devtools --report run.json -i "Where is my order A1002?"
Output
promptise run support-bot · agent.py:create_agent · once · devtools on (observe 
full)
● support  model openai:gpt-5-mini  2 tool(s)  1 MCP server(s)  observe standard
  ⇄ shop (stdio) /home/you/support-bot/… · 2 tool(s)
▸ support
  ◆ llm   1.46s  in 201 out 26 tok  → get_order
  ⚙ get_order({'order_id': 'A1002'})
  ✓ get_order 5ms  → {"order_id": "A1002", "status": "in_transit", "carrier": "DHL", "eta": "2026-10-21"}
  ◆ llm   2.82s  in 267 out 239 tok
◂ support done · 4.30s · 2 llm call(s) · 1 tool call(s) · 733 tok
■ support shut down

promptise run · exit 0 · 5.5s · 2 llm call(s) · 733 tok (in 468 out 265) · 1 
tool call(s) · 0 error(s) · cost $0.0006
agent    model              llm calls  tokens  tool calls  errors     cost
support  openai:gpt-5-mini          2     733           1       0  $0.0006
tool       calls  errors  avg ms
get_order      1       0       5
report: run.json

Each ◆ llm line is one model call with its input and output tokens. The summary prices the run with [pricing] and breaks it down per agent and per tool. [pricing] keys are matched by exact name, then without the provider: prefix, then as globs, and a model with no entry shows — and is listed as unpriced. Promptise ships no built-in price list on purpose, because a stale price is worse than none.

--report writes the same numbers as JSON, which a CI job can check:

>_Terminal
jq '.stats.totals | {llm_calls, prompt_tokens, completion_tokens, cost_usd, unpriced_models}' run.json
Output
{
  "llm_calls": 2,
  "prompt_tokens": 468,
  "completion_tokens": 265,
  "cost_usd": 0.000647,
  "unpriced_models": []
}

A CI step that runs your eval set with --report and fails when cost_usd grows by more than, say, 20% catches a prompt change that doubles the token count before it ships.


[12]

Step 6: Cap the cost of a single run

The per-user check in Step 3 stops the next run. To stop a run that is already too expensive, such as an agent stuck calling tools, cap its tokens. BudgetHook adds up the tokens of each run and ends the run when it goes over:

Pythonhookcap.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import asyncio
from promptise.engine.hooks import BudgetHook
from agent import create_agent

async def main():
    agent = await create_agent()
    agent.hooks.append(BudgetHook(max_tokens=300))
    try:
        r = await agent.ainvoke({"messages": [{"role": "user", "content": "Is TENT-2P in stock, and what about my order A1001?"}]})
        print("answer:", r["messages"][-1].content)
    except Exception as e:
        print(type(e).__name__, e)
    finally:
        await agent.shutdown()
asyncio.run(main())
Output
Token budget exceeded: 335 > 300
GraphBudgetExceededError Graph 'react' failed at node 'reason': Token budget exceeded: 335/300 tokens; BudgetHook stopped the run before the model gave an answer, after a call to check_stock, get_order whose result the model never saw

The limit is tiny here so that it fires. A real cap sits at a few times your normal run (look at your per-run numbers from Step 4), high enough that ordinary requests never hit it. When it fires before the model answered, the run raises GraphBudgetExceededError, so catch it and return a clear message. BudgetHook also takes max_cost_usd with a single blended cost_per_1k_tokens rate, which is coarser than the per-model table above.

For long-running agents, Promptise's runtime has a separate BudgetConfig with limits on tool calls, LLM turns and irreversible actions per run and per day. Its max_cost_per_run and max_cost_per_day count abstract units you assign to tools, not dollars, so they control what the agent does rather than what the model costs. AI agent cost control covers those limits and runaway loops in depth.


[13]

The complete example

Text
1
2
3
4
5
6
7
support-bot/
├── promptise.toml   # [pricing] for the dev tools and run reports
├── server.py        # the shop MCP server (two read-only tools)
├── costs.py         # price table, call_cost(), CostLedger
├── agent.py         # agent factory; feeds llm.end events to the ledger
├── main.py          # four requests, per-user limit, per-call/run/user/agent report
└── hookcap.py       # a per-run token cap with BudgetHook
>_Terminal
pip install "promptise>=1.4.1"
export OPENAI_API_KEY=sk-...
python main.py
promptise run --devtools --report run.json -i "Where is my order A1002?"

[14]

Running it in production

Persist the ledger

The ledger above lives in memory and is gone when the process restarts, and two replicas each keep their own numbers. In production, write one row per model call (timestamp, run id, user, tenant, agent, model, input and output tokens, cost) to a database or your log pipeline, and compute the totals with queries. Then the per-user check before each run reads the store, not a dictionary. Redis with a daily key per user (INCRBYFLOAT cost:alice:2026-10-18) is a common choice for the hot limit, with the raw rows in your warehouse.

Use the numbers Promptise already logs

Two outputs carry usage without extra code. promptise run --prod writes a final stats log line with total_tokens and cost_usd for the process. In serve mode, every /v1/invoke response includes a usage object with the run's model calls and tokens, which a gateway can record per user. Run, debug and deploy an AI agent shows both.

Export metrics and alert from your monitoring

The PROMETHEUS transporter exposes token counters for Grafana, and the OTLP transporter puts gen_ai.usage.input_tokens and gen_ai.usage.output_tokens on every model-call span, with enduser.id from the caller. Alert on rate of spend (dollars per hour) as well as totals: a loop shows up as a spike long before it shows up in a daily total. AI agent observability sets up both.

Keep traces free of user data

Cost tracking needs counts, not content. Tool arguments are recorded by default because they make traces useful for debugging. When events leave your trust boundary, set record_tool_io=False. The token counts, model names and user ids are still recorded. Promptise also redacts API keys, card numbers and emails from events by default (redact_sensitive=True).

Track tenants as well as users

For B2B products, store the tenant with every cost row (you know it when you build the CallerContext, which also has a tenant_id field) and group by it, so you can see whether each customer's usage fits its plan. Observability events carry user_id and session_id, not the tenant, so add it when you write the row. Multi-tenant AI agents covers isolating tenants beyond cost.


[15]

Common problems and fixes

Symptom

Cause

Fix

---|---|---

Cost shows — or None

The reported model name (gpt-5-mini-2025-08-07) has no entry in your price table

Match with a glob: "gpt-5-mini*"

The estimate is higher than the invoice

Cached input and batch discounts are priced at the full rate

Treat the estimate as an upper bound, or price cached tokens separately from the provider's usage fields

The estimate is lower than the invoice

Missing models, retries, embeddings or calls outside the agent

List unpriced models, and meter other model calls the same way

No per-call events, only totals

Observability is at BASIC

Use ObserveLevel.STANDARD

Every call shows user_id None

The request was invoked without a caller

Pass caller=CallerContext(user_id=...) to ainvoke

The ledger stays at zero, and the logs show CallbackTransporter error

Your callback raised; Promptise logs the error and keeps the agent running

Fix the callback, and alert on that log line so cost data never goes missing silently

A user goes far over the limit in one request

The limit is checked between runs only

Add a per-run token cap (BudgetHook) and a tool-call cap

Totals reset on deploy, or differ per replica

The ledger lives in process memory

Write per-call rows to a shared store and check limits against it


[16]

Limitations

The approach here estimates cost from tokens and your price table. It does not read the provider's billing API, so it can't see discounts, credits or price changes you haven't copied. Promptise's token events count prompt and completion tokens but don't split out cached input or reasoning tokens, and [pricing] takes one input and one output price per model. Tool and infrastructure costs aren't included. The ledger in this guide is in-process. For accounting, reconcile with the provider's usage export each month.


[17]

Frequently asked questions

How do I track token usage per user in an AI agent?

Attach the user to each request and record it with every model call. In Promptise, pass caller=CallerContext(user_id="alice") to ainvoke. Every observability event of that run, including each llm.end with its token counts, then carries user_id, so a callback or a query can sum tokens and cost per user.

How do I calculate the cost of an LLM call?

Multiply the prompt tokens by the input price and the completion tokens by the output price, using the price per million tokens, and divide by one million. For example, 331 input and 177 output tokens on gpt-5-mini cost (331 × 0.25 + 177 × 2.00) / 1,000,000 = $0.000437.

Why does my agent use more tokens than the conversation length suggests?

Every turn of the agent loop resends the system prompt, the tool schemas, the conversation and all earlier tool results. A request with five tool calls makes about six model calls, each larger than the last. Reasoning models also bill hidden reasoning tokens as output.

How do I set a cost limit for an AI agent?

Use two limits. Before each run, check the user's or tenant's spend for the period against a limit and refuse when it is over. During a run, cap tokens or tool calls so a single request can't run away. In Promptise, BudgetHook(max_tokens=...) caps tokens per run, and the runtime's BudgetConfig caps tool calls and turns.

Does get_stats() return the cost?

No. agent.get_stats() returns token counts (total_prompt_tokens, total_completion_tokens, total_tokens), call counts and latencies, but no dollar amount. Multiply the tokens by your prices yourself, or use promptise run with a [pricing] table, whose dev tools and run reports include cost_usd.

Should I use the provider dashboard or track cost in my app?

Both. The provider dashboard is the source of truth for what you pay. Only your application knows which user, tenant, conversation or feature a call belongs to, and only your application can refuse a run before it spends money. Use the dashboard for accounting and in-app tracking for attribution and limits.


[18]

Next steps

  • Stop runaway spend: AI agent cost control with budgets.

  • Find the step that loops: AI agent stuck in a loop.

  • Send token metrics to your monitoring: AI agent observability.

  • Cut cost with caching: Semantic cache and model fallback.

  • Watch cost per run while you develop: Run, debug and deploy an AI agent.

  • Reference: Observability docs and promptise run and pricing.

← PreviousHow to Run, Debug and Deploy an AI Agent in PythonNext lesson →How to Evaluate AI Agents: Evals, LLM Judges and pass^k