← Back to DevBytes

AI Agent Cost Alerts: Catching Bill Explosions Early

Understanding AI Agent Cost Explosions

AI agents — autonomous systems that chain multiple LLM calls, tool invocations, and reasoning steps — can generate costs at a velocity that catches even experienced developers off guard. A single agentic workflow might invoke an LLM dozens of times, each call carrying its own token-based price tag. Without deliberate monitoring, a runaway loop, an overly verbose chain-of-thought, or a misconfigured retry strategy can turn a $5 daily bill into a $500 catastrophe overnight.

An AI Agent Cost Alert is a programmatic guardrail that continuously tracks token consumption, estimates real-time dollar costs, and fires notifications — Slack messages, emails, PagerDuty alerts, or even automated circuit breakers — when spending crosses predefined thresholds. It transforms opaque API billing into actionable, time-sensitive signals.

Why Cost Alerts Matter for Agentic Workloads

Traditional LLM applications (single-prompt chatbots, classification endpoints) have predictable per-request costs. Agentic workloads break that predictability. Consider these real failure modes:

Cost alerts give you the early warning system needed to catch these scenarios within minutes, not days — before your CFO asks uncomfortable questions about a cloud services line item.

Architecture of a Cost Alert System

A production-grade cost alert pipeline has four layers:

Step 1: Instrumenting LLM Calls for Token Tracking

The foundation is a thin wrapper around your LLM provider's SDK. Here's a production-oriented example using OpenAI's API with a cost-tracking decorator pattern:

import json
import time
from dataclasses import dataclass, field
from typing import List, Callable, Optional
from openai import OpenAI

# Pricing table (USD per 1K tokens) — keep this updated
MODEL_PRICING = {
    "gpt-4o": {"prompt": 0.005, "completion": 0.015},
    "gpt-4o-mini": {"prompt": 0.00015, "completion": 0.0006},
    "gpt-4-turbo": {"prompt": 0.01, "completion": 0.03},
    "gpt-3.5-turbo": {"prompt": 0.0005, "completion": 0.0015},
    "claude-3-5-sonnet-20241022": {"prompt": 0.003, "completion": 0.015},
    "claude-3-haiku-20240307": {"prompt": 0.00025, "completion": 0.00125},
}

@dataclass
class CostEvent:
    """Immutable record of a single LLM call's cost."""
    timestamp: float
    model: str
    prompt_tokens: int
    completion_tokens: int
    cost_usd: float
    agent_session_id: str
    call_duration_ms: float

@dataclass
class CostTracker:
    """Accumulates cost events and evaluates thresholds."""
    session_id: str
    events: List[CostEvent] = field(default_factory=list)
    on_threshold_breach: Optional[Callable] = None
    max_session_cost: float = 10.0  # default: $10 per agent session
    max_hourly_cost: float = 50.0   # default: $50 per hour
    
    def record(self, event: CostEvent) -> None:
        self.events.append(event)
        self._evaluate_thresholds()
    
    def total_session_cost(self) -> float:
        return sum(e.cost_usd for e in self.events)
    
    def hourly_cost(self) -> float:
        now = time.time()
        window_start = now - 3600
        return sum(e.cost_usd for e in self.events if e.timestamp >= window_start)
    
    def _evaluate_thresholds(self) -> None:
        session_cost = self.total_session_cost()
        hour_cost = self.hourly_cost()
        
        if session_cost >= self.max_session_cost:
            if self.on_threshold_breach:
                self.on_threshold_breach(
                    "session", session_cost, self.max_session_cost, self.session_id
                )
        
        if hour_cost >= self.max_hourly_cost:
            if self.on_threshold_breach:
                self.on_threshold_breach(
                    "hourly", hour_cost, self.max_hourly_cost, self.session_id
                )

def estimate_cost(model: str, prompt_tokens: int, completion_tokens: int) -> float:
    """Calculate USD cost from token counts and pricing table."""
    if model not in MODEL_PRICING:
        # Unknown model — log warning and use a conservative estimate
        print(f"WARNING: Unknown model {model}, using fallback pricing")
        return (prompt_tokens + completion_tokens) * 0.01 / 1000
    
    pricing = MODEL_PRICING[model]
    prompt_cost = (prompt_tokens / 1000) * pricing["prompt"]
    completion_cost = (completion_tokens / 1000) * pricing["completion"]
    return prompt_cost + completion_cost

Step 2: Creating the Instrumented LLM Client

Now we build a client class that wraps OpenAI calls, extracts usage metadata from the response, and pushes cost events into the tracker:

class MonitoredLLMClient:
    """Wrapper around OpenAI client that tracks every call's cost."""
    
    def __init__(self, tracker: CostTracker, openai_client: OpenAI):
        self.tracker = tracker
        self.client = openai_client
    
    def chat_completion(
        self,
        model: str = "gpt-4o",
        messages: list = None,
        temperature: float = 0.7,
        max_tokens: int = 2048,
        **kwargs
    ) -> dict:
        """Drop-in replacement for chat.completions.create with cost tracking."""
        
        start_time = time.time()
        
        response = self.client.chat.completions.create(
            model=model,
            messages=messages,
            temperature=temperature,
            max_tokens=max_tokens,
            **kwargs
        )
        
        duration_ms = (time.time() - start_time) * 1000
        
        # Extract token counts from the response
        usage = response.usage
        if usage:
            prompt_tokens = usage.prompt_tokens
            completion_tokens = usage.completion_tokens
            cost = estimate_cost(model, prompt_tokens, completion_tokens)
            
            event = CostEvent(
                timestamp=time.time(),
                model=model,
                prompt_tokens=prompt_tokens,
                completion_tokens=completion_tokens,
                cost_usd=cost,
                agent_session_id=self.tracker.session_id,
                call_duration_ms=duration_ms
            )
            
            self.tracker.record(event)
        
        return {
            "content": response.choices[0].message.content,
            "usage": {
                "prompt_tokens": usage.prompt_tokens if usage else 0,
                "completion_tokens": usage.completion_tokens if usage else 0,
                "total_tokens": usage.total_tokens if usage else 0,
                "estimated_cost_usd": round(cost, 6) if usage else 0
            }
        }
    
    def streamed_completion(self, model: str = "gpt-4o", messages: list = None, **kwargs):
        """Generator that yields chunks AND tracks cost after completion.
        
        Note: streaming responses report usage only on the final chunk.
        """
        start_time = time.time()
        stream = self.client.chat.completions.create(
            model=model,
            messages=messages,
            stream=True,
            stream_options={"include_usage": True},  # OpenAI beta feature
            **kwargs
        )
        
        collected_content = []
        final_usage = None
        
        for chunk in stream:
            if chunk.choices and chunk.choices[0].delta.content:
                collected_content.append(chunk.choices[0].delta.content)
                yield {"delta": chunk.choices[0].delta.content, "done": False}
            
            # The final chunk may include usage when stream_options is set
            if hasattr(chunk, 'usage') and chunk.usage:
                final_usage = chunk.usage
        
        duration_ms = (time.time() - start_time) * 1000
        
        if final_usage:
            prompt_tokens = final_usage.prompt_tokens
            completion_tokens = final_usage.completion_tokens
            cost = estimate_cost(model, prompt_tokens, completion_tokens)
            
            event = CostEvent(
                timestamp=time.time(),
                model=model,
                prompt_tokens=prompt_tokens,
                completion_tokens=completion_tokens,
                cost_usd=cost,
                agent_session_id=self.tracker.session_id,
                call_duration_ms=duration_ms
            )
            self.tracker.record(event)
        
        yield {"content": "".join(collected_content), "done": True}

Step 3: Building the Alert Dispatcher

The alert dispatcher listens for threshold breaches and routes notifications to the appropriate channels. Here's a modular implementation supporting Slack, email, and a circuit breaker:

import os
import smtplib
import requests
from email.mime.text import MIMEText
from datetime import datetime

