json.loads raises RecursionError on deeply nested input, which escaped the
complete-JSON check and failed the request. Treat it like any other input
that is not a complete JSON document.
Signed-off-by: FenjuFu <fufenjupku@gmail.com>
With prefilled reasoning enabled, a structured answer such as
{"closing":"</think>"} was cut at the tag inside its string value.
Reasoning followed by a closing tag is never a valid JSON document, so a
reply that parses as one complete object or array is now returned
unchanged. Also annotate the tests and helpers this PR adds.
Signed-off-by: FenjuFu <fufenjupku@gmail.com>
Strip a balanced <think>...</think> block that opens the reply by default and
leave every other reply untouched, so answers that mention the tags (a lone
or reversed closing tag, JSON string values) are never truncated.
The close-only form produced by chat templates that prefill <think> is now
handled only when the OpenAI or Ollama provider is configured for it, via
prefilled_reasoning=True or OPENAI_PREFILLED_REASONING /
OLLAMA_PREFILLED_REASONING. Hosted providers keep the default.
Signed-off-by: FenjuFu <92919259+FenjuFu@users.noreply.github.com>
Self-hosted reasoning models served through vLLM or llama.cpp without a
reasoning parser, or through Ollama without a thinking template, return
their chain of thought inside message content. The OpenAI-compatible
providers and the Ollama provider passed it through verbatim, so
LLMOutput.content and ReActAgent final answers began with the reasoning.
Models whose chat template prefills <think> in the generation prompt
(Spark-X2.5, Qwen3 thinking variants) only emit the closing tag, so the
content arrives as "...</think>answer" with no opening tag.
Add strip_reasoning() and apply it in the OpenAI, Astraflow, Atlas and
Ollama providers. A <think> that does not open the output is kept as a
literal, and unterminated reasoning is left unchanged.
Signed-off-by: FenjuFu <fufenjupku@gmail.com>