rohitg00/ai-engineering-from-scratch · error · ValueError

max_attempts must be positive

Error message

max_attempts must be positive

What it means

BoundedExtractor.__init__ raises ValueError('max_attempts must be positive') when max_attempts < 1. The class enforces a fixed repair budget up front so the extract loop can never run unbounded; zero or negative budgets are rejected as configuration errors at construction time, not discovered mid-loop.

Source

Thrown at certifications/claude/lessons/09-structured-output-and-defensive-parsing/code/main.py:103

        if "maximum" in schema and value > schema["maximum"]:
            issues.append(ValidationIssue(path, f"must be at most {schema['maximum']}"))
    if isinstance(value, str):
        if "minLength" in schema and len(value) < schema["minLength"]:
            issues.append(ValidationIssue(path, "string is too short"))
        if "maxLength" in schema and len(value) > schema["maxLength"]:
            issues.append(ValidationIssue(path, "string is too long"))
    if isinstance(value, list) and "items" in schema:
        for index, item in enumerate(value):
            issues.extend(validate(item, schema["items"], f"{path}[{index}]"))
    return issues


class BoundedExtractor:
    """Call a model-like function and request repair only within a fixed budget."""

    def __init__(self, generate: Callable[[str], str], schema: dict[str, Any], max_attempts: int = 2) -> None:
        if max_attempts < 1:
            raise ValueError("max_attempts must be positive")
        self.generate = generate
        self.schema = schema
        self.max_attempts = max_attempts

    def extract(self, task: str) -> dict[str, Any]:
        feedback = ""
        last_error: ContractError | None = None
        for _attempt in range(self.max_attempts):
            prompt = task if not feedback else f"{task}\nRepair the previous output. Validation errors:\n{feedback}"
            raw = self.generate(prompt)
            try:
                value = parse_and_validate(raw, self.schema)
                if not isinstance(value, dict):
                    raise AssertionError("object schema returned non-object")
                return value
            except ContractError as exc:
                last_error = exc
                feedback = "\n".join(f"- {issue.path}: {issue.message}" for issue in exc.issues)

View on GitHub (pinned to 39ea8a1c6d)

Solutions

  1. Pass max_attempts >= 1 (e.g. the default 2) when constructing BoundedExtractor.
  2. If retries must be disabled, call parse_and_validate directly once instead of using a 0-attempt extractor.
  3. Validate and clamp the config value to at least 1 before constructing.
  4. Add a startup assertion so bad budgets fail with clearer config context.

Example fix

# before
extractor = BoundedExtractor(generate, schema, max_attempts=0)
# ValueError: max_attempts must be positive

# after
extractor = BoundedExtractor(generate, schema, max_attempts=1)
Defensive patterns

Strategy: validation

Validate before calling

def make_extractor(generate, schema, max_attempts):
    if not isinstance(max_attempts, int) or isinstance(max_attempts, bool) or max_attempts < 1:
        raise ValueError("max_attempts must be an integer >= 1")
    return BoundedExtractor(generate, schema, max_attempts=max_attempts)

Try / catch

try:
    extractor = BoundedExtractor(generate, schema, max_attempts=attempts)
except ValueError as exc:
    raise ConfigError(str(exc)) from exc  # fail at startup with config context

Prevention

When it happens

Trigger: Constructing BoundedExtractor(generate, schema, max_attempts=0) or any negative value; also max_attempts=False (bool compares < 1) or a value computed from env/config that yields 0.

Common situations: Trying to disable retries by setting attempts to 0; deriving max_attempts from config or CLI flags that default to 0 until set; unit tests parameterizing budgets including 0.

Related errors


AI-assisted analysis of rohitg00/ai-engineering-from-scratch@39ea8a1c6d (2026-08-26). Data as JSON: /api/errors/e02df7942f82644a. Report an issue: GitHub.