Skip to content

Commit 07af6de

Browse files
committed
Merge branch 'lusu/agentserver-1110' of https://github.com/Azure/azure-sdk-for-python into lusu/agentserver-1110
2 parents 5bbf605 + 8ba9f1b commit 07af6de

File tree

9 files changed

+151
-97
lines changed

9 files changed

+151
-97
lines changed

sdk/agentserver/azure-ai-agentserver-agentframework/azure/ai/agentserver/agentframework/__init__.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,20 @@
55

66
from typing import TYPE_CHECKING, Optional, Any
77

8-
from ._version import VERSION
98
from .agent_framework import AgentFrameworkCBAgent
9+
from .tool_client import ToolClient
10+
from ._version import VERSION
1011

1112
if TYPE_CHECKING: # pragma: no cover
1213
from azure.core.credentials_async import AsyncTokenCredential
1314

1415

15-
def from_agent_framework(agent, credentials: Optional["AsyncTokenCredential"] = None, **kwargs: Any) -> "AgentFrameworkCBAgent":
16-
from .agent_framework import AgentFrameworkCBAgent
16+
def from_agent_framework(agent,
17+
credentials: Optional["AsyncTokenCredential"] = None,
18+
**kwargs: Any) -> "AgentFrameworkCBAgent":
1719

1820
return AgentFrameworkCBAgent(agent, credentials=credentials, **kwargs)
1921

20-
from .tool_client import ToolClient
21-
2222

2323
__all__ = ["from_agent_framework", "ToolClient"]
2424
__version__ = VERSION

sdk/agentserver/azure-ai-agentserver-agentframework/azure/ai/agentserver/agentframework/agent_framework.py

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from typing import TYPE_CHECKING, Any, AsyncGenerator, Awaitable, Optional, Protocol, Union, List
1010
import inspect
1111

12-
from agent_framework import AgentProtocol
12+
from agent_framework import AgentProtocol, AIFunction
1313
from agent_framework.azure import AzureAIAgentClient # pylint: disable=no-name-in-module
1414
from opentelemetry import trace
1515

