Skip to content

Conversation

@q33566
Copy link
Contributor

@q33566 q33566 commented Dec 10, 2025

…t Strict Mode

Environmental

  • agent framework 251209

Motivation and Context

OpenAI's structured output strict mode has a specific requirement: all properties in the JSON Schema must be listed in the required array, even if they have default values.
Previously, ManagerSelectionResponse(in packages/core/agent_framework/_workflows/_group_chat.py) fields had default value.

from pydantic import BaseModel, Field
class ManagerSelectionResponse(BaseModel):
    """Response from manager agent with speaker selection decision.

    The manager agent must produce this structure (or compatible dict/JSON)
    to communicate its decision back to the orchestrator.

    Attributes:
        selected_participant: Name of participant to speak next (None = finish conversation)
        instruction: Optional instruction to provide to the selected participant
        finish: Whether the conversation should be completed
        final_message: Optional final message string when finishing conversation (will be converted to ChatMessage)
    """

    model_config = {
        "extra": "forbid",
    }

    selected_participant: str | None = None
    instruction: str | None = None
    finish: bool = False
    final_message: str | None = Field(default=None, description="Optional text content for final message")

print(ManagerSelectionResponse.model_json_schema())

the schema look like this. (No required array)

{
  "additionalProperties": false,
  "description": "Response from manager agent with speaker selection decision.\n\nThe manager agent must produce this structure (or compatible dict/JSON)\nto communicate its decision back to the orchestrator.\n\nAttributes:\n    selected_participant: Name of participant to speak next (None = finish conversation)\n    instruction: Optional instruction to provide to the selected participant\n    finish: Whether the conversation should be completed\n    final_message: Optional final message string when finishing conversation (will be converted to ChatMessage)",
  "properties": {
    "selected_participant": {
      "anyOf": [
        { "type": "string" },
        { "type": "null" }
      ],
      "default": null,
      "title": "Selected Participant"
    },
    "instruction": {
      "anyOf": [
        { "type": "string" },
        { "type": "null" }
      ],
      "default": null,
      "title": "Instruction"
    },
    "finish": {
      "type": "boolean",
      "default": false,
      "title": "Finish"
    },
    "final_message": {
      "anyOf": [
        { "type": "string" },
        { "type": "null" }
      ],
      "default": null,
      "description": "Optional text content for final message",
      "title": "Final Message"
    }
  },
  "title": "ManagerSelectionResponse",
  "type": "object"
}

Solution Proposed

Now, if we change model_config into this

model_config = {
        "extra": "forbid",
        # OpenAI strict mode requires all properties to be in required array
        "json_schema_extra": {"required": ["selected_participant", "instruction", "finish", "final_message"]},
    }

then print

print(ManagerSelectionResponse.model_json_schema())

We can have required array in the schema, which solve the problem

{
  "additionalProperties": false,
  "description": "Response from manager agent with speaker selection decision.\n\nThe manager agent must produce this structure (or compatible dict/JSON)\nto communicate its decision back to the orchestrator.\n\nAttributes:\n    selected_participant: Name of participant to speak next (None = finish conversation)\n    instruction: Optional instruction to provide to the selected participant\n    finish: Whether the conversation should be completed\n    final_message: Optional final message string when finishing conversation (will be converted to ChatMessage)",
  "properties": {
    "selected_participant": {
      "anyOf": [
        { "type": "string" },
        { "type": "null" }
      ],
      "default": null,
      "title": "Selected Participant"
    },
    "instruction": {
      "anyOf": [
        { "type": "string" },
        { "type": "null" }
      ],
      "default": null,
      "title": "Instruction"
    },
    "finish": {
      "type": "boolean",
      "default": false,
      "title": "Finish"
    },
    "final_message": {
      "anyOf": [
        { "type": "string" },
        { "type": "null" }
      ],
      "default": null,
      "description": "Optional text content for final message",
      "title": "Final Message"
    }
  },
 ----------------------------
  # required array here
  "required": [
    "selected_participant",
    "instruction",
    "finish",
    "final_message"
  ],
------------------------------
  "title": "ManagerSelectionResponse",
  "type": "object"
}

Unit Test Result

cd packages/core
uv run poe test

Test Result (All Pass)

================================================= tests coverage =================================================
________________________________ coverage: platform darwin, python 3.13.5-final-0 ________________________________

Name                                                          Stmts   Miss  Cover   Missing
-------------------------------------------------------------------------------------------
agent_framework/_agents.py                                      289     54    81%   329, 390-392, 438, 492, 498, 510, 672, 678, 853, 856-858, 986-991, 994-996, 1084, 1125, 1127, 1136-1141, 1147-1160, 1167-1193, 1198-1200, 1234, 1279-1282, 1284, 1295
agent_framework/_clients.py                                     100     11    89%   268, 384, 430-433, 477, 666-667, 798-800
agent_framework/_logging.py                                       9      1    89%   12
agent_framework/_mcp.py                                         388     62    84%   106, 110, 170, 179, 243, 253-254, 275, 300, 315, 321, 325, 361, 430, 457, 491-510, 556, 571, 589, 630, 643, 646-651, 661, 685, 688-693, 703, 734, 753, 755, 762-763, 782, 784, 790-793, 810-814, 942
agent_framework/_memory.py                                       70     15    79%   117, 138, 156, 166, 183, 253, 257, 285-292, 308-310
agent_framework/_middleware.py                                  397      9    98%   803, 819, 866-867, 1070-1071, 1116, 1266, 1480
agent_framework/_pydantic.py                                     25      1    96%   55
agent_framework/_serialization.py                               101     13    87%   335, 345-346, 351, 383, 510, 526, 536, 548, 578-579, 604, 607
agent_framework/_telemetry.py                                    19      1    95%   64
agent_framework/_threads.py                                     136     21    85%   145, 177-180, 257-260, 343, 355, 467-471, 474-475, 494-496, 499, 505
agent_framework/_tools.py                                       622     73    88%   225, 271, 321-322, 324, 487, 519-520, 621, 623, 637, 655, 669, 675, 681, 686, 688, 695, 728, 782-784, 822, 845, 847-856, 865-871, 899, 909, 947, 956, 958, 961, 967, 1341-1345, 1445, 1514, 1615-1621, 1662-1663, 1795, 1834-1835, 1863-1865, 1869, 1900-1901, 1907, 1964-1965, 1972-1973
agent_framework/_types.py                                       952    116    88%   130-131, 149-150, 287, 289, 296, 315, 355, 401-402, 438, 588, 702-705, 730, 737, 754-756, 829, 834-837, 844-847, 869, 876, 879-881, 886-887, 893-895, 1019, 1106-1109, 1117-1118, 1209, 1390, 1394, 1396, 1640-1649, 1811, 1819, 1895, 1900, 1950, 1955, 1959, 1963, 2141-2143, 2155, 2206-2210, 2220, 2225, 2241, 2430, 2444-2446, 2506, 2684, 2770-2772, 2822, 2845, 2853-2857, 3031, 3035, 3047-3049, 3150-3152, 3154-3156, 3159, 3163, 3166, 3171, 3216-3217, 3224-3225, 3259-3261, 3276, 3292, 3320, 3327
agent_framework/_workflows/_agent.py                            232     40    83%   55, 63-69, 97-98, 248, 288-291, 297, 303, 307-322, 361, 368, 374-375, 381, 393, 425, 432, 453, 460, 464, 475
agent_framework/_workflows/_agent_executor.py                   153     27    82%   27, 92, 104-106, 131-135, 150-151, 202-206, 237-239, 249-253, 257, 261, 265-266, 287
agent_framework/_workflows/_base_group_chat_orchestrator.py      78      7    91%   20, 83, 174, 184-191, 247, 274
agent_framework/_workflows/_checkpoint.py                       121      2    98%   182-183
agent_framework/_workflows/_checkpoint_encoding.py              185     62    66%   36-37, 46-56, 62-64, 70-71, 91-92, 107-108, 125-126, 144-146, 152-159, 195-196, 209, 213, 222-223, 228-250
agent_framework/_workflows/_checkpoint_summary.py                27      4    85%   36-39
agent_framework/_workflows/_concurrent.py                       137     26    81%   50, 59-60, 68-69, 88-89, 94, 99, 124, 129, 134-135, 141, 163, 173, 180, 253-259, 287, 291, 322
agent_framework/_workflows/_conversation_history.py              11      1    91%   20
agent_framework/_workflows/_conversation_state.py                39      8    79%   40, 45-51, 66
agent_framework/_workflows/_edge.py                             218     26    88%   36, 58-61, 413-419, 423-432, 542, 656, 663-665, 712, 740, 766, 820, 834
agent_framework/_workflows/_edge_runner.py                      158     13    92%   53, 58, 71, 136-141, 148, 202-207, 349-354, 395
agent_framework/_workflows/_events.py                           130     14    89%   59-60, 78, 86, 90, 180-181, 232, 257, 294, 312, 337, 378, 392
agent_framework/_workflows/_executor.py                         142      6    96%   208, 326, 341-343, 458, 468
agent_framework/_workflows/_function_executor.py                 59      4    93%   82, 88, 94, 111
agent_framework/_workflows/_group_chat.py                       555     93    83%   112, 150, 161, 385-395, 414, 441-442, 447, 509, 523, 530-536, 539, 598-599, 655-664, 673-677, 715, 729-741, 746, 748, 754-760, 763, 778, 801, 810, 975, 991-995, 1043, 1093, 1110, 1118, 1135, 1140-1142, 1349, 1355, 1514, 1528, 1595-1606, 1771, 1843-1846, 1856, 1867, 1876-1878
agent_framework/_workflows/_handoff.py                          513    135    74%   60, 71-85, 159, 167-176, 190, 199, 218-221, 230-232, 243, 246, 256-269, 275, 281, 316, 362, 455, 464-472, 483, 500, 518, 533, 545, 573, 585-593, 617-619, 622-625, 627-629, 872, 878, 882, 888, 892, 904, 954, 957, 1045, 1050, 1060, 1066-1069, 1077-1078, 1082-1103, 1118-1126, 1146-1160, 1194-1195, 1248-1249, 1334, 1495, 1503, 1575, 1587, 1591, 1596-1597
agent_framework/_workflows/_magentic.py                         995    342    66%   49, 54, 74-83, 88, 92-103, 276, 281, 298, 300, 315, 323-332, 482, 486, 500, 506, 521, 601, 614, 631, 640-647, 658, 796-805, 844, 891, 927-931, 939-942, 946-949, 1083-1084, 1101-1104, 1112, 1148, 1157-1159, 1179, 1232, 1252, 1255, 1287, 1290, 1294-1295, 1308, 1318-1328, 1354-1400, 1411-1452, 1460, 1468, 1483, 1495, 1507-1510, 1533-1556, 1565-1566, 1571-1573, 1604, 1630, 1644, 1660, 1676, 1748, 1755-1772, 1793, 1829, 1834-1843, 1874-1960, 2018-2054, 2068-2074, 2290-2291, 2470-2487, 2492, 2495, 2551, 2562, 2573-2575, 2588-2589, 2594, 2605-2607, 2618-2620, 2632-2642, 2650-2716, 2731-2734, 2745-2748, 2760-2763, 2767
agent_framework/_workflows/_message_utils.py                     18      3    83%   22, 33, 37
agent_framework/_workflows/_model_utils.py                       31     12    61%   19, 23, 35-40, 47-51
agent_framework/_workflows/_orchestration_state.py               27      5    81%   20, 28, 67, 84-85
agent_framework/_workflows/_orchestrator_helpers.py              50      5    90%   96-97, 127-129, 195
agent_framework/_workflows/_participant_utils.py                 80      4    95%   40, 51, 57, 67
agent_framework/_workflows/_request_info_mixin.py                77      5    94%   54, 99, 220, 227, 234
agent_framework/_workflows/_runner.py                           215     35    84%   115-116, 122-123, 159-166, 206-208, 230-234, 268-270, 294-298, 302, 337, 341, 343, 347, 355-358, 371, 411
agent_framework/_workflows/_runner_context.py                   165      9    95%   76-82, 376, 396, 493, 497
agent_framework/_workflows/_sequential.py                        78      7    91%   71, 80-81, 91, 143, 149, 172
agent_framework/_workflows/_shared_state.py                      45      7    84%   48-49, 78-79, 98-101
agent_framework/_workflows/_typing_utils.py                     136     55    60%   23-71, 118, 142, 221-224, 233, 235, 242, 244, 264, 266, 268, 273-292
agent_framework/_workflows/_validation.py                       145      6    96%   85, 132, 153, 247, 328, 331
agent_framework/_workflows/_viz.py                              178     30    83%   79-114, 130, 141, 152, 163-166, 285, 338
agent_framework/_workflows/_workflow.py                         249     18    93%   94, 264-269, 287, 311, 313, 410, 659, 693, 698, 701, 720-722, 787
agent_framework/_workflows/_workflow_builder.py                 286     38    87%   259, 596, 694, 700-701, 798-828, 892, 959, 969, 1028-1029, 1164, 1178-1190, 1192-1194, 1202
agent_framework/_workflows/_workflow_context.py                 168     35    79%   60-61, 69, 73, 77-88, 163, 188, 298, 403, 412, 417, 438-448, 457-465
agent_framework/_workflows/_workflow_executor.py                160     44    72%   32, 96, 384, 401-405, 411-415, 426-430, 450, 462-507, 541, 565-576, 584, 589, 600
agent_framework/azure/_chat_client.py                            58      4    93%   166, 168, 181-182
agent_framework/azure/_responses_client.py                       28      2    93%   100, 123
agent_framework/azure/_shared.py                                 79      7    91%   135-136, 199, 202, 215, 217, 227
agent_framework/observability.py                                609    101    83%   238, 264-274, 282-299, 322-333, 417, 427, 449, 455, 474-476, 484-486, 508-510, 520-522, 655-657, 744-782, 1049, 1127-1129, 1199-1201, 1359, 1367, 1371, 1375, 1381, 1383, 1385, 1393, 1403, 1431-1432, 1445-1448, 1467-1477, 1481-1484, 1542, 1558, 1562, 1696-1698
agent_framework/openai/_assistants_client.py                    209     29    86%   179, 192-212, 223, 248-269, 280, 365, 402, 438, 467-470, 521, 538
agent_framework/openai/_chat_client.py                          234     43    82%   110-115, 133, 143-157, 171, 173-176, 185, 197, 233, 264-283, 290-292, 367, 374-378, 396-397, 403, 419
agent_framework/openai/_responses_client.py                     462    104    77%   98, 144, 151-156, 161-165, 172, 191, 198, 201-210, 214-245, 272, 302, 330-331, 358, 362, 379, 384, 398, 434, 496, 571-582, 602, 617-624, 672, 692-724, 762-764, 802-804, 813-814, 832, 834, 907-913, 930-935, 954, 972, 982-984, 1002-1003, 1005-1007, 1018-1024
agent_framework/openai/_shared.py                               100     12    88%   63-72, 150, 152, 159, 161, 174, 250, 274
-------------------------------------------------------------------------------------------
TOTAL                                                         10892   1807    83%

