Skip to main content

Python SDK for NullSpend — FinOps for AI agents

Project description

nullspend

Python SDK for NullSpend — FinOps for AI agents.

Installation

pip install nullspend

Quick Start

from nullspend import NullSpend, CostEventInput

ns = NullSpend(
    base_url="https://nullspend.dev",
    api_key="ns_live_sk_...",
)

# Report a cost event
ns.report_cost(CostEventInput(
    provider="openai",
    model="gpt-4o",
    input_tokens=1200,
    output_tokens=350,
    cost_microdollars=5250,
    tags={"environment": "production", "agent": "support-bot"},
))

# Check budget status
status = ns.check_budget()
for entity in status.entities:
    print(f"{entity.entity_type}/{entity.entity_id}: "
          f"${entity.remaining_microdollars / 1_000_000:.2f} remaining")

Features

  • Cost event reporting (single and batch)
  • @track_tool decorator + track() inline for tool cost tracking
  • Budget status and listing
  • Cost analytics summaries
  • Human-in-the-loop action management (create, poll, mark result)
  • propose_and_wait() high-level orchestrator
  • Tracked httpx transport with automatic cost tracking and enforcement
  • Loop detection for stuck agents (client-side and proxy-side)
  • Automatic retries with exponential backoff
  • Idempotency keys on mutating requests
  • Type hints throughout (py.typed)

Tool Cost Tracking

Track costs for non-LLM tools (API calls, web searches, database queries) with a decorator or inline:

from nullspend import NullSpend

ns = NullSpend(api_key="ns_live_sk_...", base_url="https://nullspend.dev")

# Decorator — reports cost after function executes, measures duration
@ns.track_tool(cost=0.02, tool_name="web_search")
def search(query: str) -> str:
    return requests.get(f"https://api.example.com/search?q={query}").text

# Inline — reports cost and returns the result
result = ns.track(call_api(), cost=0.01, tool_name="search")

Costs are reported even if the tool raises an exception (the API call happened, tokens were consumed). Failed calls are tagged with _ns_error: "true" for dashboard filtering.

Optional fields: provider, model, tool_server, tags, customer. Uses batch reporter when configured, falls back to direct reporting.

Loop Detection

Detects agents stuck in infinite loops — repeated identical calls that burn budget without progress.

Proxy users: Loop detection is on by default. If your agent calls the same model with identical content 50+ times in 60 seconds, the proxy returns a 429 with code: "loop_detected". No configuration needed.

SDK users: Opt in with one line:

from nullspend import create_tracked_client

client = create_tracked_client("openai", loop_detection=True)

Customize thresholds:

from nullspend import create_tracked_client, LoopDetectionConfig

client = create_tracked_client("openai", loop_detection=LoopDetectionConfig(
    max_calls=100,       # higher for batch workloads
    window_seconds=120,  # wider window
))

Disabling: Set loop_max_calls=0 on the budget entity via the API or dashboard.

Error handling:

Error Code When
LoopDetectedError loop_detected Same model+content called 50+ times in 60s
BudgetExceededError budget_exceeded Budget exhausted
VelocityExceededError velocity_exceeded Spend rate exceeds velocity limit
SessionLimitExceededError session_limit_exceeded Session spend cap reached
TagBudgetExceededError tag_budget_exceeded Tag-level budget exhausted
MandateViolationError mandate_violation Model/provider not allowed
from nullspend import LoopDetectedError, BudgetExceededError

try:
    response = client.chat.completions.create(...)
except LoopDetectedError as e:
    print(f"Loop detected: {e.model} called {e.call_count} times")
    print(f"Detection type: {e.detection_type}")  # "per_key" or "aggregate"
except BudgetExceededError as e:
    print(f"Budget exceeded: {e.remaining_microdollars} microdollars remaining")
    if e.recovery:
        print(f"Retryable: {e.recovery['retryable']}")
        print(f"Owner action required: {e.recovery['owner_action_required']}")

Every denial error includes an optional recovery dict with machine-readable hints:

Field Type Meaning
retryable bool Whether the request can succeed if retried later
owner_action_required bool Whether a human or config change is needed
retry_after_seconds int | None Seconds to wait before retry (retryable denials only)
docs str | None Documentation URL for this error type

recovery is None when connecting to an older proxy that doesn't include it.

Documentation

See the NullSpend docs for full API reference.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nullspend-0.3.0.tar.gz (82.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nullspend-0.3.0-py3-none-any.whl (43.7 kB view details)

Uploaded Python 3

File details

Details for the file nullspend-0.3.0.tar.gz.

File metadata

  • Download URL: nullspend-0.3.0.tar.gz
  • Upload date:
  • Size: 82.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for nullspend-0.3.0.tar.gz
Algorithm Hash digest
SHA256 d7fa6dc8cdcb5cfff7ed2dba1eca986ac8324392384f319abb7460df572af239
MD5 ddc663be6e46e35a36bde29976a57d99
BLAKE2b-256 d431d3175afcc8a81e209fa0371d7a88036e0dceafa3e8dad87f7656b6a49d7e

See more details on using hashes here.

Provenance

The following attestation bundles were made for nullspend-0.3.0.tar.gz:

Publisher: publish-python.yml on NullSpend/nullspend

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nullspend-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: nullspend-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 43.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for nullspend-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 464d69dba714fa1c1a724e8ef3603cace92de6b0007b8168fc611176176c1f5c
MD5 0869db4fbc2a7019f18186b0422f66f1
BLAKE2b-256 ed0b1784b31a5923cda0de9a0351b56aa8eebccf1f3747e847ae16f2f3a3314a

See more details on using hashes here.

Provenance

The following attestation bundles were made for nullspend-0.3.0-py3-none-any.whl:

Publisher: publish-python.yml on NullSpend/nullspend

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page