You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Non-streaming /v1/completions reports finish_reason: "stop" when max_tokens was hit #227
Non-streaming /v1/completions reports finish_reason: "stop" even when generation was cut off by max_tokens. The chat endpoint and the streaming text path both report "length" in that case.
curl -s localhost:5413/v1/completions -H 'Content-Type: application/json' \
-d '{"prompt":"Write a long story about a dragon.","max_tokens":5,"temperature":0}'# → "finish_reason":"stop", "usage":{"completion_tokens":5,…}
Same request with "stream": true → the final chunk says "finish_reason":"length". Same prompt via /v1/chat/completions → "length".
Cause: handleTextNonStreaming takes generationTokenCount from the final .info event but ignores info.stopReason, and finish_reason is hardcoded:
handleChatNonStreaming already does the mapping a few hundred lines up (L3495-L3499), and handleTextStreaming does it too (L3813-L3817), so this is just the one path that was missed.
Why it matters: finish_reason is the only signal a client has to tell a truncated completion from a finished one. With this path always saying "stop", anything that ran out of budget looks complete.
I have a small fix with a model-free unit test and a contract-script assertion; will send it as a PR.
Thanks @HamedShams — good catch, and a clear report. I checked main: handleTextNonStreaming hardcodes finishReason = "stop" and ignores info.stopReason, while handleChatNonStreaming and handleTextStreaming both map .length to "length", exactly as you describe.
Please go ahead with the PR. One thing I'll look at in review: when a stop sequence matches, the response should still say "stop", even if the token limit was reached on the same request.
PR is up: #229.
It’s stacked on #228, since that rewrote the same loop, so the diff collapses to one commit once #228 lands.
--
One thing I'll look at in review: when a stop sequence matches, the response should still say "stop", even if the token limit was reached on the same request.
On your point: a stop-sequence match still reports stop whatever ended generation, and TextCompletionFinishReasonTests covers that case (stop sequence plus .length from the generator → stop, text trimmed).
Activity
solderzzc commentedon Oct 11, 2026
Thanks @HamedShams — good catch, and a clear report. I checked
main:handleTextNonStreaminghardcodesfinishReason = "stop"and ignoresinfo.stopReason, whilehandleChatNonStreamingandhandleTextStreamingboth map.lengthto"length", exactly as you describe.Please go ahead with the PR. One thing I'll look at in review: when a stop sequence matches, the response should still say
"stop", even if the token limit was reached on the same request.HamedShams commentedon Oct 11, 2026
PR is up: #229.
It’s stacked on #228, since that rewrote the same loop, so the diff collapses to one commit once #228 lands.
--
On your point: a stop-sequence match still reports
stopwhatever ended generation, andTextCompletionFinishReasonTestscovers that case (stop sequence plus.lengthfrom the generator →stop, text trimmed).