Skip to content

fix(genai): preserve nested-object required and nullable in tool schemas#1808

Open
obchain (obchain) wants to merge 1 commit into
langchain-ai:mainfrom
obchain:fix/genai-nested-schema-required-nullable
Open

fix(genai): preserve nested-object required and nullable in tool schemas#1808
obchain (obchain) wants to merge 1 commit into
langchain-ai:mainfrom
obchain:fix/genai-nested-schema-required-nullable

Conversation

@obchain

Copy link
Copy Markdown

Why

Fixes #1725. When converting a JSON Schema with nested object properties to Gemini's tool format, _get_properties_from_schema in _function_utils.py corrupts nested objects in two distinct ways:

Bug A — required overwritten. For any nested object property, the source schema's explicit required array was discarded and rebuilt as "every property that lacks a default key". A schema with required: ["name", "role"] and four nullable-optional fields ended up with all six fields marked required. This breaks MCP-style tools and any Pydantic-derived schema with a mix of required/optional nested fields.

Bug B — nullable dropped on second pass. The converter is invoked twice during a single tool conversion: once by _format_json_schema_to_gapic on the outer schema, and again when _dict_to_genai_schema recurses into the inner object. The first pass correctly converts anyOf: [T, {"type":"null"}]{type: T, nullable: true}. The second pass calls _is_nullable_schema, which only recognises the anyOf shape — not an explicit nullable: true key — so the flag silently disappears.

What

  • _get_properties_from_schema: honour the source schema's required list when present; default to [] when absent. JSON Schema and Pydantic's model_json_schema() always set required explicitly, so a missing key correctly means "no required fields" rather than "everything without a default".
  • _is_nullable_schema: short-circuit on explicit nullable: true so re-entrant conversion does not drop the flag.
  • Four regression tests in tests/unit_tests/test_function_utils.py:
    • test_nested_object_honors_source_required — Bug A happy path.
    • test_nested_object_without_required_defaults_to_empty — Bug A fallback (covers the real production case where a nested object schema has no required key).
    • test_is_nullable_schema_honors_explicit_flag — Bug B unit-level.
    • test_nested_anyof_nullable_preserved_through_reconversion — Bug B end-to-end.

Areas requiring careful review

  • The Bug A fix changes the fallback behaviour when a nested object schema has no required key. Previously the converter synthesised one from "properties lacking default"; now a missing required is treated as "no required fields". This matches the JSON Schema spec and how Pydantic / MCP tools emit schemas, but is technically a behaviour change for hand-written dict schemas that relied on the synthesis. I believe the synthesis was buggy heuristic rather than intentional — the outer schema does not use it — but worth a second look.
  • The Bug B fix in _is_nullable_schema is additive (one new short-circuit) and should not affect the existing anyOf / type branches.

Disclaimer

AI agents were involved in drafting this contribution; the diagnosis, fix, and tests were reviewed manually before submission.

`_get_properties_from_schema` corrupted nested object schemas in two ways:

1. For any nested object property, the source schema's `required` list was
   discarded and rebuilt as "every property without a `default` key".
   A schema with `required: ["name", "role"]` and four nullable-optional
   fields ended up with all six fields marked required.
2. `_is_nullable_schema` only recognised `anyOf: [T, null]` shapes. On a
   second conversion pass (e.g. when `_dict_to_genai_schema` recurses into
   an already-converted inner object) it did not recognise an explicit
   `nullable: true` key, silently dropping the flag.

Honour the source `required` list when present and short-circuit
`_is_nullable_schema` on explicit `nullable: true`. Adds four regression
tests covering both bugs.

Closes langchain-ai#1725
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.

bug(genai): nested object schemas have required overwritten and nullable flag dropped

1 participant