Skip to content

Commit ce8c753

Browse files
committed
test
1 parent 1aa7a8f commit ce8c753

File tree

7 files changed

+82
-30
lines changed

7 files changed

+82
-30
lines changed

packages/ai/src/microsoft/teams/ai/chat_prompt.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import inspect
77
from dataclasses import dataclass
88
from inspect import isawaitable
9-
from typing import Any, Awaitable, Callable, TypeVar
9+
from typing import Any, Awaitable, Callable, Self, TypeVar
1010

1111
from pydantic import BaseModel
1212

@@ -36,11 +36,11 @@ def __init__(
3636
self.functions: dict[str, Function[Any]] = {func.name: func for func in functions} if functions else {}
3737
self.plugins: list[AIPluginProtocol] = plugins or []
3838

39-
def with_function(self, function: Function[T]) -> "ChatPrompt":
39+
def with_function(self, function: Function[T]) -> Self:
4040
self.functions[function.name] = function
4141
return self
4242

43-
def with_plugin(self, plugin: AIPluginProtocol) -> "ChatPrompt":
43+
def with_plugin(self, plugin: AIPluginProtocol) -> Self:
4444
"""Add a plugin to the chat prompt."""
4545
self.plugins.append(plugin)
4646
return self
@@ -83,15 +83,14 @@ async def send(
8383
)
8484

8585
# Allow plugins to modify the functions before sending to model
86-
if wrapped_functions:
87-
functions_list = list(wrapped_functions.values())
88-
for plugin in self.plugins:
89-
plugin_result = await plugin.on_build_functions(functions_list)
90-
if plugin_result is not None:
91-
functions_list = plugin_result
86+
functions_list = list(wrapped_functions.values()) if wrapped_functions else []
87+
for plugin in self.plugins:
88+
plugin_result = await plugin.on_build_functions(functions_list)
89+
if plugin_result is not None:
90+
functions_list = plugin_result
9291

93-
# Convert back to dict for model
94-
wrapped_functions = {func.name: func for func in functions_list}
92+
# Convert back to dict for model
93+
wrapped_functions = {func.name: func for func in functions_list}
9594

9695
async def on_chunk_fn(chunk: str):
9796
if not on_chunk:

packages/ai/src/microsoft/teams/ai/function.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"""
55

66
from dataclasses import dataclass
7-
from typing import Any, Awaitable, Generic, Protocol, TypeVar, Union
7+
from typing import Any, Awaitable, Dict, Generic, Protocol, TypeVar, Union
88

99
from pydantic import BaseModel
1010

@@ -19,7 +19,7 @@ def __call__(self, params: Params) -> Union[str, Awaitable[str]]: ...
1919
class Function(Generic[Params]):
2020
name: str
2121
description: str
22-
parameter_schema: type[Params]
22+
parameter_schema: Union[type[Params], Dict[str, Any]]
2323
handler: FunctionHandler[Params]
2424

2525

packages/mcp/src/microsoft/teams/mcp/ai_plugin.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def __init__(
7575

7676
self._version = version
7777
self._cache: Dict[str, McpCachedValue] = cache or {}
78-
self._logger = logger or ConsoleLogger().create_logger(self.name)
78+
self._logger = logger.getChild(self.name) if logger else ConsoleLogger().create_logger(self.name)
7979
self._refetch_timeout_ms = refetch_timeout_ms
8080

8181
# Track MCP server URLs and their parameters
@@ -170,26 +170,26 @@ async def _fetch_tools_if_needed(self) -> None:
170170
self._logger.debug(f"Cached {len(result)} tools for {url}")
171171

172172
def _create_function_from_tool(
173-
self, url: str, tool: Union[McpToolDetails, Dict[str, Any]], plugin_params: McpClientPluginParams
173+
self, url: str, tool: McpToolDetails, plugin_params: McpClientPluginParams
174174
) -> Function[BaseModel]:
175175
"""Create a Teams AI function from an MCP tool."""
176-
if isinstance(tool, dict):
177-
tool_name = tool["name"]
178-
tool_description = tool["description"]
179-
else:
180-
tool_name = tool.name
181-
tool_description = tool.description
176+
tool_name = tool.name
177+
tool_description = tool.description
182178

183179
async def handler(params: BaseModel) -> str:
184180
"""Handle MCP tool call."""
185181
try:
182+
self._logger.debug(f"Making call to {url} tool-name={tool_name}")
186183
result = await self._call_mcp_tool(url, tool_name, params.model_dump(), plugin_params)
184+
self._logger.debug("Successfully received result for mcp call")
187185
return str(result)
188186
except Exception as e:
189187
self._logger.error(f"Error calling tool {tool_name} on {url}: {e}")
190188
raise
191189

192-
return Function(name=tool_name, description=tool_description, parameter_schema=BaseModel, handler=handler)
190+
return Function(
191+
name=tool_name, description=tool_description, parameter_schema=tool.input_schema, handler=handler
192+
)
193193

194194
async def _fetch_tools_from_server(self, url: str, params: McpClientPluginParams) -> List[McpToolDetails]:
195195
"""Fetch tools from a specific MCP server."""
@@ -212,6 +212,7 @@ async def _fetch_tools_from_server(self, url: str, params: McpClientPluginParams
212212
)
213213
)
214214

215+
self._logger.debug(f"Got {len(tools)} tools for {url}")
215216
return tools
216217

217218
async def _call_mcp_tool(

packages/openai/src/microsoft/teams/openai/completions_model.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
ChatCompletionUserMessageParam,
3939
)
4040

41+
from .function_utils import get_function_schema, parse_function_arguments
42+
4143

4244
class _ToolCallData(TypedDict):
4345
id: str
@@ -128,8 +130,8 @@ async def _execute_functions(
128130
if functions and call.name in functions:
129131
function = functions[call.name]
130132
try:
131-
# Parse arguments into Pydantic model
132-
parsed_args = function.parameter_schema(**call.arguments)
133+
# Parse arguments using utility function
134+
parsed_args = parse_function_arguments(function, call.arguments)
133135

134136
# Handle both sync and async function handlers
135137
result = function.handler(parsed_args)
@@ -259,7 +261,7 @@ def _convert_functions(self, functions: dict[str, Function[BaseModel]]) -> list[
259261
"function": {
260262
"name": func.name,
261263
"description": func.description,
262-
"parameters": func.parameter_schema.model_json_schema(),
264+
"parameters": get_function_schema(func),
263265
},
264266
}
265267
for func in functions.values()
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""
2+
Copyright (c) Microsoft Corporation. All rights reserved.
3+
Licensed under the MIT License.
4+
"""
5+
6+
from typing import Any, Dict
7+
8+
from microsoft.teams.ai import Function
9+
from pydantic import BaseModel, create_model
10+
11+
12+
def get_function_schema(func: Function[BaseModel]) -> Dict[str, Any]:
13+
"""
14+
Get JSON schema from a Function's parameter_schema.
15+
16+
Handles both dict schemas and Pydantic model classes.
17+
"""
18+
if isinstance(func.parameter_schema, dict):
19+
# Raw JSON schema - use as-is
20+
return func.parameter_schema.copy()
21+
else:
22+
# Pydantic model - convert to JSON schema
23+
return func.parameter_schema.model_json_schema()
24+
25+
26+
def parse_function_arguments(func: Function[BaseModel], arguments: Dict[str, Any]) -> BaseModel:
27+
"""
28+
Parse function arguments into a BaseModel instance.
29+
30+
Handles both dict schemas and Pydantic model classes.
31+
"""
32+
if isinstance(func.parameter_schema, dict):
33+
# For dict schemas, create a simple BaseModel dynamically
34+
DynamicModel = create_model("DynamicParams")
35+
return DynamicModel(**arguments)
36+
else:
37+
# For Pydantic model schemas, parse normally
38+
return func.parameter_schema(**arguments)

packages/openai/src/microsoft/teams/openai/responses_chat_model.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
from pydantic import BaseModel
2424

2525
from openai import NOT_GIVEN
26+
from openai.lib._pydantic import (
27+
_ensure_strict_json_schema, # pyright: ignore [reportPrivateUsage]
28+
to_strict_json_schema,
29+
)
2630
from openai.types.responses import (
2731
EasyInputMessageParam,
2832
FunctionToolParam,
@@ -34,6 +38,7 @@
3438
)
3539

3640
from .common import OpenAIBaseModel
41+
from .function_utils import get_function_schema, parse_function_arguments
3742

3843

3944
@dataclass
@@ -219,8 +224,8 @@ async def _execute_functions(
219224
if functions and call.name in functions:
220225
function = functions[call.name]
221226
try:
222-
# Parse arguments into Pydantic model
223-
parsed_args = function.parameter_schema(**call.arguments)
227+
# Parse arguments using utility function
228+
parsed_args = parse_function_arguments(function, call.arguments)
224229

225230
# Handle both sync and async function handlers
226231
result = function.handler(parsed_args)
@@ -303,9 +308,14 @@ def _convert_functions_to_tools(self, functions: dict[str, Function[BaseModel]])
303308
tools: list[ToolParam] = []
304309

305310
for func in functions.values():
306-
# Get schema and ensure additionalProperties is false for Responses API
307-
schema = func.parameter_schema.model_json_schema()
308-
schema["additionalProperties"] = False
311+
# Get strict schema for Responses API using OpenAI's transformations
312+
if isinstance(func.parameter_schema, dict):
313+
# For raw JSON schemas, use OpenAI's strict transformation
314+
schema = get_function_schema(func)
315+
schema = _ensure_strict_json_schema(schema, path=(), root=schema)
316+
else:
317+
# Use OpenAI's official strict schema transformation for Pydantic models
318+
schema = to_strict_json_schema(func.parameter_schema)
309319

310320
tools.append(
311321
FunctionToolParam(

tests/mcp/src/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from microsoft.teams.ai import Agent
1111
from microsoft.teams.ai.memory import ListMemory
1212
from microsoft.teams.api import MessageActivity
13+
from microsoft.teams.api.activities.typing import TypingActivityInput
1314
from microsoft.teams.apps import ActivityContext, App
1415
from microsoft.teams.apps.options import AppOptions
1516
from microsoft.teams.devtools import DevToolsPlugin
@@ -51,6 +52,7 @@ async def handle_message(ctx: ActivityContext[MessageActivity]):
5152
"""Handle message activities using the new generated handler system."""
5253
print(f"[GENERATED onMessage] Message received: {ctx.activity.text}")
5354
print(f"[GENERATED onMessage] From: {ctx.activity.from_}")
55+
await ctx.send(TypingActivityInput())
5456

5557
result = await responses_agent.send(ctx.activity.text)
5658
if result.response.content:

0 commit comments

Comments
 (0)