From 6ea5165311498575efb44101d4514844f47872f2 Mon Sep 17 00:00:00 2001 From: "Mark (agent)" Date: Sat, 1 Aug 2026 09:38:16 +0100 Subject: [PATCH 1/6] core: add DS4_THINK_ULTRA tier and a per-tier effort prefix table DeepSeek-V4-Flash-0731 has three reasoning-effort prompts, not two: "low" (empty), "high" ("Absolute maximum...") and "max" ("Beyond maximum - exhaustive, relentless, and uncompromising..."). ds4 carried only one constant, byte-identical to DeepSeek "high", so every ds4 tier sat one notch below its name and 0731 top tier was unreachable. - APPEND DS4_THINK_ULTRA to ds4_think_mode. The three existing enumerators keep their names and values: ~120 literals across ds4_server.c / ds4_cli.c / ds4_agent.c / ds4_eval.c and the tests spell them out, and a rename would silently re-point all of them. - Add DS4_REASONING_EFFORT_ULTRA_PREFIX, copied byte-for-byte from DeepSeeks encoding/encoding_dsv4.py including the em dash U+2014. Verified: sha256 of the C string equals sha256 of the Python string (53fb31b8392ec5b4a926481943efc67636748f137c1a49d493a7af4d4fa55d2c; the pre-existing MAX prefix is f7a24f3b... == DeepSeek "high"). - Replace the single-constant accessor with ds4_think_effort_prefix(mode), returning "" for NONE and HIGH. ds4_think_max_prefix() stays as a shim. - ds4_chat_append_effort_prefix(e, tokens, mode) generalises ds4_chat_append_max_effort_prefix(), which stays as a shim. LANDMINE, no compiler warning: ds4_think_mode_enabled() was "mode == HIGH || mode == MAX". Left alone, ULTRA would silently disable thinking at ~25 call sites. ULTRA is now listed there. The context gate steps down ONE tier (ULTRA -> MAX -> HIGH) rather than collapsing straight to HIGH. --- ds4.c | 86 +++++++++++++++++++++++++++++++++++++++++++++++------------ ds4.h | 19 +++++++++++++ 2 files changed, 88 insertions(+), 17 deletions(-) diff --git a/ds4.c b/ds4.c index 9904115da..9031cc034 100644 --- a/ds4.c +++ b/ds4.c @@ -382,14 +382,31 @@ int g_gpu_peer_ok[DS4_MAX_GPUS][DS4_MAX_GPUS]; #define DS4_DEFAULT_COMPRESS_ROPE_FREQ_BASE (160000.0f) #define DS4_DEFAULT_ROPE_ORIG_CTX UINT64_C(65536) +/* Reasoning-effort prompt prefixes, copied byte-for-byte from DeepSeek's own + * REASONING_EFFORT_PROMPTS in encoding/encoding_dsv4.py (DeepSeek-V4-Flash-0731). + * DeepSeek's "low" tier is the empty string, so only two prefixes exist. + * + * DeepSeek "high" -> DS4_THINK_MAX (this file's ..._MAX_PREFIX) + * DeepSeek "max" -> DS4_THINK_ULTRA (this file's ..._ULTRA_PREFIX, added + * for 0731; it did not exist before) + * + * The ULTRA string contains an em dash (U+2014, bytes E2 80 94) after + * "Beyond maximum". It is part of the tokenised prompt: do not "normalise" it + * to a hyphen and keep this file UTF-8. */ static const char DS4_REASONING_EFFORT_MAX_PREFIX[] = "Reasoning Effort: Absolute maximum with no shortcuts permitted.\n" "You MUST be very thorough in your thinking and comprehensively decompose the problem to resolve the root cause, rigorously stress-testing your logic against all potential paths, edge cases, and adversarial scenarios.\n" "Explicitly write out your entire deliberation process, documenting every intermediate step, considered alternative, and rejected hypothesis to ensure absolutely no assumption is left unchecked.\n\n"; -/* DeepSeek recommends Think Max only with at least a 384K-token context window. - * Below that size we keep ordinary thinking to avoid injecting a prompt that - * asks for a reasoning budget the allocated context is not meant to hold. */ +static const char DS4_REASONING_EFFORT_ULTRA_PREFIX[] = + "Reasoning Effort: Beyond maximum — exhaustive, relentless, and uncompromising.\n" + "You MUST reason with the utmost depth and rigor, leaving absolutely nothing to chance: exhaustively decompose the problem into its most fundamental components, trace every causal chain to its root, and resolve the underlying cause rather than any surface symptom.\n" + "Do not stop reasoning until you have independently verified the solution from multiple angles and are certain that no assumption remains unchecked and no error remains undiscovered.\n\n"; + +/* DeepSeek recommends the prefixed tiers only with at least a 384K-token + * context window. Below that size we step the tier down so we never inject a + * prompt that asks for a reasoning budget the allocated context is not meant + * to hold. */ #define DS4_THINK_MAX_MIN_CONTEXT 393216u static bool ds4_backend_uses_graph(ds4_backend backend) { @@ -36768,9 +36785,13 @@ static void chat_push_bos_sequence(const ds4_vocab *vocab, token_vec *out) { const char *ds4_glm_reasoning_effort_text(ds4_think_mode mode) { switch (mode) { - case DS4_THINK_HIGH: return "Reasoning Effort: High"; - case DS4_THINK_MAX: return "Reasoning Effort: Max"; - case DS4_THINK_NONE: return NULL; + case DS4_THINK_HIGH: return "Reasoning Effort: High"; + case DS4_THINK_MAX: return "Reasoning Effort: Max"; + /* GLM exposes no tier above "Max", so ULTRA saturates there. This switch + * has no default: an unlisted enumerator returns NULL and drops the effort + * text with no diagnostic. */ + case DS4_THINK_ULTRA: return "Reasoning Effort: Max"; + case DS4_THINK_NONE: return NULL; } return NULL; } @@ -36784,8 +36805,13 @@ static void chat_push_think_prefix(const ds4_vocab *vocab, token_vec_push(out, vocab->system_id); bpe_tokenize_text(vocab, effort, out); } - } else if (think_mode == DS4_THINK_MAX) { - bpe_tokenize_text(vocab, DS4_REASONING_EFFORT_MAX_PREFIX, out); + } else { + /* Tier -> prefix comes from ds4_think_effort_prefix(), which returns "" + * (never NULL) for the unprefixed tiers, so this stays a single call + * site as tiers are added. */ + const char *effort_prefix = ds4_think_effort_prefix(think_mode); + if (effort_prefix[0]) + bpe_tokenize_text(vocab, effort_prefix, out); } } @@ -36921,8 +36947,14 @@ void ds4_encode_chat_prompt( encode_chat_prompt(&e->vocab, system, prompt ? prompt : "", think_mode, out); } +void ds4_chat_append_effort_prefix(ds4_engine *e, ds4_tokens *tokens, ds4_think_mode mode) { + const char *prefix = ds4_think_effort_prefix(mode); + if (!prefix[0]) return; + bpe_tokenize_text(&e->vocab, prefix, tokens); +} + void ds4_chat_append_max_effort_prefix(ds4_engine *e, ds4_tokens *tokens) { - bpe_tokenize_text(&e->vocab, DS4_REASONING_EFFORT_MAX_PREFIX, tokens); + ds4_chat_append_effort_prefix(e, tokens, DS4_THINK_MAX); } static void bpe_tokenize_wrapped_payload_text(ds4_vocab *vocab, const char *content, @@ -47999,31 +48031,51 @@ static void ds4_linux_graph_backend_set_oom_score(ds4_backend backend) { #endif } +/* Every tier above NONE is a thinking tier. ULTRA MUST be listed here: this + * predicate gates the opener, the streaming reasoning channels and the + * tool-marker handling at ~25 call sites, and omitting a new tier disables + * thinking for it everywhere at once without a single compiler warning. */ bool ds4_think_mode_enabled(ds4_think_mode mode) { - return mode == DS4_THINK_HIGH || mode == DS4_THINK_MAX; + return mode == DS4_THINK_HIGH || mode == DS4_THINK_MAX || + mode == DS4_THINK_ULTRA; } const char *ds4_think_mode_name(ds4_think_mode mode) { switch (mode) { - case DS4_THINK_NONE: return "none"; - case DS4_THINK_HIGH: return "high"; - case DS4_THINK_MAX: return "max"; + case DS4_THINK_NONE: return "none"; + case DS4_THINK_HIGH: return "high"; + case DS4_THINK_MAX: return "max"; + case DS4_THINK_ULTRA: return "ultra"; } return "unknown"; } +const char *ds4_think_effort_prefix(ds4_think_mode mode) { + switch (mode) { + case DS4_THINK_NONE: return ""; + case DS4_THINK_HIGH: return ""; + case DS4_THINK_MAX: return DS4_REASONING_EFFORT_MAX_PREFIX; + case DS4_THINK_ULTRA: return DS4_REASONING_EFFORT_ULTRA_PREFIX; + } + return ""; +} + const char *ds4_think_max_prefix(void) { - return DS4_REASONING_EFFORT_MAX_PREFIX; + return ds4_think_effort_prefix(DS4_THINK_MAX); } uint32_t ds4_think_max_min_context(void) { return DS4_THINK_MAX_MIN_CONTEXT; } +/* Step the tier down ONE level when the context is too small, so ULTRA in a + * small context still reasons at MAX rather than collapsing all the way to the + * unprefixed tier. */ ds4_think_mode ds4_think_mode_for_context(ds4_think_mode mode, int ctx_size) { - if (mode == DS4_THINK_MAX && (uint32_t)(ctx_size > 0 ? ctx_size : 0) < DS4_THINK_MAX_MIN_CONTEXT) { - return DS4_THINK_HIGH; - } + const uint32_t ctx = (uint32_t)(ctx_size > 0 ? ctx_size : 0); + if (ctx >= DS4_THINK_MAX_MIN_CONTEXT) return mode; + if (mode == DS4_THINK_ULTRA) return DS4_THINK_MAX; + if (mode == DS4_THINK_MAX) return DS4_THINK_HIGH; return mode; } diff --git a/ds4.h b/ds4.h index a8a0177c0..5e55d306d 100644 --- a/ds4.h +++ b/ds4.h @@ -22,10 +22,22 @@ typedef enum { DS4_BACKEND_CPU, } ds4_backend; +/* Reasoning-effort tiers. The enumerators are APPENDED, never renumbered or + * renamed: the existing names are spelled out in ~120 literals across the + * server, CLI, agent and eval code, so a rename would silently re-point every + * one of them. The names are historical and do NOT line up 1:1 with DeepSeek's + * own tier names; ds4_think_effort_prefix() below is the mapping of record. + * + * DS4_THINK_NONE no thinking at all ( opener, no prefix) + * DS4_THINK_HIGH thinking with no prompt prefix == DeepSeek "low" + * DS4_THINK_MAX "Reasoning Effort: Absolute maximum" == DeepSeek "high" + * DS4_THINK_ULTRA "Reasoning Effort: Beyond maximum" == DeepSeek "max" + */ typedef enum { DS4_THINK_NONE, DS4_THINK_HIGH, DS4_THINK_MAX, + DS4_THINK_ULTRA, } ds4_think_mode; typedef enum { @@ -252,6 +264,11 @@ bool ds4_engine_is_glm_dsa(ds4_engine *e); const char *ds4_backend_name(ds4_backend backend); bool ds4_think_mode_enabled(ds4_think_mode mode); const char *ds4_think_mode_name(ds4_think_mode mode); +/* Prompt prefix for a tier. Returns "" (never NULL) for NONE and HIGH, so + * callers can append unconditionally. */ +const char *ds4_think_effort_prefix(ds4_think_mode mode); +/* Compatibility shim: the pre-tier single-constant accessor, == the + * DS4_THINK_MAX prefix. Prefer ds4_think_effort_prefix(). */ const char *ds4_think_max_prefix(void); const char *ds4_glm_reasoning_effort_text(ds4_think_mode mode); uint32_t ds4_think_max_min_context(void); @@ -306,6 +323,8 @@ void ds4_encode_chat_prompt( const char *prompt, ds4_think_mode think_mode, ds4_tokens *out); +void ds4_chat_append_effort_prefix(ds4_engine *e, ds4_tokens *tokens, ds4_think_mode mode); +/* Compatibility shim for ds4_chat_append_effort_prefix(.., DS4_THINK_MAX). */ void ds4_chat_append_max_effort_prefix(ds4_engine *e, ds4_tokens *tokens); void ds4_chat_append_message(ds4_engine *e, ds4_tokens *tokens, const char *role, const char *content); void ds4_chat_append_assistant_prefix(ds4_engine *e, ds4_tokens *tokens, ds4_think_mode think_mode); From 1590e71e4a112b4ae06745fd010d864df038faf9 Mon Sep 17 00:00:00 2001 From: "Mark (agent)" Date: Sat, 1 Aug 2026 09:39:11 +0100 Subject: [PATCH 2/6] server: map wire reasoning_effort onto the 0731 tiers, with a legacy escape New default mapping (--reasoning-effort-map deepseek): minimal/low/medium -> DS4_THINK_HIGH (DeepSeek "low", no prefix) high/xhigh -> DS4_THINK_MAX (DeepSeek "high") max -> DS4_THINK_ULTRA (DeepSeek "max") none -> DS4_THINK_NONE --reasoning-effort-map legacy restores the pre-patch table byte for byte, so the old behaviour is one flag away. Unknown names still return false in both maps, so the HTTP layer keeps answering 400. LANDMINE, no compiler warning: think_mode_from_enabled() collapsed everything non-MAX to HIGH, which would have destroyed ULTRA before the renderer saw it. It is now a pass-through and only decides thinking on/off. Renderers take the prefix from ds4_think_effort_prefix() instead of testing == DS4_THINK_MAX, and rendered_chat_system_region() strips any prefixed tier rather than only the MAX one -- otherwise the ULTRA preamble leaks into tool-error recovery messages. --- ds4_server.c | 110 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 92 insertions(+), 18 deletions(-) diff --git a/ds4_server.c b/ds4_server.c index ba5f44492..872a9926e 100644 --- a/ds4_server.c +++ b/ds4_server.c @@ -806,34 +806,91 @@ static void request_free(request *r) { memset(r, 0, sizeof(*r)); } +/* How wire reasoning_effort names map onto ds4 tiers. + * + * DEEPSEEK (default) -- align with DeepSeek-V4-Flash-0731: the wire name + * selects the prompt DeepSeek itself would use for that name. + * LEGACY -- the pre-0731 ds4 mapping, kept so the old behaviour is one flag + * away for anyone whose prompts were tuned against it. + * + * Runtime-global rather than per-request: it describes how this server reads + * the wire protocol, and every endpoint must read it the same way. */ +typedef enum { + EFFORT_MAP_DEEPSEEK = 0, + EFFORT_MAP_LEGACY, +} reasoning_effort_map; + +static reasoning_effort_map g_reasoning_effort_map = EFFORT_MAP_DEEPSEEK; + +static bool parse_reasoning_effort_map_name(const char *s, reasoning_effort_map *out) { + if (!s) return false; + if (!strcmp(s, "deepseek")) { *out = EFFORT_MAP_DEEPSEEK; return true; } + if (!strcmp(s, "legacy")) { *out = EFFORT_MAP_LEGACY; return true; } + return false; +} + +/* LANDMINE, no compiler warning: this used to collapse everything that was not + * DS4_THINK_MAX down to DS4_THINK_HIGH. With a fourth tier that would destroy + * ULTRA before the renderer ever saw it, silently. It is now a pass-through: + * the only decision left here is the thinking on/off control. */ static ds4_think_mode think_mode_from_enabled(bool enabled, ds4_think_mode effort) { if (!enabled || effort == DS4_THINK_NONE) return DS4_THINK_NONE; - return effort == DS4_THINK_MAX ? DS4_THINK_MAX : DS4_THINK_HIGH; + return effort; } -static bool parse_reasoning_effort_name(const char *s, ds4_think_mode *out) { +static bool parse_reasoning_effort_name_mapped(const char *s, + reasoning_effort_map map, + ds4_think_mode *out) +{ if (!s) return false; + if (!strcmp(s, "none")) { + *out = DS4_THINK_NONE; + return true; + } + if (map == EFFORT_MAP_LEGACY) { + /* Pre-0731 behaviour, byte for byte: only "max" reached the prefixed + * tier and everything else non-zero collapsed to HIGH. */ + if (!strcmp(s, "max")) { + *out = DS4_THINK_MAX; + return true; + } + if (!strcmp(s, "xhigh") || !strcmp(s, "high") || + !strcmp(s, "medium") || !strcmp(s, "low") || + !strcmp(s, "minimal")) + { + *out = DS4_THINK_HIGH; + return true; + } + return false; + } + /* DeepSeek 0731 mapping. DeepSeek has three prompts; the wire has six + * names, so the four below the top collapse onto DeepSeek's "low". + * + * minimal/low/medium -> HIGH (DeepSeek "low", no prefix) + * high/xhigh -> MAX (DeepSeek "high", "Absolute maximum...") + * max -> ULTRA (DeepSeek "max", "Beyond maximum...") + */ if (!strcmp(s, "max")) { - *out = DS4_THINK_MAX; + *out = DS4_THINK_ULTRA; return true; } - if (!strcmp(s, "xhigh") || !strcmp(s, "high") || - !strcmp(s, "medium") || !strcmp(s, "low") || - !strcmp(s, "minimal")) - { - /* DS4 only exposes HIGH and MAX above zero, so "minimal" collapses to - * the smallest non-zero level (HIGH). Callers that need *no* reasoning - * must use "none" instead. */ - *out = DS4_THINK_HIGH; + if (!strcmp(s, "xhigh") || !strcmp(s, "high")) { + *out = DS4_THINK_MAX; return true; } - if (!strcmp(s, "none")) { - *out = DS4_THINK_NONE; + if (!strcmp(s, "medium") || !strcmp(s, "low") || !strcmp(s, "minimal")) { + /* Callers that need *no* reasoning must use "none" instead. */ + *out = DS4_THINK_HIGH; return true; } return false; } +/* Unknown names still fail, so the HTTP layer keeps answering 400. */ +static bool parse_reasoning_effort_name(const char *s, ds4_think_mode *out) { + return parse_reasoning_effort_name_mapped(s, g_reasoning_effort_map, out); +} + static bool parse_reasoning_effort_value(const char **p, ds4_think_mode *out) { json_ws(p); if (json_lit(p, "null")) return true; @@ -2454,7 +2511,7 @@ static char *render_deepseek_chat_prompt_text(const chat_msgs *msgs, const char buf out = {0}; buf_puts(&out, "<|begin▁of▁sentence|>"); - if (think_mode == DS4_THINK_MAX) buf_puts(&out, ds4_think_max_prefix()); + buf_puts(&out, ds4_think_effort_prefix(think_mode)); buf_puts(&out, system.ptr ? system.ptr : ""); bool pending_assistant = false; @@ -10139,10 +10196,20 @@ static char *rendered_chat_system_region(const char *prompt_text) { const char *bos = "<|begin▁of▁sentence|>"; const size_t bos_len = strlen(bos); if (!strncmp(p, bos, bos_len)) p += bos_len; - const char *max_prefix = ds4_think_max_prefix(); - const size_t max_prefix_len = strlen(max_prefix); - if (max_prefix_len && !strncmp(p, max_prefix, max_prefix_len)) { - p += max_prefix_len; + /* Strip whichever effort prefix this prompt was rendered with. Every + * prefixed tier must be listed: a tier missing here leaves its prompt text + * inside the "system region" that gets echoed back into tool-error + * recovery messages. */ + static const ds4_think_mode prefixed_tiers[] = { + DS4_THINK_MAX, DS4_THINK_ULTRA, + }; + for (size_t i = 0; i < sizeof(prefixed_tiers) / sizeof(prefixed_tiers[0]); i++) { + const char *prefix = ds4_think_effort_prefix(prefixed_tiers[i]); + const size_t prefix_len = strlen(prefix); + if (prefix_len && !strncmp(p, prefix, prefix_len)) { + p += prefix_len; + break; + } } while (*p && isspace((unsigned char)*p)) p++; @@ -12834,6 +12901,13 @@ static server_config parse_options(int argc, char **argv) { c.engine.backend = parse_backend_arg(need_arg(&i, argc, argv, arg), arg); } else if (!strcmp(arg, "--cpu")) { c.engine.backend = DS4_BACKEND_CPU; + } else if (!strcmp(arg, "--reasoning-effort-map")) { + const char *name = need_arg(&i, argc, argv, arg); + if (!parse_reasoning_effort_map_name(name, &g_reasoning_effort_map)) { + server_log(DS4_LOG_DEFAULT, + "ds4-server: --reasoning-effort-map must be deepseek or legacy"); + exit(2); + } } else { server_log(DS4_LOG_DEFAULT, "ds4-server: unknown option: %s", arg); usage(stderr, NULL); From 6ccd1dc63e9d03e15910fcad143667d7157ac335 Mon Sep 17 00:00:00 2001 From: "Mark (agent)" Date: Sat, 1 Aug 2026 09:42:58 +0100 Subject: [PATCH 3/6] cli/agent/eval: --think-ultra flag and /think-ultra REPL command Expose the new top tier everywhere the other tiers are exposed: --think-ultra in ds4, ds4-agent and ds4-eval, /think-ultra in the REPL, and both in the shared ds4_help.c tables. Three call sites had to stop testing == DS4_THINK_MAX: - repl_chat_apply_max_prefix() took a bool. With two prefixed tiers a bool cannot express "still prefixed, but with different text", so switching /think-max <-> /think-ultra would have left the previous tier prompt in the transcript. It is now repl_chat_apply_effort_prefix(engine, chat, mode), remembers which tier it inserted, and replaces rather than keeps on a tier change. - agent_worker_build_system_tokens() gated the prefix on think_mode == DS4_THINK_MAX. It now appends the prefix for the context-adjusted tier, which is a no-op for the unprefixed tiers. - eval auto-context and the downgrade warnings treat ULTRA like MAX for the context floor and name the tier actually used. --- ds4_agent.c | 10 +++++++--- ds4_cli.c | 51 +++++++++++++++++++++++++++++++++------------------ ds4_eval.c | 18 +++++++++++------- ds4_help.c | 3 ++- 4 files changed, 53 insertions(+), 29 deletions(-) diff --git a/ds4_agent.c b/ds4_agent.c index d35c125fe..06d1437fe 100644 --- a/ds4_agent.c +++ b/ds4_agent.c @@ -656,6 +656,8 @@ static agent_config parse_options(int argc, char **argv) { c.gen.think_mode = DS4_THINK_HIGH; } else if (!strcmp(arg, "--think-max")) { c.gen.think_mode = DS4_THINK_MAX; + } else if (!strcmp(arg, "--think-ultra")) { + c.gen.think_mode = DS4_THINK_ULTRA; } else if (!strcmp(arg, "--nothink")) { c.gen.think_mode = DS4_THINK_NONE; } else if (!strcmp(arg, "--backend")) { @@ -4381,9 +4383,11 @@ static void agent_worker_build_system_tokens(agent_worker *w, ds4_tokens *out) { if (agent_tool_syntax_for_engine(w->engine) == AGENT_TOOL_SYNTAX_GLM) { const char *effort = ds4_glm_reasoning_effort_text(think_mode); if (effort) ds4_chat_append_message(w->engine, out, "system", effort); - } else if (w->cfg->gen.think_mode == DS4_THINK_MAX && - think_mode == DS4_THINK_MAX) { - ds4_chat_append_max_effort_prefix(w->engine, out); + } else { + /* Use the context-adjusted tier: the prompt must carry the + * prefix the run will actually use. Appending is a no-op for + * the unprefixed tiers, so no per-tier test is needed. */ + ds4_chat_append_effort_prefix(w->engine, out, think_mode); } agent_append_system_prompt(w->engine, out, w->cfg->gen.system); } diff --git a/ds4_cli.c b/ds4_cli.c index 811c56e6b..0afd643e9 100644 --- a/ds4_cli.c +++ b/ds4_cli.c @@ -284,18 +284,18 @@ static ds4_think_mode cli_effective_think_mode(const cli_generation_options *gen } static bool cli_think_max_downgraded(const cli_generation_options *gen) { - return gen->think_mode == DS4_THINK_MAX && - cli_effective_think_mode(gen) != DS4_THINK_MAX; + return cli_effective_think_mode(gen) != gen->think_mode; } static void cli_warn_think_max_downgraded(const cli_generation_options *gen, const char *name) { if (!cli_think_max_downgraded(gen)) return; ds4_log(stderr, DS4_LOG_WARNING, - "ds4: warning: %s needs --ctx >= %u; ctx=%d uses normal thinking instead\n", + "ds4: warning: %s needs --ctx >= %u; ctx=%d uses %s instead\n", name, ds4_think_max_min_context(), - gen->ctx_size); + gen->ctx_size, + ds4_think_mode_name(cli_effective_think_mode(gen))); } static double cli_now_sec(void) { @@ -1272,6 +1272,7 @@ static void print_repl_help(void) { puts(" /help Show this help."); puts(" /think Use normal thinking mode."); puts(" /think-max Use Think Max only when context is at least 393216 tokens."); + puts(" /think-ultra Use Think Ultra (DeepSeek 0731 'max') at the same context floor."); puts(" /nothink Disable thinking mode."); puts(" /ctx N Set context size for following prompts."); puts(" /power N Set GPU duty cycle percentage, 1..100."); @@ -1331,9 +1332,13 @@ static void tokens_remove(ds4_tokens *dst, int pos, int n) { static const char *repl_glm_reasoning_effort_text(ds4_think_mode mode) { switch (mode) { - case DS4_THINK_HIGH: return "Reasoning Effort: High"; - case DS4_THINK_MAX: return "Reasoning Effort: Max"; - case DS4_THINK_NONE: return NULL; + case DS4_THINK_HIGH: return "Reasoning Effort: High"; + case DS4_THINK_MAX: return "Reasoning Effort: Max"; + /* GLM has no tier above "Max", so ULTRA saturates there. No + * default: in this switch — an unlisted tier silently emits no + * effort text at all. */ + case DS4_THINK_ULTRA: return "Reasoning Effort: Max"; + case DS4_THINK_NONE: return NULL; } return NULL; } @@ -1344,8 +1349,10 @@ static void repl_chat_build_think_prefix(ds4_engine *engine, if (ds4_engine_is_glm_dsa(engine)) { const char *effort = repl_glm_reasoning_effort_text(mode); if (effort) ds4_chat_append_message(engine, prefix, "system", effort); - } else if (mode == DS4_THINK_MAX) { - ds4_chat_append_max_effort_prefix(engine, prefix); + } else { + /* Tier table, so every prefixed tier is handled here. It is a + * no-op for the unprefixed tiers. */ + ds4_chat_append_effort_prefix(engine, prefix, mode); } } @@ -1625,14 +1632,19 @@ static int run_repl(ds4_engine *engine, cli_config *cfg) { cfg->gen.think_mode = DS4_THINK_HIGH; repl_chat_apply_think_prefix(engine, &chat, DS4_THINK_HIGH); puts("Thinking mode: high."); - } else if (!strcmp(cmd, "/think-max")) { - cfg->gen.think_mode = DS4_THINK_MAX; - bool active = ds4_think_mode_for_context(cfg->gen.think_mode, - chat.ctx_size) == DS4_THINK_MAX; - repl_chat_apply_think_prefix(engine, &chat, - active ? DS4_THINK_MAX : DS4_THINK_HIGH); - cli_warn_think_max_downgraded(&cfg->gen, "/think-max"); - printf("Thinking mode: %s.\n", active ? "max" : "high (ctx below 393216)"); + } else if (!strcmp(cmd, "/think-max") || !strcmp(cmd, "/think-ultra")) { + cfg->gen.think_mode = !strcmp(cmd, "/think-ultra") ? + DS4_THINK_ULTRA : DS4_THINK_MAX; + ds4_think_mode active = ds4_think_mode_for_context(cfg->gen.think_mode, + chat.ctx_size); + repl_chat_apply_think_prefix(engine, &chat, active); + cli_warn_think_max_downgraded(&cfg->gen, cmd); + if (active == cfg->gen.think_mode) { + printf("Thinking mode: %s.\n", ds4_think_mode_name(active)); + } else { + printf("Thinking mode: %s (ctx below %u).\n", + ds4_think_mode_name(active), ds4_think_max_min_context()); + } } else if (!strcmp(cmd, "/nothink")) { cfg->gen.think_mode = DS4_THINK_NONE; repl_chat_apply_think_prefix(engine, &chat, DS4_THINK_NONE); @@ -1977,6 +1989,8 @@ static cli_config parse_options(int argc, char **argv) { c.gen.think_mode = DS4_THINK_HIGH; } else if (!strcmp(arg, "--think-max")) { c.gen.think_mode = DS4_THINK_MAX; + } else if (!strcmp(arg, "--think-ultra")) { + c.gen.think_mode = DS4_THINK_ULTRA; } else if (!strcmp(arg, "--nothink")) { c.gen.think_mode = DS4_THINK_NONE; } else if (!strcmp(arg, "--head-test")) { @@ -2177,7 +2191,8 @@ int main(int argc, char **argv) { cfg.gen.ctx_size, ds4_engine_prefill_chunk(engine), cfg.engine.ssd_streaming); - cli_warn_think_max_downgraded(&cfg.gen, "--think-max"); + cli_warn_think_max_downgraded(&cfg.gen, + cfg.gen.think_mode == DS4_THINK_ULTRA ? "--think-ultra" : "--think-max"); } int rc = 0; if (cfg.inspect) { diff --git a/ds4_eval.c b/ds4_eval.c index 7aed5d5c5..b5ee3f810 100644 --- a/ds4_eval.c +++ b/ds4_eval.c @@ -1651,6 +1651,8 @@ static eval_config parse_options(int argc, char **argv) { c.think_mode = DS4_THINK_HIGH; } else if (!strcmp(arg, "--think-max")) { c.think_mode = DS4_THINK_MAX; + } else if (!strcmp(arg, "--think-ultra")) { + c.think_mode = DS4_THINK_ULTRA; } else if (!strcmp(arg, "--nothink")) { c.think_mode = DS4_THINK_NONE; } else if (!strcmp(arg, "--plain")) { @@ -2456,7 +2458,8 @@ static int eval_auto_context_size(ds4_engine *engine, int ctx = EVAL_MAX_CONTEXT; int max_prompt = 0; int max_case = -1; - const int min_ctx = cfg->think_mode == DS4_THINK_MAX ? + const int min_ctx = (cfg->think_mode == DS4_THINK_MAX || + cfg->think_mode == DS4_THINK_ULTRA) ? (int)ds4_think_max_min_context() : 1; /* Think Max downgrades to normal thinking under its minimum context. Size @@ -2482,14 +2485,15 @@ static int eval_auto_context_size(ds4_engine *engine, } static void eval_warn_think_max_downgraded(const eval_config *cfg) { - if (cfg->think_mode != DS4_THINK_MAX || - ds4_think_mode_for_context(cfg->think_mode, cfg->ctx_size) == DS4_THINK_MAX) { - return; - } + const ds4_think_mode effective = + ds4_think_mode_for_context(cfg->think_mode, cfg->ctx_size); + if (effective == cfg->think_mode) return; fprintf(stderr, - "ds4-eval: warning: --think-max needs --ctx >= %u; ctx=%d uses normal thinking instead\n", + "ds4-eval: warning: --think-%s needs --ctx >= %u; ctx=%d uses %s instead\n", + cfg->think_mode == DS4_THINK_ULTRA ? "ultra" : "max", ds4_think_max_min_context(), - cfg->ctx_size); + cfg->ctx_size, + ds4_think_mode_name(effective)); } static void eval_warn_context_budget(const eval_config *cfg, int max_prompt_tokens, int max_prompt_case) { diff --git a/ds4_help.c b/ds4_help.c index 4dbb98d00..7453fea7a 100644 --- a/ds4_help.c +++ b/ds4_help.c @@ -206,6 +206,7 @@ static void print_sampling(FILE *fp, const help_colors *c, bool full) { para(fp, c, "GLM CLI and agent runs default to temperature 1.0, top-p 0.95, and min-p 0 unless those options are set explicitly."); opt(fp, c, "--think", "Use normal thinking mode."); opt(fp, c, "--think-max", "Use Think Max when context is large enough."); + opt(fp, c, "--think-ultra", "Use Think Ultra, DeepSeek 0731 'max' effort, when context allows."); opt(fp, c, "--nothink", "Disable thinking and ask for direct replies."); if (full) { opt(fp, c, "-sys, --system TEXT", "System prompt. Empty string disables the default where supported."); @@ -290,7 +291,7 @@ static void print_cli_diagnostics(FILE *fp, const help_colors *c) { static void print_cli_commands(FILE *fp, const help_colors *c) { title_red(fp, c, "Interactive Commands"); opt(fp, c, "/help", "Show interactive commands."); - opt(fp, c, "/think, /think-max, /nothink", "Switch thinking mode."); + opt(fp, c, "/think, /think-max, /think-ultra, /nothink", "Switch thinking mode."); opt(fp, c, "/ctx N", "Restart the interactive session with a new context size."); opt(fp, c, "/power N", "Set GPU duty cycle percentage, 1..100."); opt(fp, c, "/read FILE", "Read FILE and submit it as the next user message."); From 64ceede642891647b494dc2bc88618babb28e8ec Mon Sep 17 00:00:00 2001 From: "Mark (agent)" Date: Sat, 1 Aug 2026 09:44:10 +0100 Subject: [PATCH 4/6] core+tools: make the effort context floor runtime-settable ds4_think_set_effort_min_context() replaces the compile-time-only DS4_THINK_MAX_MIN_CONTEXT read, and --think-effort-min-ctx N is wired into ds4, ds4-agent, ds4-eval and ds4-server. The default is unchanged at DeepSeeks recommended 393216, so behaviour is identical unless the flag is passed. Process-wide by design: it is a property of the deployment, not of a request, and every endpoint must apply the same floor. Set during argument parsing, before anything is served. The REPL help line no longer hardcodes 393216, since the floor can now move. --- ds4.c | 14 ++++++++++++-- ds4.h | 4 ++++ ds4_agent.c | 7 +++++++ ds4_cli.c | 10 +++++++++- ds4_eval.c | 7 +++++++ ds4_help.c | 1 + ds4_server.c | 8 ++++++++ 7 files changed, 48 insertions(+), 3 deletions(-) diff --git a/ds4.c b/ds4.c index 9031cc034..b516efd55 100644 --- a/ds4.c +++ b/ds4.c @@ -409,6 +409,12 @@ static const char DS4_REASONING_EFFORT_ULTRA_PREFIX[] = * to hold. */ #define DS4_THINK_MAX_MIN_CONTEXT 393216u +/* Runtime-settable so an operator can lower or raise the floor without a + * rebuild. The default is DeepSeek's recommendation and is what every tool + * uses unless --think-effort-min-ctx says otherwise. Set once during argument + * parsing, before any request is served. */ +static uint32_t g_think_effort_min_context = DS4_THINK_MAX_MIN_CONTEXT; + static bool ds4_backend_uses_graph(ds4_backend backend) { return backend == DS4_BACKEND_METAL || backend == DS4_BACKEND_CUDA; } @@ -48065,7 +48071,11 @@ const char *ds4_think_max_prefix(void) { } uint32_t ds4_think_max_min_context(void) { - return DS4_THINK_MAX_MIN_CONTEXT; + return g_think_effort_min_context; +} + +void ds4_think_set_effort_min_context(uint32_t min_context) { + g_think_effort_min_context = min_context; } /* Step the tier down ONE level when the context is too small, so ULTRA in a @@ -48073,7 +48083,7 @@ uint32_t ds4_think_max_min_context(void) { * unprefixed tier. */ ds4_think_mode ds4_think_mode_for_context(ds4_think_mode mode, int ctx_size) { const uint32_t ctx = (uint32_t)(ctx_size > 0 ? ctx_size : 0); - if (ctx >= DS4_THINK_MAX_MIN_CONTEXT) return mode; + if (ctx >= g_think_effort_min_context) return mode; if (mode == DS4_THINK_ULTRA) return DS4_THINK_MAX; if (mode == DS4_THINK_MAX) return DS4_THINK_HIGH; return mode; diff --git a/ds4.h b/ds4.h index 5e55d306d..29dbc0ac3 100644 --- a/ds4.h +++ b/ds4.h @@ -272,6 +272,10 @@ const char *ds4_think_effort_prefix(ds4_think_mode mode); const char *ds4_think_max_prefix(void); const char *ds4_glm_reasoning_effort_text(ds4_think_mode mode); uint32_t ds4_think_max_min_context(void); +/* Context floor below which ds4_think_mode_for_context() steps a tier down. + * Defaults to DeepSeek's recommended 393216. Call during argument parsing, + * before any request is served: it is a process-wide setting. */ +void ds4_think_set_effort_min_context(uint32_t min_context); ds4_think_mode ds4_think_mode_for_context(ds4_think_mode mode, int ctx_size); /* Uses the active model shape selected by ds4_engine_open(); call after opening * the GGUF so Flash/Pro dimensions are known. */ diff --git a/ds4_agent.c b/ds4_agent.c index 06d1437fe..220113232 100644 --- a/ds4_agent.c +++ b/ds4_agent.c @@ -658,6 +658,13 @@ static agent_config parse_options(int argc, char **argv) { c.gen.think_mode = DS4_THINK_MAX; } else if (!strcmp(arg, "--think-ultra")) { c.gen.think_mode = DS4_THINK_ULTRA; + } else if (!strcmp(arg, "--think-effort-min-ctx")) { + int v = parse_int(need_arg(&i, argc, argv, arg), arg); + if (v < 0) { + fprintf(stderr, "ds4-agent: --think-effort-min-ctx must be >= 0\n"); + exit(2); + } + ds4_think_set_effort_min_context((uint32_t)v); } else if (!strcmp(arg, "--nothink")) { c.gen.think_mode = DS4_THINK_NONE; } else if (!strcmp(arg, "--backend")) { diff --git a/ds4_cli.c b/ds4_cli.c index 0afd643e9..957472336 100644 --- a/ds4_cli.c +++ b/ds4_cli.c @@ -1271,7 +1271,8 @@ static void print_repl_help(void) { puts("Commands:"); puts(" /help Show this help."); puts(" /think Use normal thinking mode."); - puts(" /think-max Use Think Max only when context is at least 393216 tokens."); + printf(" /think-max Use Think Max only when context is at least %u tokens.\n", + ds4_think_max_min_context()); puts(" /think-ultra Use Think Ultra (DeepSeek 0731 'max') at the same context floor."); puts(" /nothink Disable thinking mode."); puts(" /ctx N Set context size for following prompts."); @@ -1991,6 +1992,13 @@ static cli_config parse_options(int argc, char **argv) { c.gen.think_mode = DS4_THINK_MAX; } else if (!strcmp(arg, "--think-ultra")) { c.gen.think_mode = DS4_THINK_ULTRA; + } else if (!strcmp(arg, "--think-effort-min-ctx")) { + int v = parse_int(need_arg(&i, argc, argv, arg), arg); + if (v < 0) { + fprintf(stderr, "ds4: --think-effort-min-ctx must be >= 0\n"); + exit(2); + } + ds4_think_set_effort_min_context((uint32_t)v); } else if (!strcmp(arg, "--nothink")) { c.gen.think_mode = DS4_THINK_NONE; } else if (!strcmp(arg, "--head-test")) { diff --git a/ds4_eval.c b/ds4_eval.c index b5ee3f810..32a25c1c9 100644 --- a/ds4_eval.c +++ b/ds4_eval.c @@ -1653,6 +1653,13 @@ static eval_config parse_options(int argc, char **argv) { c.think_mode = DS4_THINK_MAX; } else if (!strcmp(arg, "--think-ultra")) { c.think_mode = DS4_THINK_ULTRA; + } else if (!strcmp(arg, "--think-effort-min-ctx")) { + int v = parse_int_arg(need_arg(&i, argc, argv, arg), arg); + if (v < 0) { + fprintf(stderr, "ds4-eval: --think-effort-min-ctx must be >= 0\n"); + exit(2); + } + ds4_think_set_effort_min_context((uint32_t)v); } else if (!strcmp(arg, "--nothink")) { c.think_mode = DS4_THINK_NONE; } else if (!strcmp(arg, "--plain")) { diff --git a/ds4_help.c b/ds4_help.c index 7453fea7a..2a4b45bff 100644 --- a/ds4_help.c +++ b/ds4_help.c @@ -208,6 +208,7 @@ static void print_sampling(FILE *fp, const help_colors *c, bool full) { opt(fp, c, "--think-max", "Use Think Max when context is large enough."); opt(fp, c, "--think-ultra", "Use Think Ultra, DeepSeek 0731 'max' effort, when context allows."); opt(fp, c, "--nothink", "Disable thinking and ask for direct replies."); + opt(fp, c, "--think-effort-min-ctx N", "Context floor for the prefixed think tiers. Default: 393216"); if (full) { opt(fp, c, "-sys, --system TEXT", "System prompt. Empty string disables the default where supported."); opt(fp, c, "-p, --prompt TEXT", "One-shot prompt text."); diff --git a/ds4_server.c b/ds4_server.c index 872a9926e..ff18f97b9 100644 --- a/ds4_server.c +++ b/ds4_server.c @@ -12901,6 +12901,14 @@ static server_config parse_options(int argc, char **argv) { c.engine.backend = parse_backend_arg(need_arg(&i, argc, argv, arg), arg); } else if (!strcmp(arg, "--cpu")) { c.engine.backend = DS4_BACKEND_CPU; + } else if (!strcmp(arg, "--think-effort-min-ctx")) { + const int v = parse_int_arg(need_arg(&i, argc, argv, arg), arg); + if (v < 0) { + server_log(DS4_LOG_DEFAULT, + "ds4-server: --think-effort-min-ctx must be >= 0"); + exit(2); + } + ds4_think_set_effort_min_context((uint32_t)v); } else if (!strcmp(arg, "--reasoning-effort-map")) { const char *name = need_arg(&i, argc, argv, arg); if (!parse_reasoning_effort_map_name(name, &g_reasoning_effort_map)) { From 5201d0921c886c94b9b22dc639e08159ed684f3f Mon Sep 17 00:00:00 2001 From: "Mark (agent)" Date: Sat, 1 Aug 2026 09:45:44 +0100 Subject: [PATCH 5/6] tests+docs: cover both effort maps, the ULTRA tier and the one-step gate test_reasoning_effort_mapping() now asserts: - the full deepseek table (minimal/low/medium -> high, high/xhigh -> max, max -> ultra, none -> none) and that it is the default; - the legacy table, unchanged from before the patch; - rejection of unknown strings ("banana", "", "ultra", NULL) in both maps, so the HTTP 400 path is still reachable; - ULTRA is a real tier: enabled, named, and with a prefix distinct from MAX and starting with the em dash form of the 0731 string; - think_mode_from_enabled() passes tiers through (the landmine); - the gate steps down ONE tier and the floor is runtime-settable. New test_render_think_ultra_prompt_prefix() renders an ULTRA prompt end to end. It fails if either silent landmine returns: a missed ds4_think_mode_enabled() closes , and a collapsing think_mode_from_enabled() emits the MAX text. It also checks rendered_chat_system_region() strips the ULTRA preamble. README gains the ds4-tier / DeepSeek-name mapping table, the wire effort table for both maps, and the one-step downgrade rule. Help text drops the hardcoded 393216 where the floor is now configurable. --- README.md | 41 +++++++++++++++--- ds4_help.c | 8 ++-- ds4_server.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 152 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index dcb62d16c..0b91dcbb4 100644 --- a/README.md +++ b/README.md @@ -952,7 +952,8 @@ ds4> The interactive CLI is a real multi-turn chat. It keeps the rendered chat transcript and the live graph KV checkpoint, so each turn extends the previous -conversation. Useful commands are `/help`, `/think`, `/think-max`, `/nothink`, +conversation. Useful commands are `/help`, `/think`, `/think-max`, +`/think-ultra`, `/nothink`, `/ctx N`, `/read FILE`, and `/quit`. Ctrl+C interrupts the current generation and returns to `ds4>`. @@ -1273,11 +1274,39 @@ the saved prefix instead of processing the whole prompt again. ## Thinking Modes -DeepSeek V4 Flash has distinct non-thinking, thinking, and Think Max modes. -The server defaults to thinking mode. `reasoning_effort=max` requests Think -Max, but it is only applied when the context size is large enough for the model -card recommendation; smaller contexts fall back to normal thinking. OpenAI -`reasoning_effort=xhigh` still maps to normal thinking, not Think Max. +DeepSeek V4 Flash has a non-thinking mode plus three reasoning-effort tiers, +rendered as a plain-text prefix at the very start of the conversation. The +0731 release added the top one; the tier names in the ds4 source are historical +and do not line up 1:1 with DeepSeek's own names: + +| ds4 tier | DeepSeek name | prompt prefix | +|---|---|---| +| `DS4_THINK_NONE` | - | non-thinking, no prefix | +| `DS4_THINK_HIGH` | `low` | none (this is DeepSeek's default) | +| `DS4_THINK_MAX` | `high` | "Reasoning Effort: Absolute maximum ..." | +| `DS4_THINK_ULTRA` | `max` | "Reasoning Effort: Beyond maximum ..." | + +The server defaults to the unprefixed thinking tier, matching DeepSeek's own +`low` default. Wire `reasoning_effort` names map on as follows: + +| `reasoning_effort` | `deepseek` map (default) | `legacy` map | +|---|---|---| +| `minimal`, `low`, `medium` | high | high | +| `high`, `xhigh` | max | high | +| `max` | ultra | max | +| `none` | none | none | + +`--reasoning-effort-map legacy` restores the pre-0731 ds4 mapping exactly, for +prompts that were tuned against it. Unknown names are rejected with HTTP 400 in +both maps. + +The prefixed tiers are only applied when the context is at least +`--think-effort-min-ctx` (default 393216, the model card recommendation); +below that the tier steps down one level (ultra to max, max to high), so a +small context loses one notch of effort rather than all of it. + +On the command line the tiers are `--think`, `--think-max` and `--think-ultra` +(`/think`, `/think-max`, `/think-ultra` in the REPL). For direct replies, use `thinking: {"type":"disabled"}`, `think:false`, or a non-thinking model alias such as `deepseek-chat`. diff --git a/ds4_help.c b/ds4_help.c index 2a4b45bff..0bbf0a405 100644 --- a/ds4_help.c +++ b/ds4_help.c @@ -342,9 +342,11 @@ static void print_server_api(FILE *fp, const help_colors *c) { static void print_server_thinking(FILE *fp, const help_colors *c) { title(fp, c, "Server Thinking Defaults"); - para(fp, c, "DeepSeek-compatible chat requests default to high-effort thinking."); - para(fp, c, "reasoning_effort=max or output_config.effort=max requests Think Max."); - para(fp, c, "Think Max requires --ctx >= 393216; smaller contexts use high."); + para(fp, c, "Chat requests default to thinking with no effort prefix, matching DeepSeek's own 'low' default."); + opt(fp, c, "--reasoning-effort-map MAP", "How wire effort names map to tiers: deepseek (default) or legacy."); + para(fp, c, "deepseek: minimal/low/medium -> high, high/xhigh -> max, max -> ultra."); + para(fp, c, "legacy: the pre-0731 ds4 table, where only max reached a prefixed tier."); + para(fp, c, "The prefixed tiers require --ctx >= --think-effort-min-ctx (default 393216); below it each steps down one tier."); para(fp, c, "thinking={type:disabled}, think=false, or model=deepseek-chat selects non-thinking mode."); para(fp, c, "In thinking mode, client sampling knobs are ignored like the official API."); fputc('\n', fp); diff --git a/ds4_server.c b/ds4_server.c index ff18f97b9..388d985d4 100644 --- a/ds4_server.c +++ b/ds4_server.c @@ -14518,15 +14518,83 @@ static void test_request_defaults_use_min_p_filtering(void) { static void test_reasoning_effort_mapping(void) { ds4_think_mode mode = DS4_THINK_NONE; + + /* Default map: DeepSeek 0731. Every wire name lands on the tier whose + * prompt DeepSeek itself would use for that name. */ + TEST_ASSERT(g_reasoning_effort_map == EFFORT_MAP_DEEPSEEK); + TEST_ASSERT(parse_reasoning_effort_name("minimal", &mode) && mode == DS4_THINK_HIGH); TEST_ASSERT(parse_reasoning_effort_name("low", &mode) && mode == DS4_THINK_HIGH); TEST_ASSERT(parse_reasoning_effort_name("medium", &mode) && mode == DS4_THINK_HIGH); - TEST_ASSERT(parse_reasoning_effort_name("high", &mode) && mode == DS4_THINK_HIGH); - TEST_ASSERT(parse_reasoning_effort_name("xhigh", &mode) && mode == DS4_THINK_HIGH); - TEST_ASSERT(parse_reasoning_effort_name("max", &mode) && mode == DS4_THINK_MAX); + TEST_ASSERT(parse_reasoning_effort_name("high", &mode) && mode == DS4_THINK_MAX); + TEST_ASSERT(parse_reasoning_effort_name("xhigh", &mode) && mode == DS4_THINK_MAX); + TEST_ASSERT(parse_reasoning_effort_name("max", &mode) && mode == DS4_THINK_ULTRA); + TEST_ASSERT(parse_reasoning_effort_name("none", &mode) && mode == DS4_THINK_NONE); + + /* Unknown strings must still be rejected so the HTTP layer answers 400. */ TEST_ASSERT(!parse_reasoning_effort_name("banana", &mode)); + TEST_ASSERT(!parse_reasoning_effort_name("", &mode)); + TEST_ASSERT(!parse_reasoning_effort_name("ultra", &mode)); + TEST_ASSERT(!parse_reasoning_effort_name(NULL, &mode)); + + /* Legacy map: the pre-0731 table, unchanged. */ + reasoning_effort_map legacy = EFFORT_MAP_LEGACY; + TEST_ASSERT(parse_reasoning_effort_map_name("legacy", &legacy) && + legacy == EFFORT_MAP_LEGACY); + TEST_ASSERT(parse_reasoning_effort_name_mapped("minimal", legacy, &mode) && + mode == DS4_THINK_HIGH); + TEST_ASSERT(parse_reasoning_effort_name_mapped("high", legacy, &mode) && + mode == DS4_THINK_HIGH); + TEST_ASSERT(parse_reasoning_effort_name_mapped("xhigh", legacy, &mode) && + mode == DS4_THINK_HIGH); + TEST_ASSERT(parse_reasoning_effort_name_mapped("max", legacy, &mode) && + mode == DS4_THINK_MAX); + TEST_ASSERT(parse_reasoning_effort_name_mapped("none", legacy, &mode) && + mode == DS4_THINK_NONE); + TEST_ASSERT(!parse_reasoning_effort_name_mapped("banana", legacy, &mode)); + + reasoning_effort_map deepseek = EFFORT_MAP_LEGACY; + TEST_ASSERT(parse_reasoning_effort_map_name("deepseek", &deepseek) && + deepseek == EFFORT_MAP_DEEPSEEK); + TEST_ASSERT(!parse_reasoning_effort_map_name("banana", &deepseek)); + TEST_ASSERT(!parse_reasoning_effort_map_name(NULL, &deepseek)); + + /* The new tier is a real tier, not a synonym for MAX. */ + TEST_ASSERT(ds4_think_mode_enabled(DS4_THINK_ULTRA)); + TEST_ASSERT(!strcmp(ds4_think_mode_name(DS4_THINK_ULTRA), "ultra")); + TEST_ASSERT(strcmp(ds4_think_effort_prefix(DS4_THINK_ULTRA), + ds4_think_effort_prefix(DS4_THINK_MAX)) != 0); + TEST_ASSERT(!strncmp(ds4_think_effort_prefix(DS4_THINK_ULTRA), + "Reasoning Effort: Beyond maximum \xe2\x80\x94 exhaustive,", + strlen("Reasoning Effort: Beyond maximum \xe2\x80\x94 exhaustive,"))); + TEST_ASSERT(ds4_think_effort_prefix(DS4_THINK_HIGH)[0] == '\0'); + TEST_ASSERT(ds4_think_effort_prefix(DS4_THINK_NONE)[0] == '\0'); + TEST_ASSERT(!strcmp(ds4_think_max_prefix(), + ds4_think_effort_prefix(DS4_THINK_MAX))); + + /* think_mode_from_enabled() must pass the tier through untouched: the old + * "collapse anything non-MAX to HIGH" destroyed ULTRA before rendering. */ + TEST_ASSERT(think_mode_from_enabled(true, DS4_THINK_ULTRA) == DS4_THINK_ULTRA); + TEST_ASSERT(think_mode_from_enabled(true, DS4_THINK_MAX) == DS4_THINK_MAX); + TEST_ASSERT(think_mode_from_enabled(true, DS4_THINK_HIGH) == DS4_THINK_HIGH); + TEST_ASSERT(think_mode_from_enabled(false, DS4_THINK_ULTRA) == DS4_THINK_NONE); + TEST_ASSERT(think_mode_from_enabled(true, DS4_THINK_NONE) == DS4_THINK_NONE); + + /* The context gate steps down ONE tier, not straight to HIGH. */ + const int floor_ctx = (int)ds4_think_max_min_context(); + TEST_ASSERT(ds4_think_mode_for_context(DS4_THINK_ULTRA, 32768) == DS4_THINK_MAX); TEST_ASSERT(ds4_think_mode_for_context(DS4_THINK_MAX, 32768) == DS4_THINK_HIGH); - TEST_ASSERT(ds4_think_mode_for_context(DS4_THINK_MAX, - (int)ds4_think_max_min_context()) == DS4_THINK_MAX); + TEST_ASSERT(ds4_think_mode_for_context(DS4_THINK_HIGH, 32768) == DS4_THINK_HIGH); + TEST_ASSERT(ds4_think_mode_for_context(DS4_THINK_NONE, 32768) == DS4_THINK_NONE); + TEST_ASSERT(ds4_think_mode_for_context(DS4_THINK_ULTRA, floor_ctx) == DS4_THINK_ULTRA); + TEST_ASSERT(ds4_think_mode_for_context(DS4_THINK_MAX, floor_ctx) == DS4_THINK_MAX); + + /* The floor itself is runtime-settable; restore it before returning. */ + ds4_think_set_effort_min_context(4096); + TEST_ASSERT(ds4_think_max_min_context() == 4096); + TEST_ASSERT(ds4_think_mode_for_context(DS4_THINK_ULTRA, 32768) == DS4_THINK_ULTRA); + TEST_ASSERT(ds4_think_mode_for_context(DS4_THINK_ULTRA, 2048) == DS4_THINK_MAX); + ds4_think_set_effort_min_context((uint32_t)floor_ctx); + TEST_ASSERT(ds4_think_max_min_context() == (uint32_t)floor_ctx); } static void test_model_alias_thinking_controls(void) { @@ -14551,15 +14619,18 @@ static void test_api_thinking_controls_parse(void) { TEST_ASSERT(parse_thinking_control_value(&thinking, &enabled)); TEST_ASSERT(enabled); + /* Both of these go through parse_reasoning_effort_name(), so they follow + * the active map. Under the default deepseek map every tier moved up one + * notch: "max" is now ULTRA and "xhigh" is now MAX. */ ds4_think_mode mode = DS4_THINK_HIGH; const char *anth_effort = "{\"effort\":\"max\",\"other\":true}"; TEST_ASSERT(parse_output_config_effort(&anth_effort, &mode)); - TEST_ASSERT(mode == DS4_THINK_MAX); + TEST_ASSERT(mode == DS4_THINK_ULTRA); const char *openai_effort = "\"xhigh\""; mode = DS4_THINK_HIGH; TEST_ASSERT(parse_reasoning_effort_value(&openai_effort, &mode)); - TEST_ASSERT(mode == DS4_THINK_HIGH); + TEST_ASSERT(mode == DS4_THINK_MAX); } static void test_render_think_max_prompt_prefix(void) { @@ -14584,6 +14655,39 @@ static void test_render_think_max_prompt_prefix(void) { chat_msgs_free(&msgs); } +/* The ULTRA tier must reach the renderer with its own text. Both silent + * landmines would fail here rather than in production: if + * ds4_think_mode_enabled() had missed ULTRA the prompt would close , + * and if think_mode_from_enabled() still collapsed non-MAX tiers the MAX text + * would appear instead of the ULTRA text. */ +static void test_render_think_ultra_prompt_prefix(void) { + chat_msgs msgs = {0}; + chat_msg sys = {0}; + sys.role = xstrdup("system"); + sys.content = xstrdup("You are terse."); + chat_msgs_push(&msgs, sys); + chat_msg user = {0}; + user.role = xstrdup("user"); + user.content = xstrdup("Hello"); + chat_msgs_push(&msgs, user); + + char *prompt = render_chat_prompt_text(&msgs, NULL, NULL, DS4_THINK_ULTRA); + TEST_ASSERT(prompt != NULL); + TEST_ASSERT(strstr(prompt, ds4_think_effort_prefix(DS4_THINK_ULTRA)) != NULL); + TEST_ASSERT(strstr(prompt, ds4_think_effort_prefix(DS4_THINK_MAX)) == NULL); + TEST_ASSERT(strstr(prompt, "You are terse.<|User|>Hello<|Assistant|>") != NULL); + TEST_ASSERT(strstr(prompt, "") == NULL); + + /* The system region must not swallow the ULTRA preamble. */ + char *system = rendered_chat_system_region(prompt); + TEST_ASSERT(system != NULL); + TEST_ASSERT(!strcmp(system, "You are terse.")); + free(system); + + free(prompt); + chat_msgs_free(&msgs); +} + static void test_render_non_thinking_prompt_closes_think(void) { chat_msgs msgs = {0}; chat_msg user = {0}; @@ -17519,6 +17623,7 @@ static void ds4_server_unit_tests_run(void) { test_model_alias_thinking_controls(); test_api_thinking_controls_parse(); test_render_think_max_prompt_prefix(); + test_render_think_ultra_prompt_prefix(); test_render_non_thinking_prompt_closes_think(); test_render_drops_old_reasoning_without_tools(); test_render_preserves_reasoning_with_tools(); From b66d3e372caa1e3404360df43772ddc091625317 Mon Sep 17 00:00:00 2001 From: "Mark (agent)" Date: Sat, 1 Aug 2026 11:09:04 +0100 Subject: [PATCH 6/6] server: accept --think-effort-min-ctx 0 parse_int_arg() rejects v <= 0, so the zero value that disables the gate exit(2)d before reaching the (dead) v < 0 check. Use parse_nonneg_int_arg and cover the zero case in the unit test. --- ds4_server.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/ds4_server.c b/ds4_server.c index 388d985d4..64a635d09 100644 --- a/ds4_server.c +++ b/ds4_server.c @@ -12902,12 +12902,9 @@ static server_config parse_options(int argc, char **argv) { } else if (!strcmp(arg, "--cpu")) { c.engine.backend = DS4_BACKEND_CPU; } else if (!strcmp(arg, "--think-effort-min-ctx")) { - const int v = parse_int_arg(need_arg(&i, argc, argv, arg), arg); - if (v < 0) { - server_log(DS4_LOG_DEFAULT, - "ds4-server: --think-effort-min-ctx must be >= 0"); - exit(2); - } + /* 0 disables the gate entirely, so this must accept zero: + * parse_int_arg() rejects v <= 0 and would exit(2) first. */ + const int v = parse_nonneg_int_arg(need_arg(&i, argc, argv, arg), arg); ds4_think_set_effort_min_context((uint32_t)v); } else if (!strcmp(arg, "--reasoning-effort-map")) { const char *name = need_arg(&i, argc, argv, arg); @@ -14593,6 +14590,13 @@ static void test_reasoning_effort_mapping(void) { TEST_ASSERT(ds4_think_max_min_context() == 4096); TEST_ASSERT(ds4_think_mode_for_context(DS4_THINK_ULTRA, 32768) == DS4_THINK_ULTRA); TEST_ASSERT(ds4_think_mode_for_context(DS4_THINK_ULTRA, 2048) == DS4_THINK_MAX); + /* 0 disables the gate: every tier must survive at any context. */ + ds4_think_set_effort_min_context(0); + TEST_ASSERT(ds4_think_max_min_context() == 0); + TEST_ASSERT(ds4_think_mode_for_context(DS4_THINK_ULTRA, 1) == DS4_THINK_ULTRA); + TEST_ASSERT(ds4_think_mode_for_context(DS4_THINK_ULTRA, 0) == DS4_THINK_ULTRA); + TEST_ASSERT(ds4_think_mode_for_context(DS4_THINK_MAX, 1) == DS4_THINK_MAX); + ds4_think_set_effort_min_context((uint32_t)floor_ctx); TEST_ASSERT(ds4_think_max_min_context() == (uint32_t)floor_ctx); }