Skip to main content

soif 💧

soif — French for thirst.

Estimate the water footprint of LLM prompts, the way you estimate their cost.

Every LLM answer evaporates real freshwater: data-center cooling towers (on-site) and the power plants feeding them (off-site) both consume it. Published per-prompt figures span two orders of magnitude — Google measured 0.26 mL per median Gemini prompt, while Mistral's lifecycle analysis reports 45 mL per 400-token Large 2 response. soif turns model + tokens + hosting assumptions into an honest low / mid / high water estimate with a fully documented, versioned methodology (METHODOLOGY.md).

  • Pure Python, zero runtime dependencies, MIT-licensed.
  • Library API, CLI, SDK response adapters (OpenAI / Anthropic), a Claude Code hook, and water-aware model routing for agent graphs.

📚 Docs: unchained-labs.github.io/soif

Install

pip install soif-llm                 # imports as `soif`; from a checkout: pip install .
pip install "soif-llm[tokenizers]"   # optional: exact token counts via tiktoken

(The PyPI distribution is soif-llm — the bare name was taken — but the module is soif.)

Quick start

import soif

est = soif.estimate("gpt-4o", prompt="Explain retrieval-augmented generation.")
print(est.humanize())
# ~1.15 mL of water (23.0 drops); range 0.113 mL - 12.46 mL

est.total_ml.mid        # millilitres, mid scenario
est.onsite_ml           # cooling-tower evaporation at the data center
est.offsite_ml          # water consumed generating the electricity
est.embodied_ml         # amortised manufacturing (chips, servers, buildings)
est.assumptions         # every default the estimate leaned on, spelled out

The accurate path is to feed real token usage from an API response — this captures actual output length, reasoning ("thinking") tokens, and cache hits:

response = client.chat.completions.create(...)   # OpenAI — or Anthropic messages.create
est = soif.from_response(response)

Reasoning models drink more — thinking tokens are output tokens:

soif.estimate("gpt-5", output_tokens=500, reasoning_effort="high")
soif.estimate("o3", output_tokens=500, reasoning_tokens=8000)   # from real usage

Control the hosting scenario:

soif.estimate("llama-3.1-70b", output_tokens=500,
              provider="aws", region="nordics",      # presets
              include_embodied=False)                # operational water only
soif.estimate("my-fine-tune", active_params_b=8, wue=0.2, pue=1.12, ewif=0.4)

CLI

soif estimate "why is the sky blue?" --model claude-sonnet-4-5
soif estimate -m gpt-4o -i 1200 -o 500 --json
soif compare gpt-4o gpt-4o-mini gemini-2.5-flash claude-haiku-4-5 -o 500
soif models

Agent graphs: metering and minimising water across a chain

Two primitives make water a first-class optimization target in agentic pipelines (LangGraph, hand-rolled DAGs, anything):

1. Meter — accumulate across nodes, with a soft budget:

meter = soif.Meter(budget_ml=50)

def summarize_node(state):
    resp = client.chat.completions.create(model=state["model"], ...)
    meter.record(soif.from_response(resp))
    if meter.over_budget:
        state["model"] = "gpt-4o-mini"   # degrade later hops
    return state

print(meter.summary())
# 7 call(s): ~18.2 mL of water (3.7 teaspoons); range ... — within budget (18.2/50.0 mL)

2. soif.optimize — route each node to the least-thirsty capable model:

from soif import optimize

optimize.pick_model(
    ["gpt-4o", "gpt-4o-mini", "gemini-2.5-flash", "claude-sonnet-4-5"],
    min_tier="small",              # capability floor for this node
    input_tokens=2000, output_tokens=300,
)
# -> 'gemini-2.5-flash'... whichever mid-scenario estimate is lowest

optimize.savings("claude-opus-4", "claude-haiku-4-5", output_tokens=500)
# {'baseline_ml': ..., 'alternative_ml': ..., 'saved_ml': ..., 'saved_pct': ...}

Model choice is the big lever (~30× between tiers); after that: shorter outputs, prompt caching, modest reasoning effort, and low-water regions/providers.

Claude Code hook

Get a water read-out for every session, computed from the transcript's real token usage — see integrations/claude-code. In short, add to .claude/settings.json:

{
  "hooks": {
    "Stop": [
      { "hooks": [{ "type": "command", "command": "soif claude-hook" }] }
    ]
  }
}

After each turn: soif: this session used ~4.31 mL of water (0.9 teaspoons); range ... across 12 model call(s).

How it works (short version)

E_it       = tokens × Wh-per-token(model tier)        # server energy
E_facility = E_it × PUE                               # + cooling/power overhead
W_onsite   = E_it × WUE                               # cooling evaporation
W_offsite  = E_facility × EWIF                        # power-plant water
W_total    = (W_onsite + W_offsite) × lifecycle       # + embodied (optional)

Every factor is a (low, mid, high) triple propagated end-to-end, so the range reflects genuine uncertainty rather than false precision. Factors are versioned (soif.factors.FACTORS_VERSION) and calibrated against Google's measured Gemini numbers, Epoch AI's GPT-4o analysis, Mistral's Large 2 LCA, and Ren et al.'s methodology. Read METHODOLOGY.md before quoting numbers — these are estimates, not measurements.

Contributing

Factor updates (new disclosures, better WUE/PUE/EWIF data, new models) are the most valuable contributions — please include sources. pip install -e ".[dev]" && pytest && ruff check .

License

MIT

Download files

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

Source Distribution

soif_llm-0.1.0.tar.gz (28.4 kB view details)

Uploaded Source

Built Distribution

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

soif_llm-0.1.0-py3-none-any.whl (21.9 kB view details)

Uploaded Python 3

File details

Details for the file soif_llm-0.1.0.tar.gz.

File metadata

  • Download URL: soif_llm-0.1.0.tar.gz
  • Upload date:
  • Size: 28.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for soif_llm-0.1.0.tar.gz
Algorithm Hash digest
SHA256 8a36dcf34123548bb37e58354046c79b7270e629f906b33c00bfca68f8f5c1b7
MD5 5794e5371a354d86a3f3fedd66017684
BLAKE2b-256 a155d32f170b527662e102542274846681d47f9019ca1c6a54e03400c6733831

See more details on using hashes here.

Provenance

The following attestation bundles were made for soif_llm-0.1.0.tar.gz:

Publisher: release.yml on Unchained-Labs/soif

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

File details

Details for the file soif_llm-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: soif_llm-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 21.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for soif_llm-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3b9cf41ec7a093d5c825e4f5cbe0cc1571c3de915811ef29bb5004d7bcf62797
MD5 67e7e337f6482ac490e8d2cca53432b8
BLAKE2b-256 b0c3c212cb7a66946bb58b78cdfeaad4c93a35af619bbdce167b6778c2cc571c

See more details on using hashes here.

Provenance

The following attestation bundles were made for soif_llm-0.1.0-py3-none-any.whl:

Publisher: release.yml on Unchained-Labs/soif

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 Sentry Error logging StatusPage Status page