Fix invalid function schema error for tools with None parameters

Normalize tool function parameters before forwarding to OpenAI API.
When parameters is None or missing, default to a valid empty object
schema {"type": "object", "properties": {}}. This prevents the
BadRequestError reported in #22016.

Fixes https://github.com/galaxyproject/galaxy/issues/22016
This commit is contained in:
mvdbeek
2026-03-10 14:18:12 +01:00
parent 570105babe
commit da8fcba969
2 changed files with 62 additions and 0 deletions
+3
View File
@@ -238,6 +238,9 @@ class FastAPIPlugins:
if len(original_tools) <= MAX_TOOLS:
for tool in original_tools:
tool_dict = tool.model_dump()
func = tool_dict.get("function", {})
if func.get("parameters") is None:
func["parameters"] = {"type": "object", "properties": {}}
size = len(json.dumps(tool_dict, separators=(",", ":")).encode("utf-8"))
if size > MAX_TOOL_BYTES:
return self._create_error("Tool schema too large.")
+59
View File
@@ -216,6 +216,65 @@ class TestVisualizationPluginsApi(IntegrationTestCase):
forwarded_tools = call_kwargs["tools"]
assert forwarded_tools[0]["function"]["description"] == "Select a processing step"
@patch("galaxy.webapps.galaxy.api.plugins.AsyncOpenAI")
def test_tool_with_none_parameters_normalized(self, mock_client):
mock_response = MagicMock()
mock_response.model_dump.return_value = {"id": "test", "choices": []}
mock_instance = MagicMock()
mock_instance.chat.completions.create = AsyncMock(return_value=mock_response)
mock_client.return_value = mock_instance
payload = _create_chat_payload(
{
"tools": [
{
"type": "function",
"function": {
"name": "choose_process",
"description": "Select a processing step",
"parameters": None,
},
}
]
}
)
response = self._post_payload(payload)
self._assert_status_code_is(response, 200)
call_kwargs = mock_instance.chat.completions.create.call_args.kwargs
forwarded_tools = call_kwargs["tools"]
assert forwarded_tools[0]["function"]["parameters"] == {
"type": "object",
"properties": {},
}
@patch("galaxy.webapps.galaxy.api.plugins.AsyncOpenAI")
def test_tool_with_missing_parameters_normalized(self, mock_client):
mock_response = MagicMock()
mock_response.model_dump.return_value = {"id": "test", "choices": []}
mock_instance = MagicMock()
mock_instance.chat.completions.create = AsyncMock(return_value=mock_response)
mock_client.return_value = mock_instance
payload = _create_chat_payload(
{
"tools": [
{
"type": "function",
"function": {
"name": "choose_process",
"description": "Select a processing step",
},
}
]
}
)
response = self._post_payload(payload)
self._assert_status_code_is(response, 200)
call_kwargs = mock_instance.chat.completions.create.call_args.kwargs
forwarded_tools = call_kwargs["tools"]
assert forwarded_tools[0]["function"]["parameters"] == {
"type": "object",
"properties": {},
}
@patch("galaxy.webapps.galaxy.api.plugins.AsyncOpenAI")
def test_provider_error_body_forwarded(self, mock_client):
class MockOpenAIError(APIError):