@fugood/buttress-server 2.26.0-beta.20 → 2.26.0-beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/mlx-bridge.py CHANGED
@@ -242,6 +242,17 @@ def clear_cancel(req_id):
242
242
  _cancel_flags.discard(req_id)
243
243
 
244
244
 
245
+ def _partial_stop_length(text, stop_sequences):
246
+ """Length of the longest end of `text` that a stop sequence starts with."""
247
+ longest = 0
248
+ for seq in filter(None, stop_sequences):
249
+ for length in range(min(len(seq) - 1, len(text)), longest, -1):
250
+ if text.endswith(seq[:length]):
251
+ longest = length
252
+ break
253
+ return longest
254
+
255
+
245
256
  def _get_tokenizer():
246
257
  """Return the underlying tokenizer from the processor."""
247
258
  if _processor is None:
@@ -407,6 +418,19 @@ def handle_generate(req_id, params):
407
418
 
408
419
  full_text = ""
409
420
  last_response = None
421
+ # Token events carry full_text[:sent_end]. The client builds its output
422
+ # from them, so they must never contain stop sequence text.
423
+ sent_end = 0
424
+
425
+ def send_text_until(end, token_id):
426
+ nonlocal sent_end
427
+ if end > sent_end:
428
+ send({
429
+ "id": req_id,
430
+ "event": "token",
431
+ "data": {"token": full_text[sent_end:end], "token_id": token_id},
432
+ })
433
+ sent_end = end
410
434
 
411
435
  if _is_vlm:
412
436
  from mlx_vlm import stream_generate
@@ -433,6 +457,7 @@ def handle_generate(req_id, params):
433
457
  for response in gen_iter:
434
458
  if is_cancelled(req_id):
435
459
  clear_cancel(req_id)
460
+ send_text_until(len(full_text), None)
436
461
  send({
437
462
  "id": req_id,
438
463
  "event": "result",
@@ -451,15 +476,6 @@ def handle_generate(req_id, params):
451
476
  full_text += response.text
452
477
  last_response = response
453
478
 
454
- send({
455
- "id": req_id,
456
- "event": "token",
457
- "data": {
458
- "token": response.text,
459
- "token_id": getattr(response, "token", None),
460
- },
461
- })
462
-
463
479
  # Check stop sequences
464
480
  should_stop = False
465
481
  for seq in stop_sequences:
@@ -469,9 +485,19 @@ def handle_generate(req_id, params):
469
485
  idx = full_text.index(seq)
470
486
  full_text = full_text[:idx]
471
487
  break
488
+
489
+ # Like llama.node, hold back an end that may still become a stop
490
+ # sequence until a later token completes or breaks it.
491
+ safe_end = len(full_text)
492
+ if not should_stop:
493
+ safe_end -= _partial_stop_length(full_text, stop_sequences)
494
+ send_text_until(safe_end, getattr(response, "token", None))
472
495
  if should_stop:
473
496
  break
474
497
 
498
+ # Generation ended without completing the held-back stop sequence
499
+ send_text_until(len(full_text), None)
500
+
475
501
  # Send final result
476
502
  result_data = {
477
503
  "text": full_text,