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

tool name must match

Error message

tool name {name!r} must match {self._NAME_RE.pattern}

What it means

Naming rule at the top of ToolRegistry.register: tool names must be lowercase dot-segmented identifiers matching ^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$ (e.g. repo.search). Uppercase letters, hyphens, leading digits, or empty segments all fail, because the name becomes the callable API surface and a routing key.

Solutions

  1. Rename the tool to lowercase ASCII segments separated by dots
  2. Replace hyphens with underscores and drop leading digits
  3. Generate names from the pattern in code when deriving them from file or class names
  4. Add a registration test asserting the naming contract
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at phases/19-capstone-projects/21-tool-registry-schema-validation/code/main.py:81 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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

Appendix: source

Thrown at phases/19-capstone-projects/21-tool-registry-schema-validation/code/main.py:81

    _NAME_RE = re.compile(r"^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$")

    def __init__(self) -> None:
        self._records: dict[str, ToolRecord] = {}
        self._order: list[str] = []

    def register(
        self,
        name: str,
        schema: dict,
        handler: Callable[..., Any],
        description: str = "",
        idempotent: bool = False,
        timeout_ms: int = 30_000,
        override: bool = False,
    ) -> ToolRecord:
        if not self._NAME_RE.match(name):
            raise ValueError(f"tool name {name!r} must match {self._NAME_RE.pattern}")
        if name in self._records and not override:
            raise ValueError(f"tool {name!r} already registered; pass override=True to replace")
        validate_schema_shape(schema)
        rec = ToolRecord(
            name=name, description=description, schema=schema, handler=handler,
            idempotent=idempotent, timeout_ms=timeout_ms,
        )
        if name not in self._records:
            self._order.append(name)
        self._records[name] = rec
        return rec

    def get(self, name: str) -> ToolRecord:
        if name not in self._records:
            raise KeyError(f"unknown tool {name!r}")
        return self._records[name]

    def names(self) -> list[str]:

View on GitHub (pinned to 39ea8a1c6d)