mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: fix(foundry): reconcile toolbox hosted-tool payloads with Responses API (#5414)
* fix(foundry): reconcile toolbox hosted-tool payloads with Responses API * docs(foundry): update create_sample_toolbox docstring to reflect all tools created
This commit is contained in:
committed by
GitHub
Unverified
parent
ea3320d39f
commit
fffd0acb3e
@@ -455,8 +455,18 @@ class RawFoundryChatClient( # type: ignore[misc]
|
||||
|
||||
Returns:
|
||||
An MCPTool configuration ready to pass to an Agent.
|
||||
|
||||
Raises:
|
||||
ValueError: If neither ``url`` nor ``project_connection_id`` is supplied
|
||||
— one is required by the Foundry Responses API.
|
||||
"""
|
||||
mcp = FoundryMCPTool(server_label=name.replace(" ", "_"), server_url=url or "", **kwargs)
|
||||
if not url and not project_connection_id:
|
||||
raise ValueError("MCP tool requires either 'url' or 'project_connection_id' to be specified.")
|
||||
|
||||
mcp_kwargs: dict[str, Any] = {"server_label": name.replace(" ", "_"), **kwargs}
|
||||
if url:
|
||||
mcp_kwargs["server_url"] = url
|
||||
mcp = FoundryMCPTool(**mcp_kwargs)
|
||||
|
||||
if description:
|
||||
mcp["server_description"] = description
|
||||
|
||||
@@ -133,26 +133,55 @@ def select_toolbox_tools(
|
||||
return selected
|
||||
|
||||
|
||||
def _validate_hosted_tool_payload(sanitized: Mapping[str, Any]) -> None:
|
||||
"""Fail fast on hosted tool payloads that would always be rejected by the Responses API.
|
||||
|
||||
These mismatches are not injectable defaults — the caller must supply the
|
||||
missing information — so surfacing a clear error here points at the toolbox
|
||||
definition instead of letting the API return a generic 400.
|
||||
"""
|
||||
tool_type = sanitized.get("type")
|
||||
if tool_type == "file_search" and not sanitized.get("vector_store_ids"):
|
||||
raise ValueError(
|
||||
"'file_search' tool is missing required 'vector_store_ids'. "
|
||||
"If this came from a Foundry toolbox, update the toolbox definition "
|
||||
"to include at least one vector store ID."
|
||||
)
|
||||
if tool_type == "mcp" and not sanitized.get("server_url") and not sanitized.get("project_connection_id"):
|
||||
raise ValueError(
|
||||
"'mcp' tool is missing both 'server_url' and 'project_connection_id'. "
|
||||
"If this came from a Foundry toolbox, update the toolbox definition "
|
||||
"to include one of these."
|
||||
)
|
||||
|
||||
|
||||
@experimental(feature_id=ExperimentalFeature.TOOLBOXES)
|
||||
def sanitize_foundry_response_tool(tool_item: Any) -> Any:
|
||||
"""Return a Responses-API-safe tool payload for Foundry hosted tools.
|
||||
|
||||
Azure AI Projects toolbox reads can currently return hosted tool objects with
|
||||
extra read-model decoration fields such as top-level ``name`` and
|
||||
``description``. Azure AI Foundry rejects at least ``name`` on Responses API
|
||||
requests with:
|
||||
Reconciles known mismatches between toolbox reads and the Responses API:
|
||||
|
||||
``Unknown parameter: 'tools[0].name'``.
|
||||
1. Toolbox reads can return hosted tool objects decorated with read-model
|
||||
fields such as top-level ``name`` and ``description``. The Responses API
|
||||
rejects at least ``name`` with ``Unknown parameter: 'tools[0].name'``.
|
||||
These fields are stripped from non-function hosted tool payloads.
|
||||
2. ``code_interpreter`` tools stored in a toolbox without a ``container``
|
||||
field (the Azure SDK treats it as optional) are rejected by the Responses
|
||||
API with ``Missing required parameter: 'tools[N].container'``. A default
|
||||
``{"type": "auto"}`` container is injected when absent.
|
||||
3. Hosted tools that are structurally incomplete in ways that cannot be
|
||||
defaulted (``file_search`` without ``vector_store_ids``, ``mcp`` without
|
||||
either ``server_url`` or ``project_connection_id``) raise ``ValueError``
|
||||
with a message that points at the toolbox definition.
|
||||
|
||||
We defensively strip these decoration fields for non-function hosted tools so
|
||||
the round-trip
|
||||
``toolbox.tools -> Agent(..., tools=...) -> run()`` works, while the Azure
|
||||
SDK/service behavior is corrected upstream.
|
||||
These are workarounds until the toolbox/Responses proxy normalizes payloads
|
||||
server-side.
|
||||
"""
|
||||
if isinstance(tool_item, FoundryMCPTool):
|
||||
sanitized: dict[str, Any] = dict(cast("Mapping[str, Any]", tool_item))
|
||||
sanitized.pop("name", None)
|
||||
sanitized.pop("description", None)
|
||||
_validate_hosted_tool_payload(sanitized)
|
||||
return sanitized
|
||||
|
||||
if isinstance(tool_item, Mapping):
|
||||
@@ -161,6 +190,9 @@ def sanitize_foundry_response_tool(tool_item: Any) -> Any:
|
||||
sanitized = dict(mapping)
|
||||
sanitized.pop("name", None)
|
||||
sanitized.pop("description", None)
|
||||
if sanitized.get("type") == "code_interpreter" and "container" not in sanitized:
|
||||
sanitized["container"] = {"type": "auto"}
|
||||
_validate_hosted_tool_payload(sanitized)
|
||||
return sanitized
|
||||
|
||||
return cast(Any, tool_item)
|
||||
|
||||
Reference in New Issue
Block a user