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
@@ -7,7 +7,6 @@ These samples demonstrate how to use context providers to enrich agent conversat
|
||||
| File / Folder | Description |
|
||||
|---------------|-------------|
|
||||
| [`simple_context_provider.py`](simple_context_provider.py) | Implement a custom context provider by extending `ContextProvider` to extract and inject structured user information across turns. |
|
||||
| [`foundry_toolbox_context_provider.py`](foundry_toolbox_context_provider.py) | Compose a Microsoft Foundry toolbox with a `ContextProvider` that caches the toolbox once and picks a subset of its tools per-turn via `select_toolbox_tools`, driven by keywords in the latest user message. |
|
||||
| [`azure_ai_foundry_memory.py`](azure_ai_foundry_memory.py) | Use `FoundryMemoryProvider` to add semantic memory — automatically retrieves, searches, and stores memories via Azure AI Foundry. |
|
||||
| [`azure_ai_search/`](azure_ai_search/) | Retrieval Augmented Generation (RAG) with Azure AI Search in semantic and agentic modes. See its own [README](azure_ai_search/README.md). |
|
||||
| [`mem0/`](mem0/) | Memory-powered context using the Mem0 integration (open-source and managed). See its own [README](mem0/README.md). |
|
||||
@@ -20,12 +19,6 @@ These samples demonstrate how to use context providers to enrich agent conversat
|
||||
- `FOUNDRY_MODEL`: Model deployment name
|
||||
- Azure CLI authentication (`az login`)
|
||||
|
||||
**For `foundry_toolbox_context_provider.py`:**
|
||||
- `FOUNDRY_PROJECT_ENDPOINT`: Your Microsoft Foundry project endpoint
|
||||
- `FOUNDRY_MODEL`: Model deployment name
|
||||
- A toolbox already configured in that project; set `TOOLBOX_NAME` / `TOOLBOX_VERSION` at the top of the sample
|
||||
- Azure CLI authentication (`az login`)
|
||||
|
||||
**For `azure_ai_foundry_memory.py`:**
|
||||
- `FOUNDRY_PROJECT_ENDPOINT`: Your Azure AI Foundry project endpoint
|
||||
- `FOUNDRY_MODEL`: Chat/responses model deployment name
|
||||
|
||||
@@ -1,207 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import Agent, AgentSession, ContextProvider, Message, SessionContext
|
||||
from agent_framework.foundry import (
|
||||
FoundryChatClient,
|
||||
get_toolbox_tool_name,
|
||||
get_toolbox_tool_type,
|
||||
select_toolbox_tools,
|
||||
)
|
||||
from azure.identity import AzureCliCredential
|
||||
from dotenv import load_dotenv
|
||||
from pydantic import BaseModel
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
"""
|
||||
Foundry Toolbox + Context Provider Example
|
||||
|
||||
This sample composes a Foundry toolbox with a ContextProvider so the agent's
|
||||
tool list is chosen dynamically per-turn. It uses the chat client itself as a lightweight "tool router": the
|
||||
latest user message plus a short menu of toolbox tools is sent to the model
|
||||
with a Pydantic ``response_format``, and the returned tool names drive
|
||||
``select_toolbox_tools``. The toolbox is fetched once and cached on the
|
||||
provider's state dict; subsequent turns reuse the cache.
|
||||
|
||||
Prerequisites:
|
||||
- A Microsoft Foundry project
|
||||
- A toolbox already configured in that project (set TOOLBOX_NAME below)
|
||||
- FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL environment variables set
|
||||
- Azure CLI authentication (`az login`)
|
||||
"""
|
||||
|
||||
# Replace with your own Foundry toolbox name and version.
|
||||
TOOLBOX_NAME = "research_toolbox"
|
||||
# Set to None to resolve the toolbox's current default version at fetch time.
|
||||
TOOLBOX_VERSION: str | None = None
|
||||
|
||||
# Generic queries that exercise the router without assuming any specific tool
|
||||
# types are configured. The first is introspective, the second forces a
|
||||
# non-empty pick for whichever tools the toolbox actually contains, and the
|
||||
# third should route to nothing.
|
||||
QUERIES: list[str] = [
|
||||
"Introduce yourself and briefly describe the tools you can use to help me.",
|
||||
"Pick the tool you think is most useful and demonstrate it with a short example.",
|
||||
"Say hi in one short sentence - no tools needed.",
|
||||
]
|
||||
|
||||
|
||||
def create_sample_toolbox(name: str) -> str:
|
||||
"""Create (or replace) a toolbox version in the Foundry project.
|
||||
|
||||
Toolboxes are normally configured in the Foundry portal or a deployment
|
||||
script, not the application itself. This helper exists so the sample can
|
||||
be run end-to-end without first setting a toolbox up by hand — delete any
|
||||
existing toolbox under ``name``, then create a fresh version containing a
|
||||
single MCP tool. Returns the created version identifier.
|
||||
"""
|
||||
from azure.ai.projects import AIProjectClient
|
||||
from azure.ai.projects.models import MCPTool, Tool
|
||||
from azure.core.exceptions import ResourceNotFoundError
|
||||
|
||||
with (
|
||||
AzureCliCredential() as credential,
|
||||
AIProjectClient(credential=credential, endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"]) as project_client,
|
||||
):
|
||||
try:
|
||||
project_client.beta.toolboxes.delete(name)
|
||||
print(f"Toolbox `{name}` deleted")
|
||||
except ResourceNotFoundError:
|
||||
pass
|
||||
|
||||
tools: list[Tool] = [
|
||||
MCPTool(
|
||||
server_label="api_specs",
|
||||
server_url="https://gitmcp.io/Azure/azure-rest-api-specs",
|
||||
require_approval="never",
|
||||
)
|
||||
]
|
||||
|
||||
created = project_client.beta.toolboxes.create_version(
|
||||
name=name,
|
||||
description="Toolbox version with MCP require_approval set to 'never'.",
|
||||
tools=tools,
|
||||
)
|
||||
print(f"Created toolbox {created.name}@{created.version} ({len(created.tools)} tool(s))")
|
||||
return created.version
|
||||
|
||||
|
||||
class ToolSelection(BaseModel):
|
||||
"""Structured output for the per-turn tool router."""
|
||||
|
||||
tool_names: list[str]
|
||||
|
||||
|
||||
ROUTER_INSTRUCTIONS = (
|
||||
"You are a tool router. Given the user's latest message and a menu of "
|
||||
"available tools (one per line, formatted as 'NAME - TYPE'), return the "
|
||||
"NAMES of the tools that would plausibly help answer the message. Return "
|
||||
"an empty list if no tool is needed."
|
||||
)
|
||||
|
||||
|
||||
class DynamicToolboxProvider(ContextProvider):
|
||||
"""Fetches a Foundry toolbox once and lets the model pick tools per-turn."""
|
||||
|
||||
DEFAULT_SOURCE_ID = "foundry_toolbox"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
source_id: str = DEFAULT_SOURCE_ID,
|
||||
*,
|
||||
client: FoundryChatClient,
|
||||
toolbox_name: str,
|
||||
toolbox_version: str | None = None,
|
||||
) -> None:
|
||||
super().__init__(source_id)
|
||||
self._client = client
|
||||
self._toolbox_name = toolbox_name
|
||||
self._toolbox_version = toolbox_version
|
||||
|
||||
async def before_run(
|
||||
self,
|
||||
*,
|
||||
agent: Any,
|
||||
session: AgentSession | None,
|
||||
context: SessionContext,
|
||||
state: dict[str, Any],
|
||||
) -> None:
|
||||
"""Cache the toolbox on first call, then let the model pick tools per-turn."""
|
||||
toolbox = state.get("toolbox")
|
||||
if toolbox is None:
|
||||
toolbox = await self._client.get_toolbox(self._toolbox_name, version=self._toolbox_version)
|
||||
state["toolbox"] = toolbox
|
||||
print(f"[{self.source_id}] Loaded toolbox {toolbox.name}@{toolbox.version} ({len(toolbox.tools)} tool(s))")
|
||||
|
||||
user_messages = [m for m in context.get_messages(include_input=True) if getattr(m, "role", None) == "user"]
|
||||
if not user_messages:
|
||||
context.extend_tools(self.source_id, list(toolbox.tools))
|
||||
return
|
||||
|
||||
picks = await self._route_tools(user_messages[-1].text, toolbox.tools)
|
||||
if picks:
|
||||
tools = select_toolbox_tools(toolbox, include_names=picks)
|
||||
print(f"[{self.source_id}] Router picked {sorted(picks)} - surfacing {len(tools)} tool(s)")
|
||||
else:
|
||||
tools = list(toolbox.tools)
|
||||
print(f"[{self.source_id}] Router picked nothing - surfacing all {len(tools)} tool(s)")
|
||||
context.extend_tools(self.source_id, tools)
|
||||
|
||||
async def _route_tools(self, user_text: str, tools: Any) -> list[str]:
|
||||
"""Ask the model which toolbox tools to surface for this turn."""
|
||||
menu = "\n".join(f"- {get_toolbox_tool_name(t)} - {get_toolbox_tool_type(t)}" for t in tools)
|
||||
prompt = (
|
||||
f"User message:\n{user_text}\n\n"
|
||||
f"Available tools:\n{menu}\n\n"
|
||||
"Return the names of tools that should be surfaced for this turn."
|
||||
)
|
||||
response = await self._client.get_response(
|
||||
messages=[Message("user", [prompt])],
|
||||
options={
|
||||
"instructions": ROUTER_INSTRUCTIONS,
|
||||
"response_format": ToolSelection,
|
||||
},
|
||||
)
|
||||
selection: ToolSelection = response.value # type: ignore
|
||||
return selection.tool_names
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
client = FoundryChatClient(
|
||||
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
model=os.environ["FOUNDRY_MODEL"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
# Comment out if the toolbox already exists in your Foundry project.
|
||||
create_sample_toolbox(TOOLBOX_NAME)
|
||||
|
||||
toolbox_provider = DynamicToolboxProvider(
|
||||
client=client,
|
||||
toolbox_name=TOOLBOX_NAME,
|
||||
toolbox_version=TOOLBOX_VERSION,
|
||||
)
|
||||
|
||||
async with Agent(
|
||||
client=client,
|
||||
instructions=(
|
||||
"You are a helpful assistant. Use the tools available to you on each "
|
||||
"turn to answer the user. If no tools are relevant, reply directly."
|
||||
),
|
||||
context_providers=[toolbox_provider],
|
||||
) as agent:
|
||||
session = agent.create_session()
|
||||
|
||||
for query in QUERIES:
|
||||
print(f"\nUser: {query}")
|
||||
result = await agent.run(query, session=session)
|
||||
print(f"Assistant: {result}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -26,8 +26,7 @@ This folder contains Azure AI Foundry and Foundry Local samples for Agent Framew
|
||||
| [`foundry_chat_client_with_hosted_mcp.py`](foundry_chat_client_with_hosted_mcp.py) | Foundry Chat Client with hosted MCP |
|
||||
| [`foundry_chat_client_with_local_mcp.py`](foundry_chat_client_with_local_mcp.py) | Foundry Chat Client with local MCP |
|
||||
| [`foundry_chat_client_with_session.py`](foundry_chat_client_with_session.py) | Foundry Chat Client with session management |
|
||||
| [`foundry_chat_client_with_toolbox.py`](foundry_chat_client_with_toolbox.py) | Foundry Chat Client with Foundry toolbox loading and multi-toolbox composition |
|
||||
| [`foundry_chat_client_with_toolbox_mcp.py`](foundry_chat_client_with_toolbox_mcp.py) | Foundry Chat Client connected to a toolbox via its MCP endpoint using `MCPStreamableHTTPTool` |
|
||||
| [`foundry_chat_client_with_toolbox.py`](foundry_chat_client_with_toolbox.py) | Foundry Chat Client connected to a toolbox via its MCP endpoint using `MCPStreamableHTTPTool` |
|
||||
|
||||
## FoundryLocalClient Samples
|
||||
|
||||
|
||||
@@ -2,52 +2,48 @@
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import Agent
|
||||
from agent_framework.foundry import FoundryChatClient, select_toolbox_tools
|
||||
from azure.identity import AzureCliCredential
|
||||
from agent_framework import Agent, MCPStreamableHTTPTool
|
||||
from agent_framework.foundry import FoundryChatClient
|
||||
from azure.core.credentials import TokenCredential
|
||||
from azure.identity import AzureCliCredential, DefaultAzureCredential, get_bearer_token_provider
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
"""
|
||||
Foundry Chat Client with Toolbox Example
|
||||
Foundry Toolbox via MAF ``MCPStreamableHTTPTool``
|
||||
|
||||
This sample demonstrates loading a named, versioned Foundry toolbox into an
|
||||
Agent via ``FoundryChatClient.get_toolbox()``. A toolbox is a server-side
|
||||
bundle of tool configurations (code interpreter, file search, MCP, web search,
|
||||
etc.) configured in the Foundry portal or via the raw SDK.
|
||||
Instead of fetching the toolbox and fanning out individual tool specs, point
|
||||
MAF's ``MCPStreamableHTTPTool`` at the toolbox's MCP endpoint. The agent
|
||||
discovers and calls the toolbox's tools over MCP at runtime.
|
||||
|
||||
Prerequisites:
|
||||
- A Microsoft Foundry project
|
||||
- A toolbox already configured in that project (set TOOLBOX_NAME below)
|
||||
- A Microsoft Foundry project with a toolbox configured
|
||||
- FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL environment variables set
|
||||
- FOUNDRY_TOOLBOX_ENDPOINT: the toolbox's MCP endpoint URL, e.g.
|
||||
``https://<account>.services.ai.azure.com/api/projects/<project>/toolsets/<name>/mcp?api-version=v1``
|
||||
- Azure CLI authentication (``az login``)
|
||||
"""
|
||||
|
||||
# Replace with your own Foundry toolbox name and version.
|
||||
# Must match the ``<name>`` segment of FOUNDRY_TOOLBOX_ENDPOINT.
|
||||
TOOLBOX_NAME = "research_toolbox"
|
||||
TOOLBOX_VERSION = "1"
|
||||
# Used only by combine_toolboxes() — swap in a second toolbox you own.
|
||||
SECOND_TOOLBOX_NAME = "analysis_toolbox"
|
||||
SECOND_TOOLBOX_VERSION = "1"
|
||||
|
||||
# Replace with any question that exercises the tools configured in your toolbox.
|
||||
QUERY = "Introduce yourself and briefly describe the tools you can use to help me."
|
||||
|
||||
|
||||
def create_sample_toolbox(name: str) -> str:
|
||||
"""Create (or replace) a toolbox version in the Foundry project.
|
||||
|
||||
Toolboxes are normally configured in the Foundry portal or a deployment
|
||||
script, not the application itself. This helper exists so the samples can
|
||||
script, not the application itself. This helper exists so the sample can
|
||||
be run end-to-end without first setting a toolbox up by hand — delete any
|
||||
existing toolbox under ``name``, then create a fresh version containing an
|
||||
MCP tool, a web search tool, and a code interpreter tool. Returns the
|
||||
created version identifier.
|
||||
existing toolbox under ``name``, then create a fresh version containing a
|
||||
single MCP tool. Returns the created version identifier.
|
||||
"""
|
||||
from azure.ai.projects import AIProjectClient
|
||||
from azure.ai.projects.models import CodeInterpreterTool, MCPTool, Tool, WebSearchTool
|
||||
from azure.ai.projects.models import MCPTool, Tool
|
||||
from azure.core.exceptions import ResourceNotFoundError
|
||||
|
||||
with (
|
||||
@@ -68,9 +64,6 @@ def create_sample_toolbox(name: str) -> str:
|
||||
)
|
||||
]
|
||||
|
||||
tools.append(WebSearchTool(name="web_search"))
|
||||
tools.append(CodeInterpreterTool(name="code_interpreter"))
|
||||
|
||||
created = project_client.beta.toolboxes.create_version(
|
||||
name=name,
|
||||
description="Toolbox version with MCP require_approval set to 'never'.",
|
||||
@@ -80,99 +73,46 @@ def create_sample_toolbox(name: str) -> str:
|
||||
return created.version
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""Example showing how to use a single Foundry toolbox with FoundryChatClient."""
|
||||
print("=== Foundry Chat Client with Toolbox Example ===")
|
||||
|
||||
# For authentication, run `az login` in your terminal or replace
|
||||
# AzureCliCredential with your preferred authentication option.
|
||||
client = FoundryChatClient(
|
||||
credential=AzureCliCredential(),
|
||||
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
model=os.environ["FOUNDRY_MODEL"],
|
||||
)
|
||||
credential = DefaultAzureCredential()
|
||||
|
||||
# Comment out if the toolbox already exists in your Foundry project.
|
||||
create_sample_toolbox(TOOLBOX_NAME)
|
||||
|
||||
# Omit ``version`` to resolve the toolbox's current default version at runtime.
|
||||
toolbox = await client.get_toolbox(TOOLBOX_NAME)
|
||||
print(f"Loaded toolbox {toolbox.name}@{toolbox.version} ({len(toolbox.tools)} tool(s))")
|
||||
|
||||
agent = Agent(
|
||||
client=client,
|
||||
instructions="You are a research assistant. Use the available tools to answer questions.",
|
||||
tools=toolbox,
|
||||
toolbox_tool = MCPStreamableHTTPTool(
|
||||
name="foundry_toolbox",
|
||||
description="Tools exposed by the configured Foundry toolbox",
|
||||
url=os.environ["FOUNDRY_TOOLBOX_ENDPOINT"],
|
||||
header_provider=make_toolbox_header_provider(credential),
|
||||
load_prompts=False,
|
||||
)
|
||||
|
||||
print(f"User: {QUERY}")
|
||||
result = await agent.run(QUERY)
|
||||
print(f"Result: {result}\n")
|
||||
|
||||
|
||||
async def combine_toolboxes() -> None:
|
||||
"""Alternative flow: combine the tools from multiple Foundry toolboxes."""
|
||||
client = FoundryChatClient(
|
||||
credential=AzureCliCredential(),
|
||||
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
model=os.environ["FOUNDRY_MODEL"],
|
||||
)
|
||||
|
||||
# Comment out if the toolboxes already exist in your Foundry project.
|
||||
create_sample_toolbox(TOOLBOX_NAME)
|
||||
create_sample_toolbox(SECOND_TOOLBOX_NAME)
|
||||
|
||||
toolbox_a = await client.get_toolbox(TOOLBOX_NAME, version=TOOLBOX_VERSION)
|
||||
toolbox_b = await client.get_toolbox(SECOND_TOOLBOX_NAME, version=SECOND_TOOLBOX_VERSION)
|
||||
print(
|
||||
"Loaded toolboxes: "
|
||||
f"{toolbox_a.name}@{toolbox_a.version} ({len(toolbox_a.tools)} tool(s)), "
|
||||
f"{toolbox_b.name}@{toolbox_b.version} ({len(toolbox_b.tools)} tool(s))"
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
client=client,
|
||||
instructions="You are a research assistant. Use all available tools to answer questions.",
|
||||
tools=[toolbox_a, toolbox_b],
|
||||
)
|
||||
|
||||
print(f"User: {QUERY}")
|
||||
result = await agent.run(QUERY)
|
||||
print(f"Combined-toolbox result: {result}\n")
|
||||
|
||||
|
||||
async def select_tools_from_toolbox() -> None:
|
||||
"""Alternative flow: keep only a subset of toolbox tools before agent creation."""
|
||||
client = FoundryChatClient(
|
||||
credential=AzureCliCredential(),
|
||||
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
model=os.environ["FOUNDRY_MODEL"],
|
||||
)
|
||||
|
||||
# Comment out if the toolbox already exists in your Foundry project.
|
||||
create_sample_toolbox(TOOLBOX_NAME)
|
||||
|
||||
toolbox = await client.get_toolbox(TOOLBOX_NAME, version=TOOLBOX_VERSION)
|
||||
print(f"Loaded toolbox {toolbox.name}@{toolbox.version} ({len(toolbox.tools)} tool(s))")
|
||||
|
||||
selected_tools = select_toolbox_tools(
|
||||
toolbox,
|
||||
include_types=["code_interpreter", "mcp"],
|
||||
)
|
||||
print(f"Selected {len(selected_tools)} toolbox tools for the agent")
|
||||
|
||||
agent = Agent(
|
||||
client=client,
|
||||
instructions="You are a research assistant. Use only the selected toolbox tools.",
|
||||
tools=selected_tools,
|
||||
)
|
||||
|
||||
print(f"User: {QUERY}")
|
||||
result = await agent.run(QUERY)
|
||||
print(f"Selected-toolbox result: {result}\n")
|
||||
async with Agent(
|
||||
client=FoundryChatClient(
|
||||
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
model=os.environ["FOUNDRY_MODEL"],
|
||||
credential=credential,
|
||||
),
|
||||
instructions="You are a helpful assistant. Use the available toolbox tools to answer the user.",
|
||||
tools=toolbox_tool,
|
||||
) as agent:
|
||||
query = "What tools do you have access to?"
|
||||
print(f"User: {query}")
|
||||
result = await agent.run(query)
|
||||
print(f"Assistant: {result}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
# asyncio.run(combine_toolboxes())
|
||||
# asyncio.run(select_tools_from_toolbox())
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
# 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
|
||||
from agent_framework.foundry import FoundryChatClient
|
||||
from azure.core.credentials import TokenCredential
|
||||
from azure.identity import AzureCliCredential, DefaultAzureCredential, get_bearer_token_provider
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
"""
|
||||
Foundry Toolbox via MAF ``MCPStreamableHTTPTool``
|
||||
|
||||
Instead of fetching the toolbox and fanning out individual tool specs, point
|
||||
MAF's ``MCPStreamableHTTPTool`` at the toolbox's MCP endpoint. The agent
|
||||
discovers and calls the toolbox's tools over MCP at runtime.
|
||||
|
||||
Prerequisites:
|
||||
- A Microsoft Foundry project with a toolbox configured
|
||||
- FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL environment variables set
|
||||
- FOUNDRY_TOOLBOX_ENDPOINT: the toolbox's MCP endpoint URL, e.g.
|
||||
``https://<account>.services.ai.azure.com/api/projects/<project>/toolsets/<name>/mcp?api-version=v1``
|
||||
- Azure CLI authentication (``az login``)
|
||||
"""
|
||||
|
||||
# Must match the ``<name>`` segment of FOUNDRY_TOOLBOX_ENDPOINT.
|
||||
TOOLBOX_NAME = "research_toolbox"
|
||||
|
||||
|
||||
def create_sample_toolbox(name: str) -> str:
|
||||
"""Create (or replace) a toolbox version in the Foundry project.
|
||||
|
||||
Toolboxes are normally configured in the Foundry portal or a deployment
|
||||
script, not the application itself. This helper exists so the sample can
|
||||
be run end-to-end without first setting a toolbox up by hand — delete any
|
||||
existing toolbox under ``name``, then create a fresh version containing a
|
||||
single MCP tool. Returns the created version identifier.
|
||||
"""
|
||||
from azure.ai.projects import AIProjectClient
|
||||
from azure.ai.projects.models import MCPTool, Tool
|
||||
from azure.core.exceptions import ResourceNotFoundError
|
||||
|
||||
with (
|
||||
AzureCliCredential() as credential,
|
||||
AIProjectClient(credential=credential, endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"]) as project_client,
|
||||
):
|
||||
try:
|
||||
project_client.beta.toolboxes.delete(name)
|
||||
print(f"Toolbox `{name}` deleted")
|
||||
except ResourceNotFoundError:
|
||||
pass
|
||||
|
||||
tools: list[Tool] = [
|
||||
MCPTool(
|
||||
server_label="api_specs",
|
||||
server_url="https://gitmcp.io/Azure/azure-rest-api-specs",
|
||||
require_approval="never",
|
||||
)
|
||||
]
|
||||
|
||||
created = project_client.beta.toolboxes.create_version(
|
||||
name=name,
|
||||
description="Toolbox version with MCP require_approval set to 'never'.",
|
||||
tools=tools,
|
||||
)
|
||||
print(f"Created toolbox {created.name}@{created.version} ({len(created.tools)} tool(s))")
|
||||
return created.version
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
credential = DefaultAzureCredential()
|
||||
|
||||
# Comment out if the toolbox already exists in your Foundry project.
|
||||
create_sample_toolbox(TOOLBOX_NAME)
|
||||
|
||||
toolbox_tool = MCPStreamableHTTPTool(
|
||||
name="foundry_toolbox",
|
||||
description="Tools exposed by the configured Foundry toolbox",
|
||||
url=os.environ["FOUNDRY_TOOLBOX_ENDPOINT"],
|
||||
header_provider=make_toolbox_header_provider(credential),
|
||||
load_prompts=False,
|
||||
)
|
||||
|
||||
async with Agent(
|
||||
client=FoundryChatClient(
|
||||
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
model=os.environ["FOUNDRY_MODEL"],
|
||||
credential=credential,
|
||||
),
|
||||
instructions="You are a helpful assistant. Use the available toolbox tools to answer the user.",
|
||||
tools=toolbox_tool,
|
||||
) as agent:
|
||||
query = "What tools do you have access to?"
|
||||
print(f"User: {query}")
|
||||
result = await agent.run(query)
|
||||
print(f"Assistant: {result}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -284,7 +284,9 @@ async def run_scenarios(agent, config):
|
||||
# attempt to call send_email, so the policy enforcer would never trigger.
|
||||
session = agent.create_session()
|
||||
|
||||
response = await agent.run("Please fetch my recent emails and give me a brief summary of each one.", session=session)
|
||||
response = await agent.run(
|
||||
"Please fetch my recent emails and give me a brief summary of each one.", session=session
|
||||
)
|
||||
print(f"\n📋 Agent Response:\n{'-' * 40}")
|
||||
print(response.text)
|
||||
|
||||
|
||||
@@ -20,8 +20,7 @@ from openai import OpenAI
|
||||
# https://<your-foundry-resource>.services.ai.azure.com/api/projects/<project>/agents/<agent-name>
|
||||
ENDPOINT = os.environ.get(
|
||||
"FOUNDRY_AGENT_ENDPOINT",
|
||||
"https://<your-foundry-resource>.services.ai.azure.com"
|
||||
"/api/projects/<project>/agents/<agent-name>",
|
||||
"https://<your-foundry-resource>.services.ai.azure.com/api/projects/<project>/agents/<agent-name>",
|
||||
)
|
||||
SCOPE = "https://ai.azure.com/.default"
|
||||
PROMPT = (
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
FOUNDRY_PROJECT_ENDPOINT="..."
|
||||
AZURE_AI_MODEL_DEPLOYMENT_NAME="..."
|
||||
TOOLBOX_NAME="..."
|
||||
FOUNDRY_TOOLBOX_ENDPOINT="..."
|
||||
+15
-4
@@ -14,7 +14,7 @@ You can also create a Foundry Toolbox in the Foundry portal. Read more about it
|
||||
|
||||
### Model Integration
|
||||
|
||||
The agent uses `FoundryChatClient` from the Agent Framework to create an OpenAI-compatible Responses client. It loads a named Foundry Toolbox via `client.get_toolbox(name)` — the toolbox is a server-side bundle of tool configurations (e.g., `code_interpreter`, `web_search`) defined in the Foundry portal or by `azd provision`. Omitting `version` resolves the toolbox's current default version at runtime.
|
||||
The agent uses `FoundryChatClient` from the Agent Framework to create an OpenAI-compatible Responses client. It connects to the toolbox's MCP endpoint via `MCPStreamableHTTPTool`, which discovers and invokes the toolbox's tools over MCP at runtime. The endpoint URL is provided through the `FOUNDRY_TOOLBOX_ENDPOINT` environment variable.
|
||||
|
||||
See [main.py](main.py) for the full implementation.
|
||||
|
||||
@@ -26,18 +26,29 @@ The agent is hosted using the [Agent Framework](https://github.com/microsoft/age
|
||||
|
||||
Follow the instructions in the [Running the Agent Host Locally](../../README.md#running-the-agent-host-locally) section of the README in the parent directory to run the agent host.
|
||||
|
||||
An extra environment variable `TOOLBOX_NAME` must be set to the name of the Foundry Toolbox that the agent should load at runtime. This allows the agent host to dynamically retrieve the correct toolbox from Foundry when it starts. Run the following:
|
||||
An extra environment variable must be set to point to the toolbox MCP endpoint. You can provide it in one of two ways:
|
||||
|
||||
**Option A – Set `FOUNDRY_TOOLBOX_ENDPOINT` directly** (recommended for local development):
|
||||
|
||||
```bash
|
||||
export TOOLBOX_NAME="<your-toolbox-name>"
|
||||
export FOUNDRY_TOOLBOX_ENDPOINT="https://<account>.services.ai.azure.com/api/projects/<project>/toolsets/<name>/mcp?api-version=v1"
|
||||
```
|
||||
|
||||
Or in PowerShell:
|
||||
|
||||
```powershell
|
||||
$env:TOOLBOX_NAME="<your-toolbox-name>"
|
||||
$env:FOUNDRY_TOOLBOX_ENDPOINT="https://<account>.services.ai.azure.com/api/projects/<project>/toolsets/<name>/mcp?api-version=v1"
|
||||
```
|
||||
|
||||
**Option B – Set `TOOLBOX_NAME`** (used automatically by the Foundry hosting scaffolding after `azd provision`):
|
||||
|
||||
The agent derives the endpoint at runtime as:
|
||||
```
|
||||
{FOUNDRY_PROJECT_ENDPOINT}/toolsets/{TOOLBOX_NAME}/mcp?api-version=v1
|
||||
```
|
||||
|
||||
When deployed via `azd provision`, the scaffolding injects `TOOLBOX_NAME=agent-tools` and `FOUNDRY_PROJECT_ENDPOINT` automatically from the provisioned resources declared in [`agent.manifest.yaml`](agent.manifest.yaml).
|
||||
|
||||
## Interacting with the agent
|
||||
|
||||
> Depending on how you run the agent host, you can invoke the agent using `curl` (`Invoke-WebRequest` in PowerShell) or `azd`. Please refer to the [parent README](../../README.md) for more details. Use this README for sample queries you can send to the agent.
|
||||
|
||||
+48
-12
@@ -2,40 +2,76 @@
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import Agent
|
||||
from agent_framework import Agent, MCPStreamableHTTPTool
|
||||
from agent_framework.foundry import FoundryChatClient
|
||||
from agent_framework_foundry_hosting import ResponsesHostServer
|
||||
from azure.identity import DefaultAzureCredential
|
||||
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
|
||||
|
||||
|
||||
async def main():
|
||||
credential = DefaultAzureCredential()
|
||||
|
||||
client = FoundryChatClient(
|
||||
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=DefaultAzureCredential(),
|
||||
credential=credential,
|
||||
)
|
||||
|
||||
# Load the named toolbox from the Foundry project. Omitting `version`
|
||||
# resolves the toolbox's current default version at runtime.
|
||||
toolbox = await client.get_toolbox(os.environ["TOOLBOX_NAME"])
|
||||
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,
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
async with Agent(
|
||||
client=client,
|
||||
instructions="You are a friendly assistant. Keep your answers brief.",
|
||||
tools=toolbox,
|
||||
tools=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},
|
||||
)
|
||||
|
||||
server = ResponsesHostServer(agent)
|
||||
await server.run_async()
|
||||
) as agent:
|
||||
server = ResponsesHostServer(agent)
|
||||
await server.run_async()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
FOUNDRY_PROJECT_ENDPOINT="..."
|
||||
AZURE_AI_MODEL_DEPLOYMENT_NAME="..."
|
||||
TOOLBOX_NAME="..."
|
||||
FOUNDRY_TOOLBOX_ENDPOINT="..."
|
||||
@@ -29,18 +29,29 @@ This agent uses four tools:
|
||||
|
||||
Follow the instructions in the [Running the Agent Host Locally](../../README.md#running-the-agent-host-locally) section of the README in the parent directory to run the agent host.
|
||||
|
||||
An extra environment variable `TOOLBOX_NAME` must be set to the name of the Foundry Toolbox that the agent should load at runtime. This allows the agent host to dynamically retrieve the correct toolbox from Foundry when it starts. Run the following:
|
||||
An extra environment variable must be set to point to the toolbox MCP endpoint. You can provide it in one of two ways:
|
||||
|
||||
**Option A – Set `FOUNDRY_TOOLBOX_ENDPOINT` directly** (recommended for local development):
|
||||
|
||||
```bash
|
||||
export TOOLBOX_NAME="<your-toolbox-name>"
|
||||
export FOUNDRY_TOOLBOX_ENDPOINT="https://<account>.services.ai.azure.com/api/projects/<project>/toolsets/<name>/mcp?api-version=v1"
|
||||
```
|
||||
|
||||
Or in PowerShell:
|
||||
|
||||
```powershell
|
||||
$env:TOOLBOX_NAME="<your-toolbox-name>"
|
||||
$env:FOUNDRY_TOOLBOX_ENDPOINT="https://<account>.services.ai.azure.com/api/projects/<project>/toolsets/<name>/mcp?api-version=v1"
|
||||
```
|
||||
|
||||
**Option B – Set `TOOLBOX_NAME`** (used automatically by the Foundry hosting scaffolding after `azd provision`):
|
||||
|
||||
The agent derives the endpoint at runtime as:
|
||||
```
|
||||
{FOUNDRY_PROJECT_ENDPOINT}/toolsets/{TOOLBOX_NAME}/mcp?api-version=v1
|
||||
```
|
||||
|
||||
When deployed via `azd provision`, the scaffolding injects `TOOLBOX_NAME=agent-tools` and `FOUNDRY_PROJECT_ENDPOINT` automatically from the provisioned resources declared in [`agent.manifest.yaml`](agent.manifest.yaml).
|
||||
|
||||
## Interacting with the agent
|
||||
|
||||
> Depending on how you run the agent host, you can invoke the agent using `curl` (`Invoke-WebRequest` in PowerShell) or `azd`. Please refer to the [parent README](../../README.md) for more details. Use this README for sample queries you can send to the agent.
|
||||
|
||||
@@ -9,4 +9,6 @@ resources:
|
||||
memory: '0.5Gi'
|
||||
environment_variables:
|
||||
- name: AZURE_AI_MODEL_DEPLOYMENT_NAME
|
||||
value: ${AZURE_AI_MODEL_DEPLOYMENT_NAME}
|
||||
value: ${AZURE_AI_MODEL_DEPLOYMENT_NAME}
|
||||
- name: TOOLBOX_NAME
|
||||
value: "agent-tools"
|
||||
@@ -2,18 +2,48 @@
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import Agent, tool
|
||||
from agent_framework import Agent, MCPStreamableHTTPTool, tool
|
||||
from agent_framework.foundry import FoundryChatClient
|
||||
from agent_framework_foundry import select_toolbox_tools
|
||||
from agent_framework_foundry_hosting import ResponsesHostServer
|
||||
from azure.identity import DefaultAzureCredential
|
||||
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."""
|
||||
@@ -43,40 +73,43 @@ def read_file(file_path: str) -> str:
|
||||
|
||||
|
||||
async def main():
|
||||
credential = DefaultAzureCredential()
|
||||
|
||||
client = FoundryChatClient(
|
||||
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=DefaultAzureCredential(),
|
||||
credential=credential,
|
||||
)
|
||||
|
||||
# Load the named toolbox from the Foundry project. Omitting `version`
|
||||
# resolves the toolbox's current default version at runtime.
|
||||
toolbox = await client.get_toolbox(os.environ["TOOLBOX_NAME"])
|
||||
# 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
|
||||
selected_tools = select_toolbox_tools(
|
||||
toolbox,
|
||||
include_names=["code_interpreter"],
|
||||
# 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"],
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
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] + selected_tools,
|
||||
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},
|
||||
)
|
||||
|
||||
server = ResponsesHostServer(agent)
|
||||
await server.run_async()
|
||||
) as agent:
|
||||
server = ResponsesHostServer(agent)
|
||||
await server.run_async()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user