AI Model Intelligence
Share
Local Deployment & Hardware

Ollama Empty Response? Separate Thinking from the Final Answer

Updated ยท Sources & corrections

In this guide
  1. Check the endpoint before changing the model
  2. Classify the reply before treating it as an answer
  3. Use the state to choose the next step
  4. Keep streaming and output validation separate
  5. What was tested

Quick answer: For Ollama’s native chat API, read message.content for answer text and keep message.thinking separate. An empty content field is a symptom to classify: the response may be unfinished, may contain a tool call, or may have ended without an answer. Do not silently substitute thinking text for the final answer.

This guide covers decoded JSON from local /api/chat with stream: false. It assumes Python and an installed model. SDK response objects, streamed chunks and compatibility endpoints need their own adapters; the classifier below deliberately expects the native JSON shape.

Check the endpoint before changing the model

The chat endpoint wraps its assistant output in message. The generate endpoint instead uses top-level response for answer text and thinking for reasoning. A parser built for one is not interchangeable with the other.

Fields to inspect in native responses
Request endpointAnswer textSeparate thinking text
/api/chatmessage.contentmessage.thinking
/api/generateresponsethinking

Start with a short request without tools or structured-output constraints. Send this illustrative JSON body to http://localhost:11434/api/chat, replacing the placeholder with an installed model tag:

{
  "model": "REPLACE_WITH_INSTALLED_MODEL_TAG",
  "messages": [{"role": "user", "content": "Reply with a short greeting."}],
  "stream": false
}

Leaving think unspecified uses the model’s default. Before forcing it off or choosing a level, consult the thinking capability documentation and inspect that model with /api/show. Controls are model-specific; a boolean or named level accepted by one model is not a universal setting.

Classify the reply before treating it as an answer

Save this independent helper as reply_state.py. It operates on a decoded dictionary, performs no network calls and never runs a tool:

def classify_chat_reply(reply):
    """Classify one completed/native non-streaming /api/chat JSON object."""
    if not isinstance(reply, dict):
        raise ValueError('Expected a JSON object')
    if 'error' in reply:
        raise RuntimeError(str(reply['error']))
    message = reply.get('message')
    if not isinstance(message, dict):
        raise ValueError('Expected a native chat message object')
    content = message.get('content', '')
    thinking = message.get('thinking', '')
    tools = message.get('tool_calls')
    if tools is None:
        tools = []
    if not isinstance(content, str) or not isinstance(thinking, str):
        raise ValueError('Text fields must be strings')
    if not isinstance(tools, list):
        raise ValueError('tool_calls must be an array')
    if reply.get('done') is not True:
        return 'incomplete'
    if tools:
        return 'tool_turn'
    if reply.get('done_reason') != 'stop':
        return 'review_stop_reason'
    if content.strip():
        return 'answer_candidate'
    return 'thinking_only' if thinking.strip() else 'empty'

After your HTTP client has checked the response status and decoded JSON, call classify_chat_reply(reply). An answer_candidate is text available for your application’s validation; it is not a factual correctness or safety verdict. The stop requirement is deliberately conservative: any other or missing stop reason goes to review.

The helper does not validate role, tool names, arguments, model identity or a complete API schema. Its job is to avoid collapsing several different states into one empty-string fallback. Retain the original response privately when diagnosing failures, subject to your data-handling rules.

Use the state to choose the next step

Handling an apparently blank chat reply
StateAction
incompleteCheck cancellation, connection handling and whether you accidentally supplied one streamed chunk. Do not mark partial content complete.
tool_turnRoute to your tool-handling workflow, including argument validation and authorization. Do not execute a function just because text says it should run.
review_stop_reasonInspect the exact reason and generation settings. Preserve any partial answer separately.
thinking_onlyThe completed object has thinking but no answer text. Inspect model controls, server version and limits; compare a simple request without other features.
emptyCheck the raw native response and any proxy or SDK transformations before blaming the model.

Ollama’s tool-calling guide shows an assistant tool-call turn followed by tool results and another request. The first turn need not be the user-facing conclusion. This helper only flags a nonempty tool-call list; it does not authorize or validate its contents.

For failures, inspect HTTP status as well as the JSON error field described in the error reference. An error object is not an empty successful answer. If the model is missing, use our model and endpoint diagnostic guide.

Keep streaming and output validation separate

A first chunk without answer text does not establish that the full response will be blank. For streaming, accumulate the two text fields independently and inspect completion and error events. Do not run this single-object classifier on every chunk and interpret early incomplete states as terminal failures. See our native streaming parser guide for framing and completion checks.

Keep thinking out of a downstream answer-only JSON parser. Validate the final content against its required structure and source evidence instead; our structured-output validation guide covers that next stage. A reasoning trace is not a substitute for evidence.

What was tested

On 23 October 2026, nine local tests with constructed dictionaries passed: answer plus thinking, thinking only, unfinished reply, tool call plus text, a non-stop reason, whitespace-only content, API error, the wrong endpoint shape and a null text field. The request JSON was also parsed. No live model, SDK or proxy was exercised, and no claim about model reliability or latency follows from these tests.

Reviewed against the linked official documentation on the same date. The featured image is an original generated illustration. See our sourcing and correction policy.

When a response contains tool calls, continue with our bounded Python tool-calling loop to validate arguments, retain the assistant turn and return each permitted tool result.