From d7978cff3a9a1e39ffb1eb9e4140fd9ed668708a Mon Sep 17 00:00:00 2001 From: Joel Lamy-Poirier Date: Thu, 2 Jul 2026 11:19:37 -0400 Subject: [PATCH] Retry completion responses missing 'choices' instead of crashing the actor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A malformed or error response (an HTTP 200 carrying an error body, or an empty payload) can lack 'choices'. llm_async_generate parsed it unguarded, raising KeyError, which the actor does not catch — so a single bad vLLM response killed the entire rollout loop mid-run. Raise RetryableAbortedCompletionError instead, so it is retried like other transient vLLM failures. Co-Authored-By: Claude Opus 4.8 (1M context) --- pipelinerl/async_llm.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pipelinerl/async_llm.py b/pipelinerl/async_llm.py index 26ca3da1..b54f959c 100644 --- a/pipelinerl/async_llm.py +++ b/pipelinerl/async_llm.py @@ -169,6 +169,13 @@ async def llm_async_generate( assert response_data is not None, "response_data is None" + # A malformed or error response (e.g. an HTTP 200 carrying an error body, or an empty payload) + # can lack "choices"; treat it as retryable instead of crashing the actor loop with a KeyError. + if not response_data.get("choices"): + raise RetryableAbortedCompletionError( + f"Completion response for prompt {prompt.id} is missing 'choices': {response_data}" + ) + try: content = response_data["choices"][0]["message"]["content"] raw_tool_calls = response_data["choices"][0]["message"].get("tool_calls", [])