DEV Community

MrClaw207
MrClaw207

Posted on

I Tracked Every Token My LLM Agent Spent for 30 Days. Here's the Cost Pattern That Saved Me $400/Month.

Three hundred and twelve dollars. That's what my research agent burned through in June before I started paying attention to where the money actually went.

Not because the agent was doing anything wrong. It was doing exactly what I asked — searching SEC filings, summarizing findings, drafting reports. The problem was I had no idea what each task was costing until I got the bill. And once I looked, the waste was obvious.

This is the story of how I instrumented my agent to track token usage per task, what the 30-day pattern revealed, and the three changes that cut my monthly bill by 31% without touching output quality.

The Baseline: What I Was Actually Paying For

I run a lightweight agent stack: OpenRouter for model routing, a PostgreSQL table for task logging, and a cron job that runs every morning. Before June, I was just watching the total cost tick up in the OpenRouter dashboard with no per-task granularity.

The first thing I did was add a thin logging layer to every agent invocation:

import time
import tiktoken

def estimate_cost(model: str, input_tokens: int, output_tokens: int) -> float:
    # OpenRouter pricing (simplified, per 1M tokens)
    RATES = {
        "claude-3-5-sonnet": (3, 15),
        "gpt-4o": (5, 15),
        "gemini-1.5-flash": (0.35, 1.05),
        "deepseek-chat": (0.14, 0.28),
    }
    input_rate, output_rate = RATES.get(model, (10, 30))
    return (input_tokens / 1_000_000) * input_rate + \
           (output_tokens / 1_000_000) * output_rate

def log_task(model: str, task_type: str, input_tokens: int, 
             output_tokens: int, duration_ms: int, success: bool):
    cost = estimate_cost(model, input_tokens, output_tokens)
    conn.execute("""
        INSERT INTO agent_task_log 
        (model, task_type, input_tokens, output_tokens, 
         cost_usd, duration_ms, success, created_at)
        VALUES (%s, %s, %s, %s, %s, %s, %s, NOW())
    """, (model, task_type, input_tokens, output_tokens, 
          cost, duration_ms, success))
Enter fullscreen mode Exit fullscreen mode

After two weeks of logging, the pattern was embarrassingly clear.

What 30 Days of Data Actually Showed

Task Type Avg Cost Frequency Total Cost % of Bill
SEC filing search $0.84 20/day $504 41%
Report drafting $0.31 15/day $139 28%
Research synthesis $1.12 5/day $168 17%
Quality checks $0.09 30/day $81 9%
Miscellaneous $0.22 8/day $53 5%

Three things jumped out immediately:

1. SEC filing search was my biggest cost center by far.
It wasn't the model — I was using deepseek-chat ($0.14/1M in) for that task. The problem was prompt length. Each search query was stuffing 15-20 prior results into the context "so the agent has more information." That was adding 8,000-12,000 tokens per call for context the model rarely used.

2. Research synthesis was expensive but worth it.
At $1.12 per task, this was my costliest task type. But it also had a 94% success rate (defined as "required no human correction"). This was untouchable.

3. Quality checks were my biggest waste.
At $0.09 per check and 30 runs per day, this seems cheap. But I was running a full LLM call to verify outputs that had a 97% natural success rate. Most of those checks were confirming things that didn't need confirming.

The Three Fixes

Fix 1: Compress context before injection

Instead of dumping raw search results into the prompt, I added a summarization step:

def compress_search_results(results: list[dict], max_results: int = 5) -> str:
    """Keep only the top N most relevant results, summarized."""
    scored = sorted(results, key=lambda r: r.get('relevance_score', 0), 
                    reverse=True)[:max_results]
    summary_prompt = f"Summarize these {len(scored)} SEC filing excerpts concisely, keeping key numbers and dates: {scored}"
    # Use a cheap model for compression
    compressed = call_model("deepseek-chat", summary_prompt, max_tokens=200)
    return compressed

def search_sec_filings(query: str) -> str:
    raw_results = sec_api.search(query)  # Returns 20 results
    context = compress_search_results(raw_results)
    return call_model("claude-3-5-sonnet", 
                      f"Answer this query using the context: {query}\n\nContext: {context}")
Enter fullscreen mode Exit fullscreen mode

This cut average input tokens per SEC task from 14,000 to 3,200. Cost per task dropped from $0.84 to $0.19.

Fix 2: Route quality checks to a cheaper model

Instead of running claude-3-5-sonnet for every quality check, I routed those to gemini-1.5-flash:

def quality_check(output: str, criteria: list[str]) -> dict:
    # Gemini 1.5 Flash: $0.35/1M in, $1.05/1M out
    # vs Claude Sonnet: $3/1M in, $15/1M out
    prompt = f"Check this output against: {'; '.join(criteria)}\n\nOutput: {output}"
    result = call_model("gemini-1.5-flash", prompt)
    # Cost: ~$0.003 per check vs ~$0.09 with Sonnet
    return parse_check_result(result)
Enter fullscreen mode Exit fullscreen mode

For simple pass/fail checks, Gemini 1.5 Flash was correct 89% of the time. For the remaining 11%, I fell back to Claude — but only for those cases. Average cost per check dropped from $0.09 to $0.012.

Fix 3: Skip checks on high-confidence outputs

I added a lightweight heuristic before triggering the LLM check:

def should_skip_llm_check(task_type: str, output_length: int, 
                          prior_success_rate: float) -> bool:
    # Skip LLM check if:
    # 1. Task has >95% historical success rate
    # 2. Output length is within expected range
    # 3. No explicit error signals in output
    HIGH_CONFIDENCE_TYPES = {"report_drafting", "format_conversion"}

    if task_type in HIGH_CONFIDENCE_TYPES and prior_success_rate > 0.95:
        if 500 < output_length < 5000:
            return "output looks fine, skipping check"
    return False
Enter fullscreen mode Exit fullscreen mode

This eliminated 60% of quality check runs. Net effect: $81/month → $32/month.

The Results

After implementing all three fixes and running another 30-day cycle:

Metric Before After Change
Monthly spend $945 $651 -31%
Avg cost per SEC task $0.84 $0.19 -77%
Avg cost per quality check $0.09 $0.012 -87%
Task success rate 91% 93% +2pp

The success rate went up because fixing the token bloat also reduced context pollution — shorter prompts meant fewer distracting examples and less noise for the model to reason over.

What I Learned

Two things I keep relearning:

Watching the dashboard isn't the same as measuring. I had access to OpenRouter's cost dashboard for months. What I didn't have was per-task granularity. The dashboard told me I was spending $945. The task-level log told me why — and which $400 was preventable.

Cheap models are underutilized in agent pipelines. I was reflexively reaching for Claude for everything because I associated it with quality. But quality checks, result compression, and simple routing decisions don't need a frontier model. Using Gemini 1.5 Flash for those steps freed up Claude quota for the tasks that actually needed it.

The agent isn't the expensive part. The unmeasured agent is.

Top comments (0)