Skip to content

Commit 305b459

Browse files
authored
Merge pull request #148 from kevin9327/fix/stream-trailing-replacement-char
fix(server): flush a trailing U+FFFD at the end of a chat stream
2 parents 70f3b29 + 7319438 commit 305b459

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
@@ -912,3 +912,30 @@ def test_chat_stream_incremental_decode_of_split_characters():
912912
non_streamed = decode_tokens(eng, ids)
913913
assert streamed == non_streamed == trigger
914914
assert "\ufffd" not in streamed
915+
916+
917+
class _CutTok(FakeTok):
918+
"""Decodes like a byte-level tokenizer whose generation stopped in the
919+
middle of a multi-byte character (e.g. at max_tokens): the last id
920+
leaves an incomplete sequence that decodes to U+FFFD."""
921+
922+
_PIECES = {11: "ok ", 12: "fine", 13: "\ufffd"}
923+
924+
def decode(self, tokens):
925+
return "".join(self._PIECES.get(t, "") for t in tokens)
926+
927+
928+
def test_chat_stream_flushes_trailing_replacement_char_at_end():
929+
"""Concatenated deltas must equal the non-streaming text even when the
930+
generation ends on an incomplete character, which no later id can
931+
complete."""
932+
eng = FakeEngine(tok=_CutTok())
933+
srv = QueueServer(eng, model_name="fake")
934+
body = b"".join(_chat_stream(srv, {
935+
"messages": [{"role": "user", "content": "hi"}], "stream": True,
936+
}))
937+
chunks = [json.loads(e[6:]) for e in body.decode("utf-8").split("\n\n")
938+
if e and e != "data: [DONE]"]
939+
streamed = "".join(c["choices"][0]["delta"].get("content") or ""
940+
for c in chunks)
941+
assert streamed == decode_tokens(eng, [11, 12, 13]) == "ok fine\ufffd"

0 commit comments

Comments
 (0)