PromptingInterrupted & declined replies

Handle interrupted and declined replies

A streamed reply can stop before it is complete, and a model can decline a request. Both leave your application holding a partial or unexpected answer. This page covers how to detect each case today and what to show the user.

Ways a reply can end

SituationHow you detect it todayWhat to do
Finished normallyfinish_reason: "stop", then data: [DONE]Show the reply.
Hit the length capfinish_reason: "length"Show it with a “continue” option, or raise max_tokens next time.
Waiting for toolsfinish_reason: "tool_calls"Run the tools and continue the loop.
Failed mid-streamA final event whose error.code is stream_error (error.type api_error)Treat the text as incomplete; retry or tell the user.
Connection droppedThe stream ends without data: [DONE]Same as a mid-stream failure.
Model declinedfinish_reason: "stop" with a refusal in the textShow it; offer another route if your product has one.

Detect incomplete streams

import json
import os
 
import requests
 
 
def stream_reply(body: dict):
    """Yields text pieces; returns a status of 'complete', 'length', 'tool_calls' or 'incomplete'."""
    status = "incomplete"
    with requests.post(
        "https://app.syntic.ai/v1/chat/completions",
        headers={"Authorization": f"Bearer {os.environ['SYNTIC_API_KEY']}"},
        json={**body, "stream": True},
        stream=True,
        timeout=(10, 300),
    ) as r:
        r.raise_for_status()
        finish = None
        for line in r.iter_lines(decode_unicode=True):
            if not line or not line.startswith("data: "):
                continue
            if line == "data: [DONE]":
                status = {"stop": "complete", "length": "length", "tool_calls": "tool_calls"}.get(finish, "complete")
                break
            event = json.loads(line[6:])
            if "error" in event:
                break                                   # stream_error: leave status as incomplete
            choice = (event.get("choices") or [{}])[0]
            finish = choice.get("finish_reason") or finish
            piece = choice.get("delta", {}).get("content")
            if piece:
                yield piece
    return status

What to show for a partial reply

  • Keep what arrived. Partial text is often useful. Display it with a clear marker (“The answer was cut off”) and a Retry button.
  • Do not store it as a finished turn. If you keep conversation history, mark partial assistant turns so the next request does not treat them as complete statements, or drop them before retrying.
  • Retry the whole request. There is no way to resume a stream from where it stopped. A retry produces a new reply that may be worded differently; replace the partial text rather than appending to it.
  • Bill awareness. Tokens generated before the failure are billed. Cap automatic retries at one or two; see Retries with back-off.

Declined requests

Sometimes the model declines: the request conflicts with its guidelines, or with your system message. Today this arrives as an ordinary reply that says so, with finish_reason: "stop". If your product needs to react differently to a refusal (for example, offer a human agent), detect it with a cheap follow-up classification, or ask the model in your system message to start such replies with a fixed marker you can test for:

If you cannot help with a request, start your reply with "[unable]" and then explain briefly.
⚠️
Preview — this feature is being built; the design below is the spec. A fixed list of finish reasons, including refusal and content_filter, is specified on Stop reasons, together with mid-stream errors in the standard error envelope (Streaming). When they ship, check finish_reason instead of a text marker.
Was this page helpful?