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

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
@@ -430,9 +454,11 @@ def handle_generate(req_id, params):
430
454
  **kwargs,
431
455
  )
432
456
 
457
+ should_stop = False
433
458
  for response in gen_iter:
434
459
  if is_cancelled(req_id):
435
460
  clear_cancel(req_id)
461
+ send_text_until(len(full_text), None)
436
462
  send({
437
463
  "id": req_id,
438
464
  "event": "result",
@@ -451,15 +477,6 @@ def handle_generate(req_id, params):
451
477
  full_text += response.text
452
478
  last_response = response
453
479
 
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
480
  # Check stop sequences
464
481
  should_stop = False
465
482
  for seq in stop_sequences:
@@ -469,22 +486,41 @@ def handle_generate(req_id, params):
469
486
  idx = full_text.index(seq)
470
487
  full_text = full_text[:idx]
471
488
  break
489
+
490
+ # Like llama.node, hold back an end that may still become a stop
491
+ # sequence until a later token completes or breaks it.
492
+ safe_end = len(full_text)
493
+ if not should_stop:
494
+ safe_end -= _partial_stop_length(full_text, stop_sequences)
495
+ send_text_until(safe_end, getattr(response, "token", None))
472
496
  if should_stop:
473
497
  break
474
498
 
499
+ # Generation ended without completing the held-back stop sequence
500
+ send_text_until(len(full_text), None)
501
+
475
502
  # Send final result
476
503
  result_data = {
477
504
  "text": full_text,
478
505
  "interrupted": False,
479
506
  }
480
507
  if last_response:
508
+ generation_tokens = getattr(last_response, "generation_tokens", 0)
509
+ finish_reason = getattr(last_response, "finish_reason", None)
481
510
  result_data.update({
482
511
  "prompt_tokens": getattr(last_response, "prompt_tokens", 0),
483
512
  "prompt_tps": getattr(last_response, "prompt_tps", 0),
484
- "generation_tokens": getattr(last_response, "generation_tokens", 0),
513
+ "generation_tokens": generation_tokens,
485
514
  "generation_tps": getattr(last_response, "generation_tps", 0),
486
515
  "peak_memory": getattr(last_response, "peak_memory", 0),
487
- "finish_reason": getattr(last_response, "finish_reason", None),
516
+ "finish_reason": finish_reason,
517
+ # Same name as llama.node's parallel result, which the completion routes read.
518
+ # mlx_lm reports "length" at max_tokens; mlx_vlm reports no reason, so a run
519
+ # that produced max_tokens tokens without a stop sequence hit the limit.
520
+ "stopped_limit": not should_stop and (
521
+ finish_reason == "length"
522
+ or (finish_reason is None and 0 < max_tokens <= generation_tokens)
523
+ ),
488
524
  })
489
525
 
490
526
  send({"id": req_id, "event": "result", "data": result_data})