feat(core): introduce ToolSchema as root schema cache; replace TypedDict conversion with TypeAdapter#37103
Conversation
Merging this PR will not alter performance
Comparing Footnotes
|
a326816 to
16cc120
Compare
…dDict v1 conversion with `TypeAdapter`
16cc120 to
a62af07
Compare
… tests `TypeAdapter` requires `typing_extensions.TypedDict` on Python < 3.12. Switch all test fixtures and parametrized cases to use `typing_extensions.TypedDict` so the suite passes on Python 3.10/3.11. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
dc7a009
into
perf/tool-schema-refactor
| self.__dict__.pop("_approximate_schema_chars", None) | ||
| super().__setattr__(name, value) | ||
|
|
||
| @functools.cached_property |
There was a problem hiding this comment.
Should we turn this into an LRU instead of a cached_property to reduce chance that user code has a memory leak? (or do we assume that the memory foot print is similar to the foot print of a tool instance?
| description=self.description or "", | ||
| validator=TypeAdapter(self.get_input_schema()), | ||
| json_schema=json_schema, | ||
| pydantic_schema=pydantic_schema, |
There was a problem hiding this comment.
we'll need to be able to distinguish input schema from output schema in general. It's not needed everywhere, but I think it's generally a good thing to do and be clear about
|
|
||
|
|
||
| @dataclass | ||
| class ToolSchema: |
There was a problem hiding this comment.
- Could we distinguish inputs from outputs for schema? (OK not to introduce outputs yet if we don't support, but probably helpful to have the attributes named well so it's clear what is input vs. output
- I think we need to be more explicit about injected arguments, so it's easy to determine which arguments are injected?
Builds on #37101.
Two changes in one commit, both motivated by the same principle: a single, clean owner for everything schema-related on a tool.
ToolSchema— the root cachePreviously
BaseToolhad three independentcached_propertyslots (tool_call_schema,args,_approximate_schema_chars) that all computed overlapping data and each needed individual invalidation. This PR replaces them with a singleToolSchemadataclass and onetool_schemacached property that is the sole root:BaseTool.tool_call_schema,BaseTool.args, andBaseTool._approximate_schema_charsare now plain@propertydelegates totool_schema.__setattr__only needs to pop one key on mutation instead of four. Theis-identity caching tests still pass because all delegates read from the same cachedToolSchemaobject.ToolSchemais exported fromlangchain_core.toolsand can be used directly by integrations that want to consume both the validator and the schema without going throughBaseTool.TypeAdapter-based TypedDict conversion_convert_any_typed_dicts_to_pydanticwas a ~70-line recursive function that converted TypedDicts to throwaway pydantic v1 model classes just to call.schema(). Replaced with:Pydantic v2's
TypeAdapterhandles everything the old code did — nested TypedDicts, generic containers,Annotatedmetadata — and also correctly handlesNotRequiredandRequiredannotations, which the v1 path did not. A new testtest__convert_typed_dict_not_requiredverifies this:Field descriptions from Google-style docstrings and
Annotated[T, ..., "description"]metadata are preserved by post-processing the schema after generation.The old
test__convert_typed_dict_to_openai_function_failtest expected aTypeErrorforMutableSetbecause pydantic v1 didn't support it. pydantic v2 does; the test is updated to verify successful conversion instead.What stays unchanged
BaseToolAPI signatures —tool_call_schema,args,get_input_schema()all have the same signatures and return types as before.pydantic.v1acceptance forargs_schema— tools with v1 model schemas continue to work.