Python: Fix tool normalization and provider sample consolidation (#3953)

* Fix tool normalization and provider samples

- restore callable/single-tool normalization paths and unset tool-choice behavior\n- consolidate and expand chat/provider samples (OpenAI/Azure/Anthropic/Ollama/Bedrock)\n- migrate Bedrock lazy import surface to agent_framework.amazon and move provider samples

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* small fix in sample

* Finalize provider, samples, and core cleanup

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix CopilotTool passthrough in agent

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix link

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Eduard van Valkenburg
2026-02-16 17:30:38 +01:00
committed by GitHub
Unverified
parent ed113f941c
commit aab621f5eb
99 changed files with 1190 additions and 969 deletions
@@ -720,6 +720,8 @@ class AnthropicClient(
if options.get("tool_choice") is None:
return result or None
tool_mode = validate_tool_mode(options.get("tool_choice"))
if tool_mode is None:
return result or None
allow_multiple = options.get("allow_multiple_tool_calls")
match tool_mode.get("mode"):
case "auto":
@@ -16,6 +16,7 @@ from agent_framework import (
)
from agent_framework._mcp import MCPTool
from agent_framework._settings import load_settings
from agent_framework._tools import ToolTypes
from agent_framework.exceptions import ServiceInitializationError
from azure.ai.agents.aio import AgentsClient
from azure.ai.agents.models import Agent as AzureAgent
@@ -169,11 +170,7 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
model: str | None = None,
instructions: str | None = None,
description: str | None = None,
tools: FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
| None = None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
default_options: OptionsCoT | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
context_providers: Sequence[BaseContextProvider] | None = None,
@@ -242,7 +239,12 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
normalized_tools = normalize_tools(tools)
if normalized_tools:
# Only convert non-MCP tools to Azure AI format
non_mcp_tools = [t for t in normalized_tools if not isinstance(t, MCPTool)]
non_mcp_tools: list[FunctionTool | MutableMapping[str, Any]] = []
for normalized_tool in normalized_tools:
if isinstance(normalized_tool, MCPTool):
continue
if isinstance(normalized_tool, (FunctionTool, MutableMapping)):
non_mcp_tools.append(normalized_tool)
if non_mcp_tools:
# Pass run_options to capture tool_resources (e.g., for file search vector stores)
run_options: dict[str, Any] = {}
@@ -266,11 +268,7 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
self,
id: str,
*,
tools: FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
| None = None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
default_options: OptionsCoT | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
context_providers: Sequence[BaseContextProvider] | None = None,
@@ -322,11 +320,7 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
def as_agent(
self,
agent: AzureAgent,
tools: FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
| None = None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
default_options: OptionsCoT | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
context_providers: Sequence[BaseContextProvider] | None = None,
@@ -379,7 +373,7 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
def _to_chat_agent_from_agent(
self,
agent: AzureAgent,
provided_tools: Sequence[FunctionTool | MutableMapping[str, Any]] | None = None,
provided_tools: Sequence[ToolTypes] | None = None,
default_options: OptionsCoT | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
context_providers: Sequence[BaseContextProvider] | None = None,
@@ -422,8 +416,8 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
def _merge_tools(
self,
agent_tools: Sequence[Any] | None,
provided_tools: Sequence[FunctionTool | MutableMapping[str, Any]] | None,
) -> list[FunctionTool | dict[str, Any]]:
provided_tools: Sequence[ToolTypes] | None,
) -> list[ToolTypes]:
"""Merge hosted tools from agent with user-provided function tools.
Args:
@@ -433,7 +427,7 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
Returns:
Combined list of tools for the Agent.
"""
merged: list[FunctionTool | dict[str, Any]] = []
merged: list[ToolTypes] = []
# Convert hosted tools from agent definition
hosted_tools = from_azure_ai_agent_tools(agent_tools)
@@ -459,7 +453,7 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
def _validate_function_tools(
self,
agent_tools: Sequence[Any] | None,
provided_tools: Sequence[FunctionTool | MutableMapping[str, Any]] | None,
provided_tools: Sequence[ToolTypes] | None,
) -> None:
"""Validate that required function tools are provided.
@@ -34,6 +34,7 @@ from agent_framework import (
UsageDetails,
)
from agent_framework._settings import load_settings
from agent_framework._tools import ToolTypes
from agent_framework.exceptions import ServiceInitializationError, ServiceInvalidRequestError, ServiceResponseException
from agent_framework.observability import ChatTelemetryLayer
from azure.ai.agents.aio import AgentsClient
@@ -1428,11 +1429,7 @@ class AzureAIAgentClient(
name: str | None = None,
description: str | None = None,
instructions: str | None = None,
tools: FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
| None = None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
default_options: AzureAIAgentOptionsT | Mapping[str, Any] | None = None,
context_providers: Sequence[BaseContextProvider] | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
@@ -5,7 +5,7 @@ from __future__ import annotations
import json
import logging
import sys
from collections.abc import Callable, Mapping, MutableMapping, Sequence
from collections.abc import Callable, Mapping, Sequence
from contextlib import suppress
from typing import Any, ClassVar, Generic, Literal, TypedDict, TypeVar, cast
@@ -22,6 +22,7 @@ from agent_framework import (
MiddlewareTypes,
)
from agent_framework._settings import load_settings
from agent_framework._tools import ToolTypes
from agent_framework.exceptions import ServiceInitializationError
from agent_framework.observability import ChatTelemetryLayer
from agent_framework.openai import OpenAIResponsesOptions
@@ -880,11 +881,7 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
name: str | None = None,
description: str | None = None,
instructions: str | None = None,
tools: FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
| None = None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
default_options: AzureAIClientOptionsT | Mapping[str, Any] | None = None,
context_providers: Sequence[BaseContextProvider] | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
@@ -17,6 +17,7 @@ from agent_framework import (
)
from agent_framework._mcp import MCPTool
from agent_framework._settings import load_settings
from agent_framework._tools import ToolTypes
from agent_framework.exceptions import ServiceInitializationError
from azure.ai.projects.aio import AIProjectClient
from azure.ai.projects.models import (
@@ -161,11 +162,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
model: str | None = None,
instructions: str | None = None,
description: str | None = None,
tools: FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
| None = None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
default_options: OptionsCoT | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
context_providers: Sequence[BaseContextProvider] | None = None,
@@ -226,7 +223,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
for tool in normalized_tools:
if isinstance(tool, MCPTool):
mcp_tools.append(tool)
else:
elif isinstance(tool, (FunctionTool, MutableMapping)):
non_mcp_tools.append(tool)
# Connect MCP tools and discover their functions BEFORE creating the agent
@@ -263,11 +260,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
*,
name: str | None = None,
reference: AgentReference | None = None,
tools: FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
| None = None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
default_options: OptionsCoT | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
context_providers: Sequence[BaseContextProvider] | None = None,
@@ -323,11 +316,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
def as_agent(
self,
details: AgentVersionDetails,
tools: FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
| None = None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
default_options: OptionsCoT | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
context_providers: Sequence[BaseContextProvider] | None = None,
@@ -367,7 +356,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
def _to_chat_agent_from_details(
self,
details: AgentVersionDetails,
provided_tools: Sequence[FunctionTool | MutableMapping[str, Any]] | None = None,
provided_tools: Sequence[ToolTypes] | None = None,
default_options: OptionsCoT | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
context_providers: Sequence[BaseContextProvider] | None = None,
@@ -415,8 +404,8 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
def _merge_tools(
self,
definition_tools: Sequence[Any] | None,
provided_tools: Sequence[FunctionTool | MutableMapping[str, Any]] | None,
) -> list[FunctionTool | dict[str, Any]]:
provided_tools: Sequence[ToolTypes] | None,
) -> list[ToolTypes]:
"""Merge hosted tools from definition with user-provided function tools.
Args:
@@ -426,7 +415,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
Returns:
Combined list of tools for the Agent.
"""
merged: list[FunctionTool | dict[str, Any]] = []
merged: list[ToolTypes] = []
# Convert hosted tools from definition (MCP, code interpreter, file search, web search)
# Function tools from the definition are skipped - we use user-provided implementations instead
@@ -450,11 +439,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
def _validate_function_tools(
self,
agent_tools: Sequence[Any] | None,
provided_tools: FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
| None,
provided_tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
) -> None:
"""Validate that required function tools are provided."""
# Normalize and validate function tools
+2 -2
View File
@@ -12,7 +12,7 @@ Integration with AWS Bedrock for LLM inference.
## Usage
```python
from agent_framework_bedrock import BedrockChatClient
from agent_framework.amazon import BedrockChatClient
client = BedrockChatClient(model_id="anthropic.claude-3-sonnet-20240229-v1:0")
response = await client.get_response("Hello")
@@ -21,5 +21,5 @@ response = await client.get_response("Hello")
## Import Path
```python
from agent_framework_bedrock import BedrockChatClient
from agent_framework.amazon import BedrockChatClient
```
+1 -1
View File
@@ -12,7 +12,7 @@ The Bedrock integration enables Microsoft Agent Framework applications to call A
### Basic Usage Example
See the [Bedrock sample script](samples/bedrock_sample.py) for a runnable end-to-end script that:
See the [Bedrock sample](../../samples/02-agents/providers/amazon/bedrock_chat_client.py) for a runnable end-to-end script that:
- Loads credentials from the `BEDROCK_*` environment variables
- Instantiates `BedrockChatClient`
@@ -260,7 +260,7 @@ class BedrockChatClient(
Examples:
.. code-block:: python
from agent_framework.bedrock import BedrockChatClient
from agent_framework.amazon import BedrockChatClient
# Basic usage with default credentials
client = BedrockChatClient(model_id="<model name>")
@@ -1,45 +0,0 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import logging
from agent_framework import Agent, tool
from agent_framework_bedrock import BedrockChatClient
@tool(approval_mode="never_require")
def get_weather(city: str) -> dict[str, str]:
"""Return a mock forecast for the requested city."""
normalized = city.strip() or "New York"
return {"city": normalized, "forecast": "72F and sunny"}
async def main() -> None:
"""Run the Bedrock sample agent, invoke the weather tool, and log the response."""
agent = Agent(
client=BedrockChatClient(),
instructions="You are a concise travel assistant.",
name="BedrockWeatherAgent",
tool_choice="auto",
tools=[get_weather],
)
response = await agent.run("Use the weather tool to check the forecast for new york.")
logging.info("\nAssistant reply:", response.text or "<no text returned>")
logging.info("\nConversation transcript:")
for message in response.messages:
for idx, content in enumerate(message.contents, start=1):
match content.type:
case "text":
logging.info(f" {idx}. text -> {content.text}")
case "function_call":
logging.info(f" {idx}. function_call ({content.name}) -> {content.arguments}")
case "function_result":
logging.info(f" {idx}. function_result ({content.call_id}) -> {content.result}")
case _:
logging.info(f" {idx}. {content.type}")
if __name__ == "__main__":
asyncio.run(main())
@@ -23,6 +23,7 @@ from agent_framework import (
normalize_messages,
)
from agent_framework._settings import load_settings
from agent_framework._tools import ToolTypes
from agent_framework._types import AgentRunInputs, normalize_tools
from agent_framework.exceptions import ServiceException
from claude_agent_sdk import (
@@ -217,12 +218,7 @@ class ClaudeAgent(BaseAgent, Generic[OptionsT]):
description: str | None = None,
context_providers: Sequence[BaseContextProvider] | None = None,
middleware: Sequence[AgentMiddlewareTypes] | None = None,
tools: FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| str
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any] | str]
| None = None,
tools: ToolTypes | Callable[..., Any] | str | Sequence[ToolTypes | Callable[..., Any] | str] | None = None,
default_options: OptionsT | MutableMapping[str, Any] | None = None,
env_file_path: str | None = None,
env_file_encoding: str | None = None,
@@ -289,7 +285,7 @@ class ClaudeAgent(BaseAgent, Generic[OptionsT]):
# Separate built-in tools (strings) from custom tools (callables/FunctionTool)
self._builtin_tools: list[str] = []
self._custom_tools: list[FunctionTool | MutableMapping[str, Any]] = []
self._custom_tools: list[ToolTypes] = []
self._normalize_tools(tools)
self._default_options = opts
@@ -298,12 +294,7 @@ class ClaudeAgent(BaseAgent, Generic[OptionsT]):
def _normalize_tools(
self,
tools: FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| str
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any] | str]
| None,
tools: ToolTypes | Callable[..., Any] | str | Sequence[ToolTypes | Callable[..., Any] | str] | None,
) -> None:
"""Separate built-in tools (strings) from custom tools.
@@ -316,10 +307,10 @@ class ClaudeAgent(BaseAgent, Generic[OptionsT]):
# Normalize to sequence
if isinstance(tools, str):
tools_list: Sequence[Any] = [tools]
elif isinstance(tools, (FunctionTool, MutableMapping)) or callable(tools):
tools_list = [tools]
else:
elif isinstance(tools, Sequence):
tools_list = list(tools)
else:
tools_list = [tools]
for tool in tools_list:
if isinstance(tool, str):
@@ -457,7 +448,7 @@ class ClaudeAgent(BaseAgent, Generic[OptionsT]):
def _prepare_tools(
self,
tools: list[FunctionTool | MutableMapping[str, Any]],
tools: Sequence[ToolTypes],
) -> tuple[Any, list[str]]:
"""Convert Agent Framework tools to SDK MCP server.
@@ -1,5 +1,13 @@
# Copyright (c) Microsoft. All rights reserved.
"""Public API surface for Agent Framework core.
This module exposes the primary abstractions for agents, chat clients, tools, sessions,
middleware, observability, and workflows. Connector namespaces such as
``agent_framework.azure`` and ``agent_framework.anthropic`` provide provider-specific
integrations, many of which are lazy-loaded from optional packages.
"""
import importlib.metadata
from typing import Final
+12 -57
View File
@@ -37,6 +37,8 @@ from ._sessions import AgentSession, BaseContextProvider, BaseHistoryProvider, I
from ._tools import (
FunctionInvocationLayer,
FunctionTool,
ToolTypes,
normalize_tools,
)
from ._types import (
AgentResponse,
@@ -614,12 +616,7 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
id: str | None = None,
name: str | None = None,
description: str | None = None,
tools: FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Any
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any] | Any]
| None = None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
default_options: OptionsCoT | None = None,
context_providers: Sequence[BaseContextProvider] | None = None,
**kwargs: Any,
@@ -665,24 +662,14 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
# Get tools from options or named parameter (named param takes precedence)
tools_ = tools if tools is not None else opts.pop("tools", None)
tools_ = cast(
FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| list[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
| None,
tools_,
)
# Handle instructions - named parameter takes precedence over options
instructions_ = instructions if instructions is not None else opts.pop("instructions", None)
# We ignore the MCP Servers here and store them separately,
# we add their functions to the tools list at runtime
normalized_tools: list[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]] = ( # type:ignore[reportUnknownVariableType]
[] if tools_ is None else tools_ if isinstance(tools_, list) else [tools_] # type: ignore[list-item]
)
self.mcp_tools: list[MCPTool] = [tool for tool in normalized_tools if isinstance(tool, MCPTool)] # type: ignore[misc]
normalized_tools = normalize_tools(tools_)
self.mcp_tools: list[MCPTool] = [tool for tool in normalized_tools if isinstance(tool, MCPTool)]
agent_tools = [tool for tool in normalized_tools if not isinstance(tool, MCPTool)]
# Build chat options dict
@@ -765,12 +752,7 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
*,
stream: Literal[False] = ...,
session: AgentSession | None = None,
tools: FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Any
| list[FunctionTool | Callable[..., Any] | MutableMapping[str, Any] | Any]
| None = None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
options: ChatOptions[ResponseModelBoundT],
**kwargs: Any,
) -> Awaitable[AgentResponse[ResponseModelBoundT]]: ...
@@ -782,12 +764,7 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
*,
stream: Literal[False] = ...,
session: AgentSession | None = None,
tools: FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Any
| list[FunctionTool | Callable[..., Any] | MutableMapping[str, Any] | Any]
| None = None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
options: OptionsCoT | ChatOptions[None] | None = None,
**kwargs: Any,
) -> Awaitable[AgentResponse[Any]]: ...
@@ -799,12 +776,7 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
*,
stream: Literal[True],
session: AgentSession | None = None,
tools: FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Any
| list[FunctionTool | Callable[..., Any] | MutableMapping[str, Any] | Any]
| None = None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
options: OptionsCoT | ChatOptions[Any] | None = None,
**kwargs: Any,
) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ...
@@ -815,12 +787,7 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
*,
stream: bool = False,
session: AgentSession | None = None,
tools: FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Any
| list[FunctionTool | Callable[..., Any] | MutableMapping[str, Any] | Any]
| None = None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
options: OptionsCoT | ChatOptions[Any] | None = None,
**kwargs: Any,
) -> Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]:
@@ -1000,12 +967,7 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
*,
messages: AgentRunInputs | None,
session: AgentSession | None,
tools: FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Any
| list[FunctionTool | Callable[..., Any] | MutableMapping[str, Any] | Any]
| None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
options: Mapping[str, Any] | None,
kwargs: dict[str, Any],
) -> _RunContext:
@@ -1035,9 +997,7 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
)
# Normalize tools
normalized_tools: list[FunctionTool | Callable[..., Any] | MutableMapping[str, Any] | Any] = (
[] if tools_ is None else tools_ if isinstance(tools_, list) else [tools_]
)
normalized_tools = normalize_tools(tools_)
agent_name = self._get_agent_name()
# Resolve final tool list (runtime provided tools + local MCP server tools)
@@ -1343,12 +1303,7 @@ class Agent(
id: str | None = None,
name: str | None = None,
description: str | None = None,
tools: FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Any
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any] | Any]
| None = None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
default_options: OptionsCoT | None = None,
context_providers: Sequence[BaseContextProvider] | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
@@ -10,7 +10,6 @@ from collections.abc import (
Awaitable,
Callable,
Mapping,
MutableMapping,
Sequence,
)
from typing import (
@@ -31,7 +30,7 @@ from pydantic import BaseModel
from ._serialization import SerializationMixin
from ._tools import (
FunctionInvocationConfiguration,
FunctionTool,
ToolTypes,
)
from ._types import (
ChatResponse,
@@ -436,11 +435,7 @@ class BaseChatClient(SerializationMixin, ABC, Generic[OptionsCoT]):
name: str | None = None,
description: str | None = None,
instructions: str | None = None,
tools: FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
| None = None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
default_options: OptionsCoT | Mapping[str, Any] | None = None,
context_providers: Sequence[Any] | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
+62 -46
View File
@@ -12,7 +12,6 @@ from collections.abc import (
Awaitable,
Callable,
Mapping,
MutableMapping,
Sequence,
)
from functools import partial, wraps
@@ -25,6 +24,7 @@ from typing import (
Final,
Generic,
Literal,
TypeAlias,
TypedDict,
Union,
get_args,
@@ -58,6 +58,7 @@ else:
if TYPE_CHECKING:
from ._clients import SupportsChatGetResponse
from ._mcp import MCPTool
from ._middleware import FunctionMiddlewarePipeline, FunctionMiddlewareTypes
from ._types import (
ChatOptions,
@@ -69,6 +70,8 @@ if TYPE_CHECKING:
)
ResponseModelBoundT = TypeVar("ResponseModelBoundT", bound=BaseModel)
else:
MCPTool = Any # type: ignore[assignment,misc]
logger = logging.getLogger("agent_framework")
@@ -506,9 +509,7 @@ class FunctionTool(SerializationMixin):
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED: # type: ignore[name-defined]
attributes.update({
OtelAttr.TOOL_ARGUMENTS: (
json.dumps(serializable_kwargs, default=str, ensure_ascii=False)
if serializable_kwargs
else "None"
json.dumps(serializable_kwargs, default=str, ensure_ascii=False) if serializable_kwargs else "None"
)
})
with get_function_span(attributes=attributes) as span:
@@ -623,14 +624,46 @@ class FunctionTool(SerializationMixin):
return as_dict
ToolTypes: TypeAlias = FunctionTool | MCPTool | Mapping[str, Any] | Any
def normalize_tools(
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
) -> list[ToolTypes]:
"""Normalize tool inputs while preserving non-callable tool objects.
Args:
tools: A single tool or sequence of tools.
Returns:
A normalized list where callable inputs are converted to ``FunctionTool``
using :func:`tool`, and existing tool objects are passed through unchanged.
"""
if not tools:
return []
tool_items = (
list(tools)
if isinstance(tools, Sequence) and not isinstance(tools, (str, bytes, bytearray, Mapping))
else [tools]
)
from ._mcp import MCPTool
normalized: list[ToolTypes] = []
for tool_item in tool_items:
# check known types, these are also callable, so we need to do that first
if isinstance(tool_item, (FunctionTool, Mapping, MCPTool)):
normalized.append(tool_item)
continue
if callable(tool_item):
normalized.append(tool(tool_item))
continue
normalized.append(tool_item)
return normalized
def _tools_to_dict(
tools: (
FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
| None
),
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
) -> list[str | dict[str, Any]] | None:
"""Parse the tools to a dict.
@@ -640,32 +673,20 @@ def _tools_to_dict(
Returns:
A list of tool specifications as dictionaries, or None if no tools provided.
"""
if not tools:
return None
if not isinstance(tools, list):
if isinstance(tools, FunctionTool):
return [tools.to_json_schema_spec()]
if isinstance(tools, SerializationMixin):
return [tools.to_dict()]
if isinstance(tools, dict):
return [tools]
if callable(tools):
return [tool(tools).to_json_schema_spec()]
logger.warning("Can't parse tool.")
normalized_tools = normalize_tools(tools)
if not normalized_tools:
return None
results: list[str | dict[str, Any]] = []
for tool_item in tools:
for tool_item in normalized_tools:
if isinstance(tool_item, FunctionTool):
results.append(tool_item.to_json_schema_spec())
continue
if isinstance(tool_item, SerializationMixin):
results.append(tool_item.to_dict())
continue
if isinstance(tool_item, dict):
results.append(tool_item)
continue
if callable(tool_item):
results.append(tool(tool_item).to_json_schema_spec())
if isinstance(tool_item, Mapping):
results.append(dict(tool_item))
continue
logger.warning("Can't parse tool.")
return results
@@ -1430,20 +1451,12 @@ async def _auto_invoke_function(
def _get_tool_map(
tools: FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]],
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]],
) -> dict[str, FunctionTool]:
tool_list: dict[str, FunctionTool] = {}
for tool_item in tools if isinstance(tools, list) else [tools]:
for tool_item in normalize_tools(tools):
if isinstance(tool_item, FunctionTool):
tool_list[tool_item.name] = tool_item
continue
if callable(tool_item):
# Convert to AITool if it's a function or callable
ai_tool = tool(tool_item)
tool_list[ai_tool.name] = ai_tool
return tool_list
@@ -1451,10 +1464,7 @@ async def _try_execute_function_calls(
custom_args: dict[str, Any],
attempt_idx: int,
function_calls: Sequence[Content],
tools: FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]],
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]],
config: FunctionInvocationConfiguration,
middleware_pipeline: Any = None, # Optional MiddlewarePipeline to avoid circular imports
) -> tuple[Sequence[Content], bool]:
@@ -1633,15 +1643,16 @@ async def _ensure_response_stream(
return stream
def _extract_tools(options: dict[str, Any] | None) -> Any:
def _extract_tools(
options: dict[str, Any] | None,
) -> ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None:
"""Extract tools from options dict.
Args:
options: The options dict containing chat options.
Returns:
FunctionTool | Callable[..., Any] | MutableMapping[str, Any] |
Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]] | None
ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None
"""
if options and isinstance(options, dict):
return options.get("tools")
@@ -1996,6 +2007,11 @@ class FunctionInvocationLayer(Generic[OptionsCoT]):
# Remove additional_function_arguments from options passed to underlying chat client
# It's for tool invocation only and not recognized by chat service APIs
mutable_options.pop("additional_function_arguments", None)
# Support tools passed via kwargs in direct client.get_response(...) calls.
if "tools" in filtered_kwargs:
if mutable_options.get("tools") is None:
mutable_options["tools"] = filtered_kwargs["tools"]
filtered_kwargs.pop("tools", None)
if not stream:
+17 -44
View File
@@ -15,7 +15,8 @@ from typing import TYPE_CHECKING, Any, ClassVar, Final, Generic, Literal, NewTyp
from pydantic import BaseModel
from ._serialization import SerializationMixin
from ._tools import FunctionTool, tool
from ._tools import ToolTypes
from ._tools import normalize_tools as _normalize_tools
from .exceptions import AdditionItemMismatch, ContentError
if sys.version_info >= (3, 13):
@@ -2871,10 +2872,9 @@ class _ChatOptionsBase(TypedDict, total=False):
# Tool configuration (forward reference to avoid circular import)
tools: (
FunctionTool
ToolTypes
| Callable[..., Any]
| MutableMapping[str, Any]
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
| Sequence[ToolTypes | Callable[..., Any]]
| None
)
tool_choice: ToolMode | Literal["auto", "required", "none"]
@@ -2963,18 +2963,11 @@ async def validate_chat_options(options: dict[str, Any]) -> dict[str, Any]:
def normalize_tools(
tools: (
FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
| None
),
) -> list[FunctionTool | MutableMapping[str, Any]]:
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
) -> list[ToolTypes]:
"""Normalize tools into a list.
Converts callables to FunctionTool objects and ensures all tools are either
FunctionTool instances or MutableMappings.
Converts callables to FunctionTool objects and preserves existing tool objects.
Args:
tools: Tools to normalize - can be a single tool, callable, or sequence.
@@ -2999,37 +2992,16 @@ def normalize_tools(
# List of tools
tools = normalize_tools([my_tool, another_tool])
"""
final_tools: list[FunctionTool | MutableMapping[str, Any]] = []
if not tools:
return final_tools
if not isinstance(tools, Sequence) or isinstance(tools, (str, MutableMapping)):
# Single tool (not a sequence, or is a mapping which shouldn't be treated as sequence)
if not isinstance(tools, (FunctionTool, MutableMapping)):
return [tool(tools)]
return [tools]
for tool_item in tools:
if isinstance(tool_item, (FunctionTool, MutableMapping)):
final_tools.append(tool_item)
else:
# Convert callable to FunctionTool
final_tools.append(tool(tool_item))
return final_tools
return _normalize_tools(tools)
async def validate_tools(
tools: (
FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
| None
),
) -> list[FunctionTool | MutableMapping[str, Any]]:
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
) -> list[ToolTypes]:
"""Validate and normalize tools into a list.
Converts callables to FunctionTool objects, expands MCP tools to their constituent
functions (connecting them if needed), and ensures all tools are either FunctionTool
instances or MutableMappings.
functions (connecting them if needed), while preserving non-callable tool objects.
Args:
tools: Tools to validate - can be a single tool, callable, or sequence.
@@ -3058,7 +3030,7 @@ async def validate_tools(
normalized = normalize_tools(tools)
# Handle MCP tool expansion (async-only)
final_tools: list[FunctionTool | MutableMapping[str, Any]] = []
final_tools: list[ToolTypes] = []
for tool_ in normalized:
# Import MCPTool here to avoid circular imports
from ._mcp import MCPTool
@@ -3076,20 +3048,21 @@ async def validate_tools(
def validate_tool_mode(
tool_choice: ToolMode | Literal["auto", "required", "none"] | None,
) -> ToolMode:
) -> ToolMode | None:
"""Validate and normalize tool_choice to a ToolMode dict.
Args:
tool_choice: The tool choice value to validate.
Returns:
A ToolMode dict (contains keys: "mode", and optionally "required_function_name").
A ToolMode dict (contains keys: "mode", and optionally
"required_function_name"), or ``None`` when not provided.
Raises:
ContentError: If the tool_choice string is invalid.
"""
if not tool_choice:
return {"mode": "none"}
if tool_choice is None:
return None
if isinstance(tool_choice, str):
if tool_choice not in ("auto", "required", "none"):
raise ContentError(f"Invalid tool choice: {tool_choice}")
@@ -1,5 +1,18 @@
# Copyright (c) Microsoft. All rights reserved.
"""Workflow namespace for built-in Agent Framework orchestration primitives.
This module re-exports objects from workflow implementation modules under
``agent_framework._workflows``.
Supported classes include:
- Workflow
- WorkflowBuilder
- AgentExecutor
- Runner
- WorkflowExecutor
"""
from ._agent import WorkflowAgent
from ._agent_executor import (
AgentExecutor,
@@ -1,11 +1,20 @@
# Copyright (c) Microsoft. All rights reserved.
"""A2A integration namespace for optional Agent Framework connectors.
This module lazily re-exports objects from:
- ``agent-framework-a2a``
Supported classes:
- A2AAgent
"""
import importlib
from typing import Any
IMPORT_PATH = "agent_framework_a2a"
PACKAGE_NAME = "agent-framework-a2a"
_IMPORTS = ["__version__", "A2AAgent"]
_IMPORTS = ["A2AAgent"]
def __getattr__(name: str) -> Any:
@@ -2,10 +2,8 @@
from agent_framework_a2a import (
A2AAgent,
__version__,
)
__all__ = [
"A2AAgent",
"__version__",
]
@@ -1,12 +1,24 @@
# Copyright (c) Microsoft. All rights reserved.
"""AG-UI integration namespace for optional Agent Framework connectors.
This module lazily re-exports objects from:
- ``agent-framework-ag-ui``
Supported classes and functions:
- AgentFrameworkAgent
- AGUIChatClient
- AGUIEventConverter
- AGUIHttpService
- add_agent_framework_fastapi_endpoint
"""
import importlib
from typing import Any
IMPORT_PATH = "agent_framework_ag_ui"
PACKAGE_NAME = "agent-framework-ag-ui"
_IMPORTS = [
"__version__",
"AgentFrameworkAgent",
"add_agent_framework_fastapi_endpoint",
"AGUIChatClient",
@@ -5,7 +5,6 @@ from agent_framework_ag_ui import (
AGUIChatClient,
AGUIEventConverter,
AGUIHttpService,
__version__,
add_agent_framework_fastapi_endpoint,
)
@@ -14,6 +13,5 @@ __all__ = [
"AGUIEventConverter",
"AGUIHttpService",
"AgentFrameworkAgent",
"__version__",
"add_agent_framework_fastapi_endpoint",
]
@@ -0,0 +1,35 @@
# Copyright (c) Microsoft. All rights reserved.
"""Amazon Bedrock integration namespace for optional Agent Framework connectors.
This module lazily re-exports objects from:
- ``agent-framework-bedrock``
Supported classes:
- BedrockChatClient
- BedrockChatOptions
- BedrockGuardrailConfig
- BedrockSettings
"""
import importlib
from typing import Any
IMPORT_PATH = "agent_framework_bedrock"
PACKAGE_NAME = "agent-framework-bedrock"
_IMPORTS = ["BedrockChatClient", "BedrockChatOptions", "BedrockGuardrailConfig", "BedrockSettings"]
def __getattr__(name: str) -> Any:
if name in _IMPORTS:
try:
return getattr(importlib.import_module(IMPORT_PATH), name)
except ModuleNotFoundError as exc:
raise ModuleNotFoundError(
f"The '{PACKAGE_NAME}' package is not installed, please do `pip install {PACKAGE_NAME}`"
) from exc
raise AttributeError(f"Module {IMPORT_PATH} has no attribute {name}.")
def __dir__() -> list[str]:
return _IMPORTS
@@ -0,0 +1,15 @@
# Copyright (c) Microsoft. All rights reserved.
from agent_framework_bedrock import (
BedrockChatClient,
BedrockChatOptions,
BedrockGuardrailConfig,
BedrockSettings,
)
__all__ = [
"BedrockChatClient",
"BedrockChatOptions",
"BedrockGuardrailConfig",
"BedrockSettings",
]
@@ -1,23 +1,40 @@
# Copyright (c) Microsoft. All rights reserved.
"""Anthropic integration namespace for optional Agent Framework connectors.
This module lazily re-exports objects from:
- ``agent-framework-anthropic``
- ``agent-framework-claude``
Supported classes:
- AnthropicClient
- AnthropicChatOptions
- ClaudeAgent
- ClaudeAgentOptions
"""
import importlib
from typing import Any
IMPORT_PATH = "agent_framework_anthropic"
PACKAGE_NAME = "agent-framework-anthropic"
_IMPORTS = ["__version__", "AnthropicClient", "AnthropicChatOptions"]
_IMPORTS: dict[str, tuple[str, str]] = {
"AnthropicClient": ("agent_framework_anthropic", "agent-framework-anthropic"),
"AnthropicChatOptions": ("agent_framework_anthropic", "agent-framework-anthropic"),
"ClaudeAgent": ("agent_framework_claude", "agent-framework-claude"),
"ClaudeAgentOptions": ("agent_framework_claude", "agent-framework-claude"),
}
def __getattr__(name: str) -> Any:
if name in _IMPORTS:
import_path, package_name = _IMPORTS[name]
try:
return getattr(importlib.import_module(IMPORT_PATH), name)
return getattr(importlib.import_module(import_path), name)
except ModuleNotFoundError as exc:
raise ModuleNotFoundError(
f"The '{PACKAGE_NAME}' package is not installed, please do `pip install {PACKAGE_NAME}`"
f"The '{package_name}' package is not installed, please do `pip install {package_name}`"
) from exc
raise AttributeError(f"Module {IMPORT_PATH} has no attribute {name}.")
raise AttributeError(f"Module `anthropic` has no attribute {name}.")
def __dir__() -> list[str]:
return _IMPORTS
return list(_IMPORTS.keys())
@@ -3,11 +3,12 @@
from agent_framework_anthropic import (
AnthropicChatOptions,
AnthropicClient,
__version__,
)
from agent_framework_claude import ClaudeAgent, ClaudeAgentOptions
__all__ = [
"AnthropicChatOptions",
"AnthropicClient",
"__version__",
"ClaudeAgent",
"ClaudeAgentOptions",
]
@@ -1,5 +1,19 @@
# Copyright (c) Microsoft. All rights reserved.
"""Azure integration namespace for optional Agent Framework connectors.
This module lazily re-exports objects from optional Azure connector packages and
built-in core Azure OpenAI modules.
Supported classes include:
- AzureAIClient
- AzureAIAgentClient
- AzureOpenAIChatClient
- AzureOpenAIResponsesClient
- AzureAISearchContextProvider
- DurableAIAgent
"""
import importlib
from typing import Any
@@ -1,11 +1,22 @@
# Copyright (c) Microsoft. All rights reserved.
"""ChatKit integration namespace for optional Agent Framework connectors.
This module lazily re-exports objects from:
- ``agent-framework-chatkit``
Supported classes and functions:
- ThreadItemConverter
- simple_to_agent_input
- stream_agent_response
"""
import importlib
from typing import Any
IMPORT_PATH = "agent_framework_chatkit"
PACKAGE_NAME = "agent-framework-chatkit"
_IMPORTS = ["__version__", "ThreadItemConverter", "simple_to_agent_input", "stream_agent_response"]
_IMPORTS = ["ThreadItemConverter", "simple_to_agent_input", "stream_agent_response"]
def __getattr__(name: str) -> Any:
@@ -2,14 +2,12 @@
from agent_framework_chatkit import (
ThreadItemConverter,
__version__,
simple_to_agent_input,
stream_agent_response,
)
__all__ = [
"ThreadItemConverter",
"__version__",
"simple_to_agent_input",
"stream_agent_response",
]
@@ -1,12 +1,23 @@
# Copyright (c) Microsoft. All rights reserved.
"""Declarative integration namespace for optional Agent Framework connectors.
This module lazily re-exports objects from:
- ``agent-framework-declarative``
Supported classes include:
- AgentFactory
- WorkflowFactory
- ExternalInputRequest
- ExternalInputResponse
"""
import importlib
from typing import Any
IMPORT_PATH = "agent_framework_declarative"
PACKAGE_NAME = "agent-framework-declarative"
_IMPORTS = [
"__version__",
"AgentFactory",
"AgentExternalInputRequest",
"AgentExternalInputResponse",
@@ -13,7 +13,6 @@ from agent_framework_declarative import (
ProviderTypeMapping,
WorkflowFactory,
WorkflowState,
__version__,
)
__all__ = [
@@ -29,5 +28,4 @@ __all__ = [
"ProviderTypeMapping",
"WorkflowFactory",
"WorkflowState",
"__version__",
]
@@ -1,5 +1,19 @@
# Copyright (c) Microsoft. All rights reserved.
"""DevUI integration namespace for optional Agent Framework connectors.
This module lazily re-exports objects from:
- ``agent-framework-devui``
Supported classes and functions include:
- DevServer
- AgentFrameworkRequest
- DiscoveryResponse
- ResponseStreamEvent
- serve
- main
"""
import importlib
from typing import Any
@@ -16,7 +30,6 @@ _IMPORTS = [
"main",
"register_cleanup",
"serve",
"__version__",
]
@@ -8,7 +8,6 @@ from agent_framework_devui import (
OpenAIError,
OpenAIResponse,
ResponseStreamEvent,
__version__,
main,
register_cleanup,
serve,
@@ -22,7 +21,6 @@ __all__ = [
"OpenAIError",
"OpenAIResponse",
"ResponseStreamEvent",
"__version__",
"main",
"register_cleanup",
"serve",
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
"""Exception hierarchy used across Agent Framework core and connectors."""
import logging
from typing import Any, Literal
@@ -1,5 +1,16 @@
# Copyright (c) Microsoft. All rights reserved.
"""GitHub integration namespace for optional Agent Framework connectors.
This module lazily re-exports objects from:
- ``agent-framework-github-copilot``
Supported classes:
- GitHubCopilotAgent
- GitHubCopilotOptions
- GitHubCopilotSettings
"""
import importlib
from typing import Any
@@ -7,7 +18,6 @@ _IMPORTS: dict[str, tuple[str, str]] = {
"GitHubCopilotAgent": ("agent_framework_github_copilot", "agent-framework-github-copilot"),
"GitHubCopilotOptions": ("agent_framework_github_copilot", "agent-framework-github-copilot"),
"GitHubCopilotSettings": ("agent_framework_github_copilot", "agent-framework-github-copilot"),
"__version__": ("agent_framework_github_copilot", "agent-framework-github-copilot"),
}
@@ -4,12 +4,10 @@ from agent_framework_github_copilot import (
GitHubCopilotAgent,
GitHubCopilotOptions,
GitHubCopilotSettings,
__version__,
)
__all__ = [
"GitHubCopilotAgent",
"GitHubCopilotOptions",
"GitHubCopilotSettings",
"__version__",
]
@@ -1,4 +1,10 @@
# Copyright (c) Microsoft. All rights reserved.
"""Lab namespace package for experimental Agent Framework integrations.
This module extends the package path so experimental lab integrations can be
distributed in separate packages under the ``agent_framework.lab`` namespace.
"""
# This makes agent_framework.lab a namespace package
__path__ = __import__("pkgutil").extend_path(__path__, __name__)
@@ -1,11 +1,20 @@
# Copyright (c) Microsoft. All rights reserved.
"""Mem0 integration namespace for optional Agent Framework connectors.
This module lazily re-exports objects from:
- ``agent-framework-mem0``
Supported classes:
- Mem0ContextProvider
"""
import importlib
from typing import Any
IMPORT_PATH = "agent_framework_mem0"
PACKAGE_NAME = "agent-framework-mem0"
_IMPORTS = ["__version__", "Mem0ContextProvider"]
_IMPORTS = ["Mem0ContextProvider"]
def __getattr__(name: str) -> Any:
@@ -2,10 +2,8 @@
from agent_framework_mem0 import (
Mem0ContextProvider,
__version__,
)
__all__ = [
"Mem0ContextProvider",
"__version__",
]
@@ -1,11 +1,36 @@
# Copyright (c) Microsoft. All rights reserved.
"""Microsoft integration namespace for optional Agent Framework connectors.
This module lazily re-exports objects from:
- ``agent-framework-copilotstudio``
- ``agent-framework-purview``
- ``agent-framework-foundry-local``
Supported classes:
- CopilotStudioAgent
- PurviewPolicyMiddleware
- PurviewChatPolicyMiddleware
- PurviewSettings
- PurviewAppLocation
- PurviewLocationType
- PurviewAuthenticationError
- PurviewPaymentRequiredError
- PurviewRateLimitError
- PurviewRequestError
- PurviewServiceError
- CacheProvider
- FoundryLocalChatOptions
- FoundryLocalClient
- FoundryLocalSettings
"""
import importlib
from typing import Any
_IMPORTS: dict[str, tuple[str, str]] = {
"CopilotStudioAgent": ("agent_framework_copilotstudio", "agent-framework-copilotstudio"),
"__version__": ("agent_framework_copilotstudio", "agent-framework-copilotstudio"),
"acquire_token": ("agent_framework_copilotstudio", "agent-framework-copilotstudio"),
"PurviewPolicyMiddleware": ("agent_framework_purview", "agent-framework-purview"),
"PurviewChatPolicyMiddleware": ("agent_framework_purview", "agent-framework-purview"),
@@ -18,6 +43,9 @@ _IMPORTS: dict[str, tuple[str, str]] = {
"PurviewRequestError": ("agent_framework_purview", "agent-framework-purview"),
"PurviewServiceError": ("agent_framework_purview", "agent-framework-purview"),
"CacheProvider": ("agent_framework_purview", "agent-framework-purview"),
"FoundryLocalChatOptions": ("agent_framework_foundry_local", "agent-framework-foundry-local"),
"FoundryLocalClient": ("agent_framework_foundry_local", "agent-framework-foundry-local"),
"FoundryLocalSettings": ("agent_framework_foundry_local", "agent-framework-foundry-local"),
}
@@ -2,9 +2,13 @@
from agent_framework_copilotstudio import (
CopilotStudioAgent,
__version__,
acquire_token,
)
from agent_framework_foundry_local import (
FoundryLocalChatOptions,
FoundryLocalClient,
FoundryLocalSettings,
)
from agent_framework_purview import (
CacheProvider,
PurviewAppLocation,
@@ -22,6 +26,9 @@ from agent_framework_purview import (
__all__ = [
"CacheProvider",
"CopilotStudioAgent",
"FoundryLocalChatOptions",
"FoundryLocalClient",
"FoundryLocalSettings",
"PurviewAppLocation",
"PurviewAuthenticationError",
"PurviewChatPolicyMiddleware",
@@ -32,6 +39,5 @@ __all__ = [
"PurviewRequestError",
"PurviewServiceError",
"PurviewSettings",
"__version__",
"acquire_token",
]
@@ -1,5 +1,16 @@
# Copyright (c) Microsoft. All rights reserved.
"""Observability and OpenTelemetry helpers for Agent Framework.
Commonly used exports:
- enable_instrumentation
- configure_otel_providers
- AgentTelemetryLayer
- ChatTelemetryLayer
- get_tracer
- get_meter
"""
from __future__ import annotations
import contextlib
@@ -1128,11 +1139,8 @@ class ChatTelemetryLayer(Generic[OptionsCoT]):
opts: dict[str, Any] = options or {} # type: ignore[assignment]
provider_name = str(self.otel_provider_name)
model_id = kwargs.get("model_id") or opts.get("model_id") or getattr(self, "model_id", None) or "unknown"
service_url = str(
service_url_func()
if (service_url_func := getattr(self, "service_url", None)) and callable(service_url_func)
else "unknown"
)
service_url_func = getattr(self, "service_url", None)
service_url = str(service_url_func() if callable(service_url_func) else "unknown")
attributes = _get_span_attributes(
operation_name=OtelAttr.CHAT_COMPLETION_OPERATION,
provider_name=provider_name,
@@ -1,11 +1,21 @@
# Copyright (c) Microsoft. All rights reserved.
"""Ollama integration namespace for optional Agent Framework connectors.
This module lazily re-exports objects from:
- ``agent-framework-ollama``
Supported classes:
- OllamaChatClient
- OllamaSettings
"""
import importlib
from typing import Any
IMPORT_PATH = "agent_framework_ollama"
PACKAGE_NAME = "agent-framework-ollama"
_IMPORTS = ["__version__", "OllamaChatClient", "OllamaSettings"]
_IMPORTS = ["OllamaChatClient", "OllamaSettings"]
def __getattr__(name: str) -> Any:
@@ -3,11 +3,9 @@
from agent_framework_ollama import (
OllamaChatClient,
OllamaSettings,
__version__,
)
__all__ = [
"OllamaChatClient",
"OllamaSettings",
"__version__",
]
@@ -1,5 +1,17 @@
# Copyright (c) Microsoft. All rights reserved.
"""OpenAI namespace for built-in Agent Framework clients.
This module re-exports objects from the core OpenAI implementation modules in
``agent_framework.openai``.
Supported classes include:
- OpenAIChatClient
- OpenAIResponsesClient
- OpenAIAssistantsClient
- OpenAIAssistantProvider
"""
from ._assistant_provider import OpenAIAssistantProvider
from ._assistants_client import (
AssistantToolResources,
@@ -15,8 +15,7 @@ from agent_framework._settings import SecretString, load_settings
from .._agents import Agent
from .._middleware import MiddlewareTypes
from .._sessions import BaseContextProvider
from .._tools import FunctionTool
from .._types import normalize_tools
from .._tools import FunctionTool, ToolTypes, normalize_tools
from ..exceptions import ServiceInitializationError
from ._assistants_client import OpenAIAssistantsClient
from ._shared import OpenAISettings, from_assistant_tools, to_assistant_tools
@@ -43,13 +42,6 @@ OptionsCoT = TypeVar(
covariant=True,
)
_ToolsType = (
FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
)
class OpenAIAssistantProvider(Generic[OptionsCoT]):
"""Provider for creating Agent instances from OpenAI Assistants API.
@@ -203,7 +195,7 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
model: str,
instructions: str | None = None,
description: str | None = None,
tools: _ToolsType | None = None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
metadata: dict[str, str] | None = None,
default_options: OptionsCoT | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
@@ -259,7 +251,8 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
"""
# Normalize tools
normalized_tools = normalize_tools(tools)
api_tools = to_assistant_tools(normalized_tools) if normalized_tools else []
assistant_tools = [tool for tool in normalized_tools if isinstance(tool, (FunctionTool, MutableMapping))]
api_tools = to_assistant_tools(assistant_tools) if assistant_tools else []
# Extract response_format from default_options if present
opts = dict(default_options) if default_options else {}
@@ -311,7 +304,7 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
self,
assistant_id: str,
*,
tools: _ToolsType | None = None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
instructions: str | None = None,
default_options: OptionsCoT | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
@@ -377,7 +370,7 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
self,
assistant: Assistant,
*,
tools: _ToolsType | None = None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
instructions: str | None = None,
default_options: OptionsCoT | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
@@ -442,7 +435,7 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
def _validate_function_tools(
self,
assistant_tools: list[Any],
provided_tools: _ToolsType | None,
provided_tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
) -> None:
"""Validate that required function tools are provided.
@@ -493,8 +486,8 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
def _merge_tools(
self,
assistant_tools: list[Any],
user_tools: _ToolsType | None,
) -> list[FunctionTool | MutableMapping[str, Any]]:
user_tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
) -> list[FunctionTool | MutableMapping[str, Any] | Any]:
"""Merge hosted tools from assistant with user-provided function tools.
Args:
@@ -504,7 +497,7 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
Returns:
A list of all tools (hosted tools + user function implementations).
"""
merged: list[FunctionTool | MutableMapping[str, Any]] = []
merged: list[FunctionTool | MutableMapping[str, Any] | Any] = []
# Add hosted tools from assistant using shared conversion
hosted_tools = from_assistant_tools(assistant_tools)
@@ -520,7 +513,7 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
def _create_chat_agent_from_assistant(
self,
assistant: Assistant,
tools: list[FunctionTool | MutableMapping[str, Any]] | None,
tools: list[FunctionTool | MutableMapping[str, Any] | Any] | None,
instructions: str | None,
middleware: Sequence[MiddlewareTypes] | None,
context_providers: Sequence[BaseContextProvider] | None,
@@ -36,6 +36,7 @@ from .._tools import (
FunctionInvocationConfiguration,
FunctionInvocationLayer,
FunctionTool,
normalize_tools,
)
from .._types import (
ChatOptions,
@@ -686,26 +687,26 @@ class OpenAIAssistantsClient( # type: ignore[misc]
tool_definitions: list[MutableMapping[str, Any]] = []
# Always include tools if provided, regardless of tool_choice
# tool_choice="none" means the model won't call tools, but tools should still be available
if tools is not None:
for tool in tools:
if isinstance(tool, FunctionTool):
tool_definitions.append(tool.to_json_schema_spec()) # type: ignore[reportUnknownArgumentType]
elif isinstance(tool, MutableMapping):
# Pass through dict-based tools directly (from static factory methods)
tool_definitions.append(tool)
for tool in normalize_tools(tools):
if isinstance(tool, FunctionTool):
tool_definitions.append(tool.to_json_schema_spec()) # type: ignore[reportUnknownArgumentType]
elif isinstance(tool, MutableMapping):
# Pass through dict-based tools directly (from static factory methods)
tool_definitions.append(tool)
if len(tool_definitions) > 0:
run_options["tools"] = tool_definitions
if (mode := tool_mode["mode"]) == "required" and (
func_name := tool_mode.get("required_function_name")
) is not None:
run_options["tool_choice"] = {
"type": "function",
"function": {"name": func_name},
}
else:
run_options["tool_choice"] = mode
if tool_mode is not None:
if (mode := tool_mode["mode"]) == "required" and (
func_name := tool_mode.get("required_function_name")
) is not None:
run_options["tool_choice"] = {
"type": "function",
"function": {"name": func_name},
}
else:
run_options["tool_choice"] = mode
if response_format is not None:
if isinstance(response_format, dict):
@@ -27,6 +27,8 @@ from .._tools import (
FunctionInvocationConfiguration,
FunctionInvocationLayer,
FunctionTool,
ToolTypes,
normalize_tools,
)
from .._types import (
ChatOptions,
@@ -271,21 +273,24 @@ class RawOpenAIChatClient( # type: ignore[misc]
# region content creation
def _prepare_tools_for_openai(self, tools: Sequence[Any]) -> dict[str, Any]:
def _prepare_tools_for_openai(
self,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
) -> dict[str, Any]:
"""Prepare tools for the OpenAI Chat Completions API.
Converts FunctionTool to JSON schema format. Web search tools are routed
to web_search_options parameter. All other tools pass through unchanged.
Args:
tools: Sequence of tools to prepare.
tools: Tool(s) to prepare.
Returns:
Dict containing tools and optionally web_search_options.
"""
chat_tools: list[Any] = []
web_search_options: dict[str, Any] | None = None
for tool in tools:
for tool in normalize_tools(tools):
if isinstance(tool, FunctionTool):
chat_tools.append(tool.to_json_schema_spec())
elif isinstance(tool, MutableMapping) and tool.get("type") == "web_search":
@@ -338,15 +343,16 @@ class RawOpenAIChatClient( # type: ignore[misc]
run_options.pop("tool_choice", None)
elif tool_choice := run_options.pop("tool_choice", None):
tool_mode = validate_tool_mode(tool_choice)
if (mode := tool_mode.get("mode")) == "required" and (
func_name := tool_mode.get("required_function_name")
) is not None:
run_options["tool_choice"] = {
"type": "function",
"function": {"name": func_name},
}
else:
run_options["tool_choice"] = mode
if tool_mode is not None:
if (mode := tool_mode.get("mode")) == "required" and (
func_name := tool_mode.get("required_function_name")
) is not None:
run_options["tool_choice"] = {
"type": "function",
"function": {"name": func_name},
}
else:
run_options["tool_choice"] = mode
# response format
if response_format := options.get("response_format"):
@@ -822,15 +822,16 @@ class RawOpenAIResponsesClient( # type: ignore[misc]
# tool_choice: convert ToolMode to appropriate format
if tool_choice := options.get("tool_choice"):
tool_mode = validate_tool_mode(tool_choice)
if (mode := tool_mode.get("mode")) == "required" and (
func_name := tool_mode.get("required_function_name")
) is not None:
run_options["tool_choice"] = {
"type": "function",
"name": func_name,
}
else:
run_options["tool_choice"] = mode
if tool_mode is not None:
if (mode := tool_mode.get("mode")) == "required" and (
func_name := tool_mode.get("required_function_name")
) is not None:
run_options["tool_choice"] = {
"type": "function",
"name": func_name,
}
else:
run_options["tool_choice"] = mode
else:
run_options.pop("parallel_tool_calls", None)
run_options.pop("tool_choice", None)
@@ -1,12 +1,24 @@
# Copyright (c) Microsoft. All rights reserved.
"""Orchestrations integration namespace for optional Agent Framework connectors.
This module lazily re-exports objects from:
- ``agent-framework-orchestrations``
Supported classes include:
- SequentialBuilder
- ConcurrentBuilder
- GroupChatBuilder
- MagenticBuilder
- HandoffBuilder
"""
import importlib
from typing import Any
IMPORT_PATH = "agent_framework_orchestrations"
PACKAGE_NAME = "agent-framework-orchestrations"
_IMPORTS = [
"__version__",
# Sequential
"SequentialBuilder",
# Concurrent
@@ -35,7 +35,6 @@ from agent_framework_orchestrations import (
MagenticResetSignal,
SequentialBuilder,
StandardMagenticManager,
__version__,
)
__all__ = [
@@ -73,5 +72,4 @@ __all__ = [
"MagenticResetSignal",
"SequentialBuilder",
"StandardMagenticManager",
"__version__",
]
@@ -1,11 +1,21 @@
# Copyright (c) Microsoft. All rights reserved.
"""Redis integration namespace for optional Agent Framework connectors.
This module lazily re-exports objects from:
- ``agent-framework-redis``
Supported classes:
- RedisContextProvider
- RedisHistoryProvider
"""
import importlib
from typing import Any
IMPORT_PATH = "agent_framework_redis"
PACKAGE_NAME = "agent-framework-redis"
_IMPORTS = ["__version__", "RedisContextProvider", "RedisHistoryProvider"]
_IMPORTS = ["RedisContextProvider", "RedisHistoryProvider"]
def __getattr__(name: str) -> Any:
@@ -3,11 +3,9 @@
from agent_framework_redis import (
RedisContextProvider,
RedisHistoryProvider,
__version__,
)
__all__ = [
"RedisContextProvider",
"RedisHistoryProvider",
"__version__",
]
+3
View File
@@ -45,13 +45,16 @@ all = [
"agent-framework-ag-ui",
"agent-framework-azure-ai-search",
"agent-framework-anthropic",
"agent-framework-claude",
"agent-framework-azure-ai",
"agent-framework-azurefunctions",
"agent-framework-bedrock",
"agent-framework-chatkit",
"agent-framework-copilotstudio",
"agent-framework-declarative",
"agent-framework-devui",
"agent-framework-durabletask",
"agent-framework-foundry-local",
"agent-framework-github-copilot",
"agent-framework-lab",
"agent-framework-mem0",
@@ -56,6 +56,36 @@ async def test_base_client_with_function_calling(chat_client_base: SupportsChatG
assert response.messages[2].text == "done"
async def test_base_client_with_function_calling_tools_in_kwargs(chat_client_base: SupportsChatGetResponse):
exec_counter = 0
@tool(name="test_function", approval_mode="never_require")
def ai_func(arg1: str) -> str:
nonlocal exec_counter
exec_counter += 1
return f"Processed {arg1}"
chat_client_base.run_responses = [
ChatResponse(
messages=Message(
role="assistant",
contents=[
Content.from_function_call(call_id="1", name="test_function", arguments='{"arg1": "value1"}')
],
)
),
ChatResponse(messages=Message(role="assistant", text="done")),
]
response = await chat_client_base.get_response("hello", tools=[ai_func])
assert exec_counter == 1
assert len(response.messages) == 3
assert response.messages[1].role == "tool"
assert response.messages[1].contents[0].type == "function_result"
assert response.messages[1].contents[0].result == "Processed value1"
@pytest.mark.parametrize("max_iterations", [3])
async def test_base_client_with_function_calling_resets(chat_client_base: SupportsChatGetResponse):
exec_counter = 0
@@ -921,8 +921,8 @@ def test_chat_options_tool_choice_validation():
}
assert validate_tool_mode({"mode": "none"}) == {"mode": "none"}
# None should return mode==none
assert validate_tool_mode(None) == {"mode": "none"}
# None should remain unset
assert validate_tool_mode(None) is None
with raises(ContentError):
validate_tool_mode("invalid_mode")
@@ -701,6 +701,7 @@ def test_prepare_options_basic(mock_async_openai: MagicMock) -> None:
assert run_options["model"] == "gpt-4"
assert run_options["temperature"] == 0.7
assert run_options["top_p"] == 0.9
assert "tool_choice" not in run_options
assert tool_results is None
@@ -733,6 +734,52 @@ def test_prepare_options_with_tool_tool(mock_async_openai: MagicMock) -> None:
assert run_options["tool_choice"] == "auto"
def test_prepare_options_with_tools_without_tool_choice(mock_async_openai: MagicMock) -> None:
"""Test _prepare_options keeps tool_choice unset when not provided."""
client = create_test_openai_assistants_client(mock_async_openai)
@tool(approval_mode="never_require")
def test_function(query: str) -> str:
"""A test function."""
return f"Result for {query}"
options = {
"tools": [test_function],
}
messages = [Message(role="user", text="Hello")]
run_options, _ = client._prepare_options(messages, options) # type: ignore
assert "tools" in run_options
assert "tool_choice" not in run_options
def test_prepare_options_with_single_tool_tool(mock_async_openai: MagicMock) -> None:
"""Test _prepare_options with a single FunctionTool (non-sequence)."""
client = create_test_openai_assistants_client(mock_async_openai)
@tool(approval_mode="never_require")
def test_function(query: str) -> str:
"""A test function."""
return f"Result for {query}"
options = {
"tools": test_function,
"tool_choice": "auto",
}
messages = [Message(role="user", text="Hello")]
run_options, tool_results = client._prepare_options(messages, options) # type: ignore
assert "tools" in run_options
assert len(run_options["tools"]) == 1
assert run_options["tools"][0]["type"] == "function"
assert "function" in run_options["tools"][0]
assert run_options["tool_choice"] == "auto"
assert tool_results is None
def test_prepare_options_with_code_interpreter(mock_async_openai: MagicMock) -> None:
"""Test _prepare_options with code interpreter tool."""
client = create_test_openai_assistants_client(mock_async_openai)
@@ -190,6 +190,21 @@ def test_unsupported_tool_handling(openai_unit_test_env: dict[str, str]) -> None
assert result["tools"] == [dict_tool]
def test_prepare_tools_with_single_function_tool(openai_unit_test_env: dict[str, str]) -> None:
"""Test that a single FunctionTool is accepted for tool preparation."""
client = OpenAIChatClient()
@tool(approval_mode="never_require")
def test_function(query: str) -> str:
"""A test function."""
return f"Result for {query}"
result = client._prepare_tools_for_openai(test_function)
assert "tools" in result
assert len(result["tools"]) == 1
assert result["tools"][0]["type"] == "function"
@tool(approval_mode="never_require")
def get_story_text() -> str:
"""Returns a story about Emily and David."""
+4
View File
@@ -7,3 +7,7 @@ pip install agent-framework-foundry-local --pre
```
and see the [README](https://github.com/microsoft/agent-framework/tree/main/python/README.md) for more information.
## Foundry Local Sample
See the [Foundry Local provider sample](../../samples/02-agents/providers/foundry_local/foundry_local_agent.py) for a runnable example.
@@ -1,80 +0,0 @@
# Copyright (c) Microsoft. All rights reserved.
# ruff: noqa
from __future__ import annotations
import asyncio
from random import randint
from typing import TYPE_CHECKING, Annotated
from agent_framework_foundry_local import FoundryLocalClient
if TYPE_CHECKING:
from agent_framework import Agent
"""
This sample demonstrates basic usage of the FoundryLocalClient.
Shows both streaming and non-streaming responses with function tools.
Running this sample the first time will be slow, as the model needs to be
downloaded and initialized.
Also, not every model supports function calling, so be sure to check the
model capabilities in the Foundry catalog, or pick one from the list printed
when running this sample.
"""
def get_weather(
location: Annotated[str, "The location to get the weather for."],
) -> str:
"""Get the weather for a given location."""
conditions = ["sunny", "cloudy", "rainy", "stormy"]
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
async def non_streaming_example(agent: Agent) -> None:
"""Example of non-streaming response (get the complete result at once)."""
print("=== Non-streaming Response Example ===")
query = "What's the weather like in Seattle?"
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result}\n")
async def streaming_example(agent: Agent) -> None:
"""Example of streaming response (get results as they are generated)."""
print("=== Streaming Response Example ===")
query = "What's the weather like in Amsterdam?"
print(f"User: {query}")
print("Agent: ", end="", flush=True)
async for chunk in agent.run(query, stream=True):
if chunk.text:
print(chunk.text, end="", flush=True)
print("\n")
async def main() -> None:
print("=== Basic Foundry Local Client Agent Example ===")
client = FoundryLocalClient(model_id="phi-4-mini")
print(f"Client Model ID: {client.model_id}\n")
print("Other available models (tool calling supported only):")
for model in client.manager.list_catalog_models():
if model.supports_tool_calling:
print(
f"- {model.alias} for {model.task} - id={model.id} - {(model.file_size_mb / 1000):.2f} GB - {model.license}"
)
agent = client.as_agent(
name="LocalAgent",
instructions="You are a helpful agent.",
tools=get_weather,
)
await non_streaming_example(agent)
await streaming_example(agent)
if __name__ == "__main__":
asyncio.run(main())
@@ -22,7 +22,7 @@ from agent_framework import (
normalize_messages,
)
from agent_framework._settings import load_settings
from agent_framework._tools import FunctionTool
from agent_framework._tools import FunctionTool, ToolTypes
from agent_framework._types import AgentRunInputs, normalize_tools
from agent_framework.exceptions import ServiceException
from copilot import CopilotClient, CopilotSession
@@ -151,11 +151,7 @@ class GitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
description: str | None = None,
context_providers: Sequence[BaseContextProvider] | None = None,
middleware: Sequence[AgentMiddlewareTypes] | None = None,
tools: FunctionTool
| Callable[..., Any]
| MutableMapping[str, Any]
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
| None = None,
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
default_options: OptionsT | None = None,
env_file_path: str | None = None,
env_file_encoding: str | None = None,
@@ -478,7 +474,7 @@ class GitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
def _prepare_tools(
self,
tools: list[FunctionTool | MutableMapping[str, Any]],
tools: Sequence[ToolTypes | CopilotTool],
) -> list[CopilotTool]:
"""Convert Agent Framework tools to Copilot SDK tools.
@@ -491,10 +487,12 @@ class GitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
copilot_tools: list[CopilotTool] = []
for tool in tools:
if isinstance(tool, FunctionTool):
copilot_tools.append(self._tool_to_copilot_tool(tool)) # type: ignore
elif isinstance(tool, CopilotTool):
if isinstance(tool, CopilotTool):
copilot_tools.append(tool)
elif isinstance(tool, FunctionTool):
copilot_tools.append(self._tool_to_copilot_tool(tool)) # type: ignore
elif isinstance(tool, MutableMapping):
copilot_tools.append(tool) # type: ignore[arg-type]
# Note: Other tool types (e.g., dict-based hosted tools) are skipped
return copilot_tools