Skip to content

Commit 18d1ba3

Browse files
mvanhornclaudemoonbox3
authored
Python: Strip tools from FoundryAgent request when agent_reference is present (#5101)
_prepare_options() now removes tools, tool_choice, and parallel_tool_calls from run_options after injecting agent_reference. The Foundry API rejects requests containing both fields. FunctionTools are still invoked client-side by the function invocation layer. Fixes #5087 Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
1 parent e10d448 commit 18d1ba3

2 files changed

Lines changed: 46 additions & 0 deletions

File tree

python/packages/foundry/agent_framework_foundry/_agent.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,13 @@ async def _prepare_options(
293293
# Inject agent reference
294294
run_options["extra_body"] = {"agent_reference": self._get_agent_reference()}
295295

296+
# Strip tools from request body - Foundry API rejects requests with both
297+
# agent_reference and tools present. FunctionTools are invoked client-side
298+
# by the function invocation layer, not sent to the service.
299+
run_options.pop("tools", None)
300+
run_options.pop("tool_choice", None)
301+
run_options.pop("parallel_tool_calls", None)
302+
296303
return run_options
297304

298305
@override

python/packages/foundry/tests/foundry/test_foundry_agent.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,45 @@ def my_func() -> str:
200200
assert result["extra_body"]["agent_reference"]["name"] == "test-agent"
201201

202202

203+
async def test_raw_foundry_agent_chat_client_prepare_options_strips_tools() -> None:
204+
"""Test that _prepare_options strips tools, tool_choice, and parallel_tool_calls from run_options."""
205+
206+
mock_project = MagicMock()
207+
mock_openai = MagicMock()
208+
mock_project.get_openai_client.return_value = mock_openai
209+
210+
client = RawFoundryAgentChatClient(
211+
project_client=mock_project,
212+
agent_name="test-agent",
213+
)
214+
215+
@tool(approval_mode="never_require")
216+
def my_func() -> str:
217+
"""A test function."""
218+
219+
return "ok"
220+
221+
with patch(
222+
"agent_framework_openai._chat_client.RawOpenAIChatClient._prepare_options",
223+
new_callable=AsyncMock,
224+
return_value={
225+
"tools": [{"type": "function", "function": {"name": "my_func"}}],
226+
"tool_choice": "auto",
227+
"parallel_tool_calls": True,
228+
},
229+
):
230+
result = await client._prepare_options(
231+
messages=[Message(role="user", contents="hi")],
232+
options={"tools": [my_func]},
233+
)
234+
235+
assert "tools" not in result
236+
assert "tool_choice" not in result
237+
assert "parallel_tool_calls" not in result
238+
assert "extra_body" in result
239+
assert result["extra_body"]["agent_reference"]["name"] == "test-agent"
240+
241+
203242
def test_raw_foundry_agent_chat_client_check_model_presence_is_noop() -> None:
204243
"""Test that _check_model_presence does nothing (model is on service)."""
205244

0 commit comments

Comments
 (0)