From 64b4942971f3e246ce27cfc0dbba8c44ed33242d Mon Sep 17 00:00:00 2001 From: cstolting-collab Date: Thu, 17 Sep 2026 13:11:28 -0400 Subject: [PATCH 1/3] fix: preserve Pydantic field descriptions in schemas --- .../schema/kernel_json_schema_builder.py | 7 ++----- python/tests/unit/schema/test_schema_builder.py | 12 ++++++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/python/semantic_kernel/schema/kernel_json_schema_builder.py b/python/semantic_kernel/schema/kernel_json_schema_builder.py index 5ec519b5b377..d2291f52ac1a 100644 --- a/python/semantic_kernel/schema/kernel_json_schema_builder.py +++ b/python/semantic_kernel/schema/kernel_json_schema_builder.py @@ -89,12 +89,9 @@ def build_model_schema( field_description = None if hasattr(model, "model_fields") and field_name in model.model_fields: field_info = model.model_fields[field_name] - if isinstance(field_info.metadata, dict): + field_description = getattr(field_info, "description", None) + if field_description is None and isinstance(getattr(field_info, "metadata", None), dict): field_description = field_info.metadata.get("description") - elif isinstance(field_info.metadata, list) and field_info.metadata: - field_description = field_info.metadata[0] - elif hasattr(field_info, "description"): - field_description = field_info.description if not cls._is_optional(field_type): required.append(field_name) properties[field_name] = cls.build(field_type, field_description, structured_output) diff --git a/python/tests/unit/schema/test_schema_builder.py b/python/tests/unit/schema/test_schema_builder.py index 5d24a599c96c..c20db5b70cb2 100644 --- a/python/tests/unit/schema/test_schema_builder.py +++ b/python/tests/unit/schema/test_schema_builder.py @@ -6,6 +6,7 @@ from unittest.mock import Mock import pytest +from pydantic import Field from semantic_kernel.connectors.utils.structured_output_schema import generate_structured_output_response_format_schema from semantic_kernel.kernel_pydantic import KernelBaseModel @@ -76,6 +77,10 @@ class PydanticReasoning(KernelBaseModel): final_answer: str +class PydanticModelWithConstrainedField(KernelBaseModel): + top_p: float = Field(default=0.9, ge=0.0, le=1.0, description="nucleus sampling") + + class NonPydanticStep: explanation: str output: str @@ -156,6 +161,13 @@ def test_build_model_schema(): assert result == expected_schema +def test_build_model_schema_uses_pydantic_description_with_constraints(): + schema = KernelJsonSchemaBuilder.build_model_schema(PydanticModelWithConstrainedField) + + assert schema["properties"]["top_p"] == {"type": "number", "description": "nucleus sampling"} + json.dumps(schema) + + def test_build_from_type_name(): expected_schema = {"type": "string", "description": "A simple string"} result = KernelJsonSchemaBuilder.build_from_type_name("str", description="A simple string") From b7661a78dccdbecf3b07c3c5485af0676a6da87c Mon Sep 17 00:00:00 2001 From: "S.J." Date: Tue, 29 Sep 2026 16:39:59 -0400 Subject: [PATCH 2/3] fix: preserve Annotated field descriptions --- .../schema/kernel_json_schema_builder.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/python/semantic_kernel/schema/kernel_json_schema_builder.py b/python/semantic_kernel/schema/kernel_json_schema_builder.py index d2291f52ac1a..3e4091a3775f 100644 --- a/python/semantic_kernel/schema/kernel_json_schema_builder.py +++ b/python/semantic_kernel/schema/kernel_json_schema_builder.py @@ -90,8 +90,18 @@ def build_model_schema( if hasattr(model, "model_fields") and field_name in model.model_fields: field_info = model.model_fields[field_name] field_description = getattr(field_info, "description", None) - if field_description is None and isinstance(getattr(field_info, "metadata", None), dict): - field_description = field_info.metadata.get("description") + if field_description is None: + metadata = getattr(field_info, "metadata", None) + if isinstance(metadata, dict): + field_description = metadata.get("description") + elif isinstance(metadata, (list, tuple)): + for item in metadata: + if isinstance(item, str): + field_description = item + break + if isinstance(item, dict) and isinstance(item.get("description"), str): + field_description = item["description"] + break if not cls._is_optional(field_type): required.append(field_name) properties[field_name] = cls.build(field_type, field_description, structured_output) From 7555df93c011cda1e9ad6fe521e2190afd9379f8 Mon Sep 17 00:00:00 2001 From: "S.J." Date: Tue, 29 Sep 2026 16:40:02 -0400 Subject: [PATCH 3/3] test: cover Annotated metadata descriptions --- python/tests/unit/schema/test_schema_builder.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/python/tests/unit/schema/test_schema_builder.py b/python/tests/unit/schema/test_schema_builder.py index c20db5b70cb2..2b5c47ac0536 100644 --- a/python/tests/unit/schema/test_schema_builder.py +++ b/python/tests/unit/schema/test_schema_builder.py @@ -81,6 +81,10 @@ class PydanticModelWithConstrainedField(KernelBaseModel): top_p: float = Field(default=0.9, ge=0.0, le=1.0, description="nucleus sampling") +class PydanticModelWithAnnotatedDescription(KernelBaseModel): + name: Annotated[str, "display name"] + + class NonPydanticStep: explanation: str output: str @@ -168,6 +172,13 @@ def test_build_model_schema_uses_pydantic_description_with_constraints(): json.dumps(schema) +def test_build_model_schema_uses_annotated_metadata_description(): + schema = KernelJsonSchemaBuilder.build_model_schema(PydanticModelWithAnnotatedDescription) + + assert schema["properties"]["name"] == {"type": "string", "description": "display name"} + json.dumps(schema) + + def test_build_from_type_name(): expected_schema = {"type": "string", "description": "A simple string"} result = KernelJsonSchemaBuilder.build_from_type_name("str", description="A simple string")