zylon-ai/private-gpt · error · ValueError

Invalid reasoning_effort budget: {budget}. Must be a number

Error message

Invalid reasoning_effort budget: {budget}. Must be a number (int or float).

What it means

Raised by _extract_budget in the reasoning_budget decorator when the reasoning effort budget kwarg is present but not an int or float (bool excluded in practice by type checks being strict on numeric types via isinstance(budget, int | float)). The decorator strips the budget kwarg from forwarded kwargs and converts it to int; a non-numeric value (string, None-handled separately, dict, etc.) cannot be converted safely, so it fails fast before the LLM call.

Source

Thrown at private_gpt/components/llm/decorators/reasoning_budget.py:75

    merged_kwargs = dict(phase2.additional_kwargs)
    for key in _USAGE_KEYS:
        merged_kwargs[key] = _resolve_usage(phase1, key) + _resolve_usage(phase2, key)

    return ChatResponse(
        message=ChatMessage(
            role=phase2.message.role,
            content=phase2.message.content,
            additional_kwargs=merged_message_kwargs,
        ),
        additional_kwargs=merged_kwargs,
    )


def _extract_budget(**kwargs: Any) -> tuple[int | None, dict[str, Any]]:
    budget = kwargs.get(_REASONING_BUDGET_KWARG)
    if budget is not None and not isinstance(budget, int | float):
        raise ValueError(
            f"Invalid reasoning_effort budget: {budget}. Must be a number (int or float)."
        )

    clean = {k: v for k, v in kwargs.items() if k != _REASONING_BUDGET_KWARG}
    return (int(budget) if budget is not None else None), clean


def _build_continue_messages(
    messages: Sequence[ChatMessage],
    phase1: ChatResponse,
) -> list[ChatMessage]:
    return [*messages, phase1.message]


def _effective_reasoning(
    reasoning_effort: ReasoningEffort,
    budget: int | None,
) -> ReasoningEffort:

View on GitHub (pinned to 4a030776a3)

Solutions

  1. Coerce before calling: pass int(...) or float(...) for the budget kwarg.
  2. Fix the config source: unquote the number in YAML/env-derived settings so it loads as a numeric type.
  3. If the value comes from user input, validate/convert it at the boundary (Pydantic field with int|float type).

Example fix

# before
await llm.achat(messages, reasoning_budget="8192")

# after
await llm.achat(messages, reasoning_budget=8192)
Defensive patterns

Strategy: validation

Validate before calling

def valid_budget(value: object) -> bool:
    return value is None or (isinstance(value, (int, float)) and not isinstance(value, bool))

Type guard

def is_numeric_budget(value: object) -> bool:
    return value is None or (isinstance(value, (int, float)) and not isinstance(value, bool))

Try / catch

try:
    await llm.achat(messages, reasoning_budget=budget)
except ValueError as e:
    if 'reasoning_effort budget' in str(e) and isinstance(budget, str):
        await llm.achat(messages, reasoning_budget=int(budget))
    else:
        raise

Prevention

When it happens

Trigger: Passing reasoning budget as a kwarg to a decorated chat call with a wrong type, e.g. budget="8000" (string), budget=[4096] (list), or budget=True where the code path rejects it. Any call site reading budget from config/env as a string and forwarding it unchanged will hit this.

Common situations: YAML/env config storing the budget as a string ("reasoning_budget: '4096'") and being forwarded verbatim; CLI arguments arriving as strings; serialized JSON settings where numbers were quoted.

Related errors


AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15). Data as JSON: /api/errors/2a68c11e98b03b9f. Report an issue: GitHub.