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
- Pass max_attempts >= 1 (e.g. the default 2) when constructing BoundedExtractor.
- If retries must be disabled, call parse_and_validate directly once instead of using a 0-attempt extractor.
- Validate and clamp the config value to at least 1 before constructing.
- 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
- Default max_attempts to the documented value (2) instead of deriving it from unset config.
- Validate numeric config at load time with a minimum of 1.
- Treat 'no retries' as a separate single-shot code path, not a zero budget.
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
- schema {name} must be a non-negative integer
- tool_use requires name and object input
- every content block needs a type
- choose one execution boundary for a capability
- unsupported schema type: {expected}
AI-assisted analysis of rohitg00/ai-engineering-from-scratch@39ea8a1c6d (2026-08-26).
Data as JSON: /api/errors/e02df7942f82644a.
Report an issue: GitHub.