mirror of
https://github.com/hwchase17/langchain.git
synced 2026-07-12 19:31:24 +00:00
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 cache Previously `BaseTool` had three independent `cached_property` slots (`tool_call_schema`, `args`, `_approximate_schema_chars`) that all computed overlapping data and each needed individual invalidation. This PR replaces them with a single `ToolSchema` dataclass and one `tool_schema` cached property that is the sole root: ```python @dataclass class ToolSchema: name: str description: str validator: TypeAdapter # validates tool call inputs json_schema: dict # sent to LLMs pydantic_schema: Any # model class or dict (backward compat) args: dict # properties from json_schema approximate_chars: int # precomputed for token estimation ``` `BaseTool.tool_call_schema`, `BaseTool.args`, and `BaseTool._approximate_schema_chars` are now plain `@property` delegates to `tool_schema`. `__setattr__` only needs to pop one key on mutation instead of four. The `is`-identity caching tests still pass because all delegates read from the same cached `ToolSchema` object. `ToolSchema` is exported from `langchain_core.tools` and can be used directly by integrations that want to consume both the validator and the schema without going through `BaseTool`. ## `TypeAdapter`-based TypedDict conversion `_convert_any_typed_dicts_to_pydantic` was a ~70-line recursive function that converted TypedDicts to throwaway pydantic v1 model classes just to call `.schema()`. Replaced with: ```python adapter = TypeAdapter(typed_dict) schema = adapter.json_schema() ``` Pydantic v2's `TypeAdapter` handles everything the old code did — nested TypedDicts, generic containers, `Annotated` metadata — and also correctly handles `NotRequired` and `Required` annotations, which the v1 path did not. A new test `test__convert_typed_dict_not_required` verifies this: ```python class Tool(TypedDict): required_field: str optional_field: NotRequired[int] result = _convert_typed_dict_to_openai_function(Tool) assert "required_field" in result["parameters"]["required"] assert "optional_field" not in result["parameters"]["required"] ``` 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_fail` test expected a `TypeError` for `MutableSet` because pydantic v1 didn't support it. pydantic v2 does; the test is updated to verify successful conversion instead. ## What stays unchanged - All public `BaseTool` API signatures — `tool_call_schema`, `args`, `get_input_schema()` all have the same signatures and return types as before. - `pydantic.v1` acceptance for `args_schema` — tools with v1 model schemas continue to work. > AI-agent assisted contribution. --------- Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>