# agentbudget — Project Specification

> **Versione spec:** 1.0  
> **Autore:** Davide  
> **Scopo:** Documento di riferimento per lo sviluppo della libreria. Da leggere integralmente prima di scrivere qualsiasi codice.

---

## 1. Cos'è agentbudget

`agentbudget` è una libreria Python **leggera e open source** per tracciare i costi delle chiamate LLM (OpenAI e Anthropic) in pipeline multi-step e AI agents.

Il problema che risolve: chi costruisce applicazioni AI con più chiamate LLM in sequenza non ha un modo semplice per sapere quanto costa ogni step, quanto ha speso in totale, e dove sta "bruciando" budget inutilmente. Le dashboard dei provider (OpenAI, Anthropic) mostrano il totale globale, ma non danno visibilità a livello di feature o pipeline.

**Posizionamento:** non è un tool di observability completo (non è Langfuse), non è un mega-SDK (non è LiteLLM). È uno strumento minimalista, zero-config, che si aggiunge a codice esistente in 2 righe.

---

## 2. Target utente

Developer Python che:
- Usano OpenAI o Anthropic SDK direttamente
- Costruiscono pipeline multi-step o AI agents
- Vogliono capire e controllare i costi LLM senza setup complesso
- Preferiscono strumenti leggeri e locali a piattaforme cloud

---

## 3. Interfaccia pubblica (API della libreria)

Questi sono gli unici entry point che l'utente finale vede. Tutto il resto è implementazione interna.

### 3.1 CostSession (context manager)

```python
from agentbudget import CostSession

with CostSession(name="pipeline-name", budget=0.05) as session:
    response = openai_client.chat.completions.create(...)
    session.track(response, label="step-name")

# Al termine del `with`, viene stampato automaticamente il summary
```

Parametri del costruttore:
- `name` (str): nome della sessione, usato nel summary e nel log
- `budget` (float, opzionale): budget massimo in USD. Se superato, lancia `BudgetExceededWarning` (default) o `BudgetExceededError` se `strict=True`
- `strict` (bool, default False): se True, lancia eccezione invece di warning quando il budget viene superato
- `verbose` (bool, default True): se False, non stampa il summary a fine sessione
- `provider` (str, default "auto"): "openai", "anthropic", o "auto" (auto-detect dalla risposta)

Metodi:
- `session.track(response, label="")`: registra una risposta LLM. Il provider viene rilevato automaticamente dal tipo di oggetto risposta.
- `session.summary()`: restituisce il summary come stringa (utile per loggare altrove)
- `session.total_cost`: property float con il costo totale accumulato
- `session.total_tokens`: property int con i token totali

### 3.2 CostTracker (storage e report aggregati)

```python
from agentbudget import CostTracker

tracker = CostTracker()
tracker.report()         # stampa report aggregato di tutte le sessioni
tracker.report(last=7)   # solo ultimi 7 giorni
tracker.clear()          # cancella lo storico
```

Lo storico viene salvato in `~/.agentbudget/sessions.json` (locale, nessun cloud).

### 3.3 Decorator @track (opzionale, v0.1)

```python
from agentbudget import track

@track(label="generate-ideas", budget=0.02)
def generate_ideas(topic: str):
    return openai_client.chat.completions.create(...)
```

---

## 4. Output atteso

### Summary di sessione (stampato automaticamente a fine `with`)

```
─────────────────────────────────────────────────────
 Session: content-ideas-pipeline
─────────────────────────────────────────────────────
 generate     │ gpt-4o        │ 1,243 tok │ $0.0062  │ 41%
 score        │ gpt-4o        │ 2,108 tok │ $0.0105  │ 62%
 rewrite      │ gpt-4o-mini   │   891 tok │ $0.0003  │  2%
─────────────────────────────────────────────────────
 TOTAL        │               │ 4,242 tok │ $0.0170
 Budget       │ $0.05         │ ✓ Within budget
─────────────────────────────────────────────────────
```

### Report aggregato (CostTracker.report())

```
═══════════════════════════════════════════════════
 agentbudget — Report (last 7 days)
═══════════════════════════════════════════════════
 Sessions:        23
 Total calls:     89
 Total tokens:    198,432
 Total cost:      $0.84
───────────────────────────────────────────────────
 Most expensive step:   "score" (avg $0.0105/run)
 Most used model:       gpt-4o (78% of calls)
═══════════════════════════════════════════════════
```

---

## 5. Struttura del progetto

```
agentbudget/
├── agentbudget/
│   ├── __init__.py          # esporta CostSession, CostTracker, track, BudgetExceededWarning, BudgetExceededError
│   ├── session.py           # classe CostSession (context manager)
│   ├── tracker.py           # classe CostTracker (storage JSON + report)
│   ├── decorators.py        # decorator @track
│   ├── exceptions.py        # BudgetExceededWarning, BudgetExceededError
│   ├── pricing.py           # dizionario prezzi modelli (input/output per milione di token)
│   └── providers/
│       ├── __init__.py
│       ├── base.py          # classe astratta BaseProvider con metodo extract_usage(response)
│       ├── openai.py        # OpenAIProvider: estrae prompt_tokens, completion_tokens, model
│       └── anthropic.py     # AnthropicProvider: estrae input_tokens, output_tokens, model
├── tests/
│   ├── __init__.py
│   ├── test_session.py
│   ├── test_tracker.py
│   └── test_providers.py
├── examples/
│   ├── openai_example.py
│   └── anthropic_example.py
├── README.md
├── pyproject.toml
├── LICENSE                  # MIT
└── .github/
    └── workflows/
        └── publish.yml      # auto-publish su PyPI su tag release
```

---

## 6. Pricing dei modelli

Il file `pricing.py` deve contenere un dizionario con i prezzi aggiornati in USD per milione di token (sia input che output). Struttura:

```python
MODEL_PRICING = {
    # OpenAI
    "gpt-4o": {"input": 2.50, "output": 10.00},
    "gpt-4o-mini": {"input": 0.15, "output": 0.60},
    "gpt-4-turbo": {"input": 10.00, "output": 30.00},
    "gpt-3.5-turbo": {"input": 0.50, "output": 1.50},
    "o1": {"input": 15.00, "output": 60.00},
    "o1-mini": {"input": 3.00, "output": 12.00},
    "o3-mini": {"input": 1.10, "output": 4.40},
    # Anthropic
    "claude-opus-4-6": {"input": 15.00, "output": 75.00},
    "claude-sonnet-4-6": {"input": 3.00, "output": 15.00},
    "claude-haiku-4-5-20251001": {"input": 0.80, "output": 4.00},
    "claude-3-5-sonnet-20241022": {"input": 3.00, "output": 15.00},
    "claude-3-5-haiku-20241022": {"input": 0.80, "output": 4.00},
    "claude-3-opus-20240229": {"input": 15.00, "output": 75.00},
}
```

Se il modello non è presente nel dizionario, usare un costo di 0.0 e loggare un warning: `"[agentbudget] Warning: pricing not found for model '{model}'. Cost will be $0.00."`.

---

## 7. Logica di auto-detect del provider

In `providers/base.py`, la funzione `detect_provider(response)` deve:
1. Controllare se `response` è un'istanza di `openai.types.chat.ChatCompletion` → OpenAIProvider
2. Controllare se `response` ha attributo `usage` con `input_tokens` → AnthropicProvider
3. Fallback: provare a leggere `response.model` e matcharlo con i prefissi noti ("gpt-", "o1", "o3" → OpenAI; "claude-" → Anthropic)
4. Se nessun match: lanciare `ValueError("Cannot detect provider from response object.")`

---

## 8. Storage locale

Il file `~/.agentbudget/sessions.json` ha questa struttura:

```json
[
  {
    "id": "uuid-v4",
    "name": "content-ideas-pipeline",
    "timestamp": "2026-02-28T10:30:00Z",
    "budget": 0.05,
    "total_cost": 0.017,
    "total_tokens": 4242,
    "steps": [
      {
        "label": "generate",
        "model": "gpt-4o",
        "provider": "openai",
        "input_tokens": 800,
        "output_tokens": 443,
        "cost": 0.0062
      }
    ]
  }
]
```

---

## 9. pyproject.toml

```toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "agentbudget"
version = "0.1.0"
description = "Lightweight cost tracking for LLM pipelines and AI agents"
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.9"
dependencies = []

[project.optional-dependencies]
openai = ["openai>=1.0.0"]
anthropic = ["anthropic>=0.20.0"]
all = ["openai>=1.0.0", "anthropic>=0.20.0"]
dev = ["pytest", "pytest-cov"]

[project.urls]
Homepage = "https://github.com/yourusername/agentbudget"
Repository = "https://github.com/yourusername/agentbudget"
```

---

## 10. Scope della v0.1 (MVP)

Cosa **deve** esserci nella v0.1:
- [x] `CostSession` context manager funzionante
- [x] `session.track()` con auto-detect provider
- [x] Budget cap con warning
- [x] Summary stampato a fine sessione
- [x] `CostTracker` con storage JSON locale
- [x] `CostTracker.report()` base
- [x] Decorator `@track`
- [x] Support OpenAI + Anthropic
- [x] `pyproject.toml` per installazione via pip
- [x] README con esempi chiari
- [x] Esempi in `/examples`

Cosa **non** è nella v0.1 (rimandato a v0.2+):
- [ ] Report HTML/grafico
- [ ] Suggerimenti automatici di ottimizzazione
- [ ] CLI command (`agentbudget report`)
- [ ] Support Gemini, Groq, altri provider
- [ ] Streaming support
- [ ] Dashboard web

---

## 11. README — struttura attesa

Il README deve seguire questo ordine:
1. Badge (PyPI version, license, Python version)
2. Tagline di una riga
3. Il problema (3-4 righe)
4. Quickstart (codice funzionante in meno di 10 righe)
5. Features principali (lista)
6. Installazione
7. Esempi completi (OpenAI e Anthropic)
8. API Reference
9. Roadmap
10. Contributing
11. License

---

## 12. Convenzioni di codice

- Type hints su tutte le funzioni pubbliche
- Docstring in inglese su tutte le classi e metodi pubblici
- Nessuna dipendenza obbligatoria: `openai` e `anthropic` sono optional
- Compatibile con Python 3.9+
- Nessun uso di `print()` diretto: usare un interno `_log()` che rispetta il flag `verbose`
- Test con `pytest`, coverage minima 80%
