mirror of
https://github.com/meta-llama/llama-stack.git
synced 2025-12-03 18:00:36 +00:00
fix: mcp tool with array type should include items (#3602)
Some checks failed
Test External Providers Installed via Module / test-external-providers-from-module (venv) (push) Has been skipped
Test External API and Providers / test-external (venv) (push) Failing after 6s
SqlStore Integration Tests / test-postgres (3.12) (push) Failing after 11s
Integration Auth Tests / test-matrix (oauth2_token) (push) Failing after 17s
Unit Tests / unit-tests (3.13) (push) Failing after 14s
Vector IO Integration Tests / test-matrix (push) Failing after 19s
SqlStore Integration Tests / test-postgres (3.13) (push) Failing after 21s
Python Package Build Test / build (3.12) (push) Failing after 20s
Python Package Build Test / build (3.13) (push) Failing after 23s
Integration Tests (Replay) / Integration Tests (, , , client=, ) (push) Failing after 28s
Unit Tests / unit-tests (3.12) (push) Failing after 25s
API Conformance Tests / check-schema-compatibility (push) Successful in 32s
UI Tests / ui-tests (22) (push) Successful in 57s
Pre-commit / pre-commit (push) Successful in 1m18s
Some checks failed
Test External Providers Installed via Module / test-external-providers-from-module (venv) (push) Has been skipped
Test External API and Providers / test-external (venv) (push) Failing after 6s
SqlStore Integration Tests / test-postgres (3.12) (push) Failing after 11s
Integration Auth Tests / test-matrix (oauth2_token) (push) Failing after 17s
Unit Tests / unit-tests (3.13) (push) Failing after 14s
Vector IO Integration Tests / test-matrix (push) Failing after 19s
SqlStore Integration Tests / test-postgres (3.13) (push) Failing after 21s
Python Package Build Test / build (3.12) (push) Failing after 20s
Python Package Build Test / build (3.13) (push) Failing after 23s
Integration Tests (Replay) / Integration Tests (, , , client=, ) (push) Failing after 28s
Unit Tests / unit-tests (3.12) (push) Failing after 25s
API Conformance Tests / check-schema-compatibility (push) Successful in 32s
UI Tests / ui-tests (22) (push) Successful in 57s
Pre-commit / pre-commit (push) Successful in 1m18s
# What does this PR do?
Fixes error:
```
[ERROR] Error executing endpoint route='/v1/openai/v1/responses'
method='post': Error code: 400 - {'error': {'message': "Invalid schema for function 'pods_exec': In context=('properties', 'command'), array
schema missing items.", 'type': 'invalid_request_error', 'param': 'tools[7].function.parameters', 'code': 'invalid_function_parameters'}}
```
From script:
```
#!/usr/bin/env python3
"""
Script to test Responses API with kubernetes-mcp-server.
This script:
1. Connects to the llama stack server
2. Uses the Responses API with MCP tools
3. Asks for the list of Kubernetes namespaces using the kubernetes-mcp-server
"""
import json
from openai import OpenAI
# Connect to the llama stack server
base_url = "http://localhost:8321/v1/openai/v1"
client = OpenAI(base_url=base_url, api_key="fake")
# Define the MCP tool pointing to the kubernetes-mcp-server
# The kubernetes-mcp-server is running on port 3000 with SSE endpoint at /sse
mcp_server_url = "http://localhost:3000/sse"
tools = [
{
"type": "mcp",
"server_label": "k8s",
"server_url": mcp_server_url,
}
]
# Create a response request asking for k8s namespaces
print("Sending request to list Kubernetes namespaces...")
print(f"Using MCP server at: {mcp_server_url}")
print("Available tools will be listed automatically by the MCP server.")
print()
response = client.responses.create(
# model="meta-llama/Llama-3.2-3B-Instruct", # Using the vllm model
model="openai/gpt-4o",
input="what are all the Kubernetes namespaces? Use tool call to `namespaces_list`. make sure to adhere to the tool calling format.",
tools=tools,
stream=False,
)
print("\n" + "=" * 80)
print("RESPONSE OUTPUT:")
print("=" * 80)
# Print the output
for i, output in enumerate(response.output):
print(f"\n[Output {i + 1}] Type: {output.type}")
if output.type == "mcp_list_tools":
print(f" Server: {output.server_label}")
print(f" Tools available: {[t.name for t in output.tools]}")
elif output.type == "mcp_call":
print(f" Tool called: {output.name}")
print(f" Arguments: {output.arguments}")
print(f" Result: {output.output}")
if output.error:
print(f" Error: {output.error}")
elif output.type == "message":
print(f" Role: {output.role}")
print(f" Content: {output.content}")
print("\n" + "=" * 80)
print("FINAL RESPONSE TEXT:")
print("=" * 80)
print(response.output_text)
```
## Test Plan
new unit tests
script now runs successfully
This commit is contained in:
parent
56b625d18a
commit
6cce553c93
6 changed files with 93 additions and 17 deletions
|
|
@ -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")
|
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:
|
class StreamingResponseOrchestrator:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|
@ -556,23 +586,7 @@ class StreamingResponseOrchestrator:
|
||||||
continue
|
continue
|
||||||
if not always_allowed or t.name in always_allowed:
|
if not always_allowed or t.name in always_allowed:
|
||||||
# Add to chat tools for inference
|
# Add to chat tools for inference
|
||||||
from llama_stack.models.llama.datatypes import ToolDefinition, ToolParamDefinition
|
openai_tool = convert_tooldef_to_chat_tool(t)
|
||||||
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)
|
|
||||||
if self.ctx.chat_tools is None:
|
if self.ctx.chat_tools is None:
|
||||||
self.ctx.chat_tools = []
|
self.ctx.chat_tools = []
|
||||||
self.ctx.chat_tools.append(openai_tool)
|
self.ctx.chat_tools.append(openai_tool)
|
||||||
|
|
|
||||||
5
tests/unit/providers/inline/__init__.py
Normal file
5
tests/unit/providers/inline/__init__.py
Normal file
|
|
@ -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.
|
||||||
5
tests/unit/providers/inline/agents/__init__.py
Normal file
5
tests/unit/providers/inline/agents/__init__.py
Normal file
|
|
@ -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.
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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"}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue