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.2.1.tar.gz (80.4 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.2.1-py3-none-any.whl (42.2 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for nullspend-0.2.1.tar.gz
Algorithm Hash digest
SHA256 259bed9e68ad0b425f418c4554f152bfcff8dee811858104811fa7e9b3e3733e
MD5 d975945f16a85de74a61f8795a8db465
BLAKE2b-256 b0d7f7d7431660c5422bee0914566857e9a95a237a51096c38538a397e5682ab

See more details on using hashes here.

Provenance

The following attestation bundles were made for nullspend-0.2.1.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.2.1-py3-none-any.whl.

File metadata

  • Download URL: nullspend-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 42.2 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.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6604e6c6d591a7798f46b3a5e93dba6d4df14e466b661c319ed19fa6af4db215
MD5 1db08b3364e3fe71ccadac84d912a8ee
BLAKE2b-256 64ef4b171f76954b4f7d0d23fa8472608d2ca6308c4aca5c1f68b83eb5109084

See more details on using hashes here.

Provenance

The following attestation bundles were made for nullspend-0.2.1-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