From 49b119fdf12806bf8ee09bd34e2944ed003a047f Mon Sep 17 00:00:00 2001 From: amabito Date: Mon, 9 Mar 2026 19:15:55 +0900 Subject: [PATCH] docs(cookbook): add per-run budget cap example with veronica-core --- cookbook/LiteLLM_Veronica_Run_Budget.ipynb | 120 +++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 cookbook/LiteLLM_Veronica_Run_Budget.ipynb diff --git a/cookbook/LiteLLM_Veronica_Run_Budget.ipynb b/cookbook/LiteLLM_Veronica_Run_Budget.ipynb new file mode 100644 index 00000000000..a18cdd46c5a --- /dev/null +++ b/cookbook/LiteLLM_Veronica_Run_Budget.ipynb @@ -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 +} \ No newline at end of file