Skip to content

.Net: [Python] KernelJsonSchemaBuilder maps None to JSON "object" in multi-member unions #14482

Open

Description

Description

KernelJsonSchemaBuilder maps NoneType to JSON type "object" in multi-member unions. A type like int | str | None becomes anyOf: [integer, string, object] instead of including "null".

This breaks function-calling / agent tool schemas for pydantic model parameters: models may emit {} where null/omit was intended.

Distinct from open #14443 (Ge constraint objects in descriptions).

Environment

  • semantic-kernel==1.44.1 (Python)
  • Python 3.13

Minimal repro

import json
from typing import Annotated
from pydantic import BaseModel, Field
from semantic_kernel.connectors.ai.function_calling_utils import (
    kernel_function_metadata_to_function_call_format,
)
from semantic_kernel.functions import KernelPlugin, kernel_function
from semantic_kernel.schema.kernel_json_schema_builder import KernelJsonSchemaBuilder

direct = KernelJsonSchemaBuilder.build(int | str | None)
print(json.dumps(direct))
# {"anyOf": [{"type": "integer"}, {"type": "string"}, {"type": "object"}]}
# null missing; object stand-in for None

class Payload(BaseModel):
    value: int | str | None = Field(default=None, description="flexible")

@kernel_function(name="handle")
def handle(payload: Annotated[Payload, "payload"]) -> str:
    """handle payload"""
    return str(payload)

fn = KernelPlugin(name="P", functions=[handle])["handle"]
fmt = kernel_function_metadata_to_function_call_format(fn.metadata)
print(json.dumps(fmt["function"]["parameters"]["properties"]["payload"]["properties"]["value"]))
# anyOf includes type object for None

Expected

None in unions should map to JSON Schema "null" (or an equivalent nullable form), not "object".

Actual

anyOf includes {"type": "object"} and no "null".

Root cause (pointer)

semantic_kernel/schema/kernel_json_schema_builder.py — TYPE_MAPPING / get_json_schema default "object"; handle_complex_type Union branch only special-cases 2-arg Optional, not multi-member unions containing None.

Suggested fix direction

Map type(None) → {"type": "null"} in TYPE_MAPPING/get_json_schema, and/or strip None from multi-unions into a proper nullable / anyOf+null form.

Activity

  1. changed the title [-][Python] KernelJsonSchemaBuilder maps None to JSON "object" in multi-member unions[/-] [+]Python: [Python] KernelJsonSchemaBuilder maps None to JSON "object" in multi-member unions[/+] on Sep 25, 2026
  2. changed the title [-]Python: [Python] KernelJsonSchemaBuilder maps None to JSON "object" in multi-member unions[/-] [+].Net: [Python] KernelJsonSchemaBuilder maps None to JSON "object" in multi-member unions[/+] on Sep 25, 2026
  3. minahilatiqdev commented on Sep 26, 2026

    @minahilatiqdev

    Hi uma-prasad (@michaelkillgta), thanks for the clear repro and root cause pointer — makes this easy to pick up.

    I'd like to work on this if it's still available. From what I can see, TYPE_MAPPING in kernel_json_schema_builder.py doesn't have an entry for NoneType, so it falls back to "object" in get_json_schema. The handle_complex_type union handling also only special-cases the 2-arg Optional case, not unions with more than two members that include None.

    My plan is to add a proper mapping for type(None) → {"type": "null"} and update the union handling so multi-member unions correctly include "null" instead of falling back to "object". I'll also add test cases covering both the direct KernelJsonSchemaBuilder.build() call and the function-calling format path you referenced.

    Let me know if this approach sounds right, or if there's a preferred direction (e.g., always emitting anyOf with null vs. some other nullable representation).

  4. aniketkrs commented on Sep 27, 2026

    @aniketkrs

    Taking this on — opening a PR with the fix shortly.

  5. michaelkillgta commented on Sep 28, 2026

    @michaelkillgta
    Author

    Thanks Minahil Atiq (@minahilatiqdev) and Aniket Kumar (@aniketkrs) — I see #14493 is open for this, so I'll leave it there and won't open a competing PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    .NETIssue or Pull requests regarding .NET codepythonPull requests for the Python Semantic Kerneltriage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions