Skip to content

Commit 6319495

Browse files
committed
test
1 parent 36226be commit 6319495

File tree

7 files changed

+83
-30
lines changed

7 files changed

+83
-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
@@ -5,7 +5,7 @@
55

66
from dataclasses import dataclass
77
from inspect import isawaitable
8-
from typing import Any, Awaitable, Callable, TypeVar
8+
from typing import Any, Awaitable, Callable, Self, TypeVar
99

1010
from pydantic import BaseModel
1111

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

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

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

8484
# Allow plugins to modify the functions before sending to model
85-
if wrapped_functions:
86-
functions_list = list(wrapped_functions.values())
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
85+
functions_list = list(wrapped_functions.values()) if wrapped_functions else []
86+
for plugin in self.plugins:
87+
plugin_result = await plugin.on_build_functions(functions_list)
88+
if plugin_result is not None:
89+
functions_list = plugin_result
9190

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

9594
response = await self.model.generate_text(
9695
current_input, memory=memory, functions=wrapped_functions, on_chunk=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_ai_model/src/microsoft/teams/openai_ai_model/completions_model.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
)
3838
from pydantic import BaseModel
3939

40+
from .function_utils import get_function_schema, parse_function_arguments
41+
4042

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

133135
# Handle both sync and async function handlers
134136
result = function.handler(parsed_args)
@@ -258,7 +260,7 @@ def _convert_functions(self, functions: dict[str, Function[BaseModel]]) -> list[
258260
"function": {
259261
"name": func.name,
260262
"description": func.description,
261-
"parameters": func.parameter_schema.model_json_schema(),
263+
"parameters": get_function_schema(func),
262264
},
263265
}
264266
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_ai_model/src/microsoft/teams/openai_ai_model/responses_chat_model.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
)
2323
from microsoft.teams.openai_ai_model.common import OpenAIBaseModel
2424
from openai import NOT_GIVEN
25+
from openai.lib._pydantic import (
26+
_ensure_strict_json_schema, # pyright: ignore [reportPrivateUsage]
27+
to_strict_json_schema,
28+
)
2529
from openai.types.responses import (
2630
EasyInputMessageParam,
2731
FunctionToolParam,
@@ -33,6 +37,8 @@
3337
)
3438
from pydantic import BaseModel
3539

40+
from .function_utils import get_function_schema, parse_function_arguments
41+
3642

3743
@dataclass
3844
class OpenAIResponsesAIModel(OpenAIBaseModel, AIModel):
@@ -217,8 +223,8 @@ async def _execute_functions(
217223
if functions and call.name in functions:
218224
function = functions[call.name]
219225
try:
220-
# Parse arguments into Pydantic model
221-
parsed_args = function.parameter_schema(**call.arguments)
226+
# Parse arguments using utility function
227+
parsed_args = parse_function_arguments(function, call.arguments)
222228

223229
# Handle both sync and async function handlers
224230
result = function.handler(parsed_args)
@@ -301,9 +307,14 @@ def _convert_functions_to_tools(self, functions: dict[str, Function[BaseModel]])
301307
tools: list[ToolParam] = []
302308

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

308319
tools.append(
309320
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)