# Dense Reasoning Models: --reasoning off + Open WebUI Empty Responses

## Symptom

Model returns `error: {"content": ""}` in Open WebUI chat messages, but direct API calls (via curl) work fine. The server log shows:
```
recovered from upstream disconnection during streaming
no valid JSON data found in stream
```

And/or the model streams excessive `reasoning_content` events before any content token.

## Root Cause

Two distinct failure modes:

### Mode A: --reasoning off causes upstream crash (dense models)

Dense reasoning models (Ornith-9B, Qwen3.5-9B) with `--reasoning off` can crash the llama-server process mid-stream. The server disconnects, llama-swap catches the broken pipe, and Open WebUI saves `error: {"content": ""}`.

**Fix:** Test without `--reasoning off`. If the model doesn't output `` tags naturally (dense 9B models rarely use them), the flag is harmless but can trigger a server-side bug. If removing it causes excessive `reasoning_content` events, increase `--n-predict` server-side or raise `max_tokens` in Open WebUI.

### Mode B: Endless reasoning without content (MoE/any model without --reasoning off)

Without `--reasoning off`, the model outputs `reasoning_content` SSE events (the `` tag content) before actual content. Open WebUI's streaming handler accumulates these for the reasoning UI, but:
- 185+ reasoning chunks for a simple "hello" is excessive
- The content field stays `null` through all reasoning events
- If the model never transitions to content generation (stuck thinking), the stream eventually times out at `AIOHTTP_CLIENT_TIMEOUT=300` (5 min)

**Fix:** Add `--reasoning off` to the llama-server flags. This strips `` tags and stops the reasoning pipeline, sending content directly.

## Diagnosis Flow

```bash
# 1. Check if --reasoning off is set (modifies tool behavior)
grep "reasoning off" ~/.config/llama-swap/config.yaml | grep -c "ornith-9b"

# 2. Test non-streaming (bypasses SSE issues)
curl -s http://localhost:9292/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{"model":"<model>","messages":[{"role":"user","content":"hi"}],"max_tokens":256,"stream":false}'

# 3. Test streaming (reproduces Open WebUI behavior)
curl -s ... -d '{"stream":true}' > /tmp/stream.txt
grep -c "reasoning_content" /tmp/stream.txt  # count thinking tokens
grep -c '"content"' /tmp/stream.txt          # count actual content tokens

# 4. Check Open WebUI DB for error pattern
docker exec open-webui python3 -c "
import sqlite3
conn = sqlite3.connect('/app/backend/data/webui.db')
cur = conn.execute('''
    SELECT role, content, model_id, error, done
    FROM chat_message WHERE error IS NOT NULL AND error != \"\"
    ORDER BY id DESC LIMIT 5
''')
for r in cur.fetchall():
    print(f'{r[0]} model={r[2]} done={r[4]} error={r[3][:80]}')
conn.close()
"
```

## Prevention

- Dense 9B models (fully fit in VRAM): `--reasoning off` is safe and recommended
- MoE models (partial offload): `--reasoning off` prevents thinking token bloat
- If you see `error: {"content": ""}` AND direct API works: the reasoning handler is the culprit
- If direct API also fails: check server crash logs (`journalctl -u llama-swap | grep disconnect`)
