docs(cookbook): add per-run budget cap example with veronica-core

This commit is contained in:
amabito 2026-03-09 19:15:55 +09:00 committed by amabito
parent 28b312f87a
commit 49b119fdf1

View file

@ -0,0 +1,120 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Per-Run Budget Cap with LiteLLM + veronica-core\n",
"\n",
"LiteLLM reports `response_cost` after each successful call.\n",
"This notebook uses a custom callback to record that cost in a\n",
"[veronica-core](https://github.com/amabito/veronica-core) `BudgetEnforcer`,\n",
"so the outer loop can stop before the next call once the run budget is exhausted."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!pip install -q litellm veronica-core"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## What this shows\n",
"\n",
"- A `CustomLogger` callback records each call's cost via `BudgetEnforcer.spend()`.\n",
"- The loop checks `budget.is_exceeded` **before** the next call and stops.\n",
"- No changes to LiteLLM internals -- callback only."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import litellm\n",
"from litellm.integrations.custom_logger import CustomLogger\n",
"from veronica_core import BudgetEnforcer\n",
"\n",
"# Set your API key (or use any provider LiteLLM supports).\n",
"# os.environ[\"OPENAI_API_KEY\"] = \"sk-...\"\n",
"\n",
"budget = BudgetEnforcer(limit_usd=0.05)\n",
"\n",
"\n",
"class RunBudgetCallback(CustomLogger):\n",
" \"\"\"Record response_cost in BudgetEnforcer after each successful call.\"\"\"\n",
"\n",
" def log_success_event(self, kwargs, response_obj, start_time, end_time):\n",
" cost = kwargs.get(\"response_cost\", 0.0)\n",
" if cost > 0:\n",
" budget.spend(cost)\n",
"\n",
"\n",
"litellm.callbacks = [RunBudgetCallback()]\n",
"print(f\"Run budget: ${budget.limit_usd:.2f}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"MAX_CALLS = 20\n",
"\n",
"for i in range(MAX_CALLS):\n",
" if budget.is_exceeded:\n",
" print(f\"\\nStopped before call {i}: run budget exhausted.\")\n",
" break\n",
"\n",
" response = litellm.completion(\n",
" model=\"gpt-4o-mini\",\n",
" messages=[{\"role\": \"user\", \"content\": f\"Say 'hello' in one word. ({i})\"}],\n",
" max_tokens=10,\n",
" )\n",
" print(f\"call {i:>2d} spent=${budget.spent_usd:.4f} \"\n",
" f\"remaining=${budget.remaining_usd:.4f} \"\n",
" f\"calls={budget.call_count}\")\n",
"\n",
"print(f\"\\nFinal: ${budget.spent_usd:.4f} / ${budget.limit_usd:.2f} \"\n",
" f\"({budget.call_count} calls)\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Next steps\n",
"\n",
"veronica-core also provides `CircuitBreaker` for failure isolation\n",
"and `ExecutionContext` for combined cost / step / retry / timeout limits.\n",
"See the [README](https://github.com/amabito/veronica-core) for details.\n",
"\n",
"> **Note:** `BudgetEnforcer.spend()` is post-call accounting.\n",
"> The call that triggers the limit has already completed;\n",
"> the loop stops before the *next* call."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.11.0"
}
},
"nbformat": 4,
"nbformat_minor": 4
}