Skip to content

Commit e3d5fe8

Browse files
author
octo-patch
committed
Python: fix: coerce dict response_format to Azure AI typed model to fix Azure Monitor compatibility
When response_format is passed as a plain Python dict (e.g. {"type": "json_object"}), the Azure AI telemetry instrumentor raises ValueError: Unknown response format <class 'dict'> because it only handles the typed SDK models (AgentsResponseFormat, ResponseFormatJsonSchemaType, AgentsResponseFormatMode, str). Add AgentThreadActions._coerce_response_format() to convert plain dicts to the appropriate typed model before the options dict is forwarded to agents.runs.create(), preventing the telemetry error when Azure Monitor is enabled. Fixes #13715
1 parent 77eaa7f commit e3d5fe8

2 files changed

Lines changed: 78 additions & 1 deletion

File tree

python/semantic_kernel/agents/azure_ai/agent_thread_actions.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from azure.ai.agents.models import (
99
AgentsNamedToolChoiceType,
1010
AgentStreamEvent,
11+
AgentsResponseFormat,
12+
AgentsResponseFormatMode,
1113
AsyncAgentEventHandler,
1214
AsyncAgentRunStream,
1315
BaseAsyncAgentEventHandler,
@@ -923,7 +925,7 @@ def _generate_options(cls: type[_T], **kwargs: Any) -> dict[str, Any]:
923925
return {
924926
"model": merged.get("model"),
925927
"top_p": merged.get("top_p"),
926-
"response_format": merged.get("response_format"),
928+
"response_format": cls._coerce_response_format(merged.get("response_format")),
927929
"temperature": merged.get("temperature"),
928930
"truncation_strategy": truncation_strategy,
929931
"metadata": merged.get("metadata"),
@@ -933,6 +935,28 @@ def _generate_options(cls: type[_T], **kwargs: Any) -> dict[str, Any]:
933935
"additional_messages": additional_messages,
934936
}
935937

938+
@staticmethod
939+
def _coerce_response_format(
940+
response_format: Any,
941+
) -> "str | AgentsResponseFormatMode | AgentsResponseFormat | ResponseFormatJsonSchemaType | None":
942+
"""Coerce a plain dict response_format to the appropriate Azure AI typed model.
943+
944+
When users supply ``response_format`` as a plain Python :class:`dict` (e.g.
945+
``{"type": "json_object"}``), the Azure AI telemetry instrumentor raises
946+
``ValueError: Unknown response format <class 'dict'>`` because it only handles
947+
the typed SDK models. This method converts plain dicts to the correct type so
948+
that the Azure AI telemetry layer can serialize them without error.
949+
"""
950+
if response_format is None or isinstance(
951+
response_format, (str, AgentsResponseFormatMode, AgentsResponseFormat, ResponseFormatJsonSchemaType)
952+
):
953+
return response_format
954+
if isinstance(response_format, dict):
955+
if response_format.get("type") == "json_schema":
956+
return ResponseFormatJsonSchemaType(response_format)
957+
return AgentsResponseFormat(response_format)
958+
return response_format
959+
936960
@classmethod
937961
def _translate_additional_messages(
938962
cls: type[_T], messages: "list[ChatMessageContent] | None"

python/tests/unit/agents/azure_ai_agent/test_agent_thread_actions.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
from datetime import datetime, timezone
44
from unittest.mock import AsyncMock, MagicMock, patch
55

6+
import pytest
67
from azure.ai.agents.models import (
8+
AgentsResponseFormat,
9+
AgentsResponseFormatMode,
710
MessageTextContent,
811
MessageTextDetails,
912
RequiredFunctionToolCall,
1013
RequiredFunctionToolCallDetails,
14+
ResponseFormatJsonSchemaType,
1115
RunStep,
1216
RunStepCodeInterpreterToolCall,
1317
RunStepCodeInterpreterToolCallDetails,
@@ -354,3 +358,52 @@ async def test_agent_thread_actions_invoke_stream(ai_project_client, ai_agent_de
354358
collected_messages.append(content)
355359
assert isinstance(content, ChatMessageContent)
356360
assert content.metadata.get("message_id") == "msg_1"
361+
362+
363+
# region _coerce_response_format tests
364+
365+
366+
@pytest.mark.parametrize(
367+
"input_value, expected_type",
368+
[
369+
(None, type(None)),
370+
("auto", str),
371+
(AgentsResponseFormatMode.AUTO, AgentsResponseFormatMode),
372+
(AgentsResponseFormat({"type": "json_object"}), AgentsResponseFormat),
373+
(
374+
ResponseFormatJsonSchemaType({"type": "json_schema", "json_schema": {"name": "t", "schema": {}}}),
375+
ResponseFormatJsonSchemaType,
376+
),
377+
],
378+
)
379+
def test_coerce_response_format_passthrough(input_value, expected_type):
380+
"""Values that are already the correct type should be returned unchanged."""
381+
result = AgentThreadActions._coerce_response_format(input_value)
382+
assert isinstance(result, expected_type) or result is None
383+
384+
385+
def test_coerce_response_format_dict_json_object():
386+
"""A plain dict with type 'json_object' should become AgentsResponseFormat."""
387+
result = AgentThreadActions._coerce_response_format({"type": "json_object"})
388+
assert isinstance(result, AgentsResponseFormat)
389+
390+
391+
def test_coerce_response_format_dict_json_schema():
392+
"""A plain dict with type 'json_schema' should become ResponseFormatJsonSchemaType."""
393+
payload = {"type": "json_schema", "json_schema": {"name": "MySchema", "strict": True, "schema": {}}}
394+
result = AgentThreadActions._coerce_response_format(payload)
395+
assert isinstance(result, ResponseFormatJsonSchemaType)
396+
397+
398+
def test_coerce_response_format_unknown_type_passthrough():
399+
"""An unknown (non-dict) type should be returned as-is to let the SDK handle it."""
400+
401+
class Custom:
402+
pass
403+
404+
custom = Custom()
405+
result = AgentThreadActions._coerce_response_format(custom)
406+
assert result is custom
407+
408+
409+
# endregion

0 commit comments

Comments
 (0)