Skip to content

Python: fix(python): normalize Azure AI agent response_format#13891

Open
MukundaKatta wants to merge 1 commit intomicrosoft:mainfrom
MukundaKatta:codex/semantic-kernel-azure-response-format
Open

Python: fix(python): normalize Azure AI agent response_format#13891
MukundaKatta wants to merge 1 commit intomicrosoft:mainfrom
MukundaKatta:codex/semantic-kernel-azure-response-format

Conversation

@MukundaKatta
Copy link
Copy Markdown

Summary

  • normalize dict-based structured output configs into Azure SDK ResponseFormatJsonSchemaType objects before creating runs
  • avoid passing raw dict response_format values into Azure Monitor-instrumented agent clients
  • add a focused unit test around the option generation path

Testing

  • python3 -m py_compile python/semantic_kernel/agents/azure_ai/agent_thread_actions.py python/tests/unit/agents/azure_ai_agent/test_agent_thread_actions.py
  • python3 -m pytest tests/unit/agents/azure_ai_agent/test_agent_thread_actions.py -k response_format (fails locally because repo test setup imports openai, which is not installed in this environment)

@MukundaKatta MukundaKatta requested a review from a team as a code owner April 20, 2026 03:03
@moonbox3 moonbox3 added the python Pull requests for the Python Semantic Kernel label Apr 20, 2026
@github-actions github-actions bot changed the title fix(python): normalize Azure AI agent response_format Python: fix(python): normalize Azure AI agent response_format Apr 20, 2026
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Code Review

Reviewers: 4 | Confidence: 89%

✓ Correctness

The diff adds a _normalize_response_format classmethod that converts dict-based json_schema response formats into strongly-typed ResponseFormatJsonSchemaType objects before passing them to the Azure SDK. The normalization is integrated into _merge_options and applied to both run-level and agent-level response formats. The implementation correctly handles all edge cases (None, already-typed, non-dict, non-json_schema dicts, dicts with non-dict json_schema). The new test verifies the main use case of dict-to-typed conversion via _generate_options. The import of ResponseFormatJsonSchema is consistent with existing usage in the sample code. No correctness issues found.

✓ Security Reliability

This change adds a _normalize_response_format helper that converts dict-based JSON schema response formats into typed ResponseFormatJsonSchemaType SDK objects, and integrates it into _merge_options. The normalization logic is defensively written with multiple guard clauses that correctly handle None, already-typed objects, non-dict inputs, dicts without the expected structure, and dicts with a non-dict json_schema field. The AgentsApiResponseFormatOption type alias (str | ResponseFormatJsonSchemaType) means agent.definition.response_format could also be a plain string, which the normalizer handles correctly by returning it as-is (the isinstance(response_format, dict) check fails for strings). No security or reliability issues were identified.

✓ Test Coverage

The PR adds _normalize_response_format to convert dict-based response formats into ResponseFormatJsonSchemaType objects and includes one integration test covering the happy path via _generate_options. While the happy path is covered, several branches of _normalize_response_format lack tests (None input, already-typed input, non-json_schema dicts, missing/non-dict json_schema), and the test doesn't verify all propagated fields (description, schema). There is also no test exercising the run-level response_format parameter normalization path.

✓ Design Approach

The change adds a _normalize_response_format helper that converts a raw dict with type: "json_schema" to the SDK's ResponseFormatJsonSchemaType object, and calls it in _merge_options before returning the merged options. The approach is sound: agent.definition.response_format can arrive as a plain dict from the Azure SDK deserialization layer, and normalizing it at the single merge/consume point (_merge_options) is a defensible place. There are no fragile assumptions, no leaky abstractions, and no symptom-level masking. One minor observation worth noting: the _merge_options signature still declares response_format: ResponseFormatJsonSchemaType | None, which is narrower than both AgentsApiResponseFormatOption = str | ResponseFormatJsonSchemaType (used by all three public invoke/get_response methods in azure_ai_agent.py) and _normalize_response_format's own input type ResponseFormatJsonSchemaType | dict[str, Any] | None. The str branch silently passes through _normalize_response_format (it hits the not isinstance(response_format, dict) guard and returns as-is), so there is no runtime breakage, but the annotation is misleading. This is a pre-existing mismatch that the diff does not worsen, but it would be cleaner to align the annotation with the actual accepted types.

