mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-26 06:03:19 +00:00
test(mcp): cover /mcp/<name> gateway routing on the proxy and Messages tools on a base SDK install
This commit is contained in:
parent
e280439629
commit
a0cfb4dcfd
2 changed files with 100 additions and 0 deletions
|
|
@ -156,6 +156,50 @@ def check_responses(base: str, asynchronous: bool = False) -> None:
|
|||
assert json.loads(body) == {"model": "gpt-6", "input": "ping", "tools": tools}
|
||||
|
||||
|
||||
def check_messages_tools(base: str) -> None:
|
||||
tools: Final = [
|
||||
{
|
||||
"name": "lookup",
|
||||
"description": "Look up the forecast for a city",
|
||||
"input_schema": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
|
||||
}
|
||||
]
|
||||
content: Final = [{"type": "tool_use", "id": "toolu-http-check", "name": "lookup", "input": {"city": "Paris"}}]
|
||||
enqueue(
|
||||
{
|
||||
"id": "msg-tools-http-check",
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"model": "claude-sonnet-5-5",
|
||||
"content": content,
|
||||
"stop_reason": "tool_use",
|
||||
"stop_sequence": None,
|
||||
"usage": {"input_tokens": 3, "output_tokens": 2},
|
||||
}
|
||||
)
|
||||
|
||||
async def send() -> object:
|
||||
create: Final = cast( # cast-ok: SDK facade exposes untyped kwargs
|
||||
Callable[..., Awaitable[object]], litellm.anthropic.messages.acreate
|
||||
)
|
||||
return await create(
|
||||
model="anthropic/claude-sonnet-5-5",
|
||||
messages=MESSAGES,
|
||||
tools=tools,
|
||||
max_tokens=16,
|
||||
api_key="test-key",
|
||||
api_base=base,
|
||||
)
|
||||
|
||||
response: Final = JSON_OBJECT.validate_python(asyncio.run(send()))
|
||||
assert response["content"] == content, response
|
||||
path, headers, body = REQUESTS.get(timeout=10)
|
||||
assert path == "/v1/messages"
|
||||
assert headers["x-api-key"] == "test-key"
|
||||
sent: Final = JSON_OBJECT.validate_json(body)
|
||||
assert (sent["model"], sent["messages"], sent["tools"]) == ("claude-sonnet-5-5", MESSAGES, tools), sent
|
||||
|
||||
|
||||
@skipIf(importlib.util.find_spec("fastapi") is None, "BUG: LIT-9333 cache hits import proxy-only fastapi")
|
||||
def check_cache_hit(base: str) -> None:
|
||||
from litellm.caching.caching import Cache
|
||||
|
|
@ -394,6 +438,7 @@ def main() -> None:
|
|||
partial(check_tools, asynchronous=True),
|
||||
check_responses,
|
||||
partial(check_responses, asynchronous=True),
|
||||
check_messages_tools,
|
||||
check_cache_hit,
|
||||
check_messages_stream_and_callback,
|
||||
):
|
||||
|
|
|
|||
|
|
@ -566,6 +566,61 @@ def test_server_scoped_gateway_url_exposes_only_that_servers_tools(gateway: Gate
|
|||
assert [call["body"]["params"]["name"] for call in _peer_add_calls(rig.peer)] == ["add"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("surface", ("chat", "responses", "messages"))
|
||||
def test_proxy_path_url_of_a_served_server_is_listed_and_executed_by_the_gateway(
|
||||
gateway: Gateway, surface: Surface
|
||||
) -> None:
|
||||
with _rig(gateway, surface) as rig:
|
||||
server_url: Final = f"{str(gateway.client.base_url).rstrip('/')}/mcp/{rig.alias}"
|
||||
response: Final = rig.send(_granted_key(rig), [{**AUTO, "server_url": server_url}])
|
||||
assert response.status_code == 200, response.text
|
||||
requests: Final = rig.upstream_tools()
|
||||
assert len(requests) == 2 and all(rig.tool in names for names in requests), requests
|
||||
peer_calls: Final = tuple(JSON_OBJECT.validate_python(call["body"]) for call in _peer_add_calls(rig.peer))
|
||||
assert [object_value(call["params"])["name"] for call in peer_calls] == ["add"], peer_calls
|
||||
assert rig.final_text(JSON_OBJECT.validate_json(response.content)) == ANSWER, response.text
|
||||
|
||||
|
||||
LOOKUP_PARAMETERS: Final = {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
|
||||
CALLER_FUNCTION_TOOLS: Final[Mapping[Surface, Mapping[str, object]]] = {
|
||||
"chat": {"type": "function", "function": {"name": "lookup", "parameters": LOOKUP_PARAMETERS}},
|
||||
"responses": {"type": "function", "name": "lookup", "parameters": LOOKUP_PARAMETERS},
|
||||
"messages": {"name": "lookup", "input_schema": LOOKUP_PARAMETERS},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("surface", ("chat", "responses", "messages"))
|
||||
def test_tools_that_do_not_name_the_gateway_reach_the_provider_unchanged_and_never_the_peer(
|
||||
gateway: Gateway, surface: Surface
|
||||
) -> None:
|
||||
with _rig(gateway, surface) as rig:
|
||||
remote_mcp_tools: Final = [
|
||||
{
|
||||
"type": "mcp",
|
||||
"server_label": "docs",
|
||||
"server_url": "https://mcp.example.com/sse",
|
||||
"require_approval": "never",
|
||||
},
|
||||
{
|
||||
"type": "mcp",
|
||||
"server_label": "repos",
|
||||
"server_url": f"https://mcp.example.com/mcp/unserved{uuid.uuid4().hex[:8]}",
|
||||
"require_approval": "never",
|
||||
},
|
||||
]
|
||||
response: Final = rig.send(_granted_key(rig), [CALLER_FUNCTION_TOOLS[surface], *remote_mcp_tools])
|
||||
assert response.status_code == 200, response.text
|
||||
bodies: Final = tuple(
|
||||
JSON_OBJECT.validate_json(request.body) for request in rig.wire.drain() if request.method == "POST"
|
||||
)
|
||||
assert len(bodies) == 1, bodies
|
||||
forwarded: Final = bodies[0].get("tools")
|
||||
assert isinstance(forwarded, list) and len(forwarded) == 3, forwarded
|
||||
assert [tool for tool in forwarded if isinstance(tool, dict) and tool.get("type") == "mcp"] == remote_mcp_tools
|
||||
assert "lookup" in json.dumps([tool for tool in forwarded if tool not in remote_mcp_tools]), forwarded
|
||||
assert tool_calls(rig.peer.drain()) == (), "a tool that does not name the gateway reached the peer"
|
||||
|
||||
|
||||
def test_streaming_chat_executes_the_tool_once_and_streams_the_follow_up(gateway: Gateway) -> None:
|
||||
with _rig(gateway, "chat") as rig:
|
||||
key: Final = _granted_key(rig)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue