Stop Sequences in the DeepSeek API
7 min read · updated August 11, 2026
A stop sequence is a string that terminates generation the moment the model produces it. DeepSeek implements the OpenAI parameter, including the two behaviours that make it confusing: the string is removed from what you receive, and the response looks exactly like a natural finish.
The parameter shape
stop takes either a single string or an array of strings, at the top level of the request body alongside model and messages. There is no object form and no regular-expression form; matching is exact and literal.
{
"model": "deepseek-chat",
"messages": [
{"role": "user", "content": "Continue this dialogue.\n\nAlice: Where were you?\nBob:"}
],
"stop": ["\nAlice:", "\nBob:", "\n\n"],
"max_tokens": 200
}The most common use is exactly that one: a completion-style prompt where the model would otherwise keep going and write the other participant’s next line too. The same pattern applies to generating one item of a list, one section of a document, or one field of a form — anywhere the natural continuation runs past what you wanted.
Note the escaping. A newline in JSON is \n inside a string, and hand-written newlines are where this parameter most often silently fails to match. Build the array in your language and serialise it rather than typing JSON by hand.
The limit and what counts against it
Up to 16 sequences, following the OpenAI-compatible limit DeepSeek documents in its API reference. Exceeding it is a 400 rather than a silent truncation of the array, which is the right behaviour but does mean a dynamically built stop list needs a bound.
Sixteen is generous for handwritten sequences and tight for generated ones. If you are deriving stop strings from user data — say, one per known section heading — cap the list and prefer the shortest discriminating strings, because a long list of near-identical prefixes gives you no more control than a short one.
The sequence is trimmed from the output
When a stop sequence matches, generation halts and the matched text is removed from the returned content. You get everything up to the sequence, not including it.
# stop: ["\nAlice:"] # model generated, internally: # " I was at the harbour all morning.\nAlice:" # returned content: # " I was at the harbour all morning."
This is convenient — you rarely want the delimiter — and it is the source of the two most common complaints about the parameter. First, you cannot tell from the text that anything was trimmed. Second, if the delimiter was structurally necessary, you have to add it back yourself: stopping at "}" to end a JSON object leaves you with invalid JSON. For JSON specifically, do not do this at all — the documented JSON output mode is the mechanism designed for it.
Billing follows generation, not the trimmed result. The tokens the model produced to match the sequence were generated and are counted in completion_tokens, so your token count will exceed the length of the text you received by a token or two. That is not an error.
Detecting that one fired
finish_reason is stop whether the model reached a natural ending or hit one of your sequences. There is no separate value and no field naming the matched sequence. This is inherited from the OpenAI schema and it means detection has to be indirect.
The reliable approach is to make the outcomes structurally distinguishable rather than trying to recover the information after the fact:
- Design the prompt so a natural ending is unlikely. In a completion-style prompt where the model has no reason to stop on its own,
finish_reason: "stop"effectively means a sequence fired. - Check what the content ends with. If your sequences begin with a newline, a completion trimmed at one typically ends mid-sentence with no terminal punctuation. Weak evidence, but it distinguishes the common cases.
- Do not rely on token counts. Comparing
completion_tokensagainst the length of the returned text to infer trimming is fragile, because tokenization is not a character count. - Distinguish
lengthfirst. Truncation bymax_tokensgivesfinish_reason: "length"and is a different problem with a different fix. Handle it before you reason about stop sequences at all.
Where it goes wrong
- The sequence appears legitimately inside the answer. Stopping on
"\n\n"truncates any multi-paragraph response at the first paragraph break. Pick delimiters that cannot occur in valid output, and be specific:"\nAlice:"rather than"Alice". - Tokenization boundaries. Matching happens on generated text, but the model emits tokens, and a token can span the boundary of your sequence. Very short or unusual stop strings behave less predictably than longer, more natural ones for this reason.
- Streaming still stops correctly, but your buffer may not. The stream simply ends after the last delta before the sequence. If your client is doing its own marker detection on top, remember that a delta can split any string across chunks — the streaming page covers the accumulation rule.
- Using it to control length. Stop sequences end output at a marker, not at a size. If you want a bounded response, that is
max_tokens, and the defaults are not what you would guess. - Assuming they apply to the reasoning phase. On the reasoning endpoint, a stop sequence that matches during the trace would end the response before any answer exists. Prefer to leave
stopunset there.
When it is the wrong tool
Stop sequences solve one problem well: the model continues past a boundary you can name in advance as a literal string. Most of the things people reach for them to do are better served by something else, and the substitution is usually a straight improvement rather than a compromise.
- To bound length — use
max_tokens. A stop sequence cannot know how long the output is, and a request that hits no sequence runs to the default ceiling. - To end a JSON document — use JSON output mode. Constrained decoding ends the document at the right place with the closing brace intact, which is exactly what stopping on a brace destroys.
- To extract structured fields — use tool calling. Delimiting fields with markers and stopping at them is a reconstruction of what the schema already gives you, and it fails whenever a value contains the delimiter.
- To stop a chat model rambling — the fix is the prompt. A model that keeps writing after answering is responding to an instruction that did not tell it to stop; truncating the symptom leaves you with an answer that ends abruptly instead of one that ends.
- To enforce a format across a whole response — a stop sequence is a single point, not a constraint over the output. Validate the response and retry, which is a check you can trust rather than a boundary you hope is hit.
What remains after those substitutions is the genuine use: completion-style generation where the model is continuing a structured document and would otherwise write the next section, the next speaker, or the next example. For that, it is the right parameter and there is no substitute.