Skip to content

Python: [BREAKING] Replace Hosted*Tool classes with tool methods#3634

Open
giles17 wants to merge 15 commits intomicrosoft:mainfrom
giles17:refactor-hosted-tools
Open

Python: [BREAKING] Replace Hosted*Tool classes with tool methods#3634
giles17 wants to merge 15 commits intomicrosoft:mainfrom
giles17:refactor-hosted-tools

Conversation

@giles17
Copy link
Contributor

@giles17 giles17 commented Feb 3, 2026

Motivation and Context

This PR refactors hosted tool support from standalone Hosted*Tool classes to @staticmethod factory methods on the chat clients that support them. This is a breaking change that improves API discoverability and makes tool support explicit per client

Breaking Changes

Removed classes:

  • HostedCodeInterpreterTool
  • HostedWebSearchTool
  • HostedImageGenerationTool
  • HostedFileSearchTool
  • HostedMCPTool

Migration:

# BEFORE (old API - removed)
from agent_framework import HostedCodeInterpreterTool, HostedWebSearchTool
tool = HostedCodeInterpreterTool()
search = HostedWebSearchTool(additional_properties={"user_location": {...}})

# AFTER (new API)
from agent_framework.openai import OpenAIResponsesClient

tool = OpenAIResponsesClient.get_code_interpreter_tool()
search = OpenAIResponsesClient.get_web_search_tool(user_location={"city": "Seattle"})

#3586

Description

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? If yes, add "[BREAKING]" prefix to the title of the PR.

Copilot AI review requested due to automatic review settings February 3, 2026 04:29
@markwallace-microsoft markwallace-microsoft added documentation Improvements or additions to documentation python lab Agent Framework Lab labels Feb 3, 2026
@github-actions github-actions bot changed the title [BREAKING] Replace Hosted*Tool classes with client static factory methods Python: [BREAKING] Replace Hosted*Tool classes with client static factory methods Feb 3, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a breaking change that refactors hosted tool support from standalone Hosted*Tool classes to static factory methods on chat clients. This improves API discoverability by making tool support explicit per client type.

Changes:

  • Removed HostedCodeInterpreterTool, HostedWebSearchTool, HostedFileSearchTool, HostedImageGenerationTool, and HostedMCPTool classes
  • Added static factory methods (get_code_interpreter_tool(), get_web_search_tool(), etc.) to relevant client classes
  • Introduced protocol classes (SupportsCodeInterpreterTool, SupportsWebSearchTool, etc.) for runtime checking of tool support
  • Updated all samples, tests, and documentation to use the new API

Reviewed changes

Copilot reviewed 119 out of 119 changed files in this pull request and generated no comments.

Show a summary per file
File Description
python/packages/core/agent_framework/_clients.py Added tool support protocol definitions
python/packages/core/agent_framework/openai/_chat_client.py Added get_web_search_tool() static method
python/packages/core/agent_framework/openai/_assistants_client.py Added get_code_interpreter_tool() and get_file_search_tool() static methods
python/packages/core/agent_framework/openai/_responses_client.py Added static methods for all hosted tools (code interpreter, web search, file search, image generation, MCP)
python/packages/azure-ai/agent_framework_azure_ai/_client.py Added Azure-specific get_mcp_tool() implementation
python/packages/anthropic/agent_framework_anthropic/_chat_client.py Added static methods for Anthropic-supported tools
Multiple sample files Updated to use new static factory methods instead of Hosted*Tool classes
Multiple test files Updated test assertions to check for dict-based tools instead of class instances
python/packages/declarative/agent_framework_declarative/_loader.py Updated to create dict-based tools directly

@markwallace-microsoft
Copy link
Member

markwallace-microsoft commented Feb 3, 2026

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/ag-ui/agent_framework_ag_ui
   _utils.py99198%72
packages/anthropic/agent_framework_anthropic
   _chat_client.py36914959%442, 471, 503, 505, 520, 542–545, 554, 556, 593–597, 599, 601–602, 604, 609–610, 612, 645–646, 655, 657–658, 663, 680–681, 726, 736, 752, 761, 763, 767–768, 811–813, 815, 828–829, 836–838, 842–844, 848–851, 862, 864, 886, 896, 918–924, 931–932, 940–941, 949–952, 959–960, 966–967, 973–974, 980, 988–990, 994, 1001–1002, 1008–1009, 1015–1016, 1022, 1030–1033, 1040–1041, 1060, 1067–1068, 1087, 1109, 1111, 1120–1121, 1127, 1149–1150, 1156–1157, 1166–1176, 1183–1189, 1196–1202, 1209–1218, 1225–1228
packages/azure-ai/agent_framework_azure_ai
   _agent_provider.py115397%121–122, 250
   _chat_client.py5058882%382, 385, 388, 391–392, 394–400, 572, 577–578, 580–581, 584, 587, 589, 594, 855–856, 858, 861, 864, 867–872, 875, 877, 885, 897–899, 903, 906–907, 915–918, 928, 936–939, 941–942, 944–945, 952, 960–961, 969–970, 975–976, 980–987, 992, 995, 1003, 1009, 1017–1019, 1022, 1044–1045, 1178, 1206, 1221, 1255, 1344, 1360, 1368, 1382, 1502
   _client.py1943283%359, 361, 410, 439–444, 487, 522, 524, 584, 586–587, 589–590, 592–595, 597–598, 600–602, 604–607, 609, 656
   _project_provider.py115694%131–132, 210, 308, 352, 385
   _shared.py2822491%112, 153, 157, 165, 179, 251, 294, 306–308, 337, 339, 341, 345, 421, 437–438, 451, 453, 481, 522, 529, 541, 560
