Repository navigation
Python: close native generators when stream invocations end - #14701
Conversation
There was a problem hiding this comment.
🟢 Approval recommended
No active review issues were identified in the generator-cleanup changes.
No open issues 🎉
What changed in this PR
This PR adds prompt cleanup of native Python generators when streaming invocations end through KernelFunction.invoke_stream() or Kernel.invoke_stream(), without changing public signatures or ordinary return-value behavior.
Changes:
- Wrap synchronous and asynchronous generators in closing context managers.
- Close the function-invocation stream owned by
Kernel.invoke_stream(). - Add 18 regression cases covering early closure, injected cancellation, exhaustion, exception identity, and ordinary close-bearing results.
| File | Description |
|---|---|
python/tests/unit/functions/test_kernel_function_from_method.py |
Adds retained-generator cleanup and compatibility tests for both entry points. |
python/semantic_kernel/kernel.py |
Closes the owned function-invocation stream with aclosing. |
python/semantic_kernel/functions/kernel_function.py |
Uses closing and aclosing when consuming native generators. |
Review effort: Balanced
|
Eduard van Valkenburg (@eavanvalkenburg) Thanks for the review. I traced the merge-queue failure to Python Integration Tests run 41451726672: Could you rerun the failed integration check and requeue this PR if it passes? |
Motivation and Context
Closing a native function's streaming invocation after its first item does not promptly close the underlying generator. With a retained reference to that generator, its
finallyblock remains suspended after the invocation ends. The same occurs whenCancelledErroris injected into the suspended invocation. A plugin that releases resources infinallytherefore keeps them live beyond the closed invocation.This reproduces through both
KernelFunction.invoke_stream()andKernel.invoke_stream()with ordinary decorated sync/async native functions.Description
Use
closing/aclosingaround actual Python generators consumed byKernelFunction.invoke_stream, and close the newly created function-invocation stream owned byKernel.invoke_stream. Ordinary returned values and custom close-bearing iterable objects retain their current behavior. Public signatures are unchanged.Validation
Base:
96c9e860db4d7c6da40652d9b421a31b05e98692.aclose, injected cancellation, normal exhaustion, original exception identity and an ordinary close-bearing list that must remain untouched.git diff --check: passed.Test environment: Python 3.13, local editable source and OpenAI 2.26.0 selected from the repository lockfile; not a complete locked all-connectors environment. Full SDK/integration/provider suites were not run.
Scope: the native-generator invocation path. Consumers still need to close a retained stream when leaving their own iteration body, for example with
aclosing. The separateinvoke_prompt_streamforwarding layer and deeper provider wrappers are outside this fix; no general provider-resource guarantee is claimed.Contribution Checklist
AI disclosure: Codex implemented and tested this change, with an independent AI review, as part of an autonomous upstream contribution benchmark. No personal human code review is claimed.