fix(responses): encode deployment id into streamed bridge response ids

The chat-completions -> Responses API streaming bridge minted a bare uuid for response.created / response.in_progress and only encoded provider + deployment id on response.completed, so a client that read the id off response.created and sent it back as previous_response_id lost deployment affinity and had the unknown id forwarded to the upstream provider.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Devin AI 2026-07-30 22:56:58 +00:00
parent 7c56317edf
commit 40157343db
4 changed files with 70 additions and 4 deletions

View file

@ -310,10 +310,22 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator):
)
self._pending_tool_events.append(item_done_event)
def _build_encoded_response_id(self) -> str:
"""Build the provider/deployment-encoded id shared by every event of this stream."""
metadata: dict[str, object] = cast(dict[str, object], self.litellm_metadata or {})
model_info = metadata.get("model_info")
raw_model_id = cast(dict[str, object], model_info).get("id") if isinstance(model_info, dict) else None
model_id = raw_model_id if isinstance(raw_model_id, str) else None
return ResponsesAPIRequestUtils.build_responses_api_response_id(
custom_llm_provider=self.custom_llm_provider,
model_id=model_id,
response_id=f"resp_{uuid.uuid4()}",
)
def _default_response_created_event_data(self) -> dict:
# Use cached response ID if available, otherwise generate a new one
if self._cached_response_id is None:
self._cached_response_id = f"resp_{str(uuid.uuid4())}"
self._cached_response_id = self._build_encoded_response_id()
response_created_event_data = {
"id": self._cached_response_id,

View file

@ -207,7 +207,7 @@ class ResponsesAPIRequestUtils:
if ResponsesAPIRequestUtils._is_litellm_encoded_response_id(response_id):
return responses_api_response
updated_id = ResponsesAPIRequestUtils._build_responses_api_response_id(
updated_id = ResponsesAPIRequestUtils.build_responses_api_response_id(
model_id=model_id,
custom_llm_provider=custom_llm_provider,
response_id=response_id,
@ -408,7 +408,7 @@ class ResponsesAPIRequestUtils:
return request_input
@staticmethod
def _build_responses_api_response_id(
def build_responses_api_response_id(
custom_llm_provider: Optional[str],
model_id: Optional[str],
response_id: str,

View file

@ -0,0 +1,54 @@
"""
Tests for the response id emitted by the chat-completions -> Responses API streaming bridge.
Every streaming event has to carry the same litellm-encoded id (provider + deployment id), so a
client that reads the id off `response.created` can send it back as `previous_response_id` and
still get routed to the deployment that served the session.
"""
from unittest.mock import AsyncMock
from litellm.responses.litellm_completion_transformation.streaming_iterator import (
LiteLLMCompletionStreamingIterator,
)
from litellm.responses.utils import ResponsesAPIRequestUtils
from litellm.types.utils import Choices, Message, ModelResponse
def _build_iterator() -> LiteLLMCompletionStreamingIterator:
return LiteLLMCompletionStreamingIterator(
model="test-model",
litellm_custom_stream_wrapper=AsyncMock(),
request_input="Test input",
responses_api_request={},
custom_llm_provider="anthropic",
litellm_metadata={"model_info": {"id": "deployment-123"}},
)
def test_response_created_event_id_is_encoded_with_the_deployment_id():
iterator = _build_iterator()
created_event = iterator.create_response_created_event()
decoded = ResponsesAPIRequestUtils._decode_responses_api_response_id(created_event.response.id)
assert decoded["model_id"] == "deployment-123"
assert decoded["custom_llm_provider"] == "anthropic"
def test_streaming_events_all_share_the_completed_event_id():
iterator = _build_iterator()
created_event = iterator.create_response_created_event()
in_progress_event = iterator.create_response_in_progress_event()
completed_event = iterator._emit_response_completed_event(
ModelResponse(
id="chatcmpl-1",
choices=[Choices(finish_reason="stop", index=0, message=Message(role="assistant", content="hi"))],
model="test-model",
)
)
assert completed_event is not None
assert created_event.response.id == completed_event.response.id
assert in_progress_event.response.id == completed_event.response.id

View file

@ -102,7 +102,7 @@ class TestResponsesAPIRequestUtils:
original_response_id = "resp_abc123"
# Use the helper method to build an encoded response ID
encoded_id = ResponsesAPIRequestUtils._build_responses_api_response_id(
encoded_id = ResponsesAPIRequestUtils.build_responses_api_response_id(
custom_llm_provider=test_provider,
model_id=test_model_id,
response_id=original_response_id,