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
| Constant | Value | Meaning | Provider values |
|---|---|---|---|
StopEndTurn | end_turn | The model finished naturally. | end_turn, stop, STOP |
StopMaxTokens | max_tokens | The output hit Request.MaxTokens. The response is truncated — treat it as incomplete. | max_tokens, length, MAX_TOKENS, model_context_window_exceeded |
StopToolUse | tool_use | The model wants a tool run. Execute the calls and send the results back. | tool_use, tool_calls, function_call |
StopStopSequence | stop_sequence | A 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) |
StopRefusal | refusal | The 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 |
StopUnknown | unknown | The provider reported something skyl does not model. Read Response.Raw. | anything else, including pause_turn, OTHER, MALFORMED_FUNCTION_CALL |
Caveats
StopMaxTokensmeans the response is truncated. Treat it as incomplete, not as a short answer.StopStopSequenceonly ever comes from Anthropic. OpenAI reports a stop-sequence hit as plainstop, so it arrives asStopEndTurn; Gemini reportsSTOP.- On Gemini, any response containing a function call reports
StopToolUseregardless of its actualfinishReason— so a Gemini response can be both truncated and reported astool_use. - A refusal with text arrives here as
StopRefusal; one without text is anErrRefusalerror instead. Handle both. - Unmapped values become
StopUnknown; the original is inResponse.Raw.
Usage#
An exhaustive switch
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
// 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.