Suggestions

  • Add a test that passes a dict response_format as a run-level override to _generate_options (or _merge_options), verifying the run-level normalization path.
  • The _merge_options response_format parameter annotation is ResponseFormatJsonSchemaType | None, but callers pass str | ResponseFormatJsonSchemaType | None. Now that _normalize_response_format formally accepts dict[str, Any] as well, consider updating the annotation to AgentsApiResponseFormatOption | dict[str, Any] | None so the declared type matches what is actually handled.

Automated review by MukundaKatta's agents

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Azure AI Agent run-option construction path to normalize dict-based structured output response_format configurations into Azure SDK model objects before invoking runs.create/stream, and adds a unit test to cover that normalization.

Changes:

  • Normalize dict-based response_format (specifically type: "json_schema") into ResponseFormatJsonSchemaType in AgentThreadActions option merging.
  • Ensure response_format passed to run creation is always the normalized value from merged agent/run options.
  • Add a focused unit test asserting _generate_options converts a dict response_format into ResponseFormatJsonSchemaType.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
python/semantic_kernel/agents/azure_ai/agent_thread_actions.py Adds _normalize_response_format and applies it during option merging so Azure SDK receives normalized response_format.
python/tests/unit/agents/azure_ai_agent/test_agent_thread_actions.py Adds a unit test validating dict response_format gets normalized to ResponseFormatJsonSchemaType.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 890 to +924
@classmethod
def _merge_options(
cls: type[_T],
*,
agent: "AzureAIAgent",
model: str | None = None,
response_format: ResponseFormatJsonSchemaType | None = None,
temperature: float | None = None,
top_p: float | None = None,
metadata: dict[str, str] | None = None,
**kwargs: Any,
) -> dict[str, Any]:
"""Merge run-time options with the agent-level options.

Run-level parameters take precedence.
"""
normalized_response_format = (
cls._normalize_response_format(response_format)
if response_format is not None
else cls._normalize_response_format(agent.definition.response_format)
)
return {
"model": model if model is not None else agent.definition.model,
"response_format": response_format if response_format is not None else agent.definition.response_format,
"response_format": normalized_response_format,
"temperature": temperature if temperature is not None else None,
"top_p": top_p if top_p is not None else None,
"metadata": metadata if metadata is not None else agent.definition.metadata,
**kwargs,
}

@classmethod
def _normalize_response_format(
cls: type[_T], response_format: ResponseFormatJsonSchemaType | dict[str, Any] | None
) -> ResponseFormatJsonSchemaType | dict[str, Any] | None:
"""Normalize structured output response formats for Azure SDK consumers."""
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_merge_options/_normalize_response_format currently assume response_format is ResponseFormatJsonSchemaType | dict | None, but AzureAIAgent passes response_format through as str | ResponseFormatJsonSchemaType (see AgentsApiResponseFormatOption), and this method already returns non-dict values unchanged. To keep type hints accurate (and avoid needing type: ignore), consider widening the accepted/returned types here to include str (and ideally align with AzureAIAgent.AgentsApiResponseFormatOption).

Copilot uses AI. Check for mistakes.
Comment on lines +928 to +937
if not isinstance(response_format, dict):
return response_format

if response_format.get("type") != "json_schema":
return response_format

json_schema = response_format.get("json_schema")
if not isinstance(json_schema, dict):
return response_format

Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_normalize_response_format returns the original dict for any dict shapes it doesn't recognize (including {"type": "json_object"} or other unexpected type values). Since AgentThreadActions.invoke/_generate_options feed this value directly into agent.client.agents.runs.create/stream, this can still result in passing raw dicts into the Azure SDK/monitoring pipeline. If the intent is to avoid dicts entirely, normalize additional supported shapes (e.g., map {"type": "json_object"} to the string form the SDK expects) and/or raise a clear error for unsupported dict formats instead of passing them through.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Pull requests for the Python Semantic Kernel

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants