keep a caller's passthrough through the backend-negotiation ladder (v0.4.66)

`enable_thinking: false` came back still thinking, verified on a pod running
the v0.4.65 image with --reasoning-parser qwen3 (the other four checks passed:
reasoning_content populated, content clean, and streamed thinking starting
0.4s in against the answer at 2.8s).

generate_chat negotiates optional kwargs by calling the backend and retrying
on TypeError. Every variant that carried `passthrough` ALSO carried
`enable_thinking`, which a proxy backend does not accept -- so those variants
raised TypeError, and the variant that survived had dropped the passthrough
with it. A caller's `chat_template_kwargs` therefore never reached a pod at
all. `enable_thinking` was merely the visible case: OCR's
`continue_final_message` would have been dropped on a pod the same way.

`_lp` and `_pt` are already SIGNATURE-gated -- empty for a backend that cannot
take them, so they never raise TypeError. Only enable_thinking and the
penalties are negotiated that way, so only those are peeled off now, and the
passthrough is dropped last rather than first.
Co-Authored-By: 's avatarClaude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S8LAP3ueNzQJtwzhgVbq66
parent ccea5f43
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
# Canonical product version for CoderAI — single source of truth. Both the API # Canonical product version for CoderAI — single source of truth. Both the API
# metadata and the admin web UI read from here. # metadata and the admin web UI read from here.
__version__ = "0.4.65" __version__ = "0.4.66"
__tagline__ = "Complete orchestration, distribution and escalation of remotizable advanced inference." __tagline__ = "Complete orchestration, distribution and escalation of remotizable advanced inference."
# Configure the CUDA caching allocator BEFORE torch is imported anywhere. # Configure the CUDA caching allocator BEFORE torch is imported anywhere.
......
...@@ -999,10 +999,22 @@ class ModelManager: ...@@ -999,10 +999,22 @@ class ModelManager:
# business with the upstream server, never a reason to fail the # business with the upstream server, never a reason to fail the
# request on a backend that cannot carry it. # request on a backend that cannot carry it.
_pt = self._passthrough_kwargs(self.backend.generate_chat, passthrough) _pt = self._passthrough_kwargs(self.backend.generate_chat, passthrough)
for _extra in (dict(enable_thinking=enable_thinking, **_pen, **_lp, **_pt), # `_lp` and `_pt` are already SIGNATURE-gated, so they are empty for a
dict(enable_thinking=enable_thinking, **_pen, **_lp), # backend that cannot take them and never raise TypeError. Only
dict(enable_thinking=enable_thinking, **_lp), # enable_thinking and the penalties are negotiated by TypeError, so
dict(**_lp), {}): # only those are peeled off here.
#
# They used to be peeled off TOGETHER with the passthrough: every
# variant carrying `_pt` also carried `enable_thinking`, which a
# proxy backend does not accept, so the first variants raised
# TypeError and the ones that survived had dropped the passthrough.
# A caller's `chat_template_kwargs` therefore never reached a pod --
# which is how `enable_thinking: false` came back still thinking.
_safe = dict(**_lp, **_pt)
for _extra in (dict(enable_thinking=enable_thinking, **_pen, **_safe),
dict(enable_thinking=enable_thinking, **_safe),
dict(**_pen, **_safe),
dict(**_safe), {}):
try: try:
return self.backend.generate_chat(messages, max_tokens, temperature, top_p, return self.backend.generate_chat(messages, max_tokens, temperature, top_p,
stop, tools, response_format, **_extra) stop, tools, response_format, **_extra)
...@@ -1049,10 +1061,22 @@ class ModelManager: ...@@ -1049,10 +1061,22 @@ class ModelManager:
# doesn't accept (older signatures) so we never hard-fail on plumbing. # doesn't accept (older signatures) so we never hard-fail on plumbing.
_lp = self._logprob_kwargs(self.backend.generate_chat_stream, logprobs, top_logprobs) _lp = self._logprob_kwargs(self.backend.generate_chat_stream, logprobs, top_logprobs)
_pt = self._passthrough_kwargs(self.backend.generate_chat_stream, passthrough) _pt = self._passthrough_kwargs(self.backend.generate_chat_stream, passthrough)
for _extra in (dict(enable_thinking=enable_thinking, **_pen, **_lp, **_pt), # `_lp` and `_pt` are already SIGNATURE-gated, so they are empty for a
dict(enable_thinking=enable_thinking, **_pen, **_lp), # backend that cannot take them and never raise TypeError. Only
dict(enable_thinking=enable_thinking, **_lp), # enable_thinking and the penalties are negotiated by TypeError, so
dict(**_lp), {}): # only those are peeled off here.
#
# They used to be peeled off TOGETHER with the passthrough: every
# variant carrying `_pt` also carried `enable_thinking`, which a
# proxy backend does not accept, so the first variants raised
# TypeError and the ones that survived had dropped the passthrough.
# A caller's `chat_template_kwargs` therefore never reached a pod --
# which is how `enable_thinking: false` came back still thinking.
_safe = dict(**_lp, **_pt)
for _extra in (dict(enable_thinking=enable_thinking, **_pen, **_safe),
dict(enable_thinking=enable_thinking, **_safe),
dict(**_pen, **_safe),
dict(**_safe), {}):
try: try:
_gen = self.backend.generate_chat_stream( _gen = self.backend.generate_chat_stream(
messages, max_tokens, temperature, top_p, stop, tools, messages, max_tokens, temperature, top_p, stop, tools,
......
...@@ -385,3 +385,65 @@ def test_the_families_each_get_their_own(tmp_path): ...@@ -385,3 +385,65 @@ def test_the_families_each_get_their_own(tmp_path):
cs.forget() cs.forget()
cs._CACHE["p"] = tpl cs._CACHE["p"] = tpl
assert vllm_reasoning_parser("p") == want, style assert vllm_reasoning_parser("p") == want, style
# ------------------- the passthrough must survive the backend-negotiation ladder #
def _manager(backend):
from codai.models.manager import ModelManager
m = ModelManager.__new__(ModelManager)
m.backend = backend
return m
class _ProxyBackend:
"""A proxy backend's real shape: it takes `passthrough`, and it does NOT
take `enable_thinking` or the penalties."""
def __init__(self):
self.seen = None
def generate_chat(self, messages, max_tokens=None, temperature=0.7, top_p=1.0,
stop=None, tools=None, response_format=None,
logprobs=None, top_logprobs=None, passthrough=None):
self.seen = passthrough
return "ok"
def test_the_passthrough_reaches_a_proxy_backend():
"""THE BUG: every ladder variant carrying the passthrough also carried
enable_thinking, which a proxy backend does not accept -- so those variants
raised TypeError and the surviving one had dropped the passthrough. A
caller's chat_template_kwargs never reached a pod, which is why
`enable_thinking: false` came back still thinking."""
b = _ProxyBackend()
out = _manager(b).generate_chat(
[{"role": "user", "content": "x"}],
passthrough={"chat_template_kwargs": {"enable_thinking": False}})
assert out == "ok"
assert b.seen == {"chat_template_kwargs": {"enable_thinking": False}}
def test_a_backend_that_takes_everything_still_gets_it_all():
class _Full(_ProxyBackend):
def generate_chat(self, messages, max_tokens=None, temperature=0.7,
top_p=1.0, stop=None, tools=None, response_format=None,
enable_thinking=False, repeat_penalty=1.0,
presence_penalty=0.0, frequency_penalty=0.0,
logprobs=None, top_logprobs=None, passthrough=None):
self.seen = (enable_thinking, repeat_penalty, passthrough)
return "ok"
b = _Full()
_manager(b).generate_chat([{"role": "user", "content": "x"}],
enable_thinking=True, repeat_penalty=1.2,
passthrough={"k": 1})
assert b.seen == (True, 1.2, {"k": 1})
def test_a_backend_that_takes_none_of_it_is_still_callable():
class _Bare:
def generate_chat(self, messages, max_tokens=None, temperature=0.7,
top_p=1.0, stop=None, tools=None, response_format=None):
return "ok"
assert _manager(_Bare()).generate_chat(
[{"role": "user", "content": "x"}], passthrough={"k": 1}) == "ok"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment