Repository navigation
Python: fix(core): return MiddlewareTermination(result=...) from agent and chat middleware - #9747
Somay kousis (Somay-kousis) wants to merge 2 commits into
Conversation
…t and chat middleware Fixes microsoft#9471.
There was a problem hiding this comment.
🟡 Changes recommended
The shared test helpers must move into a non-test support module to satisfy the repository’s mandatory helper-layout requirement.
1 open issue
What changed in this PR
Fixes #9471 by preserving MiddlewareTermination(result=...) in Python’s agent and chat middleware pipelines, matching the function pipeline’s behavior.
Changes:
- Use the exception’s result when
context.resultis unset, preserving existing precedence and post-processing. - Add pipeline tests for streaming, non-streaming, and result precedence.
- Add end-to-end
Agent.runtests confirming termination returns a response without calling the model.
| File | Description |
|---|---|
python/packages/core/tests/core/test_middleware.py |
Adds response factories and pipeline regression tests. |
python/packages/core/tests/core/test_middleware_with_agent.py |
Adds end-to-end termination-result tests. |
python/packages/core/agent_framework/_middleware.py |
Preserves exception results in agent and chat pipelines. |
Review effort: Balanced
| from agent_framework._sessions import InMemoryHistoryProvider | ||
|
|
||
| from .conftest import MockBaseChatClient, MockChatClient | ||
| from .test_middleware import _TERMINATION_TEXT, _termination_result |
There was a problem hiding this comment.
Done in ca09565: TERMINATION_TEXT and termination_result now live in tests/core/utils.py and both test files import them from there. Same tests, all pass.
…utils.py CODING_STANDARD.md asks for non-fixture helpers in a helper module, not imported from another test module.
|
Thanks for the fix and regression tests, Somay kousis (@Somay-kousis). The same agent/chat |

Motivation & Context
A middleware that raises
MiddlewareTermination(result=response)should getresponseback fromAgent.run, as it does when it setscontext.result. The agent and chat pipelines suppressed the exception and droppedresult=: agent middleware returned no response, and for chat middlewareAgent.runcrashed withAttributeError: 'NoneType' object has no attribute 'usage_details'. Details in #9471.Description & Review Guide
AgentMiddlewarePipeline.executeandChatMiddlewarePipeline.executecatchMiddlewareTerminationand useexc.resultwhencontext.resultwas not set, the same rule the function pipeline already follows in_tools.py. The result then goes through the existing post-processing (stream buffering etc.) like acontext.resultwould.result=works in all three pipelines. Acontext.resultset by the middleware still wins. On a streaming run, a non-streaming result now fails with a clearValueError: Expected ResponseStream for streaminginstead of anAttributeError. A bareMiddlewareTerminationwith no result is unchanged, as the issue scoped it out.to_updates()) instead of raising; I kept it out to stay in the issue's scope.Tests:
TestMiddlewareTerminationResultintest_middleware.py(agent and chat pipelines, streaming and non-streaming) andTestMiddlewareTerminationResultWithAgentintest_middleware_with_agent.py(Agent.runend to end): with_middleware.pyfrom main all 8 fail, with the change they pass.test_context_result_wins_over_termination_resultpins the precedence (it passes on main too, and fails ifexc.resultis made to win).tests/core: 7047 passed, 21 skipped. Ruff is clean, and the coretypingandtest-typingtasks (pyrefly, ty, zuban, pyright, mypy) pass.Same as the function pipeline: a middleware that raises after
call_next()already has the model's answer incontext.result, soresult=does not replace it.Related Issue
Fixes #9471
AI Assistance
AI assistance details: AI tools helped find the bug and write the code, the tests and this description. I reviewed all of it and ran the repro, the core tests, and the lint and typing checks myself.
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.