Skip to content
skyl

StopReason

Why the model stopped generating.

Six values, of which two need explicit handling and one is effectively provider-specific. Adapters map vendor values onto these and fall back to StopUnknown rather than inventing a new one.

Reference#

type StopReason string
ConstantValueMeaningProvider values
StopEndTurnend_turnThe model finished naturally.end_turn, stop, STOP
StopMaxTokensmax_tokensThe output hit Request.MaxTokens. The response is truncated — treat it as incomplete.max_tokens, length, MAX_TOKENS, model_context_window_exceeded
StopToolUsetool_useThe model wants a tool run. Execute the calls and send the results back.tool_use, tool_calls, function_call
StopStopSequencestop_sequenceA sequence from Request.Stop was produced. Only ever comes from Anthropic — OpenAI reports a stop-sequence hit as plain "stop", so it arrives as StopEndTurn.stop_sequence (Anthropic only)
StopRefusalrefusalThe model or its safety classifiers declined. Content may be empty or partial; do not retry the same request.refusal, content_filter, SAFETY, RECITATION, BLOCKLIST, PROHIBITED_CONTENT, SPII
StopUnknownunknownThe provider reported something skyl does not model. Read Response.Raw.anything else, including pause_turn, OTHER, MALFORMED_FUNCTION_CALL

Caveats

  • StopMaxTokens means the response is truncated. Treat it as incomplete, not as a short answer.
  • StopStopSequence only ever comes from Anthropic. OpenAI reports a stop-sequence hit as plain stop, so it arrives as StopEndTurn; Gemini reports STOP.
  • On Gemini, any response containing a function call reports StopToolUse regardless of its actual finishReason — so a Gemini response can be both truncated and reported as tool_use.
  • A refusal with text arrives here as StopRefusal; one without text is an ErrRefusal error instead. Handle both.
  • Unmapped values become StopUnknown; the original is in Response.Raw.

Usage#

An exhaustive switch

goCompiles
switch resp.StopReason {
case skyl.StopEndTurn, skyl.StopStopSequence:
	return resp.Text(), nil
case skyl.StopMaxTokens:
	return "", fmt.Errorf("truncated at %d tokens; raise MaxTokens", req.MaxTokens)
case skyl.StopToolUse:
	return "", errToolCallsPending
case skyl.StopRefusal:
	return "", fmt.Errorf("the model declined: %s", resp.Text())
default:
	// StopUnknown, or something newer than this build. Be honest about it.
	log.Printf("unmapped stop reason; raw: %s", resp.Raw)
	return resp.Text(), nil
}
switch resp.StopReason {
case skyl.StopEndTurn, skyl.StopStopSequence:
	return resp.Text(), nil
case skyl.StopMaxTokens:
	return "", fmt.Errorf("truncated at %d tokens; raise MaxTokens", req.MaxTokens)
case skyl.StopToolUse:
	return "", errToolCallsPending
case skyl.StopRefusal:
	return "", fmt.Errorf("the model declined: %s", resp.Text())
default:
	// StopUnknown, or something newer than this build. Be honest about it.
	log.Printf("unmapped stop reason; raw: %s", resp.Raw)
	return resp.Text(), nil
}

Detecting a stop sequence portably

goCompiles
// StopStopSequence is Anthropic-only, so fall back to reading Raw.
var raw struct {
	StopSequence string `json:"stop_sequence"`
}
_ = json.Unmarshal(resp.Raw, &raw)
// StopStopSequence is Anthropic-only, so fall back to reading Raw.
var raw struct {
	StopSequence string `json:"stop_sequence"`
}
_ = json.Unmarshal(resp.Raw, &raw)

Troubleshooting#

Answers are cut off mid-sentence

StopMaxTokens. Raise MaxTokens; retrying unchanged gives the same result.

My StopStopSequence branch stopped firing after switching provider

Expected. Only Anthropic reports it. Read Raw, or check whether the text ends where you expected.

I got StopUnknown

The provider reported something skyl does not model — pause_turn, MALFORMED_FUNCTION_CALL, or something newer. Raw has the original value. This is deliberate: minting a constant per vendor value would require a skyl release before you could branch on it.

Edit this page on GitHub