litellm/tests/benchmarks/test_mcp_benchmarks.py
Yassin Kortam 1eb7122465
test(benchmarks): add CodSpeed benchmarks for inference, MCP and A2A hot paths (#31716)
Guard the per-request CPU cost of the chat completion, MCP tool and A2A
message transforms against regressions on every commit. All benchmarks are
pure in-process work with no network I/O so they stay deterministic under
CodSpeed's simulation mode, and they import under the base dependency set the
benchmark job installs.

Inference covers the full SDK overhead via mock_response (simple, multi-turn,
tools, streaming) plus convert_to_model_response_object as a deterministic
anchor. MCP covers the client-side tool translation and the proxy server-side
tool-name prefix round-trip. A2A covers the client request/response transforms
and the proxy server-ingress message conversion.

Adds the mcp and a2a-sdk packages to the benchmark run since those transform
modules need them, and broadens the workflow triggers to litellm_internal_staging
so the internal branch flow is benchmarked too.
2026-06-30 10:27:12 -07:00

84 lines
2.6 KiB
Python

"""
Performance benchmarks for the MCP tool hot path.
Two layers are covered: the client-side translation between MCP and OpenAI
function-calling formats, and the server-side tool-name prefixing that the proxy
runs on every list-tools (prefix each tool) and call-tool (strip prefix to route)
request. Both are pure-CPU and deterministic.
"""
import pytest
from mcp.types import Tool as MCPTool
from litellm.experimental_mcp_client.tools import (
transform_mcp_tool_to_openai_tool,
transform_openai_tool_call_request_to_mcp_tool_call_request,
)
from litellm.proxy._experimental.mcp_server.utils import (
add_server_prefix_to_name,
split_server_prefix_from_name,
)
def _make_tool(index: int) -> MCPTool:
return MCPTool(
name=f"tool_{index}",
description=f"Test tool number {index} that performs an operation",
inputSchema={
"type": "object",
"properties": {
"query": {"type": "string", "description": "The search query"},
"limit": {"type": "integer", "description": "Max results"},
},
"required": ["query"],
},
)
SINGLE_TOOL = _make_tool(0)
TOOL_LIST = tuple(_make_tool(i) for i in range(20))
TOOL_NAMES = tuple(t.name for t in TOOL_LIST)
SERVER_NAME = "github_mcp"
PREFIXED_TOOL_NAME = add_server_prefix_to_name("tool_0", SERVER_NAME)
OPENAI_TOOL_CALL = {
"id": "call_abc123",
"type": "function",
"function": {
"name": "tool_0",
"arguments": '{"query": "weather in San Francisco", "limit": 5}',
},
}
@pytest.mark.benchmark
def test_transform_single_mcp_tool_to_openai():
"""Benchmark translating one MCP tool into OpenAI tool format."""
transform_mcp_tool_to_openai_tool(mcp_tool=SINGLE_TOOL)
@pytest.mark.benchmark
def test_transform_mcp_tool_list_to_openai():
"""Benchmark translating a full list-tools response into OpenAI format."""
for tool in TOOL_LIST:
transform_mcp_tool_to_openai_tool(mcp_tool=tool)
@pytest.mark.benchmark
def test_transform_openai_tool_call_to_mcp():
"""Benchmark translating an OpenAI tool call into an MCP call request."""
transform_openai_tool_call_request_to_mcp_tool_call_request(openai_tool=OPENAI_TOOL_CALL)
@pytest.mark.benchmark
def test_mcp_server_prefix_tool_list():
"""Benchmark the proxy prefixing every tool name on a list-tools response."""
for name in TOOL_NAMES:
add_server_prefix_to_name(name, SERVER_NAME)
@pytest.mark.benchmark
def test_mcp_server_strip_prefix_on_call():
"""Benchmark the proxy stripping the server prefix to route a tool call."""
split_server_prefix_from_name(PREFIXED_TOOL_NAME)