From d87459790814f5cf87a4e2f33cb25e4dc73317d7 Mon Sep 17 00:00:00 2001 From: Eric Huang Date: Mon, 29 Sep 2025 22:39:06 -0700 Subject: [PATCH] fix: mcp tool with array type should include items # What does this PR do? ## Test Plan --- .../meta_reference/responses/streaming.py | 48 ++++++++++++------- tests/unit/providers/inline/__init__.py | 0 .../unit/providers/inline/agents/__init__.py | 0 .../inline/agents/meta_reference/__init__.py | 0 .../meta_reference/responses/__init__.py | 5 ++ .../responses/test_streaming.py | 42 ++++++++++++++++ 6 files changed, 78 insertions(+), 17 deletions(-) create mode 100644 tests/unit/providers/inline/__init__.py create mode 100644 tests/unit/providers/inline/agents/__init__.py create mode 100644 tests/unit/providers/inline/agents/meta_reference/__init__.py create mode 100644 tests/unit/providers/inline/agents/meta_reference/responses/__init__.py create mode 100644 tests/unit/providers/inline/agents/meta_reference/responses/test_streaming.py diff --git a/llama_stack/providers/inline/agents/meta_reference/responses/streaming.py b/llama_stack/providers/inline/agents/meta_reference/responses/streaming.py index 3e69fa5cd..2f45ad2a3 100644 --- a/llama_stack/providers/inline/agents/meta_reference/responses/streaming.py +++ b/llama_stack/providers/inline/agents/meta_reference/responses/streaming.py @@ -50,6 +50,36 @@ from .utils import convert_chat_choice_to_response_message, is_function_tool_cal logger = get_logger(name=__name__, category="agents::meta_reference") +def convert_tooldef_to_chat_tool(tool_def): + """Convert a ToolDef to OpenAI ChatCompletionToolParam format. + + Args: + tool_def: ToolDef from the tools API + + Returns: + ChatCompletionToolParam suitable for OpenAI chat completion + """ + + from llama_stack.models.llama.datatypes import ToolDefinition, ToolParamDefinition + from llama_stack.providers.utils.inference.openai_compat import convert_tooldef_to_openai_tool + + internal_tool_def = ToolDefinition( + tool_name=tool_def.name, + description=tool_def.description, + parameters={ + param.name: ToolParamDefinition( + param_type=param.parameter_type, + description=param.description, + required=param.required, + default=param.default, + items=param.items, + ) + for param in tool_def.parameters + }, + ) + return convert_tooldef_to_openai_tool(internal_tool_def) + + class StreamingResponseOrchestrator: def __init__( self, @@ -556,23 +586,7 @@ class StreamingResponseOrchestrator: continue if not always_allowed or t.name in always_allowed: # Add to chat tools for inference - from llama_stack.models.llama.datatypes import ToolDefinition, ToolParamDefinition - from llama_stack.providers.utils.inference.openai_compat import convert_tooldef_to_openai_tool - - tool_def = ToolDefinition( - tool_name=t.name, - description=t.description, - parameters={ - param.name: ToolParamDefinition( - param_type=param.parameter_type, - description=param.description, - required=param.required, - default=param.default, - ) - for param in t.parameters - }, - ) - openai_tool = convert_tooldef_to_openai_tool(tool_def) + openai_tool = convert_tooldef_to_chat_tool(t) if self.ctx.chat_tools is None: self.ctx.chat_tools = [] self.ctx.chat_tools.append(openai_tool) diff --git a/tests/unit/providers/inline/__init__.py b/tests/unit/providers/inline/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/providers/inline/agents/__init__.py b/tests/unit/providers/inline/agents/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/providers/inline/agents/meta_reference/__init__.py b/tests/unit/providers/inline/agents/meta_reference/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/providers/inline/agents/meta_reference/responses/__init__.py b/tests/unit/providers/inline/agents/meta_reference/responses/__init__.py new file mode 100644 index 000000000..756f351d8 --- /dev/null +++ b/tests/unit/providers/inline/agents/meta_reference/responses/__init__.py @@ -0,0 +1,5 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. diff --git a/tests/unit/providers/inline/agents/meta_reference/responses/test_streaming.py b/tests/unit/providers/inline/agents/meta_reference/responses/test_streaming.py new file mode 100644 index 000000000..6fda2b508 --- /dev/null +++ b/tests/unit/providers/inline/agents/meta_reference/responses/test_streaming.py @@ -0,0 +1,42 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from llama_stack.apis.tools import ToolDef, ToolParameter +from llama_stack.providers.inline.agents.meta_reference.responses.streaming import ( + convert_tooldef_to_chat_tool, +) + + +def test_convert_tooldef_to_chat_tool_preserves_items_field(): + """Test that array parameters preserve the items field during conversion. + + This test ensures that when converting ToolDef with array-type parameters + to OpenAI ChatCompletionToolParam format, the 'items' field is preserved. + Without this fix, array parameters would be missing schema information about their items. + """ + tool_def = ToolDef( + name="test_tool", + description="A test tool with array parameter", + parameters=[ + ToolParameter( + name="tags", + parameter_type="array", + description="List of tags", + required=True, + items={"type": "string"}, + ) + ], + ) + + result = convert_tooldef_to_chat_tool(tool_def) + + assert result["type"] == "function" + assert result["function"]["name"] == "test_tool" + + tags_param = result["function"]["parameters"]["properties"]["tags"] + assert tags_param["type"] == "array" + assert "items" in tags_param, "items field should be preserved for array parameters" + assert tags_param["items"] == {"type": "string"}