sgl-project/sglang · error · DS32EncodingError

Invalid messages at {index}: {assistant_msg}

Error message

Invalid messages at {index}:
{assistant_msg}

What it means

DSv32 tool-message validation failed: a tool-role message appears where the preceding assistant turn is invalid. The encoder requires each tool message to immediately follow an assistant message (or be at index 0 with a prior assistant found), otherwise it raises DS32EncodingError showing the offending assistant message.

Source

Thrown at python/sglang/srt/entrypoints/openai/encoding_dsv32.py:235

        prompt += user_msg_template.format(content=content)

        if index == last_user_idx and thinking_mode == "thinking":
            prompt += thinking_start_token
        else:
            prompt += thinking_end_token

    elif role == "tool":
        prev_assistant_idx = index - 1
        assistant_msg = messages[prev_assistant_idx]
        while prev_assistant_idx >= 0 and assistant_msg.get("role") == "tool":
            prev_assistant_idx -= 1
            assistant_msg = messages[prev_assistant_idx]

        if not (
            index == 0
            or (prev_assistant_idx >= 0 and assistant_msg.get("role") == "assistant")
        ):
            raise DS32EncodingError(f"Invalid messages at {index}:\n{assistant_msg}")

        tool_call_order = index - prev_assistant_idx
        assistant_tool_calls = assistant_msg.get("tool_calls")
        if not (assistant_tool_calls and len(assistant_tool_calls) >= tool_call_order):
            raise DS32EncodingError("No tool calls but found tool output")

        if tool_call_order == 1:
            prompt += "\n\n<function_results>"

        prompt += tool_output_template.format(content=content)

        if tool_call_order == len(assistant_tool_calls):
            prompt += "\n</function_results>"

            if index >= last_user_idx and thinking_mode == "thinking":
                prompt += "\n\n" + thinking_start_token
            else:
                prompt += "\n\n" + thinking_end_token

View on GitHub (pinned to 0132848349)

Solutions

  1. Ensure every tool message directly follows the assistant message whose tool_calls produced it.
  2. Restore the missing assistant tool_call turn in the history.
  3. Rebuild the transcript so assistant(tool_calls) -> tool pairs stay adjacent.

Example fix

# before
[{"role":"user","content":"..."},{"role":"tool","content":"42"}]
# after
[{"role":"user","content":"..."},{"role":"assistant","tool_calls":[...]},{"role":"tool","content":"42"}]
Defensive patterns

Strategy: validation

Validate before calling

prev=None
for m in messages:
    if m['role']=='tool' and (prev is None or prev['role']!='assistant'):
        raise ValueError('tool message must follow assistant')
    prev=m

Prevention

When it happens

Trigger: A 'tool' role message that follows a user/system message instead of an assistant message, or two consecutive tool outputs not traceable to a preceding assistant turn with tool_calls.

Common situations: Client-side truncation that drops the assistant tool_call turn; history replay that reorders messages; manually constructed conversation logs missing the assistant step.

Related errors


AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28). Data as JSON: /api/errors/780fa681ba6dadf9. Report an issue: GitHub.