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_tokenView on GitHub (pinned to 0132848349)
Solutions
- Ensure every tool message directly follows the assistant message whose tool_calls produced it.
- Restore the missing assistant tool_call turn in the history.
- 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
- Keep assistant(tool_calls)->tool pairs adjacent.
- Validate transcript ordering before each request.
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
- No call message found for {call_id}
- No tool calls but found tool output
- Assistant tool call function.arguments must be a JSON object
- sparse_attn_v4_paged_decode expects fp16/bf16 q, got {q.dtyp
- bad compress_ratio {compress_ratio}
AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28).
Data as JSON: /api/errors/780fa681ba6dadf9.
Report an issue: GitHub.