This commit is contained in:
Тимур Комолов 2026-08-27 22:53:14 +03:00 • committed by GitHub
commit 148c48c628
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 81 additions and 0 deletions

View file

@ -816,6 +816,8 @@ class CustomStreamWrapper:
model_response: Final = ModelResponseStream(**args)
if self.response_id is not None:
model_response.id = self.response_id
elif model_response.id:
self.response_id = model_response.id
if self.system_fingerprint is not None:
model_response.system_fingerprint = self.system_fingerprint

View file

@ -4460,3 +4460,82 @@ def test_handle_stream_fallback_error_restores_context_only_after_exception_mapp
finally:
trace_id_var.set("")
session_id_var.set("")
class TestStableStreamingResponseId:
"""
All chunks of one streamed response must share the same top-level id
(OpenAI streaming contract). Providers streaming via GenericStreamingChunk
(e.g. GigaChat) do not propagate an upstream response id, so
CustomStreamWrapper must pin the id from the first chunk it creates,
mirroring the existing `created` pinning (issue #11437).
Clients such as goose merge streamed deltas into one assistant message by
chunk id; per-chunk ids split a single reply into many messages.
"""
def test_generic_chunks_share_one_id(self):
def _generic_chunks():
return iter(
[
{
"text": "Hello",
"tool_use": None,
"is_finished": False,
"finish_reason": "",
"usage": None,
"index": 0,
},
{
"text": " world",
"tool_use": None,
"is_finished": False,
"finish_reason": "",
"usage": None,
"index": 0,
},
{
"text": "",
"tool_use": None,
"is_finished": True,
"finish_reason": "stop",
"usage": {
"prompt_tokens": 1,
"completion_tokens": 2,
"total_tokens": 3,
},
"index": 0,
},
]
)
wrapper = CustomStreamWrapper(
completion_stream=_generic_chunks(),
model="gigachat/GigaChat-2-Max",
logging_obj=MagicMock(),
custom_llm_provider="gigachat",
)
ids = [chunk.id for chunk in wrapper if chunk.id]
assert ids, "no chunks emitted"
assert len(set(ids)) == 1, f"chunk ids differ across one stream: {ids}"
def test_creator_pins_id_from_first_chunk(self):
wrapper = CustomStreamWrapper(
completion_stream=iter([]),
model="gigachat/GigaChat-2-Max",
logging_obj=MagicMock(),
custom_llm_provider="gigachat",
)
first = wrapper.model_response_creator()
assert wrapper.response_id == first.id
assert wrapper.model_response_creator().id == first.id
def test_provider_supplied_id_still_wins(self):
wrapper = CustomStreamWrapper(
completion_stream=iter([]),
model="gigachat/GigaChat-2-Max",
logging_obj=MagicMock(),
custom_llm_provider="gigachat",
)
wrapper.response_id = "chatcmpl-from-provider"
assert wrapper.model_response_creator().id == "chatcmpl-from-provider"