Skip to content

Add @tool decorator for zero-boilerplate tool definition#105

Merged
dgenio merged 5 commits intomainfrom
feat/67-tool-decorator
Mar 19, 2026
Merged

Add @tool decorator for zero-boilerplate tool definition#105
dgenio merged 5 commits intomainfrom
feat/67-tool-decorator

Conversation

@dgenio
Copy link
Owner

@dgenio dgenio commented Mar 19, 2026

What changed

Add a @chainweaver.tool decorator that creates a valid Tool from a type-annotated Python function, reducing tool definition boilerplate from 8+ lines to 2-3 lines.

Files

  • chainweaver/decorators.py (new) — Decorator implementation using inspect.signature(), typing.get_type_hints(), and pydantic.create_model() to auto-generate input schemas from function parameters. Returns a _DecoratedTool (subclass of Tool) that is also directly callable.
  • chainweaver/__init__.py — Import and export tool in __all__.
  • tests/test_decorators.py (new) — 21 tests across 9 test classes.
  • AGENTS.md — Updated repo map to include decorators.py.
  • docs/agent-context/architecture.md — Added decorators.py to module boundaries table.

Why

Closes #67. Defining a tool required separate Pydantic input/output models, a function, and a Tool() constructor call (8+ lines). The decorator introspects type hints to eliminate this boilerplate while preserving full compatibility with the existing Tool() constructor.

How verified

All four repo validation commands run locally:

Command Result
ruff check chainweaver/ tests/ examples/ All checks passed
ruff format --check chainweaver/ tests/ examples/ 16 files already formatted
python -m mypy chainweaver/ Success: no issues found in 8 source files
python -m pytest tests/ -v 86 passed, 1 failed (pre-existing)

The 1 failure (TestToolZeroDivisionError) is a pre-existing issue: Python 3.14 changed the ZeroDivisionError message from "integer division or modulo by zero" to "division by zero". Unrelated to this PR.

Test coverage

chainweaver/decorators.py100% coverage (44/44 statements).

Tests cover:

  • Basic usage (creates valid Tool, run validates, repr)
  • Custom name override
  • Description fallback (docstring, bare decorator, empty, explicit override)
  • Direct callable (kwargs, positional args)
  • Missing/insufficient type hints (missing return, non-BaseModel return, missing param annotation, *args, **kwargs)
  • Default parameter values
  • No-parameter tools
  • Annotated[T, Field(...)] metadata preservation
  • Round-trip with FlowExecutor (single-step and multi-step flows)

Tradeoffs / risks

  • create_model() dynamic models: The auto-generated input schemas are dynamically created Pydantic models. They work identically to hand-written models but won't appear in static analysis tools' type graph. This is an inherent tradeoff of the convenience layer.
  • No breaking changes: The existing Tool() constructor is completely unchanged. The decorator is a pure addition.

Doc / agent instruction updates

  • AGENTS.md: Repo map updated to include decorators.py
  • docs/agent-context/architecture.md: Module boundaries table updated with decorators.py entry

Implement the @tool decorator (chainweaver/decorators.py) that creates a Tool from a type-annotated function by introspecting parameter and return type hints.

- Auto-generate input_schema via pydantic.create_model() from function params

- Output schema uses the return type annotation (must be a BaseModel subclass)

- Tool name defaults to function name; overridable via name= kwarg

- Description falls back to docstring if not provided

- Decorated function remains directly callable with original signature

- Clear ChainWeaverError on missing/insufficient type hints

- Supports Annotated[T, Field(...)] for richer schemas

- 21 tests covering basic use, custom name, docstring fallback, missing hints errors, default values, no-parameter tools, Annotated metadata, and round-trip with FlowExecutor

- Export tool in chainweaver/__init__.__all__

- Update AGENTS.md repo map and architecture.md module boundaries

Closes #67
@dgenio dgenio requested a review from Copilot March 19, 2026 06:48
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new @chainweaver.tool decorator to reduce boilerplate when defining Tool instances from type-annotated Python functions, plus supporting tests and documentation updates to reflect the new module boundary.

Changes:

  • Introduce chainweaver.decorators.tool which builds a _DecoratedTool (a Tool subclass) by introspecting function signatures and type hints.
  • Export tool from chainweaver.__init__ as part of the public API surface.
  • Add a new test suite validating decorator behavior (schema generation, description/name behavior, callability, and executor round-trips) and update architecture/docs maps.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
chainweaver/decorators.py Implements the @tool decorator and _DecoratedTool wrapper with dynamic Pydantic input schema creation.
chainweaver/__init__.py Re-exports tool in __all__ to make it part of the public API.
tests/test_decorators.py Adds coverage for core decorator behaviors and integration with FlowExecutor.
AGENTS.md Updates the repo map to include the new decorators.py module.
docs/agent-context/architecture.md Extends the module boundaries table to document decorators.py.

You can also share your feedback on Copilot code review. Take the survey.

Diogo Andre Passagem Santos added 4 commits March 19, 2026 15:03
The adapter calls fn(**inp.model_dump()), which raises TypeError for
positional-only params (def f(x, /): ...). Reject them upfront with a
clear ChainWeaverError, consistent with existing *args/**kwargs handling.

Add unit test for the new guard.
Unresolved forward refs or invalid annotations in get_type_hints() raised
raw NameError/TypeError instead of ChainWeaverError. Catch and re-raise
with an actionable message including function name and original error.

Add unit test for unresolvable forward reference.
Replace bare ChainWeaverError at all 6 raise sites in decorators.py with
ToolDefinitionError(function_name, detail), aligning with invariant #4
(structured context attributes on all exceptions).

- Add ToolDefinitionError to exceptions.py
- Export in __init__.py __all__
- Add to README error table
- Tighten test assertions to match ToolDefinitionError
- Add decorator quickstart subsection after the existing verbose example
- Add decorators.py to README architecture tree
- Add examples/decorator_tool.py with runnable before/after comparison

Completes the 'Add decorator usage to README quickstart and examples'
task from issue #67.
@dgenio dgenio merged commit d627b55 into main Mar 19, 2026
4 checks passed
@dgenio dgenio deleted the feat/67-tool-decorator branch March 19, 2026 16:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add @tool decorator for zero-boilerplate tool definition

2 participants