sgl-project/sglang · error · ValueError

No tool call found

Error message

No tool call found

What it means

Raised by the agentic Context.call_tool dispatcher when the last assistant message contains no parsable tool call, or the recipient string doesn't match any known tool session prefix (browser/python/etc.). It is the fallthrough after all supported tool handlers have been tried, signaling the caller attempted to execute tools on a message that has none.

Source

Thrown at python/sglang/srt/entrypoints/context.py:142

        return recipient is not None and (
            recipient.startswith("browser.") or recipient.startswith("python")
        )

    async def call_tool(self) -> list[Message]:
        if not self.messages:
            return []
        last_msg = self.messages[-1]
        recipient = last_msg.recipient
        if recipient is not None:
            if recipient.startswith("browser."):
                return await self.call_search_tool(
                    self.tool_sessions["browser"], last_msg
                )
            elif recipient.startswith("python"):
                return await self.call_python_tool(
                    self.tool_sessions["python"], last_msg
                )
        raise ValueError("No tool call found")

    def render_for_completion(self) -> list[int]:
        return render_for_completion(self.messages)

    async def call_search_tool(
        self, tool_session: Union["ClientSession", Tool], last_msg: Message
    ) -> list[Message]:
        if isinstance(tool_session, Tool):
            return await tool_session.get_result(self)
        tool_name = last_msg.recipient.split(".")[1]
        args = orjson.loads(last_msg.content[0].text)
        result = await tool_session.call_tool(tool_name, args)
        result_str = result.content[0].text
        content = TextContent(text=result_str)
        author = Author(role=Role.TOOL, name=last_msg.recipient)
        return [Message(author=author, content=[content], recipient=Role.ASSISTANT)]

    async def call_python_tool(

View on GitHub (pinned to 0132848349)

Solutions

  1. Check the last assistant message for tool_use blocks before calling call_tool; skip if it's plain text (the model finished)
  2. If truncation is the cause, raise max_tokens or adjust stop handling so tool calls aren't cut off
  3. Verify you pass the most recent assistant message and that the recipient prefix matches a registered tool session

Example fix

# before
result = await context.call_tool(last_msg)
# after
if any(getattr(b, "type", None) == "tool_use" for b in last_msg.content):
    result = await context.call_tool(last_msg)
else:
    result = None  # model produced a final answer
Defensive patterns

Strategy: type-guard

Validate before calling

has_tool = any(getattr(b, "type", None) == "tool_use" for b in last_msg.content)
if has_tool:
    result = await context.call_tool(last_msg)

Type guard

def message_has_tool_call(msg) -> bool:
    return any(getattr(b, "type", None) == "tool_use" for b in getattr(msg, "content", []))

Try / catch

try:
    result = await context.call_tool(last_msg)
except ValueError as e:
    if 'No tool call found' in str(e):
        result = None  # treat as final answer
    else:
        raise

Prevention

When it happens

Trigger: Calling context.call_tool(...) with an assistant Message whose content has no tool_use block, or after a model response that ended without invoking a tool (e.g. a final text answer).

Common situations: Agent loops that unconditionally call call_tool after every completion instead of checking whether the model requested a tool; stop_sequence or max_tokens truncation cutting off the tool call; parsing the wrong message (e.g. the user turn or an earlier assistant message).

Related errors


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