Skip to content

Commit 7319438

Browse files
committed
fix(server): flush a trailing U+FFFD at the end of a chat stream
1 parent 8c2d422 commit 7319438

2 files changed

Lines changed: 33 additions & 2 deletions

File tree

‎python/src/edge0/server/app.py‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,12 @@ def produce():
180180
# incomplete multi-byte sequence at the very end of the
181181
# generation) so the concatenated deltas equal the
182182
# non-streaming text exactly.
183-
tail = _incremental_suffix(
184-
emitted, decode_tokens(server.engine, seen_ids))
183+
# The generation is over, so nothing will complete a
184+
# trailing U+FFFD any more: emit it as the non-streaming
185+
# response does instead of holding it back forever.
186+
final = decode_tokens(server.engine, seen_ids)
187+
tail = (final[len(emitted):] if final.startswith(emitted)
188+
else _incremental_suffix(emitted, final))
185189
if tail:
186190
_emit(tail)
187191
events.put(sse_format({

‎python/tests/test_server.py‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,3 +904,30 @@ def test_chat_stream_incremental_decode_of_split_characters():
904904
non_streamed = decode_tokens(eng, ids)
905905
assert streamed == non_streamed == trigger
906906
assert "\ufffd" not in streamed
907+
908+
909+
class _CutTok(FakeTok):
910+
"""Decodes like a byte-level tokenizer whose generation stopped in the
911+
middle of a multi-byte character (e.g. at max_tokens): the last id
912+
leaves an incomplete sequence that decodes to U+FFFD."""
913+
914+
_PIECES = {11: "ok ", 12: "fine", 13: "\ufffd"}
915+
916+
def decode(self, tokens):
917+
return "".join(self._PIECES.get(t, "") for t in tokens)
918+
919+
920+
def test_chat_stream_flushes_trailing_replacement_char_at_end():
921+
"""Concatenated deltas must equal the non-streaming text even when the
922+
generation ends on an incomplete character, which no later id can
923+
complete."""
924+
eng = FakeEngine(tok=_CutTok())
925+
srv = QueueServer(eng, model_name="fake")
926+
body = b"".join(_chat_stream(srv, {
927+
"messages": [{"role": "user", "content": "hi"}], "stream": True,
928+
}))
929+
chunks = [json.loads(e[6:]) for e in body.decode("utf-8").split("\n\n")
930+
if e and e != "data: [DONE]"]
931+
streamed = "".join(c["choices"][0]["delta"].get("content") or ""
932+
for c in chunks)
933+
assert streamed == decode_tokens(eng, [11, 12, 13]) == "ok fine\ufffd"

0 commit comments

Comments
 (0)