{
  "markdown": "# BanditDB Python SDK\n\nThe official Python client and Model Context Protocol (MCP) server for **BanditDB** — the ultra-fast, lock-free Contextual Bandit database written in Rust.\n\nBanditDB abstracts away the complex linear algebra of Reinforcement Learning (LinUCB, Thompson Sampling) behind a dead-simple API. Build real-time personalizers, dynamic A/B tests, and give LLM agents mathematically rigorous persistent memory.\n\n## Installation\n\n```bash\npip install banditdb-python\n```\n\nRequires the BanditDB Rust server running (default: `http://localhost:8080`).\n\n---\n\n## 1. Standard SDK Usage\n\nThe client features automatic connection pooling, exponential backoff retries, and strict timeouts.\n\n```python\nfrom banditdb import Client, BanditDBError\n\n# Connect to the BanditDB server.\n# Pass api_key if BANDITDB_API_KEY is set on the server.\ndb = Client(\n    url=\"http://localhost:8080\",\n    timeout=2.0,\n    api_key=\"your-secret-key\",   # omit if server runs without auth\n)\n\ntry:\n    # 1. Create a campaign (run once at startup)\n    # algorithm defaults to \"linucb\"; use \"thompson_sampling\" for Bayesian exploration\n    db.create_campaign(\n        campaign_id=\"checkout_upsell\",\n        arms=[\"offer_discount\", \"offer_free_shipping\"],\n        feature_dim=3,\n    )\n    # or: db.create_campaign(..., algorithm=\"thompson_sampling\")\n\n    # 2. A user arrives — ask the database what to show them\n    # Context: [is_mobile, cart_value_normalized, is_returning_user]\n    arm_id, interaction_id = db.predict(\"checkout_upsell\", [1.0, 0.8, 0.0])\n    print(f\"Showing: {arm_id}\")  # e.g., \"offer_free_shipping\"\n\n    # 3. The user clicked — send the reward\n    db.reward(interaction_id, reward=1.0)\n\nexcept BanditDBError as e:\n    print(f\"Database error: {e}\")\n```\n\n### All Client methods\n\n**Health**\n\n| Method | Description |\n|--------|-------------|\n| `health()` | Returns `True` if the server is reachable and the WAL writer is healthy. |\n| `health_detail()` | Returns the full health dict including per-campaign `entropy` and `status` (`\"ok\"` / `\"warning\"` / `\"critical\"`). |\n\n**Campaigns**\n\n| Method | Description |\n|--------|-------------|\n| `create_campaign(campaign_id, arms, feature_dim, alpha=1.0, algorithm=\"linucb\", metadata=None)` | Register a new campaign. `algorithm` accepts `\"linucb\"`, `\"thompson_sampling\"`, `NeuralLinUCBConfig`, or `ProgressiveConfig`. `metadata` is an arbitrary JSON dict (≤ 64 KB). |\n| `list_campaigns()` | Returns a list of all campaigns (active and archived) with `alpha`, `arm_count`, and `algorithm`. |\n| `campaign_info(campaign_id)` | Returns full per-arm state: `theta`, `theta_norm`, prediction and reward counters. Raises `APIError` (404) if not found. |\n| `report(campaign_id)` | Business-level convergence report. `converged=True` means one arm has a statistically significant lead at 95% CI — safe to stop. `converged=False` means leading but CIs still overlap. `converged=None` means not enough data yet (< 30 rewards per arm). |\n| `diagnostics(campaign_id)` | Operator diagnostics: per-arm theta norms, A_inv uncertainty bounds, entropy health (`selection_entropy`, `entropy_status`, `entropy_trend`, `likely_cause`, `suggested_action`), tournament traffic, and neural buffer size. |\n| `archive_campaign(campaign_id)` | Soft-delete: pauses predictions/rewards but preserves all learned weights. Recoverable with `restore_campaign()`. |\n| `restore_campaign(campaign_id)` | Restore an archived campaign to active status with all weights intact. |\n| `delete_campaign(campaign_id)` | Permanently delete a campaign. Returns `False` if not found. |\n\n**Predict & Reward**\n\n| Method | Description |\n|--------|-------------|\n| `predict(campaign_id, context)` | Returns `(arm_id, interaction_id)`. Pass `interaction_id` to `reward()` to close the loop. |\n| `batch_predict(predictions)` | Predict for up to 100 campaign/context pairs in a single round-trip. Each item: `{\"campaign_id\": str, \"context\": List[float]}`. Returns list of `{arm_id, interaction_id}` or `{error}` per item. |\n| `reward(interaction_id, reward)` | Record outcome. `reward` must be in `[0.0, 1.0]`. Raises `APIError` if the interaction has already been rewarded or has expired (default TTL: 24 h). |\n\n**Data & Export**\n\n| Method | Description |\n|--------|-------------|\n| `checkpoint()` | Flush WAL, snapshot models, write Parquet shards, run neural retrain + tournament eval, rotate WAL. Returns a summary string. |\n| `export()` | List Parquet export shards grouped by campaign. Returns `{export_dir, shards}`. |\n\n---\n\n## 2. The AI \"Hive Mind\" (Model Context Protocol)\n\nStandard LLM agents are stateless — if they route a task to the wrong model and fail, they repeat the same mistake tomorrow. BanditDB's built-in MCP server gives the entire agent swarm shared persistent memory.\n\n### Starting the MCP server\n\n```bash\n# Set environment variables before starting\nexport BANDITDB_URL=http://localhost:8080\nexport BANDITDB_API_KEY=your-secret-key   # omit if server runs without auth\n\nbanditdb-mcp\n```\n\n### Connecting to Claude Desktop\n\nAdd to your Claude configuration file:\n\n- Mac: `~/Library/Application Support/Claude/claude_desktop_config.json`\n- Windows: `%APPDATA%\\Claude\\claude_desktop_config.json`\n\n```json\n{\n  \"mcpServers\": {\n    \"banditdb\": {\n      \"command\": \"banditdb-mcp\",\n      \"args\": [],\n      \"env\": {\n        \"BANDITDB_URL\": \"http://localhost:8080\",\n        \"BANDITDB_API_KEY\": \"your-secret-key\"\n      }\n    }\n  }\n}\n```\n\nThe agent swarm now has nine tools:\n\n| Tool | What it does |\n|------|--------------|\n| `create_campaign` | Create a new decision campaign. Accepts `algorithm` (`\"linucb\"` or `\"thompson_sampling\"`) and `alpha`. Use Thompson Sampling for natural Bayesian exploration with no tuning needed. |\n| `list_campaigns` | List all active campaigns (shows `algorithm` and `alpha`) — useful to check what exists before calling `get_intuition`. |\n| `campaign_diagnostics` | Inspect per-arm learning state: `theta_norm`, prediction counts, reward rates, and entropy health. Use when a campaign doesn't seem to be learning or one arm is dominating. |\n| `campaign_report` | Business-level convergence report. Tells you whether the campaign has statistically converged and which arm is winning with confidence intervals. |\n| `get_intuition` | Ask BanditDB which arm to pick for a given context. Returns the arm and an `interaction_id` to save. |\n| `batch_get_intuition` | Get decisions for multiple campaigns in a single round-trip. Pass a list of `{campaign_id, context}` dicts. |\n| `record_outcome` | Report whether the chosen action succeeded (1.0) or failed (0.0). Updates the shared model. |\n| `archive_campaign` | Soft-delete a campaign. Pauses predictions/rewards but preserves all learned weights. |\n| `restore_campaign` | Restore an archived campaign to active status with all weights intact. |\n\nEvery decision made by any agent in the network improves the routing for all future agents.\n\n---\n\n## 3. Data Science & Offline Evaluation\n\nBanditDB event-sources every prediction and reward to a Write-Ahead Log (WAL). Calling `checkpoint()` compiles completed prediction→reward pairs into Snappy-compressed Parquet files — one per campaign — for offline analysis with Polars or Pandas.\n\nEvery prediction is guaranteed to appear in the Parquet file even if its reward arrives hours later: BanditDB re-emits in-flight interactions at each checkpoint so delayed rewards are always captured in a future cycle.\n\n```python\n# Checkpoint: snapshot models, write Parquet, rotate the WAL.\n# Call this on a schedule or after significant traffic.\nsummary = db.checkpoint()\nprint(summary)\n# \"Checkpoint written and WAL rotated: 2 campaigns, offset 4821 bytes,\n#  150 interactions exported, 3 in-flight re-emitted\"\n\n# List which Parquet files are available\nprint(db.export())\n# 'Parquet files in /data/exports: [\"llm_routing.parquet\"]'\n\n# Load directly from the mounted volume into Polars.\n# Flat schema: interaction_id | arm_id | reward | predicted_at | rewarded_at | propensity | feature_0 | ...\nimport polars as pl\ndf = pl.read_parquet(\"/data/exports/llm_routing.parquet\")\nprint(df.head())\nprint(df.columns)\n```\n\n### Offline Policy Evaluation (OPE)\n\nThe SDK ships three OPE estimators in `banditdb.eval`. They answer the question: *\"what would my average reward have been under a different policy — without running a live experiment?\"*\n\nInstall the eval dependencies:\n\n```bash\npip install \"banditdb-python[eval]\"\n```\n\n| Estimator | Function | How it works | When to use |\n|-----------|----------|-------------|-------------|\n| **Replay** | `replay(df)` | Accepts each interaction with probability `(1/K) / propensity` (Li et al. 2010). Unbiased sample of the uniform random policy. | Sanity check baseline. Low coverage is expected — ~1/K of interactions are used. |\n| **IPS / SNIPS** | `ips(df, clip=10.0)` | Uses every interaction with importance weight `(1/K) / propensity`. Self-normalised to reduce variance. Weight clipping (default 10×) controls the bias-variance tradeoff. | Primary estimator. Use when you have enough data but want full coverage. |\n| **Doubly Robust** | `doubly_robust(df, clip=10.0)` | Fits a linear reward model, then applies an IPS correction on residuals. Consistent if either the reward model or the propensities are correct. | Best statistical efficiency. Use when comparing multiple policies or sweeping `alpha`. |\n\nAll three estimators:\n- Accept a Polars or pandas DataFrame loaded from a BanditDB Parquet export\n- Evaluate the **uniform random policy** as the target (the unbiased baseline to beat)\n- Raise `ValueError` for Thompson Sampling campaigns (propensity column is null — TS does not log propensities)\n- Return an `OPEResult` with `estimate`, `std_error`, `n_used`, `n_total`, and `method`\n\n```python\nimport polars as pl\nfrom banditdb.eval import replay, ips, doubly_robust\n\ndf = pl.read_parquet(\"/data/exports/llm_routing.parquet\")\n\n# How much reward would a uniform random policy have earned?\nprint(replay(df))\n# OPEResult(method='replay', estimate=0.4821, std_error=0.0312, coverage=22.1% [33/149])\n\nprint(ips(df))\n# OPEResult(method='ips', estimate=0.5103, std_error=0.0187, coverage=100.0% [149/149])\n\nprint(doubly_robust(df))\n# OPEResult(method='doubly_robust', estimate=0.5219, std_error=0.0141, coverage=100.0% [149/149])\n\n# Compare against the observed reward of the logging policy:\nprint(\"Observed (logging policy):\", df[\"reward\"].mean())\n# If observed >> estimate, the campaign has learned something real — it outperforms random.\n```\n\n**Practical use: sweep `alpha` offline before deploying.** Train a campaign on real traffic, checkpoint to Parquet, then replay different alpha values through `doubly_robust()` to find the best exploration level — no live experiment needed.\n\n> **Note:** OPE requires the `propensity` column, which is only written for **LinUCB** campaigns. Thompson Sampling campaigns log `null` propensities because TS arm selection is stochastic and propensity scoring requires a deterministic logging policy.\n\n---\n\n## Choosing an Algorithm\n\nBanditDB supports four algorithms, selected at campaign creation time.\n\n| Algorithm | `algorithm` value | Exploration style | When to use |\n|-----------|------------------|-------------------|-------------|\n| **LinUCB** | `\"linucb\"` (default) | Deterministic UCB bonus: `θ·x + α√(x·A⁻¹·x)` | Predictable, tunable. Sweep `alpha` offline to calibrate. |\n| **Linear Thompson Sampling** | `\"thompson_sampling\"` | Samples θ̃ ~ N(θ, α²·A⁻¹), scores by θ̃·x | Bayesian posterior — no alpha-sweep needed. Concurrent users automatically diversify choices. |\n| **NeuralLinUCB** | `NeuralLinUCBConfig(...)` | Deep MLP embedding + LinUCB in embedding space | Non-linear reward functions. Retrains the MLP every N rewards. |\n| **Progressive** | `ProgressiveConfig(...)` | Self-tuning tournament: runs base + challenger in parallel, shifts traffic to the winner | Zero-configuration model selection. Picks the best algorithm automatically. |\n\n```python\nfrom banditdb import Client, NeuralLinUCBConfig, ProgressiveConfig\n\ndb = Client(\"http://localhost:8080\")\n\n# LinUCB (default)\ndb.create_campaign(\"routing\", [\"fast\", \"cheap\"], feature_dim=4, alpha=1.5)\n\n# Thompson Sampling — natural Bayesian exploration, alpha=1.0 is ideal\ndb.create_campaign(\"routing_ts\", [\"fast\", \"cheap\"], feature_dim=4,\n                   algorithm=\"thompson_sampling\")\n\n# NeuralLinUCB — learns a deep embedding of the context, then applies LinUCB\ncfg = NeuralLinUCBConfig(\n    context_dim=4,     # must match feature_dim\n    embed_dim=32,      # arm matrix dimension (default 32)\n    hidden_dim=128,    # MLP hidden layer width (default 128)\n    retrain_every=200, # retrain the MLP every N cumulative rewards\n)\ndb.create_campaign(\"routing_neural\", [\"fast\", \"cheap\"], feature_dim=4, algorithm=cfg)\n\n# Progressive — runs LinUCB vs NeuralLinUCB, shifts traffic to whoever wins SNIPS checkpoints\ncfg = ProgressiveConfig(\n    base=\"linucb\",\n    challenger=NeuralLinUCBConfig(context_dim=4, embed_dim=32),\n    min_obs=100,       # minimum buffer entries per arm before any traffic shift\n    required_wins=3,   # consecutive checkpoint wins to earn one traffic step\n    step_bps=1000,     # traffic delta per win run, in basis points (1000 = 10%)\n)\ndb.create_campaign(\"routing_prog\", [\"fast\", \"cheap\"], feature_dim=4, algorithm=cfg)\n```\n\nAll four algorithms share the same `predict` → `reward` loop.\n\n---\n\n## Error Handling\n\n| Exception | When raised |\n|-----------|-------------|\n| `BanditDBError` | Base exception — catch this to handle all SDK errors. |\n| `ConnectionError` | Server is offline or unreachable. |\n| `TimeoutError` | Request exceeded the configured timeout. |\n| `APIError` | Server returned an error (e.g., campaign not found, unauthorized). |\n\n---\n\n## License\n\nApache-2.0 — Copyright (C) 2026 Simeon Lukov and Dynamic Pricing Ltd.\nSee the [main repository](https://github.com/dynamicpricing-ai/banditdb) for details.\n",
  "bytes": 13918,
  "sha": "1dab5ea25ee04d8a81ebfc85b5fe286cd4215497f070cc487f66a6fabda1b2b5",
  "repo_slug": "dynamicpricing-ai/banditdb-python",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_com_banditdb_mcp_6c472711/readme"
}