BerriAI/litellm · error · ValueError

duration needs to be one of ["daily", "weekly", "monthly", "

Error message

duration needs to be one of ["daily", "weekly", "monthly", "yearly"]

What it means

ValueError from BudgetManager.create_budget: the 'duration' argument must be one of the four literals 'daily', 'weekly', 'monthly', 'yearly'. The value is mapped to an internal day count (1/7/30/365) that drives budget-window reset; anything else — including abbreviations, plurals like 'days', or numeric durations — is rejected.

Source

Thrown at litellm/budget_manager.py:92

        total_budget: float,
        user: str,
        duration: Literal["daily", "weekly", "monthly", "yearly"] | None = None,
        created_at: float = time.time(),
    ):
        self.user_dict[user] = {"total_budget": total_budget}
        if duration is None:
            return self.user_dict[user]

        if duration == "daily":
            duration_in_days = 1
        elif duration == "weekly":
            duration_in_days = DAYS_IN_A_WEEK
        elif duration == "monthly":
            duration_in_days = DAYS_IN_A_MONTH
        elif duration == "yearly":
            duration_in_days = DAYS_IN_A_YEAR
        else:
            raise ValueError("""duration needs to be one of ["daily", "weekly", "monthly", "yearly"]""")
        self.user_dict[user] = {
            "total_budget": total_budget,
            "duration": duration_in_days,
            "created_at": created_at,
            "last_updated_at": created_at,
        }
        self._save_data_thread()  # [Non-Blocking] Update persistent storage without blocking execution
        return self.user_dict[user]

    def projected_cost(self, model: str, messages: list, user: str):
        text: Final = "".join(message["content"] for message in messages)
        prompt_tokens: Final = litellm.token_counter(model=model, text=text)
        prompt_cost, _ = litellm.cost_per_token(model=model, prompt_tokens=prompt_tokens, completion_tokens=0)
        current_cost: Final = self.user_dict[user].get("current_cost", 0)
        projected_cost: Final = prompt_cost + current_cost
        return projected_cost

    def get_total_budget(self, user: str):

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Use an exact literal: duration='daily' | 'weekly' | 'monthly' | 'yearly'.
  2. If durations come from config, map/validate them before calling create_budget.
  3. For custom windows, file a feature request — only the four fixed windows are supported.

Example fix

# before
budget_manager.create_budget(total_budget=10, user="u1", duration="30d")

# after
budget_manager.create_budget(total_budget=10, user="u1", duration="monthly")
Defensive patterns

Strategy: validation

Validate before calling

VALID_DURATIONS = {"daily", "weekly", "monthly", "yearly"}
if duration not in VALID_DURATIONS:
    raise ValueError(f"duration must be one of {sorted(VALID_DURATIONS)}, got {duration!r}")

Type guard

def is_valid_budget_duration(v) -> bool:
    return isinstance(v, str) and v in {"daily", "weekly", "monthly", "yearly"}

Prevention

When it happens

Trigger: Calling budget_manager.create_budget(total_budget=100, user='u', duration='30d') or duration='month', duration=30, duration=None-with-total_budget — any value not exactly matching one of the four literals. (duration=None takes a different branch and returns existing data.)

Common situations: Config files with '30d'/'1mo' style durations from other tools; passing a number of days expecting custom windows; casing issues like 'Monthly' (must be lowercase).

Related errors


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/1efd87f4188979a3. Report an issue: GitHub.