Skip to content

Conversation

@eavanvalkenburg
Copy link
Member

@eavanvalkenburg eavanvalkenburg commented Jan 22, 2026

Motivation and Context

Summary

  • Migrate chat/agent telemetry to mixin-based usage and remove legacy decorators, with streaming telemetry now using finalizers/teardown hooks instead of consuming streams.
    • This makes understanding the code a lot simpler, because we can set attributes on the chat client in the init of those mixin (making them technically not a mixin)
    • Added those parameters to the constructors, making it easier to configure things like function calling
  • Replace function invocation decorators with FunctionInvokingChatClient/FunctionInvokingMixin across clients, tests, and samples; update docs/comments accordingly.
  • Introducing a ResponseStream object that can is created to unify the API's
    • It is generic over TUpdate and TFinal, which in our case is usually ChatResponseUpdate and ChatReponse or the agent equivalent.
    • It features a update_hook mechanism, to allow you to run code while the internal stream is being unpacked, this can mostly be leveraged by middleware
    • It features a teardown hook mechanism, this get's run when the stream is exhausted, it's used now by the telemtry to record the duration
    • It features a finalizer (one or more) mechanism, that runs after the end of the stream, which is used to turn the updates list into a final object, this can be used by middleware and is also used in function calling and telemetry
    • In principle the ResponseStream is created by the most lowlevel object, the actual chat client implementations, and ideally all the layers in between should only use the hooks to do something, FunctionCalling does not work that way, because there are multiple calls to the underlying chat client that all then have to be combined into a single stream at runtime. Agent also creates a new stream, because it goes from ResponseStream[ChatResponseUpdate, ChatResponse] to ResponseStream[AgentResponseUpdate, AgentResponse], but the object has a classmethod called wrap that is used to wrap the ResponseStream from the chat client into the new ResponseStream in the Agent.
  • Overall this change reduces the number of times we iterate the stream and return a new AsyncGenerator and the new hooks actually make it simpler to create middleware that alters the stream (as the sample shows), it should therefore also improve performance a bit.
  • Removed use_instrumentation/use_agent_instrumentation and use_function_invocation decorators; mixins are now the supported path.

Description

Contribution Checklist

  • 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.

Copilot AI review requested due to automatic review settings January 22, 2026 17:34
@markwallace-microsoft markwallace-microsoft added documentation Improvements or additions to documentation python labels Jan 22, 2026
Copy link
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 consolidates the Python Agent Framework's streaming and non-streaming APIs into a unified interface. The primary changes include:

Changes:

  • Unified run() and get_response() methods with stream parameter replacing separate run_stream() and get_streaming_response() methods
  • Migration from decorator-based (@use_instrumentation, @use_function_invocation) to mixin-based architecture for telemetry and function invocation
  • Introduction of ResponseStream class for unified stream handling with hooks, finalizers, and teardown support
  • Renamed AgentExecutionException to AgentRunException

Reviewed changes

Copilot reviewed 84 out of 85 changed files in this pull request and generated 28 comments.

Show a summary per file
File Description
_types.py Added ResponseStream class for unified streaming, updated prepare_messages to handle None
_clients.py Refactored BaseChatClient with unified get_response() method, introduced FunctionInvokingChatClient mixin
openai/_responses_client.py Consolidated streaming/non-streaming into single _inner_get_response() method
openai/_chat_client.py Similar consolidation for chat completions API
openai/_assistants_client.py Unified assistants API with stream parameter
_workflows/_workflow.py Consolidated run() and run_stream() into single run(stream=bool) method
_workflows/_agent.py Updated WorkflowAgent.run() to use stream parameter
Test files (multiple) Updated all tests to use run(stream=True) and get_response(stream=True)
Sample files (multiple) Updated samples to demonstrate new unified API
Provider clients Updated all provider implementations (Azure, Anthropic, Bedrock, Ollama, etc.) to use mixins

@eavanvalkenburg eavanvalkenburg force-pushed the python_single_response branch 3 times, most recently from 07afd46 to dd65afa Compare January 23, 2026 10:46
@markwallace-microsoft
Copy link
Member

markwallace-microsoft commented Jan 23, 2026

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/a2a/agent_framework_a2a
   _agent.py138794%351–352, 389–390, 419–421
packages/ag-ui/agent_framework_ag_ui
   _client.py1541987%30, 80–81, 85–89, 93–97, 244, 276, 445–447, 464
   _run.py44112471%154–161, 304, 323–324, 339–340, 351, 354–355, 357–358, 360–362, 372, 382–385, 389–391, 393, 403, 406–409, 411–412, 415–421, 424–426, 429, 445–447, 454, 460–461, 463–464, 478–484, 495, 508, 510–511, 545–546, 603–605, 617–619, 643, 648–650, 766, 777–778, 785, 803–805, 839–841, 856, 862, 870, 872, 908–914, 917–920, 922–931, 934, 941–942, 947, 953–955, 968–970
   _types.py36197%12
   _utils.py101298%257, 262
packages/ag-ui/agent_framework_ag_ui/_orchestration
   _tooling.py570100% 
packages/anthropic/agent_framework_anthropic
   _chat_client.py36115158%49, 346, 378, 380, 395, 417–420, 429, 431, 462–466, 468, 470–471, 473, 478–479, 481, 514–515, 524, 526–527, 532, 549–550, 592, 607, 611–612, 628, 637, 639, 643–644, 687–689, 691, 704–705, 712–714, 718–720, 724–727, 738, 740, 762, 772, 794–800, 807–808, 816–817, 825–828, 835–836, 842–843, 849–850, 856, 864–866, 870, 877–878, 884–885, 891–892, 898, 906–909, 916–917, 936, 943–944, 963, 985, 987, 996–997, 1003, 1025–1026, 1032–1033, 1042–1052, 1059–1065, 1072–1078, 1085–1094, 1101–1104
packages/azure-ai/agent_framework_azure_ai
   _chat_client.py48210278%363, 368–369, 371–372, 375, 378, 380, 385, 646–647, 649, 652, 655, 658–663, 666, 668, 676, 688–690, 694, 697–698, 706–709, 719, 727–730, 732–733, 735–736, 743, 751–752, 760–761, 766–767, 771–778, 783, 786, 794, 800, 808–810, 813, 835–836, 946, 948, 950, 965, 993, 1006–1010, 1049–1051, 1054–1055, 1101–1107, 1129, 1155, 1164, 1173, 1175, 1180, 1189–1194, 1306
   _client.py1894576%242–245, 250, 253–256, 261, 264–265, 268, 275, 339, 341, 389, 417–420, 463, 498, 500, 505, 507–508, 511–514, 516, 518–519, 521–529, 531, 576
packages/core/agent_framework
   _agents.py3165781%60, 99, 107, 110, 113, 418–420, 466, 650, 656, 676, 873, 925, 930, 947–948, 950–951, 953, 961, 968, 1021–1023, 1136, 1177, 1179, 1188–1193, 1199, 1201, 1211–1212, 1219, 1221–1222, 1230–1234, 1242–1243, 1245, 1250, 1252, 1286, 1331–1332, 1334, 1336, 1347
   _clients.py56689%46, 276, 300–301, 465, 467
   _middleware.py3841396%25, 755, 757, 839, 855, 885, 904, 1037, 1052, 1054, 1261, 1410, 1489
   _tools.py7827590%231, 277, 328, 330, 358, 528, 560–561, 663, 665, 685, 703, 717, 729, 734, 736, 743, 776, 832–834, 875, 900–909, 915–924, 960, 970, 1211, 1416, 1504, 1508, 1591–1595, 1723, 1760, 1762, 1774, 1776, 1841, 1868, 1904, 1983, 2162, 2184–2185, 2247–2248, 2270–2271, 2293–2298
   _types.py109316285%74, 97–98, 152, 157, 176, 178, 182, 186, 188, 190, 192, 209–210, 212–214, 216–217, 219–220, 222–223, 238–240, 242–245, 262, 267, 272, 276, 302, 306, 650–651, 1022, 1084, 1101, 1119, 1124, 1142, 1150–1152, 1169–1170, 1172, 1190–1191, 1193, 1200–1201, 1203, 1238, 1249–1250, 1252, 1271–1272, 1275–1284, 1287–1290, 1292, 1296, 1330, 1363, 1535, 1540, 1544, 1548, 1734, 1743, 1753, 1798, 1843–1848, 1870, 1875, 2152, 2234, 2243, 2391, 2451–2462, 2481, 2488, 2504–2507, 2509, 2514, 2519, 2522–2523, 2526, 2530, 2563, 2568, 2572, 2660–2662, 2701, 2757, 2778, 2787, 3018–3020, 3023–3025, 3029, 3034, 3038, 3150–3152, 3180, 3216, 3234, 3238–3240, 3242, 3253–3254, 3257–3261, 3267
   exceptions.py480100% 
   observability.py59514775%25, 248, 316–321, 323, 325–326, 328, 330–332, 335–337, 342–343, 349–350, 356–357, 364, 366–368, 371–373, 378–379, 385–386, 392–393, 400, 437, 440, 443–445, 448, 451–452, 455–457, 459–461, 464, 551, 553, 635, 653–654, 656, 659, 667–668, 671–674, 676, 679–681, 684–685, 698–704, 706–715, 718–722, 725–728, 730–733, 736–737, 745, 846, 848, 873–875, 997, 999, 1003–1008, 1010, 1013–1017, 1019, 1121–1122, 1124, 1142, 1298, 1316, 1364–1366, 1450, 1453, 1512, 1596, 1598, 1605, 1621, 1624, 1684, 1700, 1704, 1838, 1840
packages/core/agent_framework/_workflows
   _agent.py2824584%56, 64–70, 98–99, 291, 349, 363, 376, 425–428, 434, 440, 444–445, 448–454, 458–459, 528, 535, 541–542, 553, 585, 592, 613, 622, 626, 628–630, 637
   _agent_executor.py1722486%27, 94, 116, 150, 166–167, 218–219, 221–222, 254–256, 264–266, 276–278, 280, 284, 288, 292–293
   _handoff.py3865984%58, 110–111, 113, 142–143, 163–173, 175, 177, 179, 184, 284, 338, 363, 389, 397–398, 412, 461–462, 492, 539–541, 729, 736, 741, 828, 831, 840–843, 853, 858, 865, 871–874, 909, 914, 1104, 1117, 1120, 1128, 1146, 1153, 1228
   _workflow.py2511793%88, 258–260, 262–263, 281, 309, 410, 678, 712, 717, 720, 739–741, 806
packages/core/agent_framework/azure
   _chat_client.py75494%273, 275, 288–289
   _responses_client.py33681%132, 155, 182–185
packages/core/agent_framework/openai
   _assistants_client.py2803786%50, 343, 345, 347, 350, 354–355, 358, 361, 366–367, 369, 372–374, 379, 390, 415, 417, 419, 421, 423, 428, 431, 434, 438, 449, 534, 619, 654, 691–694, 746, 763, 808
   _chat_client.py2652291%43, 154–155, 159, 272, 279, 360–367, 369–372, 382, 467, 504, 520
   _responses_client.py5586288%253–254, 259, 290, 298, 321, 383, 415, 440, 446, 464–465, 487, 492, 548, 562, 579, 592, 647, 726, 731, 735–737, 741–742, 765, 834, 856–857, 872–873, 891–892, 1023–1024, 1040, 1042, 1117–1125, 1173, 1228, 1243, 1279–1280, 1282–1284, 1298–1300, 1310–1311, 1317, 1332
   _shared.py1351688%63, 69–72, 151, 153, 155, 162, 164, 177, 253, 277, 341–342, 344
TOTAL17175263384% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
3299 217 💤 0 ❌ 0 🔥 1m 3s ⏱️

@eavanvalkenburg eavanvalkenburg changed the title Python: [BREAKING} Python single response Python: [BREAKING] Moved to a single get_response and run API Jan 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants