{"record":{"id":"a52d3b605ee0f902","repo":"sgl-project/sglang","slug":"no-tool-calls-but-found-tool-output","errorCode":null,"errorMessage":"No tool calls but found tool output","messagePattern":"No tool calls but found tool output","errorType":"exception","errorClass":"DS32EncodingError","httpStatus":null,"severity":"error","filePath":"python/sglang/srt/entrypoints/openai/encoding_dsv32.py","lineNumber":240,"sourceCode":"            prompt += thinking_end_token\n\n    elif role == \"tool\":\n        prev_assistant_idx = index - 1\n        assistant_msg = messages[prev_assistant_idx]\n        while prev_assistant_idx >= 0 and assistant_msg.get(\"role\") == \"tool\":\n            prev_assistant_idx -= 1\n            assistant_msg = messages[prev_assistant_idx]\n\n        if not (\n            index == 0\n            or (prev_assistant_idx >= 0 and assistant_msg.get(\"role\") == \"assistant\")\n        ):\n            raise DS32EncodingError(f\"Invalid messages at {index}:\\n{assistant_msg}\")\n\n        tool_call_order = index - prev_assistant_idx\n        assistant_tool_calls = assistant_msg.get(\"tool_calls\")\n        if not (assistant_tool_calls and len(assistant_tool_calls) >= tool_call_order):\n            raise DS32EncodingError(\"No tool calls but found tool output\")\n\n        if tool_call_order == 1:\n            prompt += \"\\n\\n<function_results>\"\n\n        prompt += tool_output_template.format(content=content)\n\n        if tool_call_order == len(assistant_tool_calls):\n            prompt += \"\\n</function_results>\"\n\n            if index >= last_user_idx and thinking_mode == \"thinking\":\n                prompt += \"\\n\\n\" + thinking_start_token\n            else:\n                prompt += \"\\n\\n\" + thinking_end_token\n\n    elif role == \"assistant\":\n        prev_assistant_idx = index\n        thinking_part = \"\"\n","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/entrypoints/openai/encoding_dsv32.py#L222-L258","documentation":"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.","triggerScenarios":"Assistant message without 'tool_calls', or with fewer tool_calls than the number of subsequent tool messages; orphan tool outputs after the assistant turn.","commonSituations":"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.","solutions":["Attach the matching tool_calls array to the assistant message.","Remove orphan tool messages that have no corresponding assistant tool_call.","Ensure the count of tool messages after an assistant turn does not exceed len(assistant['tool_calls'])."],"exampleFix":"# before\n{\"role\":\"assistant\",\"content\":\"let me check\"}\n{\"role\":\"tool\",\"content\":\"42\"}\n# after\n{\"role\":\"assistant\",\"content\":\"\",\"tool_calls\":[{\"id\":\"1\",\"function\":{\"name\":\"calc\",\"arguments\":\"{}\"}}]}\n{\"role\":\"tool\",\"tool_call_id\":\"1\",\"content\":\"42\"}","handlingStrategy":"validation","validationCode":"for i,m in enumerate(messages):\n    if m['role']=='tool':\n        a=next((x for x in reversed(messages[:i]) if x['role']=='assistant'),None)\n        assert a and a.get('tool_calls')","typeGuard":"null","tryCatchPattern":null,"preventionTips":["Never emit a tool result without a matching assistant tool_call.","Verify len(tool msgs) <= len(assistant tool_calls)."],"tags":["deepseek","tool-calls","validation"],"backgroundTag":"tool-call-sequence-invalid","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}