Skip to content

Messages carrying both reasoning and reasoning_content are rejected with 400 #46

Open

Description

@andjiang0083

What happened

/v1/chat/completions rejects a message that carries both reasoning and reasoning_content, with

{"error": "Use one reasoning field per message"}

Either field on its own is accepted. This makes the server unusable for multi-turn clients that replay assistant reasoning on every carrier they know about — such a client works for the first turn and then fails on every turn afterwards.

Steps to reproduce

Server running (rec status → ready, 0.1.1, flint-int4).

Plain request works:

curl -s http://127.0.0.1:11500/v1/chat/completions \
  -H "Authorization: Bearer $(rec auth token)" -H 'Content-Type: application/json' \
  -d '{"model":"flint","messages":[{"role":"user","content":"hi"}],"max_tokens":8}'
→ 200, message.reasoning = "Hmm, the user just said ..."

reasoning only → 200. reasoning_content only → 200. Both → 400:

curl -s http://127.0.0.1:11500/v1/chat/completions \
  -H "Authorization: Bearer $(rec auth token)" -H 'Content-Type: application/json' \
  -d '{"model":"flint","max_tokens":8,"messages":[
        {"role":"user","content":"hi"},
        {"role":"assistant","content":"ok","reasoning":"t","reasoning_content":"t"}]}'
{"error": "Use one reasoning field per message"}

Why it matters

Local OpenAI-compatible servers disagree about the replay carrier: llama.cpp/SGLang read reasoning_content, vLLM/Ollama read reasoning. Harnesses therefore send both (Hermes Agent does exactly this for any localhost endpoint), and every other server tested simply ignores the key it does not read. A 400 here turns a benign extra key into a hard failure that only appears from turn 2 onwards, so it reads as "the model is broken" rather than "the request shape is rejected".

The message also does not name the offending field, so clients that recover from strict-schema 400s by dropping the named key have nothing to match on.

Expected

Per OpenAI-compat convention, accept the message and pick one field (e.g. prefer reasoning, ignore the alias) — or ignore unknown message keys. If you prefer to keep the check, naming the field (reasoning / reasoning_content) would at least let clients drop it and retry.

Environment

  • rec 0.1.1, model flint (flint-int4), context 262144
  • macOS 27.0.1, Apple M5 Max, 128 GiB
  • Client: Hermes Agent 0.22.5 (chat_completions, replays both carriers to localhost endpoints)

Activity

  1. strin commented on Nov 5, 2026

    @strin

    Thanks for reporting this. I reproduced the 400 and opened #52. It accepts either alias and normalizes once; when both are present, reasoning takes precedence by field presence, including empty/null values. The focused suite passed 38 tests, including equal/different aliases and both response modes. This has not shipped yet, and I have not claimed a full Hermes end-to-end run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions