mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
e56e6dad4d
* 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>
117 lines
4.1 KiB
Python
117 lines
4.1 KiB
Python
# Copyright (c) Microsoft. All rights reserved.
|
|
|
|
import asyncio
|
|
import os
|
|
from collections.abc import Callable
|
|
from typing import Any
|
|
|
|
from agent_framework import Agent, MCPStreamableHTTPTool, tool
|
|
from agent_framework.foundry import FoundryChatClient
|
|
from agent_framework_foundry_hosting import ResponsesHostServer
|
|
from azure.core.credentials import TokenCredential
|
|
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
|
|
from dotenv import load_dotenv
|
|
|
|
# Load environment variables from .env file
|
|
load_dotenv()
|
|
|
|
|
|
def _resolve_toolbox_endpoint() -> str:
|
|
"""Resolve the toolbox MCP endpoint URL.
|
|
|
|
Prefers the explicit ``FOUNDRY_TOOLBOX_ENDPOINT`` env var; falls back to
|
|
constructing the URL from ``FOUNDRY_PROJECT_ENDPOINT`` and ``TOOLBOX_NAME``
|
|
(the variables injected by the Foundry hosting scaffolding after ``azd provision``).
|
|
"""
|
|
if (endpoint := os.environ.get("FOUNDRY_TOOLBOX_ENDPOINT")) is not None:
|
|
if not endpoint:
|
|
raise ValueError("FOUNDRY_TOOLBOX_ENDPOINT is set but empty")
|
|
return endpoint
|
|
project_endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"].rstrip("/")
|
|
toolbox_name = os.environ["TOOLBOX_NAME"]
|
|
return f"{project_endpoint}/toolsets/{toolbox_name}/mcp?api-version=v1"
|
|
|
|
|
|
def make_toolbox_header_provider(credential: TokenCredential) -> Callable[[dict[str, Any]], dict[str, str]]:
|
|
"""Build a header_provider that injects a fresh Azure AI bearer token on every MCP request."""
|
|
get_token = get_bearer_token_provider(credential, "https://ai.azure.com/.default")
|
|
|
|
def provide(_kwargs: dict[str, Any]) -> dict[str, str]:
|
|
return {
|
|
"Authorization": f"Bearer {get_token()}",
|
|
}
|
|
|
|
return provide
|
|
|
|
|
|
@tool(description="Get the current working directory.", approval_mode="never_require")
|
|
def get_cwd() -> str:
|
|
"""Get the current working directory."""
|
|
try:
|
|
return os.getcwd()
|
|
except Exception as e:
|
|
return f"Error getting current working directory: {e}"
|
|
|
|
|
|
@tool(description="List files in a directory.", approval_mode="never_require")
|
|
def list_files(directory: str) -> list[str]:
|
|
"""List files in a directory."""
|
|
try:
|
|
return os.listdir(directory)
|
|
except Exception as e:
|
|
return [f"Error listing files in {directory}: {e}"]
|
|
|
|
|
|
@tool(description="Read the contents of a file.", approval_mode="never_require")
|
|
def read_file(file_path: str) -> str:
|
|
"""Read the contents of a file."""
|
|
try:
|
|
with open(file_path) as f:
|
|
return f.read()
|
|
except Exception as e:
|
|
return f"Error reading file {file_path}: {e}"
|
|
|
|
|
|
async def main():
|
|
credential = DefaultAzureCredential()
|
|
|
|
client = FoundryChatClient(
|
|
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
|
|
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
|
credential=credential,
|
|
)
|
|
|
|
# Connect to the toolbox MCP endpoint and expose only the code_interpreter tool.
|
|
# The toolbox deployed has two tools: (see agent.manifest.yaml)
|
|
# - `code_interpreter`
|
|
# - `web_search`
|
|
# We only need the `code_interpreter` tool for this sample.
|
|
toolbox_tool = MCPStreamableHTTPTool(
|
|
name="foundry_toolbox",
|
|
description="Tools exposed by the configured Foundry toolbox",
|
|
url=_resolve_toolbox_endpoint(),
|
|
header_provider=make_toolbox_header_provider(credential),
|
|
load_prompts=False,
|
|
allowed_tools=["code_interpreter"],
|
|
)
|
|
|
|
async with Agent(
|
|
client=client,
|
|
instructions=(
|
|
"You are a friendly assistant. Keep your answers brief. "
|
|
"Make sure all mathematical calculations are performed using the code interpreter "
|
|
"instead of mental arithmetic."
|
|
),
|
|
tools=[get_cwd, list_files, read_file, toolbox_tool],
|
|
# History will be managed by the hosting infrastructure, thus there
|
|
# is no need to store history by the service. Learn more at:
|
|
# https://developers.openai.com/api/reference/resources/responses/methods/create
|
|
default_options={"store": False},
|
|
) as agent:
|
|
server = ResponsesHostServer(agent)
|
|
await server.run_async()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|