Revert "drop the undocumented response.swarm.llm_delta mapping"

A live stream against apodex-1-1-deep-research shows the event is real and
load-bearing: 181 of the 193 events are response.swarm.llm_delta and
response.output_text.delta never appears, so without the mapping the answer
text only arrives in the final response.completed snapshot.

Restores the transform with the provenance recorded in a docstring, and
covers it with the payload shape captured off the wire, including the
reasoning channel that carries 176 of those deltas and must not be mistaken
for the answer.
This commit is contained in:
zhanghanduo 2026-08-16 17:42:46 +08:00
parent c3f8e5aa67
commit 64dcb95268
2 changed files with 108 additions and 0 deletions

View file

@ -29,6 +29,7 @@ from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfi
from litellm.types.llms.openai import (
ResponsesAPIOptionalRequestParams,
ResponsesAPIResponse,
ResponsesAPIStreamingResponse,
)
from litellm.types.router import GenericLiteLLMParams
from litellm.types.utils import LlmProviders
@ -108,6 +109,48 @@ class ApodexResponsesConfig(OpenAIResponsesAPIConfig):
logging_obj=logging_obj,
)
def transform_streaming_response(
self,
model: str,
parsed_chunk: dict, # mutable-ok: matches the base-class signature
logging_obj: LiteLLMLoggingObj,
) -> ResponsesAPIStreamingResponse:
"""Surface the Deep Research answer text as the OpenAI delta event callers expect.
Observed live, not documented: a Deep Research stream carries its text in
`response.swarm.llm_delta` and never emits `response.output_text.delta`, so
without this the answer arrives only in the final `response.completed`
snapshot. `channel` splits the agent's reasoning from its answer; everything
else falls through to the base class as a GenericEvent.
"""
swarm: Final = parsed_chunk.get("swarm")
swarm_data: Final = swarm.get("data") if isinstance(swarm, dict) else None
if (
parsed_chunk.get("type") != "response.swarm.llm_delta"
or not isinstance(swarm_data, dict)
or swarm_data.get("channel") != "output_text"
or not isinstance(swarm_data.get("delta"), str)
):
return super().transform_streaming_response(
model=model,
parsed_chunk=parsed_chunk,
logging_obj=logging_obj,
)
response_id: Final = str(parsed_chunk.get("response_id", ""))
return super().transform_streaming_response(
model=model,
parsed_chunk={ # mutable-ok: JSON event payload
"type": "response.output_text.delta",
"item_id": f"msg_{response_id}",
"output_index": 0,
"content_index": 0,
"delta": swarm_data["delta"],
"sequence_number": parsed_chunk.get("sequence_number", 0),
},
logging_obj=logging_obj,
)
def get_supported_openai_params(self, model: str) -> list: # mutable-ok: matches the base-class signature
inherited: Final = super().get_supported_openai_params(model)
if is_deep_research_model(model):

View file

@ -231,6 +231,71 @@ class TestDeepResearchKeepsState:
assert response.status == "cancelled"
assert response._hidden_params["headers"]["x-ratelimit-remaining-requests"] == "42"
def test_output_text_delta_is_normalized(self):
"""A Deep Research stream never emits response.output_text.delta of its own.
Payload shape captured from a live stream; the extra swarm.data keys ride
along untouched and must not affect the mapping.
"""
config = _responses_config("apodex-1-1-deep-research")
event = config.transform_streaming_response(
model="apodex-1-1-deep-research",
parsed_chunk={
"type": "response.swarm.llm_delta",
"created_at": 1786873219.3543231,
"response_id": "w_c4b77c96",
"sequence_number": 12,
"swarm": {
"agent_id": "stateful_react",
"data": {
"channel": "output_text",
"delta": "Hello there, friend!",
"delta_index": 0,
"call_id": "llm_fce8e965",
"turn": 1,
},
},
},
logging_obj=None,
)
assert event.type == "response.output_text.delta"
assert event.item_id == "msg_w_c4b77c96"
assert event.delta == "Hello there, friend!"
assert event.sequence_number == 12
@pytest.mark.parametrize(
"channel",
("reasoning", None),
ids=("reasoning-channel", "no-channel"),
)
def test_non_answer_deltas_are_not_claimed_as_output_text(self, channel):
"""Most of the stream is the agent thinking; only `output_text` is the answer."""
data = {"delta": "The"} if channel is None else {"channel": channel, "delta": "The"}
config = _responses_config("apodex-1-1-deep-research")
event = config.transform_streaming_response(
model="apodex-1-1-deep-research",
parsed_chunk={
"type": "response.swarm.llm_delta",
"response_id": "w_c4b77c96",
"sequence_number": 7,
"swarm": {"agent_id": "stateful_react", "data": data},
},
logging_obj=None,
)
assert event.type == "response.swarm.llm_delta"
def test_documented_events_pass_through_untouched(self):
config = _responses_config("apodex-1-1-deep-research")
event = config.transform_streaming_response(
model="apodex-1-1-deep-research",
parsed_chunk={"type": "response.in_progress", "sequence_number": 2},
logging_obj=None,
)
assert event.type == "response.in_progress"
def test_non_json_cancel_body_raises_the_provider_error(self):
"""The gateway answers a timed-out cancel with an HTML 504, not the JSON envelope."""
config = _responses_config("apodex-1-1-deep-research")