sgl-project/sglang · error · DS32EncodingError

No tool calls but found tool output

Error message

No tool calls but found tool output

What it means

A tool output was found but the preceding assistant message has no (or too few) tool_calls. The encoder computes tool_call_order = index - prev_assistant_idx and requires the assistant's tool_calls list to be at least that long.

Source

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

            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

    elif role == "assistant":
        prev_assistant_idx = index
        thinking_part = ""

View on GitHub (pinned to 0132848349)

Solutions

  1. Attach the matching tool_calls array to the assistant message.
  2. Remove orphan tool messages that have no corresponding assistant tool_call.
  3. Ensure the count of tool messages after an assistant turn does not exceed len(assistant['tool_calls']).

Example fix

# before
{"role":"assistant","content":"let me check"}
{"role":"tool","content":"42"}
# after
{"role":"assistant","content":"","tool_calls":[{"id":"1","function":{"name":"calc","arguments":"{}"}}]}
{"role":"tool","tool_call_id":"1","content":"42"}
Defensive patterns

Strategy: validation

Validate before calling

for i,m in enumerate(messages):
    if m['role']=='tool':
        a=next((x for x in reversed(messages[:i]) if x['role']=='assistant'),None)
        assert a and a.get('tool_calls')

Type guard

null

Prevention

When it happens

Trigger: Assistant message without 'tool_calls', or with fewer tool_calls than the number of subsequent tool messages; orphan tool outputs after the assistant turn.

Common situations: Framework strips empty tool_calls arrays; partial history where only some tool results were kept; assistant replied with content only but a stale tool result follows.

Related errors


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