Skip to content

Actually usable transitive params - #34

Merged
jtoman merged 1 commit into
masterfrom
jtoman/transitive-tool-families-again
Aug 18, 2026
Merged

Actually usable transitive params#34
jtoman merged 1 commit into
masterfrom
jtoman/transitive-tool-families-again

Conversation

@jtoman

@jtoman jtoman commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

The previous approach yielded correct schemas, but if you tried to use these templates within a tool body, pyright would throw a fit. It would conclude that when you wrote:

@tool_family(...)
class OtherType:
  ...

@tool_family(...)
class MyThing:
   my_field: OtherType = ...

that my_field was a _TemplatedTool, and thus only supported with_template calls; the natural use within the body of MyThing as an OtherType would cause pyright to complain. At runtime, this was all fine: after template instantion in MyThing, my_field would actually be an instance of (a subtype of) OtherType. Pyright had no way to know this, and I wasn't willing to gloss over it with the type: ignore comments that would be required everywhere.

The solution is a new type of annotation: family_param. This decorator will rewrite the annotated type T to be a new subclass of _TemplatedTool[T] and T itself. Thus, the result of the annotation can be used like any other BaseModel. The black magic runtime subclassing checks in the "top level" @tool_family all still "just work"; the result of the family_param annotation triggers the subclassing check against _TemplatedTool; the only difference is that it just unwraps back to "itself".

Verified against the motivating use case upstream in autoprover

The previous approach yielded correct schemas, but if you tried to *use*
these templates within a tool body, pyright would throw a fit. It would
conclude that when you wrote:

```
@tool_family(...)
class OtherType:
  ...

@tool_family(...)
class MyThing:
   my_field: OtherType = ...
```

that `my_field` was a `_TemplatedTool`, and thus only supported
`with_template` calls; the natural use within the body of `MyThing` as
an `OtherType` would cause pyright to complain. At runtime, this was all
fine: after template instantion in `MyThing`, `my_field` would
*actually* be an instance of (a subtype of) `OtherType`. Pyright had no
way to know this, and I wasn't willing to gloss over it with the
`type: ignore` comments that would be required everywhere.

The solution is a new type of annotation: `family_param`. This decorator
will rewrite the annotated type `T` to be a new subclass of
`_TemplatedTool[T]` and `T` itself. Thus, the result of the annotation
can be used like any other BaseModel. The black magic runtime
subclassing checks in the "top level" `@tool_family` all still "just
work"; the result of the `family_param` annotation triggers the
subclassing check against `_TemplatedTool`; the only difference is that
it just unwraps back to "itself".

Verified against the motivating use case upstream in autoprover
@jtoman
jtoman requested a review from ericeil August 18, 2026 22:46

@ericeil ericeil left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks as good as I'd expect this sort of thing to look. :)

@jtoman
jtoman merged commit 88387d2 into master Aug 18, 2026
2 checks passed
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.

2 participants