[Bugfix][Mooncake] Fix MultiConnector stats and proxy SSE responses (#61218)

Signed-off-by: whx-sjtu <xiaowang990929@gmail.com>
Signed-off-by: Nicolò Lucchesi <nicolo.lucchesi@gmail.com>
This commit is contained in:
Hexiang Wang
2026-10-16 17:54:48 +08:00
committed by GitHub
parent 07f3857fb9
commit 9a16d43ef0
5 changed files with 72 additions and 6 deletions
+1
View File
@@ -214,6 +214,7 @@ steps:
- vllm/v1/
- tests/v1
- tests/watermarking
- examples/disaggregated/mooncake_connector/mooncake_connector_proxy.py
device: cpu-small
commands:
# split the test to avoid interference
@@ -340,7 +340,10 @@ async def _handle_completions(api: str, request: Request):
):
yield chunk
return StreamingResponse(generate_stream(), media_type="application/json")
media_type = (
"text/event-stream" if req_data.get("stream") else "application/json"
)
return StreamingResponse(generate_stream(), media_type=media_type)
except Exception as e:
import sys
@@ -0,0 +1,47 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
import asyncio
import itertools
import runpy
from pathlib import Path
from unittest.mock import AsyncMock
import httpx
import pytest
pytestmark = pytest.mark.cpu_test
@pytest.mark.asyncio
@pytest.mark.parametrize("endpoint", ["/v1/completions", "/v1/chat/completions"])
@pytest.mark.parametrize("stream", [False, True])
async def test_response_media_type_matches_payload(monkeypatch, endpoint, stream):
"""Clients must select SSE parsing for streaming completion responses."""
path = (
Path(__file__).parents[4]
/ "examples/disaggregated/mooncake_connector/mooncake_connector_proxy.py"
)
proxy = runpy.run_path(str(path))["_handle_completions"].__globals__
app = proxy["app"]
app.state.ready = asyncio.Event()
app.state.ready.set()
app.state.prefill_iterator = itertools.cycle([({}, 0)])
app.state.decode_iterator = itertools.cycle([0])
app.state.decode_clients = [{}]
payload = b'{"choices":[{"text":"hello"}]}'
body = b"data: " + payload + b"\n\ndata: [DONE]\n\n" if stream else payload
async def decode_response(*args, **kwargs):
yield body
monkeypatch.setitem(proxy, "send_request_to_service", AsyncMock())
monkeypatch.setitem(proxy, "stream_service_response", decode_response)
async with httpx.AsyncClient(
transport=httpx.ASGITransport(app=app), base_url="http://proxy"
) as client:
response = await client.post(endpoint, json={"stream": stream})
expected_type = "text/event-stream" if stream else "application/json"
assert response.headers["content-type"].split(";")[0] == expected_type
assert response.content == body
@@ -805,6 +805,23 @@ class TestMultiConnectorStats:
child_metrics.observe.assert_called_once_with({"transfer_duration": [1.5]}, 2)
@pytest.mark.parametrize("with_exporter", [False, True])
def test_prom_metrics_skips_stats_only_connectors(self, with_exporter):
"""Stats-only children must not prevent supported metrics from exporting."""
child_metrics = MagicMock()
metrics = object.__new__(MultiKVConnectorPromMetrics)
metrics._prom_metrics = (
{"MooncakeStoreConnector": child_metrics} if with_exporter else {}
)
payload = {"MooncakeConnector": {"bytes_transferred": [1024]}}
if with_exporter:
payload["MooncakeStoreConnector"] = {"load_get_bytes": [2048]}
metrics.observe(payload, engine_idx=2)
if with_exporter:
child_metrics.observe.assert_called_once_with({"load_get_bytes": [2048]}, 2)
def test_aggregate_same_connector(self):
"""Test aggregating stats from the same connector type."""
stats1 = MultiKVConnectorStats(
@@ -125,11 +125,9 @@ class MultiKVConnectorPromMetrics(KVConnectorPromMetrics):
def observe(self, transfer_stats_data: dict[str, Any], engine_idx: int = 0):
for connector_id, stats_data in transfer_stats_data.items():
assert connector_id in self._prom_metrics, (
f"{connector_id} is not contained in the list of registered connectors "
f"with Prometheus metrics support: {self._prom_metrics.keys()}"
)
self._prom_metrics[connector_id].observe(stats_data, engine_idx)
prom_metrics = self._prom_metrics.get(connector_id)
if prom_metrics is not None:
prom_metrics.observe(stats_data, engine_idx)
class MultiConnector(KVConnectorBase_V1, SupportsHMA):