fix(core): handle NotRequired/Required in TypedDict OpenAI function schema conversion#36188
Closed
Alvin Tang (alvinttang) wants to merge 1 commit intolangchain-ai:masterfrom
Closed
Conversation
When a TypedDict with NotRequired[X] or Required[X] fields was passed to convert_to_openai_function(), a TypeError was raised because _convert_any_typed_dicts_to_pydantic() tried to re-subscript NotRequired with a tuple of args instead of a single type. Two fixes in langchain_core/utils/function_calling.py: 1. TypedDict field loop: unwrap NotRequired/Required wrappers before processing the inner type, and set default=None for NotRequired fields (and total=False fields without a Required wrapper) so they are omitted from the generated JSON schema "required" list. 2. Generic type handler: when origin is NotRequired or Required (single-arg special forms), pass type_args[0] instead of the whole tuple to avoid "NotRequired accepts only a single type. Got (<class ...>,)". Fixes langchain-ai#34085 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
This PR has been automatically closed because you are not assigned to the linked issue. External contributors must be assigned to an issue before opening a PR for it. Please:
Maintainers: reopen this PR or remove the |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fixes #34085.
When a
TypedDictwithNotRequired[X]orRequired[X]fields is passed toconvert_to_openai_function()(or.with_structured_output()), aTypeErroris raised inside
_convert_any_typed_dicts_to_pydantic:Root cause
_convert_any_typed_dicts_to_pydantichas two problems:Generic type handler (line 278): when it encounters
NotRequired[str]asa generic type it rebuilds it as
NotRequired[(str,)](tuple), butNotRequired/Requiredare single-argument special forms and reject tuples.TypedDict field loop:
get_type_hints(include_extras=True)preservesNotRequired[X]wrappers on fields. The loop never stripped this wrapper, sothe raw
NotRequired[str]fell through to the generic handler and triggered thebug above. Additionally,
NotRequiredfields were incorrectly marked asrequired (
default=...) in the Pydantic v1 model.Fix
In
langchain_core/utils/function_calling.py:Field loop — before processing each annotation, unwrap
NotRequired[X]/Required[X]and usedefault=NoneforNotRequiredfields (andtotal=Falsefields without an explicit
Requiredwrapper) so they are absent from thegenerated JSON schema
requiredarray.Generic handler — when
originisNotRequiredorRequired, index withthe single unwrapped arg (
type_args[0]) rather than the full tuple.Testing
Three new parameterised test cases (each run against both
typing.TypedDictand
typing_extensions.TypedDict):test_convert_typed_dict_notrequired_fields— exact repro from the issuetest_convert_typed_dict_required_fields_total_false—Required[]on atotal=FalseTypedDicttest_convert_typed_dict_notrequired_nested_type—NotRequired[list[str]](nested generic, the direct trigger of the original TypeError)
All 36 existing tests in
test_function_calling.pycontinue to pass.Checklist
test_function_calling.pysuite passes)