Repository navigation
runner: a turn the provider refused says why, when the CLI could not read the refusal - #614
Conversation
…read the refusal (HarnessRouter#612) Qwen Code 0.26.3 reads a refusal's reason only under an `error` key; vLLM up to 0.10.0 puts `message` at the top level. A turn vLLM refused for a window too small read "API Error: 400 status code (no body)" while the relay held vLLM's words on the route. The generic failure path now uses the route's last_refusal when the CLI's reason lacks those words, as the hermes path did, and an answered call clears it, so a refusal that was repaired or got past is never the reason for a later failure. Measured on a real vLLM v0.8.5.post1 with Qwen Code 0.26.3: main says "(no body)", this branch says "The model provider refused the request (400: This model's maximum context length is 8192 tokens ...)", and a turn whose calls are answered is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…provider's words lead whenever the reason a person reads lacks them (HarnessRouter#612) - A refusal of an earlier call stayed on the route until some call was answered, so a turn whose next call went unanswered (a dropped connection, a timeout) and failed of that read the earlier refusal as its reason. Each call now clears it first; its own refusal sets it again. - The words are compared with the reason the turn would otherwise give, not with all the CLI printed: the provider's sentence in the stderr tail behind a result that says "(no body)" is not what a person reads, and a tail line that only names the status no longer shadows them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
richard-epsilla
left a comment
There was a problem hiding this comment.
Thank you, @yuesu0202. The diagnosis is exactly right: the relay already had vLLM's sentence, and only hermes read it. Clearing the refusal on an answer is the right instinct too. I reproduced the bug in the full product on 0.37.11, with a stub that answers the way vLLM 0.8.5 does added as a custom connection and a real Qwen Code turn through it. The turn's error read The turn failed: API Error: 400 status code (no body).
I pushed one commit on top (c811915, after merging main) for two cases the rule as written still got wrong:
1. A refusal from an earlier call outlived the call. last_refusal was cleared only when some call was answered. Take a turn that gets past a refused call, then makes a call that is never answered (the connection closes or times out) and fails of that. Its reason was the earlier refusal, which is not why it failed. The record now describes the last call: each relayed call clears it first, its own refusal sets it again, and an answer clears it, as you had. The new test test_a_refusal_before_a_call_that_went_unanswered_is_not_the_reason fails on 7d2f5ea.
2. The comparison read text the person never sees. _relay_refusal_unsaid(env, ev_err, tail) kept the CLI's sentence when the words appeared anywhere in the stderr tail. _failure_reason puts the result's message ahead of the tail, though. A CLI that printed vLLM's sentence on stderr and (no body) in its result still showed (no body). The other way round, a tail line that only names the status (429 status code (no body)) was shown ahead of a result that carried the words. The words are now compared with the reason the turn would give without them (_failure_reason(said, ev_err, tail, rc)), so they lead exactly when that reason lacks them. The new test test_words_only_in_the_stderr_tail_are_not_what_the_turn_says fails on 7d2f5ea.
Runner suite: 1426 passed, 2 skipped. All 7 tests in test_relay_refusal_reason.py pass, and the two new ones fail on 7d2f5ea. Next I'll put it on a release candidate on the test instance and run the same stub turn there, before it ships as a bugfix release.
richard-epsilla
left a comment
There was a problem hiding this comment.
Verified on 0.37.12-rc.1 (sha256:2631be53…) on the test instance, through the whole product: a real Qwen Code turn at an endpoint that answers as vLLM 0.8.5 does, added as a custom connection. With an 8192 window the turn's error is now "The model provider refused the request (400: This model's maximum context length is 8192 tokens. However, you requested 25765 tokens ...)", where 0.37.11 said "API Error: 400 status code (no body)". With a large window the same endpoint's turn completes. Deck turns on Qwen Code and the DeepSeek Harness complete. Merging for 0.37.12.
Closes #612.
The bug
When vLLM ≤0.10.0 refuses a Qwen Code call, the turn fails with "API Error: 400 status code (no body)". Qwen Code 0.26.3 reads the reason only under an
errorkey, and vLLM putsmessageat the top level. The relay already recorded vLLM's words on the route (last_refusal), but only the hermes path used them.Expected behavior
The turn's error gives the provider's reason:
The model provider refused the request (400: This model's maximum context length is 8192 tokens. ...). A CLI that already says the reason keeps its own sentence.The fix
_relay_refusal_unsaidinrunner/server.py: the generic failure path in_run_turn_bguses the route'slast_refusalwhen the CLI's reason doesn't contain it.last_refusal, so a refusal the relay repaired is never blamed for a later failure. This applies to the hermes path too.Verification
Real vLLM v0.8.5.post1 (
Qwen/Qwen3-4B-AWQ), Qwen Code 0.26.3, HarnessRouter images from main3fa39adand from this branch:The turn failed: API Error: 400 status code (no body)The turn failed: The model provider refused the request (400: This model's maximum context length is 8192 tokens. However, you requested 16584 tokens ...)hello.txt= HELLOUnit tests:
runner/tests/test_relay_refusal_reason.py, 5 passed;runner/tests, 1424 passed, 2 skipped. Two of the new tests fail on main, and the repaired-refusal test fails without the clear. No test removed.Not run on a real server: a refusal the relay repairs and then a different failure (unit test only); vLLM ≥0.10.1's nested shape (stub only); bases other than qwen.
🤖 Generated with Claude Code