mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: Fix FoundryAgent stripping model from PromptAgent requests (#5526)
* Fix FoundryAgent stripping model from PromptAgent requests
Move run_options.pop('model', None) inside the _uses_foundry_agent_session()
conditional so that model is only stripped for hosted agent sessions (where
the server manages the model) and preserved for PromptAgent requests that
require it in the Responses API call.
Fixes #5525
* test: add coverage for resp_* continuation preserving model
Adds test_raw_foundry_agent_chat_client_prepare_options_preserves_model_for_resp_continuation
to explicitly verify that HostedAgent v1 / v2-no-session paths (where conversation_id
starts with resp_) preserve model and previous_response_id without triggering the
hosted-session gate.
---------
Co-authored-by: Benke Qu <bequ@microsoft.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
6de4c24fdd
commit
fa8cfb7567
@@ -351,6 +351,7 @@ class RawFoundryAgentChatClient( # type: ignore[misc]
|
||||
if _uses_foundry_agent_session(conversation_id):
|
||||
run_options.pop("previous_response_id", None)
|
||||
run_options.pop("conversation", None)
|
||||
run_options.pop("model", None)
|
||||
extra_body["agent_session_id"] = conversation_id
|
||||
# Non-preview Prompt/Hosted Agent calls need agent_reference in the request body to
|
||||
# tell the Responses API which Foundry agent (and version) is in use, since ``model``
|
||||
@@ -366,7 +367,6 @@ class RawFoundryAgentChatClient( # type: ignore[misc]
|
||||
# Strip tools from request body - Foundry API rejects requests with both
|
||||
# agent endpoint and tools present. FunctionTools are invoked client-side
|
||||
# by the function invocation layer, not sent to the service.
|
||||
run_options.pop("model", None)
|
||||
if not self.allow_preview:
|
||||
run_options.pop("tools", None)
|
||||
run_options.pop("tool_choice", None)
|
||||
|
||||
@@ -203,7 +203,7 @@ async def test_raw_foundry_agent_chat_client_prepare_options_accepts_function_to
|
||||
|
||||
|
||||
async def test_raw_foundry_agent_chat_client_prepare_options_strips_client_side_fields() -> None:
|
||||
"""Test that _prepare_options strips model and tool-loop fields from run_options."""
|
||||
"""Test that _prepare_options strips tool-loop fields but preserves model for non-session requests."""
|
||||
|
||||
mock_project = MagicMock()
|
||||
mock_openai = MagicMock()
|
||||
@@ -235,16 +235,49 @@ async def test_raw_foundry_agent_chat_client_prepare_options_strips_client_side_
|
||||
options={"tools": [my_func]},
|
||||
)
|
||||
|
||||
assert "model" not in result
|
||||
# model is preserved for non-session (PromptAgent) requests
|
||||
assert result["model"] == "gpt-4.1"
|
||||
assert "tools" not in result
|
||||
assert "tool_choice" not in result
|
||||
assert "parallel_tool_calls" not in result
|
||||
# agent_reference is required so the Responses API can resolve model server-side; see #5582.
|
||||
assert result == {
|
||||
"model": "gpt-4.1",
|
||||
"extra_body": {"agent_reference": {"name": "test-agent", "type": "agent_reference"}},
|
||||
}
|
||||
|
||||
|
||||
async def test_raw_foundry_agent_chat_client_prepare_options_strips_model_for_hosted_session() -> None:
|
||||
"""Test that model is stripped when using a hosted agent session (not a PromptAgent)."""
|
||||
|
||||
mock_project = MagicMock()
|
||||
mock_openai = MagicMock()
|
||||
mock_project.get_openai_client.return_value = mock_openai
|
||||
|
||||
client = RawFoundryAgentChatClient(
|
||||
project_client=mock_project,
|
||||
agent_name="test-agent",
|
||||
)
|
||||
|
||||
with patch(
|
||||
"agent_framework_openai._chat_client.RawOpenAIChatClient._prepare_options",
|
||||
new_callable=AsyncMock,
|
||||
return_value={
|
||||
"model": "gpt-4.1",
|
||||
"previous_response_id": "resp_abc",
|
||||
},
|
||||
):
|
||||
result = await client._prepare_options(
|
||||
messages=[Message(role="user", contents="hi")],
|
||||
options={"conversation_id": "agent-session-123"},
|
||||
)
|
||||
|
||||
assert "model" not in result
|
||||
assert "previous_response_id" not in result
|
||||
assert result["extra_body"]["agent_session_id"] == "agent-session-123"
|
||||
assert result["extra_body"]["agent_reference"] == {"name": "test-agent", "type": "agent_reference"}
|
||||
|
||||
|
||||
async def test_raw_foundry_agent_chat_client_prepare_options_injects_agent_reference_first_turn() -> None:
|
||||
"""First-turn (no conversation_id) Prompt Agent calls must carry agent_reference in extra_body.
|
||||
|
||||
@@ -272,7 +305,6 @@ async def test_raw_foundry_agent_chat_client_prepare_options_injects_agent_refer
|
||||
options={},
|
||||
)
|
||||
|
||||
assert "model" not in result
|
||||
assert result["extra_body"] == {
|
||||
"agent_reference": {"name": "test-agent", "type": "agent_reference", "version": "2"},
|
||||
}
|
||||
@@ -333,7 +365,8 @@ async def test_raw_foundry_agent_chat_client_prepare_options_skips_agent_referen
|
||||
options={},
|
||||
)
|
||||
|
||||
assert "model" not in result
|
||||
# model is preserved for non-session requests (platform tolerates it for hosted agents)
|
||||
assert result["model"] == "gpt-4.1"
|
||||
# No extra_body at all is the cleanest signal — agent_reference must not be injected here.
|
||||
assert "extra_body" not in result
|
||||
|
||||
@@ -363,6 +396,39 @@ async def test_raw_foundry_agent_chat_client_prepare_options_respects_caller_age
|
||||
assert result["extra_body"]["agent_reference"] == caller_reference
|
||||
|
||||
|
||||
async def test_raw_foundry_agent_chat_client_prepare_options_preserves_model_for_resp_continuation() -> None:
|
||||
"""Test that model is preserved when conversation_id is a resp_* continuation (HostedAgent v1 / v2-no-session)."""
|
||||
|
||||
mock_project = MagicMock()
|
||||
mock_openai = MagicMock()
|
||||
mock_project.get_openai_client.return_value = mock_openai
|
||||
|
||||
client = RawFoundryAgentChatClient(
|
||||
project_client=mock_project,
|
||||
agent_name="test-agent",
|
||||
)
|
||||
|
||||
with patch(
|
||||
"agent_framework_openai._chat_client.RawOpenAIChatClient._prepare_options",
|
||||
new_callable=AsyncMock,
|
||||
return_value={
|
||||
"model": "gpt-4.1",
|
||||
"previous_response_id": "resp_abc123",
|
||||
},
|
||||
):
|
||||
result = await client._prepare_options(
|
||||
messages=[Message(role="user", contents="hi")],
|
||||
options={"conversation_id": "resp_abc123"},
|
||||
)
|
||||
|
||||
# model preserved — resp_* is standard Responses API continuity, not a hosted session
|
||||
assert result["model"] == "gpt-4.1"
|
||||
# previous_response_id preserved — not stripped outside hosted session path
|
||||
assert result["previous_response_id"] == "resp_abc123"
|
||||
# no agent_session_id injected
|
||||
assert "extra_body" not in result or "agent_session_id" not in result.get("extra_body", {})
|
||||
|
||||
|
||||
async def test_raw_foundry_agent_chat_client_prepare_options_maps_agent_session_id_to_extra_body() -> None:
|
||||
"""Test that service_session_id is forwarded as agent_session_id for hosted sessions."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user