.Net: Python: KernelJsonSchemaBuilder emits {"type": "object"} with no enum for typing.Literal parameters and fields
Maintainers usually reply within 4 days
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 82/100
Research direction
Read python/semantic_kernel/schema/kernel_json_schema_builder.py, focusing on handle_complex_type() and get_json_schema(). Run the direct KernelJsonSchemaBuilder.build() examples and the provided kernel-function reproduction first. Done means Literal values produce typed enum schemas, including nullable and list-nested cases, rather than object schemas.
Written by the indexing model from the issue text.
Description
What happens
KernelJsonSchemaBuilder has no handling for typing.Literal. A parameter typed Literal["fast", "slow"] is advertised to the model as a bare {"type": "object"} with no enum. The model is told the argument is an object and is never told the allowed values.
This affects @kernel_function parameters, Pydantic model fields, and Literal nested inside list[...] or Optional[...].
Where
python/semantic_kernel/schema/kernel_json_schema_builder.py, KernelJsonSchemaBuilder.handle_complex_type() and get_json_schema().
Literal[...] has __args__, so build() sends it to handle_complex_type(). That method handles list, set, dict, tuple and Union, but not Literal, so it reaches the final fallback:
schema = cls.get_json_schema(parameter_type) # TYPE_MAPPING.get(Literal[...], "object")
TYPE_MAPPING has no entry for a Literal alias, so the default "object" is returned. build_enum_schema() already produces the right shape for Enum classes ({"type": ..., "enum": [...]}), but nothing does that for Literal.
Repro
Tested with semantic-kernel==1.44.1. I diffed kernel_json_schema_builder.py against current main and the file is identical.
import json
from typing import Annotated, Literal
from pydantic import BaseModel
from semantic_kernel import Kernel
from semantic_kernel.functions import kernel_function
from semantic_kernel.connectors.ai.function_calling_utils import kernel_function_metadata_to_function_call_format
class Opts(BaseModel):
level: Literal["low", "high"]
class Plugin:
@kernel_function(name="run")
def run(self, mode: Annotated[Literal["fast", "slow"], "speed mode"], opts: Annotated[Opts, "options"]) -> str:
return mode
k = Kernel(); k.add_plugin(Plugin(), "p")
fn = k.get_function("p", "run")
print(json.dumps(kernel_function_metadata_to_function_call_format(fn.metadata)["function"]["parameters"], indent=1))
Output:
{
"type": "object",
"properties": {
"mode": {"type": "object", "description": "speed mode"},
"opts": {
"type": "object",
"properties": {"level": {"type": "object"}},
"required": ["level"],
"description": "options"
}
},
"required": ["mode", "opts"]
}
Direct builder calls:
KernelJsonSchemaBuilder.build(Literal["fast", "slow"]) # {'type': 'object'}
KernelJsonSchemaBuilder.build(Optional[Literal["fast", "slow"]]) # {'type': ['object', 'null']}
KernelJsonSchemaBuilder.build(list[Literal["a", "b"]]) # {'type': 'array', 'items': {'type': 'object'}}
Expected
{"type": "string", "enum": ["fast", "slow"], "description": "speed mode"}
This is the same shape build_enum_schema() already returns for an Enum subclass. Optional[Literal[...]] should be nullable and list[Literal[...]] should have an enum on items.
Actual
{"type": "object"} with no allowed values. The model can emit any value, including a JSON object, and the kernel then tries to bind it to a str parameter. Tools that use Literal to constrain choices (a very common pattern) lose that constraint entirely.
Suggested fix
Add a Literal branch in handle_complex_type():
if origin is Literal:
values = list(args)
types = {TYPE_MAPPING.get(type(v), "string") for v in values}
schema = {"type": types.pop() if len(types) == 1 else sorted(types), "enum": values}
if description:
schema["description"] = description
return schema
The Optional[...] branch also needs to keep working with an enum schema (add None to the enum when making it nullable).
I searched open and closed issues and PRs for Literal, KernelJsonSchemaBuilder, and schema builder problems. #14239, #14241 and #14310 are about string forward references and only make sure Literal values are left alone while resolving them. #14482 and #14155 are about NoneType in unions. None of them adds Literal support.
- Dominant language
- C#
- Stars
- 28.6k
- Forks
- 4.8k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 12
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from microsoft/semantic-kernel
-
.Net: gpt-image-1 is the default image model in the .NET OpenAI connector, and OpenAI shuts it down on October 23Possibly taken @nightcityblade claimed this 3 days ago. Open.NET triage
Difficulty 2/5 1-3 hours Newbie friendliness 73/100
microsoft/semantic-kernel#14526 · 1 comment ·
Maintainers usually reply within 4 days
-
Python: VolatileMemoryStore.get_batch and get_nearest_matches ignore with_embeddings=False (deepcopy result is discarded)Possibly taken @VANDRANKI claimed this 4 days ago. Openpython triage
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
microsoft/semantic-kernel#14522 ·
Maintainers usually reply within 4 days
-
Python: VolatileMemoryStore.get_nearest_match returns an un-awaited coroutine instead of a (MemoryRecord, score) tuplePossibly taken @VANDRANKI claimed this 4 days ago. Openpython triage
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
microsoft/semantic-kernel#14521 ·
Maintainers usually reply within 4 days
-
.Net: Bug: BinaryContent does not decode the %xx escapes of a non-base64 data URIPossibly taken @Laurianti claimed this 5 days ago. Open.NET triage
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
microsoft/semantic-kernel#14518 ·
Maintainers usually reply within 4 days
-
Python: FunctionCallContent.combine_arguments drops a streamed "{}" chunk, producing invalid JSON argumentsPossibly taken @VANDRANKI claimed this 5 days ago. Openpython triage
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
microsoft/semantic-kernel#14512 · 2 comments ·
Maintainers usually reply within 4 days
All issues in microsoft/semantic-kernel
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 66/100
MicrosoftLearning/PL-400_Microsoft-Power-Platform-Developer#231 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 66/100
joinrpg/joinrpg-net#5313 ·
Maintainers usually reply within 1 day
-
[12.x] FixIncorrectOwnerIdRelationships can delete legitimate library roots when UserView shares the same pathPossibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 79/100
Maintainers usually reply within 1 day