packages/core/agent_framework
   _agents.py3203589%472, 884, 920, 1019–1021, 1134, 1175, 1177, 1186–1191, 1197, 1199, 1209–1210, 1217, 1219–1220, 1228–1232, 1240–1241, 1243, 1248, 1250, 1284, 1324, 1344
   _clients.py72395%299, 500, 502
   _mcp.py3915386%125, 187, 196, 259, 267, 288, 378, 445, 480, 482, 486–487, 489–490, 544, 559, 577, 618, 724, 737–742, 764, 785, 788–790, 805–806, 812–814, 833, 842, 845–847, 862–863, 867–871, 888–892, 1032
   _memory.py26196%152
   _tools.py7168288%176–177, 291, 293, 310–312, 319, 337, 351, 363, 368, 370, 377, 410, 481–483, 524, 546–574, 609, 617, 858, 1063, 1120, 1124, 1203–1207, 1225, 1227–1228, 1333, 1337, 1387, 1389, 1405, 1407, 1471, 1498, 1551, 1619, 1798–1799, 1826, 1834, 1847, 1857–1858, 1893, 1949, 1981
   _types.py10389690%81, 90–91, 145, 150, 169, 171, 175, 179, 181, 183, 185, 203, 207, 233, 255, 260, 265, 269, 295, 299, 645–646, 1017, 1079, 1096, 1114, 1119, 1137, 1147, 1164–1165, 1167, 1185–1186, 1188, 1195–1196, 1198, 1233, 1244–1245, 1247, 1285, 1542, 1595, 1602, 1624, 1630, 1678, 1721–1726, 1748, 1753, 1882, 1894, 2133, 2142, 2162, 2254, 2475, 2743, 2747, 2759, 2766, 2777, 2981–2983, 2986–2988, 2992, 2997, 3001, 3113–3115, 3143, 3197, 3201–3203, 3205, 3216–3217, 3220–3224, 3230
packages/core/agent_framework/openai
   _assistant_provider.py1101190%156–157, 169, 294, 360, 475–480
   _assistants_client.py2773587%412, 414, 416, 419, 423–424, 427, 430, 435–436, 438, 441–443, 448, 459, 484, 486, 488, 490, 492, 497, 500, 503, 507, 518, 603, 688, 717, 754–757, 809, 826
   _chat_client.py2732291%200, 230–231, 235, 354, 361, 442–449, 451–454, 464, 549, 586, 602
   _responses_client.py5666688%272–273, 278, 309, 317, 340, 420, 511, 514, 569, 573, 575, 577, 579, 655, 665, 670, 713, 771, 785, 802, 815, 870, 949, 954, 958–960, 964–965, 988, 1057, 1079–1080, 1095–1096, 1114–1115, 1246–1247, 1263, 1265, 1340–1348, 1396, 1451, 1466, 1502–1503, 1505–1507, 1521–1523, 1533–1534, 1540, 1555
   _shared.py1281687%63, 69–72, 151, 153, 155, 162, 164, 177, 253, 277, 336–337, 339
TOTAL16548206887% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
3897 225 💤 0 ❌ 0 🔥 1m 9s ⏱️

@giles17 giles17 changed the title Python: [BREAKING] Replace Hosted*Tool classes with client static factory methods Python: [BREAKING] Replace Hosted*Tool classes with tool methods Feb 3, 2026
Copy link
Member

@eavanvalkenburg eavanvalkenburg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of notes, overall I think we will have to move to remove some additional pieces of from tools and streamline things even more, I would expect only the functionTool to be handled in the each clients prepare code, everything else should already be setup correctly.

})
elif isinstance(tool, MutableMapping):
# Handle dict-based tools from static factory methods
tool_dict = tool if isinstance(tool, dict) else dict(tool)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed, you can safely ignore and assume these are dicts


server_label = tool_dict.get("server_label")
if not server_label:
continue
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems strange, it is a mcp, but not valid, then it should raise!

}

mcp_resources.append(mcp_resource)
for tool in tools:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be simpler, just do a list comprehension with if type == 'mcp' and then pop type, and pass through, the server label should come with the get.. Method already, if people do not put in the server label when constructing manually, let the service raise

if not bing_search:
if isinstance(tool, FunctionTool):
tool_definitions.append(tool.to_json_schema_spec()) # type: ignore[reportUnknownArgumentType]
elif isinstance(tool, ToolDefinition):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to me like we should allow Any as type for the tool list, and then we can make it more specific for each implementation

if not server_label or not server_url:
raise ServiceInitializationError("MCP tool requires 'server_label' and 'server_url'.")
allowed_tools = tool_dict.get("allowed_tools", [])
mcp_tool = McpTool(server_label=server_label, server_url=server_url, allowed_tools=allowed_tools)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why wouldnt a MCPTool be passed instead of this dict?

allowed_tools: list[str] | None = None,
headers: dict[str, str] | None = None,
project_connection_id: str | None = None,
) -> Any:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You know what type this is, no?

"""

@staticmethod
def get_code_interpreter_tool(**kwargs: Any) -> dict[str, Any]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return type should be more flexibel

async def main() -> None:
"""Example of streaming response (get results as they are generated)."""
# Create MCP tool configuration using static method
mcp_tool = AnthropicClient.get_mcp_tool(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the samples I think we should follow the pattern of client. Instead of class, because clients should be easy and this makes it harder!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation lab Agent Framework Lab python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants