From 57e2a819adba96a99576d3116f3d46734970906a Mon Sep 17 00:00:00 2001 From: LEDazzio01 Date: Sat, 28 Feb 2026 20:44:02 -0500 Subject: [PATCH 1/2] fix: strip server-assigned IDs from reasoning/function_call items when store=False When store is disabled, server-assigned item IDs (rs_*, fc_*) reference non-existent server-persisted objects. During handoff workflows, these IDs are replayed in the input, causing 'Item not found' API errors. This fix adds a post-processing step in _prepare_options() that strips the 'id' field from reasoning and function_call input items when store=False, so they are replayed by value rather than by reference. Fixes #4357 --- .../openai/_responses_client.py | 11 ++ .../openai/test_openai_responses_client.py | 121 ++++++++++++++++++ 2 files changed, 132 insertions(+) diff --git a/python/packages/core/agent_framework/openai/_responses_client.py b/python/packages/core/agent_framework/openai/_responses_client.py index fa140ee0b7..b18d0615b6 100644 --- a/python/packages/core/agent_framework/openai/_responses_client.py +++ b/python/packages/core/agent_framework/openai/_responses_client.py @@ -847,6 +847,17 @@ async def _prepare_options( if response_format: run_options["text_format"] = response_format + # When store=False, strip server-assigned IDs from reasoning and function_call + # items. These IDs (rs_*, fc_*) reference server-persisted objects that don't exist + # when store is disabled, causing "Item not found" API errors during handoff workflows. + if run_options.get("store") is False: + for item in run_options.get("input", []): + if isinstance(item, dict): + if item.get("type") == "reasoning" and "id" in item: + del item["id"] + elif item.get("type") == "function_call" and "id" in item: + del item["id"] + return run_options def _check_model_presence(self, options: dict[str, Any]) -> None: diff --git a/python/packages/core/tests/openai/test_openai_responses_client.py b/python/packages/core/tests/openai/test_openai_responses_client.py index 6c98f3bdfa..48ad3d5b8d 100644 --- a/python/packages/core/tests/openai/test_openai_responses_client.py +++ b/python/packages/core/tests/openai/test_openai_responses_client.py @@ -2881,4 +2881,125 @@ async def test_prepare_options_excludes_continuation_token() -> None: assert run_options["background"] is True +# region Store=False ID Stripping Tests (Issue #4357) + + +async def test_prepare_options_strips_reasoning_and_function_call_ids_when_store_false() -> None: + """Test that _prepare_options strips server-assigned IDs from reasoning and function_call + items when store=False. + + When store is disabled, server-assigned IDs (rs_*, fc_*) reference non-existent + server-persisted objects, causing 'Item not found' API errors during handoff workflows. + See: https://github.com/microsoft/agent-framework/issues/4357 + """ + client = OpenAIResponsesClient(model_id="test-model", api_key="test-key") + + # Simulate a handoff conversation with reasoning + function_call from a previous turn + messages = [ + Message(role="user", contents=[Content.from_text(text="search for hotels")]), + Message( + role="assistant", + contents=[ + Content.from_text_reasoning( + id="rs_abc123", + text="I need to search for hotels", + additional_properties={"status": "completed"}, + ), + Content.from_function_call( + call_id="call_1", + name="search_hotels", + arguments='{"city": "Paris"}', + additional_properties={"fc_id": "fc_def456"}, + ), + ], + ), + Message( + role="tool", + contents=[ + Content.from_function_result( + call_id="call_1", + result="Found 3 hotels in Paris", + ), + ], + ), + Message(role="assistant", contents=[Content.from_text(text="I found hotels for you")]), + Message(role="user", contents=[Content.from_text(text="Book the first one")]), + ] + + chat_options = ChatOptions(store=False) + run_options = await client._prepare_options(messages, chat_options) # type: ignore + + assert run_options["store"] is False + + # Find reasoning and function_call items in the prepared input + reasoning_items = [item for item in run_options["input"] if isinstance(item, dict) and item.get("type") == "reasoning"] + fc_items = [item for item in run_options["input"] if isinstance(item, dict) and item.get("type") == "function_call"] + + # Verify that IDs have been stripped + for item in reasoning_items: + assert "id" not in item, f"Reasoning item should not have 'id' when store=False, got: {item}" + + for item in fc_items: + assert "id" not in item, f"Function call item should not have 'id' when store=False, got: {item}" + + +async def test_prepare_options_preserves_reasoning_and_function_call_ids_when_store_true() -> None: + """Test that _prepare_options preserves server-assigned IDs from reasoning and function_call + items when store=True. + + When store is enabled, server-assigned IDs are needed for the API to reference + previously persisted items. + """ + client = OpenAIResponsesClient(model_id="test-model", api_key="test-key") + + messages = [ + Message(role="user", contents=[Content.from_text(text="search for hotels")]), + Message( + role="assistant", + contents=[ + Content.from_text_reasoning( + id="rs_abc123", + text="I need to search for hotels", + additional_properties={"status": "completed"}, + ), + Content.from_function_call( + call_id="call_1", + name="search_hotels", + arguments='{"city": "Paris"}', + additional_properties={"fc_id": "fc_def456"}, + ), + ], + ), + Message( + role="tool", + contents=[ + Content.from_function_result( + call_id="call_1", + result="Found 3 hotels in Paris", + ), + ], + ), + Message(role="assistant", contents=[Content.from_text(text="I found hotels for you")]), + Message(role="user", contents=[Content.from_text(text="Book the first one")]), + ] + + chat_options = ChatOptions(store=True) + run_options = await client._prepare_options(messages, chat_options) # type: ignore + + assert run_options["store"] is True + + # Find reasoning and function_call items in the prepared input + reasoning_items = [item for item in run_options["input"] if isinstance(item, dict) and item.get("type") == "reasoning"] + fc_items = [item for item in run_options["input"] if isinstance(item, dict) and item.get("type") == "function_call"] + + # Verify that IDs are preserved when store=True + for item in reasoning_items: + assert "id" in item, f"Reasoning item should have 'id' when store=True, got: {item}" + assert item["id"] == "rs_abc123" + + for item in fc_items: + assert "id" in item, f"Function call item should have 'id' when store=True, got: {item}" + assert item["id"] == "fc_def456" + + # endregion From eef02f15d691c5fc7e99ff123e3945833c330820 Mon Sep 17 00:00:00 2001 From: LEDazzio01 <170764058+LEDazzio01@users.noreply.github.com> Date: Mon, 2 Mar 2026 18:15:21 -0500 Subject: [PATCH 2/2] fix: remove handoff mentions from comments per reviewer nit --- .../packages/core/agent_framework/openai/_responses_client.py | 2 +- .../core/tests/openai/test_openai_responses_client.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python/packages/core/agent_framework/openai/_responses_client.py b/python/packages/core/agent_framework/openai/_responses_client.py index b18d0615b6..b1756208ad 100644 --- a/python/packages/core/agent_framework/openai/_responses_client.py +++ b/python/packages/core/agent_framework/openai/_responses_client.py @@ -849,7 +849,7 @@ async def _prepare_options( # When store=False, strip server-assigned IDs from reasoning and function_call # items. These IDs (rs_*, fc_*) reference server-persisted objects that don't exist - # when store is disabled, causing "Item not found" API errors during handoff workflows. + # when store is disabled, causing "Item not found" API errors. if run_options.get("store") is False: for item in run_options.get("input", []): if isinstance(item, dict): diff --git a/python/packages/core/tests/openai/test_openai_responses_client.py b/python/packages/core/tests/openai/test_openai_responses_client.py index 48ad3d5b8d..8f72602e81 100644 --- a/python/packages/core/tests/openai/test_openai_responses_client.py +++ b/python/packages/core/tests/openai/test_openai_responses_client.py @@ -2889,12 +2889,12 @@ async def test_prepare_options_strips_reasoning_and_function_call_ids_when_store items when store=False. When store is disabled, server-assigned IDs (rs_*, fc_*) reference non-existent - server-persisted objects, causing 'Item not found' API errors during handoff workflows. + server-persisted objects, causing 'Item not found' API errors. See: https://github.com/microsoft/agent-framework/issues/4357 """ client = OpenAIResponsesClient(model_id="test-model", api_key="test-key") - # Simulate a handoff conversation with reasoning + function_call from a previous turn + # Simulate a multi-turn conversation with reasoning + function_call from a previous turn messages = [ Message(role="user", contents=[Content.from_text(text="search for hotels")]), Message(