@@ -28,7 +28,6 @@
2828
from .models.agent_framework_output_non_streaming_converter import (
2929
AgentFrameworkOutputNonStreamingConverter,
3030
)
31-
from agent_framework import AIFunction
3231
from .models.agent_framework_output_streaming_converter import AgentFrameworkOutputStreamingConverter
3332
from .models.constants import Constants
3433
from .tool_client import ToolClient
@@ -45,7 +44,7 @@ class AgentFactory(Protocol):
4544
An agent factory is a callable that takes a ToolClient and returns
4645
an AgentProtocol, either synchronously or asynchronously.
4746
"""
48-
47+
4948
def __call__(self, tools: List[AIFunction]) -> Union[AgentProtocol, Awaitable[AgentProtocol]]:
5049
"""Create an AgentProtocol using the provided ToolClient.
5150
@@ -74,18 +73,20 @@ class AgentFrameworkCBAgent(FoundryCBAgent):
7473
- Supports both streaming and non-streaming responses based on the `stream` flag.
7574
"""
7675

77-
def __init__(self, agent: Union[AgentProtocol, AgentFactory], credentials: "Optional[AsyncTokenCredential]" = None, **kwargs: Any):
76+
def __init__(self, agent: Union[AgentProtocol, AgentFactory],
77+
credentials: "Optional[AsyncTokenCredential]" = None,
78+
**kwargs: Any):
7879
"""Initialize the AgentFrameworkCBAgent with an AgentProtocol or a factory function.
7980
80-
:param agent: The Agent Framework agent to adapt, or a callable that takes ToolClient and returns AgentProtocol (sync or async).
81+
:param agent: The Agent Framework agent to adapt, or a callable that takes ToolClient
82+
and returns AgentProtocol (sync or async).
8183
:type agent: Union[AgentProtocol, AgentFactory]
8284
:param credentials: Azure credentials for authentication.
8385
:type credentials: Optional[AsyncTokenCredential]
8486
"""
85-
super().__init__(credentials=credentials, **kwargs)
87+
super().__init__(credentials=credentials, **kwargs) # pylint: disable=unexpected-keyword-arg
8688
self._agent_or_factory: Union[AgentProtocol, AgentFactory] = agent
8789
self._resolved_agent: "Optional[AgentProtocol]" = None
88-
8990
# If agent is already instantiated, use it directly
9091
if isinstance(agent, AgentProtocol):
9192
self._resolved_agent = agent
@@ -126,21 +127,24 @@ async def _resolve_agent(self, context: AgentRunContext):
126127
"""Resolve the agent if it's a factory function (for single-use/first-time resolution).
127128
Creates a ToolClient and calls the factory function with it.
128129
This is used for the initial resolution.
130+
131+
:param context: The agent run context containing tools and user information.
132+
:type context: AgentRunContext
129133
"""
130134
if callable(self._agent_or_factory):
131135
logger.debug("Resolving agent from factory function")
132-
136+
133137
# Create ToolClient with credentials
134-
tool_client = self.get_tool_client(tools=context.get_tools(), user_info=context.get_user_info())
138+
tool_client = self.get_tool_client(tools=context.get_tools(), user_info=context.get_user_info()) # pylint: disable=no-member
135139
tool_client_wrapper = ToolClient(tool_client)
136140
tools = await tool_client_wrapper.list_tools()
137-
141+
138142
result = self._agent_or_factory(tools)
139143
if inspect.iscoroutine(result):
140144
self._resolved_agent = await result
141145
else:
142146
self._resolved_agent = result
143-
147+
144148
logger.debug("Agent resolved successfully")
145149
else:
146150
# Should not reach here, but just in case
@@ -149,19 +153,18 @@ async def _resolve_agent(self, context: AgentRunContext):
149153
async def _resolve_agent_for_request(self, context: AgentRunContext):
150154

151155
logger.debug("Resolving fresh agent from factory function for request")
152-
156+
153157
# Create ToolClient with credentials
154-
tool_client = self.get_tool_client(tools=context.get_tools(), user_info=context.get_user_info())
158+
tool_client = self.get_tool_client(tools=context.get_tools(), user_info=context.get_user_info()) # pylint: disable=no-member
155159
tool_client_wrapper = ToolClient(tool_client)
156160
tools = await tool_client_wrapper.list_tools()
157-
158-
import inspect
161+
159162
result = self._agent_or_factory(tools)
160163
if inspect.iscoroutine(result):
161164
agent = await result
162165
else:
163166
agent = result
164-
167+
165168
logger.debug("Fresh agent resolved successfully for request")
166169
return agent, tool_client_wrapper
167170

@@ -184,7 +187,7 @@ def init_tracing(self):
184187
agent_client.setup_azure_ai_observability()
185188
self.tracer = trace.get_tracer(__name__)
186189

187-
async def agent_run(
190+
async def agent_run( # pylint: disable=too-many-statements
188191
self, context: AgentRunContext
189192
) -> Union[
190193
OpenAIResponse,
@@ -201,7 +204,7 @@ async def agent_run(
201204
agent = self._resolved_agent
202205
else:
203206
agent = self._resolved_agent
204-
207+
205208
logger.info(f"Starting agent_run with stream={context.stream}")
206209
request_input = context.request.get("input")
207210

@@ -248,7 +251,7 @@ async def stream_updates():
248251
try:
249252
await tool_client.close()
250253
logger.debug("Closed tool_client after streaming completed")
251-
except Exception as e:
254+
except Exception as e: # pylint: disable=broad-exception-caught
252255
logger.warning(f"Error closing tool_client in stream: {e}")
253256

254257
return stream_updates()
@@ -267,5 +270,5 @@ async def stream_updates():
267270
try:
268271
await tool_client.close()
269272
logger.debug("Closed tool_client after request processing")
270-
except Exception as e:
273+
except Exception as e: # pylint: disable=broad-exception-caught
271274
logger.warning(f"Error closing tool_client: {e}")

sdk/agentserver/azure-ai-agentserver-agentframework/azure/ai/agentserver/agentframework/tool_client.py

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
# ---------------------------------------------------------
44
"""Tool client for integrating AzureAIToolClient with Agent Framework."""
55

6-
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional
6+
from typing import TYPE_CHECKING, Any, Dict, List, Optional
77
from agent_framework import AIFunction
8-
from pydantic import BaseModel, Field, create_model
9-
8+
from pydantic import Field, create_model
9+
from azure.ai.agentserver.core.logger import get_logger
1010
if TYPE_CHECKING:
1111
from azure.ai.agentserver.core.client.tools.aio import AzureAIToolClient, FoundryTool
1212

13+
logger = get_logger()
14+
15+
# pylint: disable=client-accepts-api-version-keyword,missing-client-constructor-parameter-credential,missing-client-constructor-parameter-kwargs
1316
class ToolClient:
1417
"""Client that integrates AzureAIToolClient with Agent Framework.
1518
@@ -46,7 +49,7 @@ class ToolClient:
4649
4750
:meta private:
4851
"""
49-
52+
5053
def __init__(self, tool_client: "AzureAIToolClient") -> None:
5154
"""Initialize the ToolClient.
5255
@@ -55,7 +58,7 @@ def __init__(self, tool_client: "AzureAIToolClient") -> None:
5558
"""
5659
self._tool_client = tool_client
5760
self._aifunction_cache: List[AIFunction] = None
58-
61+
5962
async def list_tools(self) -> List[AIFunction]:
6063
"""List all available tools as Agent Framework tool definitions.
6164
@@ -77,7 +80,7 @@ async def list_tools(self) -> List[AIFunction]:
7780
# Get tools from AzureAIToolClient
7881
if self._aifunction_cache is not None:
7982
return self._aifunction_cache
80-
83+
8184
azure_tools = await self._tool_client.list_tools()
8285
self._aifunction_cache = []
8386

@@ -98,42 +101,48 @@ def _convert_to_agent_framework_tool(self, azure_tool: "FoundryTool") -> AIFunct
98101
"""
99102
# Get the input schema from the tool descriptor
100103
input_schema = azure_tool.input_schema or {}
101-
104+
102105
# Create a Pydantic model from the input schema
103106
properties = input_schema.get("properties", {})
104107
required_fields = set(input_schema.get("required", []))
105-
108+
106109
# Build field definitions for the Pydantic model
107110
field_definitions: Dict[str, Any] = {}
108111
for field_name, field_info in properties.items():
109112
field_type = self._json_schema_type_to_python(field_info.get("type", "string"))
110113
field_description = field_info.get("description", "")
111114
is_required = field_name in required_fields
112-
115+
113116
if is_required:
114117
field_definitions[field_name] = (field_type, Field(description=field_description))
115118
else:
116-
field_definitions[field_name] = (Optional[field_type], Field(default=None, description=field_description))
117-
119+
field_definitions[field_name] = (Optional[field_type],
120+
Field(default=None, description=field_description))
121+
118122
# Create the Pydantic model dynamically
119123
input_model = create_model(
120124
f"{azure_tool.name}_input",
121125
**field_definitions
122126
)
123-
127+
124128
# Create a wrapper function that calls the Azure tool
125129
async def tool_func(**kwargs: Any) -> Any:
126-
"""Dynamically generated function to invoke the Azure AI tool."""
127-
return await self.invoke_tool(azure_tool.name, kwargs)
128-
130+
"""Dynamically generated function to invoke the Azure AI tool.
131+
132+
:return: The result from the tool invocation.
133+
:rtype: Any
134+
"""
135+
logger.debug("Invoking tool: %s with input: %s", azure_tool.name, kwargs)
136+
return await azure_tool.ainvoke(kwargs)
137+
129138
# Create and return the AIFunction
130139
return AIFunction(
131140
name=azure_tool.name,
132141
description=azure_tool.description or "No description available",
133142
func=tool_func,
134143
input_model=input_model
135144
)
136-
145+
137146
def _json_schema_type_to_python(self, json_type: str) -> type:
138147
"""Convert JSON schema type to Python type.
139148
@@ -151,14 +160,23 @@ def _json_schema_type_to_python(self, json_type: str) -> type:
151160
"object": dict,
152161
}
153162
return type_map.get(json_type, str)
154-
163+
155164
async def close(self) -> None:
165+
"""Close the tool client and release resources."""
156166
await self._tool_client.close()
157-
167+
158168
async def __aenter__(self) -> "ToolClient":
159-
"""Async context manager entry."""
169+
"""Async context manager entry.
170+
171+
:return: The ToolClient instance.
172+
:rtype: ToolClient
173+
"""
160174
return self
161-
175+
162176
async def __aexit__(self, *exc_details: Any) -> None:
163-
"""Async context manager exit."""
177+
"""Async context manager exit.
178+
179+
:param exc_details: Exception details if an exception occurred.
180+
:type exc_details: Any
181+
"""
164182
await self.close()

sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/client/tools/_utils/_model_base.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -751,8 +751,11 @@ def from_dict(cls, data: Mapping[str, Any], tool_definitions: List[ToolDefinitio
751751
result_data = data.get("result", {})
752752
tools_list = []
753753
tool_definitions_map = {f"{td.type.lower()}": td for td in tool_definitions}
754-
754+
filter_tools = len(tool_definitions_map) > 0
755755
for tool_data in result_data.get("tools", []):
756+
757+
if filter_tools and tool_data["name"].lower() not in tool_definitions_map:
758+
continue
756759
# Parse inputSchema
757760
input_schema_data = tool_data.get("inputSchema", {})
758761
input_schema = MCPToolSchema(

sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/client/tools/operations/_operations.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
# mypy: ignore-errors
55

66
import json
7-
import logging
8-
from typing import Any, Dict, List, Mapping, MutableMapping, Optional, Tuple, Union
7+
from typing import Any, Dict, List, Mapping, MutableMapping, Tuple, Union
98
from azure.core import PipelineClient
109
from .._configuration import AzureAIToolClientConfiguration
1110
from .._model_base import FoundryTool, ToolSource, UserInfo
@@ -26,7 +25,6 @@
2625
map_error,
2726
)
2827

29-
logger = logging.getLogger(__name__)
3028

3129
# Shared constants
3230
API_VERSION = "2025-11-15-preview"
@@ -192,7 +190,7 @@ def build_invoke_mcp_tool_request(
192190
_params = {}
193191

194192
_content = prepare_mcptools_invoke_tool_request_content(tool, arguments, TOOL_PROPERTY_OVERRIDES)
195-
logger.info("Invoking MCP tool: %s with arguments: %s", tool.name, dict(arguments))
193+
196194
content = json.dumps(_content)
197195
_request = build_mcptools_invoke_tool_request(api_version=api_version, headers=_headers, params=_params, content=content)
198196

@@ -370,7 +368,7 @@ def prepare_mcptools_invoke_tool_request_content(tool: FoundryTool, arguments: M
370368
)
371369
if meta_config:
372370
params["_meta"] = meta_config
373-
logger.info("Prepared MCP tool invocation params: %s", params)
371+
374372
payload = {
375373
"jsonrpc": "2.0",
376374
"id": 2,

sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/server/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ def setup_otlp_exporter(self, endpoint, provider):
337337
def get_tool_client(
338338
self, tools: Optional[list[ToolDefinition]], user_info: Optional[UserInfo]
339339
) -> AzureAIToolClient:
340+
logger.debug("Creating AzureAIToolClient with tools: %s", tools)
340341
if not self.credentials:
341342
raise ValueError("Credentials are required to create Tool Client.")
342343
return AzureAIToolClient(

sdk/agentserver/azure-ai-agentserver-langgraph/azure/ai/agentserver/langgraph/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,23 @@
66
from typing import TYPE_CHECKING, Optional, Any
77

88
from ._version import VERSION
9+
from .tool_client import ToolClient
910

1011
if TYPE_CHECKING: # pragma: no cover
1112
from . import models
1213
from azure.core.credentials_async import AsyncTokenCredential
1314

1415

15-
def from_langgraph(agent, credentials: Optional["AsyncTokenCredential"] = None, state_converter: Optional["models.LanggraphStateConverter"] = None, **kwargs: Any) -> "LangGraphAdapter":
16+
def from_langgraph(
17+
agent,
18+
credentials: Optional["AsyncTokenCredential"] = None,
19+
state_converter: Optional["models.LanggraphStateConverter"] = None,
20+
**kwargs: Any
21+
) -> "LangGraphAdapter":
1622
from .langgraph import LangGraphAdapter
1723

1824
return LangGraphAdapter(agent, credentials=credentials, state_converter=state_converter, **kwargs)
1925

20-
from .tool_client import ToolClient
21-
2226

2327
__all__ = ["from_langgraph", "ToolClient"]
2428
__version__ = VERSION

0 commit comments

Comments
 (0)