Python: Preserve approval decisions under OpenAI continuation - #7407
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes OpenAI Responses API continuation under service-side storage by ensuring the user’s tool-approval decision is not dropped during serialization, so approval-paused runs can reliably resume with previous_response_id / conversation_id.
Changes:
- Update the OpenAI chat client serializer to skip only stored
function_approval_requestitems under service-side storage, while still serializingfunction_approval_responseasmcp_approval_response. - Expand unit test coverage to validate both approved and rejected decisions under storage-on vs storage-off behavior.
- Document the request/response storage boundary and update the function-calling loop spec to reflect the covered scenario.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| python/packages/openai/agent_framework_openai/_chat_client.py | Stops stripping function_approval_response under service-side storage, preserving approval decisions for continuation. |
| python/packages/openai/tests/openai/test_openai_chat_client.py | Adds parameterized test coverage ensuring approval responses are kept (approved/rejected) while requests are omitted under storage. |
| python/packages/openai/AGENTS.md | Documents how approval request vs response behave under service-side continuation and warns about manual history replay. |
| docs/specs/004-python-function-calling-loop.md | Updates required invariants/coverage lists to include service-side approval decision behavior and its regression test. |
Python Test Coverage Report •
Python Unit Test Overview
|
||||||||||||||||||||||||||||||
acdde65 to
b22beee
Compare
b22beee to
3415dc7
Compare
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1ee1d250-d1e2-4c6f-8c36-aae0d94fe7a1
3415dc7 to
f96346b
Compare
|
Thanks for this — the continuation case it fixes is real. Reporting a regression it introduces for approvals on local tools, found while bumping an app from What changedThe PR splits one arm into two and drops the storage guard from the response side: - case "function_approval_response" | "function_approval_request":
+ case "function_approval_request":
if request_uses_service_side_storage:
continue
...
+ case "function_approval_response":
+ prepared = self._prepare_content_for_openai(...) # no guardSo under case "function_approval_response":
return {"type": "mcp_approval_response", "approval_request_id": content.id, "approve": content.approved}Why that breaks for a local toolThe change is sound for a hosted/MCP approval: the service issued and stored the It assumes every Before this PR the shared guard kept the pair consistent — both omitted under storage, both sent without it — so this could not happen. The old test name says as much: There's a hint the local case wasn't in scope: the sibling ReproAny Responses-API run with service-side storage where a local tool is approval-gated:
Observed on Azure Foundry ( 'input': [
{'type': 'message', 'role': 'user', 'content': [...]},
{'call_id': 'call_OWTUay…', 'type': 'function_call_output', 'output': "File 'notes393.md' not found."},
{'type': 'mcp_approval_response', 'approval_request_id': 'call_OWTUay…', 'approve': True},
...
Suggested fixGate the response arm on the same hosted test core already uses case "function_approval_response":
if not _is_hosted_tool_approval(content):
continue # local approvals are resolved in-process; the service has no request
prepared = self._prepare_content_for_openai(...)…and consider the same guard on the request arm rather than emitting On the AGENTS.md note
Worth revisiting: an application that replays nothing by hand still hits this, because the framework's own |
Motivation & Context
OpenAI service-side continuation already stores the approval request, but the current serializer drops the new
approval response as well. The service therefore never receives the user's approved or rejected decision.
Description & Review Guide
function_approval_request.function_approval_responseasmcp_approval_response.previous_response_idor conversation continuation.Related Issue
Fixes #7125
Shared spec reconciliation is in #7409, which should merge after #7407 and #7408.
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.