mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: Remove bespoke Foundry toolbox helpers; standardize on MCP for toolbox consumption (#5671)
* Remove Foundry toolbox helpers; standardize on MCP for toolbox consumption - Remove RawFoundryChatClient.get_toolbox() and its fetch_toolbox import - Remove fetch_toolbox, select_toolbox_tools, get_toolbox_tool_name, get_toolbox_tool_type, FoundryHostedToolType, ToolboxToolSelectionInput from agent_framework_foundry._tools - Remove ExperimentalFeature.TOOLBOXES from _feature_stage.py (no consumers) - Drop toolbox re-exports from agent_framework_foundry/__init__.py and agent_framework.foundry namespace - Update _sanitize_foundry_response_tool docstring to remove toolbox framing; sanitization logic itself is unchanged - Update _agent.py docstring: 'toolbox-fetched MCP' → 'hosted MCP' - Delete tests/test_toolbox.py (all tests covered removed helpers) - Update test_foundry_chat_client.py: rename/redoc tests that mentioned toolbox but test sanitization that remains - Delete foundry_chat_client_with_toolbox.py (bespoke toolbox API sample) - Delete foundry_toolbox_context_provider.py (relied on select_toolbox_tools) - Rename foundry_chat_client_with_toolbox_mcp.py → foundry_chat_client_with_toolbox.py (canonical MCP pattern) - Rewrite 04_foundry_toolbox/main.py to use MCPStreamableHTTPTool - Update provider/README, context_providers/README, 04_foundry_toolbox/README Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(samples): update 06_files sample to consume toolbox via MCP (#5670) Replace removed get_toolbox/select_toolbox_tools APIs with MCPStreamableHTTPTool, using allowed_tools=["code_interpreter"] to select only the code interpreter from the toolbox endpoint. Update .env.example and README to use FOUNDRY_TOOLBOX_ENDPOINT instead of TOOLBOX_NAME. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(foundry): remove non-existent toolbox helper APIs from README (#5670) Remove the 'fetch, optionally filter, and pass tools directly' pattern from the FoundryChatClient toolbox documentation, as select_toolbox_tools and get_toolbox were removed. Only the MCP endpoint pattern is documented. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(foundry): remove residual toolbox docstring references and reproduction report Remove REPRODUCTION_REPORT.md (workflow artifact that should not be committed), and update two remaining docstring references that still said 'toolbox reads' /'toolbox definition' after the toolbox helpers were removed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Remove bespoke Foundry toolbox helpers; standardize on MCP for toolbox consumption Fixes #5670 * fix(#5670): resolve toolbox endpoint from TOOLBOX_NAME fallback; add namespace regression tests - Add _resolve_toolbox_endpoint() helper in 04_foundry_toolbox/main.py and 06_files/main.py that prefers FOUNDRY_TOOLBOX_ENDPOINT but falls back to deriving the MCP URL from FOUNDRY_PROJECT_ENDPOINT + TOOLBOX_NAME — fixing the startup KeyError when agents are deployed via azd provision (which injects TOOLBOX_NAME, not FOUNDRY_TOOLBOX_ENDPOINT). - Update 04_foundry_toolbox/.env.example to use FOUNDRY_TOOLBOX_ENDPOINT (consistent with 06_files). - Add TOOLBOX_NAME env var to 06_files/agent.yaml so deployed agents have it available for the fallback derivation. - Update both READMEs to document the two ways to supply the toolbox endpoint. - Add test_foundry_namespace_no_longer_exposes_toolbox_helpers() with negative assertions for FoundryHostedToolType, get_toolbox_tool_name, get_toolbox_tool_type, and select_toolbox_tools — guarding against accidental re-introduction of removed symbols. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(samples): fail fast on empty FOUNDRY_TOOLBOX_ENDPOINT; add unit tests Addresses review feedback for #5670: - In _resolve_toolbox_endpoint() (04_foundry_toolbox/main.py and 06_files/main.py) change the walrus-operator check from a truthy test to an explicit 'is not None' guard. An explicitly set empty string now raises ValueError immediately with a clear message instead of silently falling through to the fallback URL construction. - Add tests/samples/hosting/test_toolbox_endpoint.py covering both sample modules: (a) FOUNDRY_TOOLBOX_ENDPOINT set → returned as-is (b) FOUNDRY_TOOLBOX_ENDPOINT set to empty string → ValueError (c) fallback constructs URL from FOUNDRY_PROJECT_ENDPOINT + TOOLBOX_NAME, stripping trailing slashes (d) neither variable group set → KeyError Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review feedback: remove extraneous test and docstring content - Remove test_foundry_namespace_no_longer_exposes_toolbox_helpers (no longer warranted) - Remove docstring from _agent.py _prepare_tools_for_openai (extraneous) - Trim _chat_client.py _prepare_tools_for_openai docstring to one-liner (toolbox references no longer relevant) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: remove remaining extraneous docstring from RawFoundryChatClient._prepare_tools_for_openai Address review comment on PR #5671: reviewer noted the description isn't warranted now that toolbox helpers have been removed. Matches the pattern in RawFoundryAgentChatClient which has no docstring. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
51ad460d5f
commit
e56e6dad4d
@@ -0,0 +1,102 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Unit tests for _resolve_toolbox_endpoint() in the foundry-hosted-agents response samples.
|
||||
|
||||
Covers both 04_foundry_toolbox/main.py and 06_files/main.py which share the same
|
||||
implementation of _resolve_toolbox_endpoint().
|
||||
"""
|
||||
|
||||
import importlib
|
||||
import importlib.util
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Stub out packages unavailable in the unit-test environment so that importing
|
||||
# the sample modules does not fail.
|
||||
# ---------------------------------------------------------------------------
|
||||
_MISSING_MODULES = (
|
||||
"agent_framework_foundry_hosting",
|
||||
"azure.ai.agentserver",
|
||||
"azure.ai.agentserver.responses",
|
||||
)
|
||||
for _mod_name in _MISSING_MODULES:
|
||||
sys.modules.setdefault(_mod_name, MagicMock())
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Load the two sample modules by file path to avoid needing them on sys.path.
|
||||
# ---------------------------------------------------------------------------
|
||||
_RESPONSES_DIR = (
|
||||
Path(__file__).parent.parent.parent.parent
|
||||
/ "samples"
|
||||
/ "04-hosting"
|
||||
/ "foundry-hosted-agents"
|
||||
/ "responses"
|
||||
)
|
||||
|
||||
|
||||
def _load_sample(subdir: str, module_alias: str):
|
||||
spec = importlib.util.spec_from_file_location(module_alias, _RESPONSES_DIR / subdir / "main.py")
|
||||
mod = importlib.util.module_from_spec(spec) # type: ignore[arg-type]
|
||||
spec.loader.exec_module(mod) # type: ignore[union-attr]
|
||||
return mod
|
||||
|
||||
|
||||
_toolbox_mod = _load_sample("04_foundry_toolbox", "foundry_toolbox_main")
|
||||
_files_mod = _load_sample("06_files", "files_main")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Parameterise over both modules so the same test cases run for each.
|
||||
# ---------------------------------------------------------------------------
|
||||
@pytest.fixture(params=["04_foundry_toolbox", "06_files"])
|
||||
def resolve_endpoint(request):
|
||||
"""Return _resolve_toolbox_endpoint from the requested sample module."""
|
||||
mod = _toolbox_mod if request.param == "04_foundry_toolbox" else _files_mod
|
||||
return mod._resolve_toolbox_endpoint
|
||||
|
||||
|
||||
class TestResolveToolboxEndpoint:
|
||||
def test_explicit_endpoint_returned_as_is(self, resolve_endpoint, monkeypatch: pytest.MonkeyPatch):
|
||||
monkeypatch.setenv("FOUNDRY_TOOLBOX_ENDPOINT", "https://example.com/mcp")
|
||||
monkeypatch.delenv("FOUNDRY_PROJECT_ENDPOINT", raising=False)
|
||||
monkeypatch.delenv("TOOLBOX_NAME", raising=False)
|
||||
|
||||
assert resolve_endpoint() == "https://example.com/mcp"
|
||||
|
||||
def test_empty_string_raises_value_error(self, resolve_endpoint, monkeypatch: pytest.MonkeyPatch):
|
||||
monkeypatch.setenv("FOUNDRY_TOOLBOX_ENDPOINT", "")
|
||||
|
||||
with pytest.raises(ValueError, match="FOUNDRY_TOOLBOX_ENDPOINT is set but empty"):
|
||||
resolve_endpoint()
|
||||
|
||||
def test_fallback_constructs_url_from_project_vars(self, resolve_endpoint, monkeypatch: pytest.MonkeyPatch):
|
||||
monkeypatch.delenv("FOUNDRY_TOOLBOX_ENDPOINT", raising=False)
|
||||
monkeypatch.setenv("FOUNDRY_PROJECT_ENDPOINT", "https://project.azure.com/")
|
||||
monkeypatch.setenv("TOOLBOX_NAME", "my-toolbox")
|
||||
|
||||
result = resolve_endpoint()
|
||||
|
||||
assert result == "https://project.azure.com/toolsets/my-toolbox/mcp?api-version=v1"
|
||||
|
||||
def test_fallback_strips_trailing_slash_from_project_endpoint(
|
||||
self, resolve_endpoint, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
monkeypatch.delenv("FOUNDRY_TOOLBOX_ENDPOINT", raising=False)
|
||||
monkeypatch.setenv("FOUNDRY_PROJECT_ENDPOINT", "https://project.azure.com///")
|
||||
monkeypatch.setenv("TOOLBOX_NAME", "my-toolbox")
|
||||
|
||||
result = resolve_endpoint()
|
||||
|
||||
assert result == "https://project.azure.com/toolsets/my-toolbox/mcp?api-version=v1"
|
||||
|
||||
def test_neither_variable_group_set_raises_key_error(self, resolve_endpoint, monkeypatch: pytest.MonkeyPatch):
|
||||
monkeypatch.delenv("FOUNDRY_TOOLBOX_ENDPOINT", raising=False)
|
||||
monkeypatch.delenv("FOUNDRY_PROJECT_ENDPOINT", raising=False)
|
||||
monkeypatch.delenv("TOOLBOX_NAME", raising=False)
|
||||
|
||||
with pytest.raises(KeyError):
|
||||
resolve_endpoint()
|
||||
Reference in New Issue
Block a user