5 files skipped due to complete coverage.
1179 passed, 86 skipped, 38 warnings in 40.08s
  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? If yes, add "[BREAKING]" prefix to the title of the PR.

@github-actions github-actions bot changed the title fix: GroupChat ManagerSelectionResponse JSON Schema for OpenAI Structured Outpu… Python: fix: GroupChat ManagerSelectionResponse JSON Schema for OpenAI Structured Outpu… Dec 10, 2025
@q33566
Copy link
Contributor Author

q33566 commented Dec 10, 2025

The fact that OpenAI strict mode requires every property to appear in required. is also described in the following samples

  • samples/getting_started/agents/openai/openai_chat_client_with_runtime_json_schema.py
  • samples/getting_started/agents/azure_ai/azure_ai_with_runtime_json_schema.py

@markwallace-microsoft
Copy link
Member

markwallace-microsoft commented Dec 10, 2025

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/core/agent_framework/_workflows
   _group_chat.py60410782%113, 149, 160, 384–394, 413, 440–441, 446, 508, 522, 529, 533–535, 538, 590–591, 593–595, 619–620, 676–685, 694–695, 698, 743, 757–759, 763, 765–769, 774, 776, 782, 786–788, 793–798, 804, 819, 842, 851, 874, 883, 888–889, 1051, 1070, 1074, 1128, 1178, 1196, 1204, 1229–1231, 1470, 1476, 1635, 1649, 1716–1717, 1719–1720, 1722, 1724, 1726–1727, 1963, 2035–2038, 2048, 2059, 2068–2070
