Python: omit failed Foundry turns from conversation chat history - #7637
Conversation
|
@microsoft-github-policy-service agree |
|
Unfortunately this doesn't fix the issue, as the problem is that the messages get saved to the chat history, not the session. |
|
You're right — the first commit skipped the wrong store. The agentserver response provider was persisting input items for failed turns, so the next request on the same conversation replayed them via chat history (get_history()), not the MAF session. 24ebe8b wraps the response store so failed turns persist without input items (including the in_progress → failed update path). A follow-up request on the same conversation no longer sees the bad function_call_output. Tests cover both the store wrapper and the HTTP conversation repro from the issue. |
9c43deb to
4b6b97e
Compare
Python Test Coverage Report •
Python Unit Test Overview
|
||||||||||||||||||||||||||||||
|
|
||
| The agentserver orchestrator persists input items for every stored response, | ||
| including ``status=failed``. Conversation history then replays those items on | ||
| the next turn, which is the #7630 failure mode. Azure OpenAI does not keep |
There was a problem hiding this comment.
we do not need to refer to issues in docstrings, especially on private methods
There was a problem hiding this comment.
Addressed locally: the private provider docstring no longer refers to the issue number. I will include this with the design revision once the streaming ownership question is confirmed.
|
|
||
| async def create_response( | ||
| self, | ||
| response: Any, |
There was a problem hiding this comment.
can we be more specific here, I don't love a bunch of Any definitions, then we might as well not type at all...
There was a problem hiding this comment.
Addressed locally: create_response and update_response now use ResponseObject, Iterable[OutputItem] | None, Iterable[str] | None, and PlatformContext | None; the failed-response helper is typed to ResponseObject as well. Strict package Pyright and all five test type-checkers pass. I will include this with the design revision once the streaming ownership question is confirmed.
| async for event in events: | ||
| buffered.append(event) | ||
| except Exception as ex: | ||
| handler_error = ex | ||
| failed = handler_error is not None or any( | ||
| _response_field(event, "type") == "response.failed" for event in buffered | ||
| ) | ||
| if store and failed: | ||
| self._failed_sync_response_ids.add(response_id) | ||
| for event in buffered: | ||
| yield event | ||
| if handler_error is not None: | ||
| raise handler_error |
There was a problem hiding this comment.
this concerns me, because we are first exhausting the stream and then checking and yielding, I would prefer if we can yield directly because this will slow down the whole response.
There was a problem hiding this comment.
Thanks—agreed that exhausting the handler stream before yielding is undesirable.
The constraint I ran into is that agentserver persists the input items during the initial response.created / in_progress write, while the terminal failure is only known later. ResponseProviderProtocol.update_response() cannot atomically clear those input references. Yielding directly with the current wrapper would therefore reintroduce the failed-conversation-history bug, and the earlier delete-then-create approach was removed because it was non-atomic.
Would you prefer this behavior to be fixed in azure-ai-agentserver-responses—for example, by atomically excluding or clearing input references for failed responses—or should the provider protocol expose an operation that this host can use?
I will address the docstring and concrete typing comments regardless.
Motivation & Context
When a hosted Foundry agent is used with
conversation_id, a failed turn still stored its input items on the agentserver response/conversation store. The next request on that conversation replayed them viaget_history(). Azure OpenAI does not keep failed input on the conversation.Fixes #7630
Description & Review Guide
ResponsesAgentServerHostresolve and validate its configured/default response store before wrapping it, preserving hosted and local persistence defaults plus the resilient-background guard.python/packages/foundry_hosting/tests/test_responses.pycover the HTTP conversation repro, provider behavior, default persistent storage, and the resilience guard.Please focus on: failed synchronous turns stay out of
get_history(), successful turns still persist, non-conversation runs are unchanged, and the base host's storage behavior is preserved.Related Issue
Fixes #7630
Contribution Checklist
Assistance: drafted with an AI coding agent and reviewed before opening.