class AlertDispatcher:
    """Routes cost alerts to Slack, email, and/or programmatic circuit breakers."""
    
    def __init__(self, slack_webhook_url: str = None, email_config: dict = None):
        self.slack_webhook_url = slack_webhook_url
        self.email_config = email_config
        self.circuit_breakers = {}  # session_id -> bool (True = open/blocked)
    
    def handle_breach(self, breach_type: str, current_cost: float, 
                      threshold: float, session_id: str) -> None:
        """Entry point called by CostTracker when a threshold is breached."""
        
        alert_message = (
            f"⚠️ **AI Agent Cost Alert**\n"
            f"Breach Type: `{breach_type}`\n"
            f"Session ID: `{session_id}`\n"
            f"Current Cost: `${current_cost:.4f}`\n"
            f"Threshold: `${threshold:.2f}`\n"
            f"Timestamp: {datetime.now().isoformat()}\n"
            f"Overrun: `${current_cost - threshold:.4f}` ({(current_cost/threshold - 1)*100:.1f}%)\n"
        )
        
        if self.slack_webhook_url:
            self._send_slack(alert_message)
        
        if self.email_config:
            self._send_email(alert_message)
        
        # Open circuit breaker for runaway sessions
        if breach_type == "session" and current_cost >= threshold * 1.5:
            self.circuit_breakers[session_id] = True
            print(f"CIRCUIT BREAKER OPENED for session {session_id}")
    
    def is_circuit_open(self, session_id: str) -> bool:
        return self.circuit_breakers.get(session_id, False)
    
    def _send_slack(self, message: str) -> None:
        payload = {
            "text": message,
            "username": "AI Cost Monitor",
            "icon_emoji": ":warning:"
        }
        try:
            response = requests.post(
                self.slack_webhook_url,
                json=payload,
                timeout=5
            )
            if response.status_code != 200:
                print(f"Slack webhook failed: {response.status_code} {response.text}")
        except Exception as e:
            print(f"Failed to send Slack alert: {e}")
    
    def _send_email(self, message: str) -> None:
        if not self.email_config:
            return
        
        msg = MIMEText(message.replace('`', '').replace('*', ''))
        msg['Subject'] = f"AI Agent Cost Alert - ${message.split('$')[1].split('\\n')[0]}"
        msg['From'] = self.email_config.get('from', 'ai-monitor@company.com')
        msg['To'] = self.email_config.get('to', 'devops@company.com')
        
        try:
            with smtplib.SMTP(self.email_config['smtp_host'], 
                              self.email_config.get('smtp_port', 587)) as server:
                server.starttls()
                server.login(
                    self.email_config['smtp_user'],
                    self.email_config['smtp_password']
                )
                server.send_message(msg)
        except Exception as e:
            print(f"Failed to send email alert: {e}")

Step 4: Tying It All Together in an Agent Loop

Here's how to integrate cost monitoring into a typical agentic workflow — a ReAct-style agent that reasons, acts, and observes. Notice the circuit breaker check before each LLM call:

class CostMonitoredAgent:
    """ReAct agent with per-step cost tracking and circuit breaker protection."""
    
    def __init__(self, system_prompt: str, tools: list, 
                 model: str = "gpt-4o-mini",
                 max_steps: int = 20,
                 session_cost_limit: float = 5.0,
                 hourly_cost_limit: float = 25.0):
        
        self.tools = {t.__name__: t for t in tools}
        self.model = model
        self.max_steps = max_steps
        self.system_prompt = system_prompt
        
        # Set up cost monitoring infrastructure
        self.tracker = CostTracker(
            session_id=f"agent-{id(self)}-{int(time.time())}",
            max_session_cost=session_cost_limit,
            max_hourly_cost=hourly_cost_limit
        )
        
        self.dispatcher = AlertDispatcher(
            slack_webhook_url=os.environ.get("SLACK_WEBHOOK_URL")
        )
        
        self.tracker.on_threshold_breach = self.dispatcher.handle_breach
        
        openai_client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
        self.llm = MonitoredLLMClient(self.tracker, openai_client)
        
        self.messages = [{"role": "system", "content": system_prompt}]
    
    def run(self, user_query: str) -> str:
        self.messages.append({"role": "user", "content": user_query})
        
        for step in range(self.max_steps):
            # Circuit breaker check before any LLM call
            if self.dispatcher.is_circuit_open(self.tracker.session_id):
                return (
                    f"Agent halted: cost circuit breaker open. "
                    f"Session cost: ${self.tracker.total_session_cost():.4f}. "
                    f"Please investigate."
                )
            
            # Optional: dynamic model downgrade when costs are high
            active_model = self.model
            if self.tracker.hourly_cost() > self.tracker.max_hourly_cost * 0.7:
                active_model = "gpt-4o-mini"  # downgrade to cheaper model
                print(f"⚠️ Downgrading to {active_model} due to cost pressure")
            
            response = self.llm.chat_completion(
                model=active_model,
                messages=self.messages,
                temperature=0.7
            )
            
            assistant_message = response["content"]
            call_info = (
                f"Step {step+1} | "
                f"Tokens: {response['usage']['total_tokens']} | "
                f"Cost: ${response['usage']['estimated_cost_usd']:.4f} | "
                f"Session Total: ${self.tracker.total_session_cost():.4f}"
            )
            print(call_info)
            
            # Parse for tool calls (simplified — adapt to your agent framework)
            tool_name, tool_args = self._parse_tool_call(assistant_message)
            
            if tool_name is None:
                # Agent provided final answer
                return assistant_message
            
            if tool_name in self.tools:
                tool_result = self.tools[tool_name](**tool_args)
                self.messages.append({
                    "role": "assistant",
                    "content": assistant_message
                })
                self.messages.append({
                    "role": "user",
                    "content": f"Tool {tool_name} returned: {tool_result}"
                })
            else:
                self.messages.append({
                    "role": "assistant",
                    "content": assistant_message
                })
                self.messages.append({
                    "role": "user",
                    "content": f"Error: tool '{tool_name}' not found. Available: {list(self.tools.keys())}"
                })
        
        return "Agent reached maximum steps without final answer."
    
    def _parse_tool_call(self, text: str):
        """Basic tool call parser — replace with structured output in production."""
        import re
        match = re.search(r'Action:\s*(\w+)\s*\((.*?)\)', text, re.DOTALL)
        if match:
            tool_name = match.group(1)
            args_str = match.group(2)
            try:
                args = json.loads(args_str)
            except json.JSONDecodeError:
                args = {"input": args_str.strip()}
            return tool_name, args
        return None, None
    
    def get_cost_report(self) -> dict:
        return {
            "session_id": self.tracker.session_id,
            "total_cost": self.tracker.total_session_cost(),
            "call_count": len(self.tracker.events),
            "hourly_cost": self.tracker.hourly_cost(),
            "circuit_open": self.dispatcher.is_circuit_open(self.tracker.session_id),
            "events": [
                {
                    "model": e.model,
                    "cost": e.cost_usd,
                    "tokens": e.prompt_tokens + e.completion_tokens
                }
                for e in self.tracker.events[-10:]  # last 10 for brevity
            ]
        }