TOTAL16543257684% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
2369 130 💤 0 ❌ 0 🔥 56.602s ⏱️

@moonbox3
Copy link
Contributor

Thanks for working on this, @q33566. Can you please make sure you install the pre-commit hooks that run while committing the code to your branch? See the following: https://github.com/microsoft/agent-framework/blob/main/python/DEV_SETUP.md#after-installing-uv

uv run poe pre-commit-install

@q33566 q33566 force-pushed the fix/manager-selection-response-required-fields branch from e52790b to a294911 Compare December 11, 2025 02:05
@q33566
Copy link
Contributor Author

q33566 commented Dec 11, 2025

Hi @moonbox3, thank you very much for the guidance. I’ve installed the pre-commit hooks and committed the change, so everything should be working now.

Here are the pre-commit check results (previously it failed at Run checks through Poe):

Check TOML files.....................................(no files to check)Skipped
Check YAML files.....................................(no files to check)Skipped
Check JSON files.....................................(no files to check)Skipped
Fix End of File..........................................................Passed
Check Mixed Line Endings.................................................Passed
Check Valid Python Samples...............................................Passed
Check Valid Python Notebooks.........................(no files to check)Skipped
Upgrade Python syntax....................................................Passed
Run checks through Poe...................................................Passed
Update uv lockfile...................................(no files to check)Skipped
Bandit Security Checks...................................................Passed

Copy link
Contributor

@moonbox3 moonbox3 left a comment

Choose a reason for hiding this comment

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

LGTM, thanks again.

@moonbox3 moonbox3 added this pull request to the merge queue Dec 11, 2025
Merged via the queue into microsoft:main with commit 4c6a5d4 Dec 11, 2025
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Issues related to single agents python

Projects

Status: Done

3 participants