Step 5: Running the Agent with Cost Visibility

Here's a complete example demonstrating the agent with two simulated tools and full cost monitoring:

import os
import time

# Simulated tools
def search_web(query: str) -> str:
    """Simulate a web search — returns truncated results."""
    return f"Search results for '{query}': Found 3 relevant articles about {query[:30]}..."

def calculator(expression: str) -> str:
    """Evaluate a mathematical expression safely."""
    try:
        result = eval(expression, {"__builtins__": {}}, {})
        return f"Result: {result}"
    except Exception as e:
        return f"Error: {str(e)}"

# Configure environment (set these in your .env or secrets manager)
os.environ["OPENAI_API_KEY"] = "sk-your-key-here"
os.environ["SLACK_WEBHOOK_URL"] = "https://hooks.slack.com/services/..."

# Initialize the agent
agent = CostMonitoredAgent(
    system_prompt="You are a helpful assistant. Use tools when needed. "
                  "Format tool calls as: Action: tool_name({\"arg\": \"value\"})",
    tools=[search_web, calculator],
    model="gpt-4o-mini",
    max_steps=10,
    session_cost_limit=5.0,
    hourly_cost_limit=25.0
)

# Run a query
print("=" * 60)
print("STARTING AGENT RUN — Cost monitoring active")
print("=" * 60)

result = agent.run(
    "What is the population of Tokyo in 2024? "
    "Search for the data, then calculate the population density "
    "if the area is 2,194 square kilometers."
)

print("\\n" + "=" * 60)
print("AGENT RESULT:")
print(result)
print("\\n" + "=" * 60)

# Print cost report
import json
cost_report = agent.get_cost_report()
print("\\nCOST REPORT:")
print(json.dumps(cost_report, indent=2))
print(f"\\nTotal Session Cost: ${cost_report['total_cost']:.4f}")
print(f"Number of LLM Calls: {cost_report['call_count']}")
print(f"Average Cost Per Call: ${cost_report['total_cost']/max(1,cost_report['call_count']):.4f}")

Advanced: Multi-Provider Cost Aggregation Service

In production, you'll likely have multiple agents running concurrently across different LLM providers (OpenAI, Anthropic, Google, Azure). A centralized cost aggregation service with persistent storage is essential. Here's a lightweight version using Redis for real-time counters:

import redis
import json
from datetime import datetime

class RedisCostAggregator:
    """Multi-agent, multi-provider cost tracking backed by Redis."""
    
    def __init__(self, redis_client: redis.Redis, retention_hours: int = 24):
        self.redis = redis_client
        self.retention_hours = retention_hours
    
    def record_cost(self, agent_id: str, model: str, 
                    prompt_tokens: int, completion_tokens: int, 
                    cost_usd: float) -> None:
        """Atomically update multiple cost counters."""
        
        now = datetime.now()
        hour_bucket = now.strftime("%Y-%m-%dT%H")
        minute_bucket = now.strftime("%Y-%m-%dT%H:%M")
        
        pipeline = self.redis.pipeline()
        
        # Per-agent running totals
        pipeline.incrbyfloat(f"agent:{agent_id}:total_cost", cost_usd)
        pipeline.incrby(f"agent:{agent_id}:call_count", 1)
        pipeline.incrby(f"agent:{agent_id}:total_tokens", 
                        prompt_tokens + completion_tokens)
        
        # Per-model global totals
        pipeline.incrbyfloat(f"model:{model}:total_cost", cost_usd)
        pipeline.incrby(f"model:{model}:call_count", 1)
        
        # Time-bucketed costs for dashboarding
        pipeline.incrbyfloat(f"costs:hourly:{hour_bucket}", cost_usd)
        pipeline.incrbyfloat(f"costs:minutely:{minute_bucket}", cost_usd)
        
        # Add to agent's event log with TTL
        event_key = f"agent:{agent_id}:events"
        event_data = json.dumps({
            "timestamp": now.isoformat(),
            "model": model,
            "cost_usd": cost_usd,
            "tokens": prompt_tokens + completion_tokens
        })
        pipeline.rpush(event_key, event_data)
        pipeline.expire(event_key, self.retention_hours * 3600)
        
        pipeline.execute()
    
    def get_agent_cost(self, agent_id: str) -> dict:
        """Retrieve current cost state for an agent."""
        total_cost = float(self.redis.get(f"agent:{agent_id}:total_cost") or 0)
        call_count = int(self.redis.get(f"agent:{agent_id}:call_count") or 0)
        total_tokens = int(self.redis.get(f"agent:{agent_id}:total_tokens") or 0)
        
        return {
            "agent_id": agent_id,
            "total_cost_usd": total_cost,
            "call_count": call_count,
            "total_tokens": total_tokens,
            "avg_cost_per_call": total_cost / max(1, call_count),
            "avg_tokens_per_call": total_tokens / max(1, call_count)
        }
    
    def get_hourly_cost(self, hour_bucket: str = None) -> float:
        """Get cost for a specific hour bucket, defaulting to current."""
        if hour_bucket is None:
            hour_bucket = datetime.now().strftime("%Y-%m-%dT%H")
        return float(self.redis.get(f"costs:hourly:{hour_bucket}") or 0)
    
    def check_and_alert(self, agent_id: str, 
                         session_limit: float = 10.0,
                         hourly_limit: float = 50.0) -> list:
        """Evaluate thresholds and return triggered alerts."""
        alerts = []
        agent_cost = self.get_agent_cost(agent_id)
        current_hour = datetime.now().strftime("%Y-%m-%dT%H")
        hourly_cost = self.get_hourly_cost(current_hour)
        
        if agent_cost["total_cost_usd"] >= session_limit:
            alerts.append({
                "type": "session",
                "agent_id": agent_id,
                "current": agent_cost["total_cost_usd"],
                "limit": session_limit,
                "severity": "critical" if agent_cost["total_cost_usd"] >= session_limit * 1.5 else "warning"
            })
        
        if hourly_cost >= hourly_limit:
            alerts.append({
                "type": "hourly",
                "hour_bucket": current_hour,
                "current": hourly_cost,
                "limit": hourly_limit,
                "severity": "critical" if hourly_cost >= hourly_limit * 1.5 else "warning"
            })
        
        return alerts

Setting Up a Cron-Based Alert Sweeper

For a lightweight production setup, run a periodic sweeper that checks all active agents and dispatches alerts. This pairs well with the Redis aggregator above:

import redis
import os
import requests
from datetime import datetime

def sweep_and_alert():
    """Periodic job — run every 60 seconds via cron or a background thread."""
    
    r = redis.Redis(
        host=os.environ.get("REDIS_HOST", "localhost"),
        port=int(os.environ.get("REDIS_PORT", 6379)),
        decode_responses=True
    )
    
    aggregator = RedisCostAggregator(r)
    slack_url = os.environ.get("SLACK_WEBHOOK_URL")
    
    # Find all active agent keys
    agent_keys = r.keys("agent:*:total_cost")
    alerted_agents = set()
    
    for key in agent_keys:
        agent_id = key.decode() if isinstance(key, bytes) else key
        agent_id = agent_id.split(":")[1]  # Extract agent ID
        
        alerts = aggregator.check_and_alert(
            agent_id,
            session_limit=float(os.environ.get("SESSION_COST_LIMIT", "10.0")),
            hourly_limit=float(os.environ.get("HOURLY_COST_LIMIT", "50.0"))
        )
        
        for alert in alerts:
            # Deduplicate: don't re-alert the same agent within 5 minutes
            dedup_key = f"{agent_id}:{alert['type']}"
            if dedup_key in alerted_agents:
                continue
            alerted_agents.add(dedup_key)
            
            # Set dedup TTL in Redis
            r.setex(f"alert_dedup:{dedup_key}", 300, "1")
            
            if slack_url and alert['severity'] == 'critical':
                payload = {
                    "text": (
                        f"🚨 *CRITICAL COST ALERT*\n"
                        f"Agent: `{agent_id}`\n"
                        f"Type: {alert['type']}\n"
                        f"Current: ${alert['current']:.4f}\n"
                        f"Limit: ${alert['limit']:.2f}\n"
                        f"Time: {datetime.now().isoformat()}"
                    ),
                    "channel": "#ai-cost-alerts"
                }
                requests.post(slack_url, json=payload, timeout=5)
            
            print(f"[{datetime.now().isoformat()}] ALERT: {alert}")

if __name__ == "__main__":
    sweep_and_alert()

Best Practices for AI Agent Cost Alerts

Handling Edge Cases

Several edge cases deserve explicit attention in production systems:

Dashboard Integration: Making Costs Visible

Alerts are the reactive layer. A dashboard gives you proactive visibility. Here's a minimal FastAPI endpoint that serves cost data for a Grafana or Datadog integration:

from fastapi import FastAPI
import redis
import os
from datetime import datetime, timedelta

app = FastAPI(title="AI Cost Monitor API")
redis_client = redis.Redis(
    host=os.environ.get("REDIS_HOST", "localhost"),
    decode_responses=True
)

@app.get("/costs/current")
def get_current_costs():
    """Return snapshot of all agent costs for dashboard widgets."""
    agent_keys = redis_client.keys("agent:*:total_cost")
    agents = []
    
    for key in agent_keys:
        agent_id = key.split(":")[1]
        total_cost = float(redis_client.get(key) or 0)
        call_count = int(redis_client.get(f"agent:{agent_id}:call_count") or 0)
        
        agents.append({
            "agent_id": agent_id,
            "total_cost_usd": round(total_cost, 4),
            "call_count": call_count,
            "avg_cost_per_call": round(total_cost / max(1, call_count), 4)
        })
    
    global_total = sum(a["total_cost_usd"] for a in agents)
    
    return {
        "timestamp": datetime.now().isoformat(),
        "total_cost_all_agents": round(global_total, 4),
        "active_agents": len(agents),
        "agents": agents
    }

@app.get("/costs/timeseries")
def get_timeseries(hours: int = 24):
    """Return hourly cost buckets for the last N hours."""
    buckets = []
    now = datetime.now()
    
    for i in range(hours):
        bucket_time = now - timedelta(hours=i)
        bucket_key = bucket_time.strftime("costs:hourly:%Y-%m-%dT%H")
        cost = float(redis_client.get(bucket_key) or 0)
        buckets.append({
            "hour": bucket_time.strftime("%Y-%m-%dT%H:00"),
            "cost_usd": round(cost, 4)
        })
    
    return {
        "timeseries": sorted(buckets, key=lambda b: b["hour"]),
        "total_in_window": round(sum(b["cost_usd"] for b in buckets), 4)
    }

@app.get("/costs/anomalies")
def get_anomalies():
    """Detect sessions with abnormal cost patterns."""
    agent_keys = redis_client.keys("agent:*:total_cost")
    anomalies = []
    
    for key in agent_keys:
        agent_id = key.split(":")[1]
        call_count = int(redis_client.get(f"agent:{agent_id}:call_count") or 0)
        total_tokens = int(redis

— Ad —

Google AdSense will appear here after approval

← Back to all articles